Skip to content

Commit 32e7f31

Browse files
ubugeeeiCopilotcsy19Hal-Spidernight
authored
merge main into production (sold out after party ticket) (#973)
* feat: sold out after party (#969) * feat: feature flags improvement (#970) * wip * save * feat: feature flags improvement * save * save * Update modules/00.feature-flags.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/00.feature-flags.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: timetable (#908) * add: image * Create timetable.ts * feat: i18n * feat: timetable component * wip feat: timetable page * feat: timetable schedule * wip table内容調整 * feat: PC timetable * feat: sp style * feat: en * Update cspell.config.yaml * fix: lint * wip feat: og * fix: 時間修正・ハンズオンタイトル修正 Keynote(Evan You) 10:11-10:50 Hands-on1 12:50-14:50(セッションタイトルもBeginner Hands-onに) Hands-on2 15:10-17:10(セッションタイトルもIntermediate Hands-onに) ランチタイム明け1つめのセッション時間、終わりを13:40→13:20 * fmt * feat: 戻るボタンを正しく戻るにする composablesに切り出してもいい? * chore: timetableのトラックをstickyに * chore: 学生支援とパネルディスカッションにスピーカーの追加 * update: timetable ja aaaaaa~~~~~~ * en * chore * feat: eventへのリンク * feat: 登壇時間と場所の追加 * Update TimetableCell.vue * chore * fix: 時間微修正 * save * feat: server side i18n * save * save * save * save * save * save * save * save * save * save * feat: server i18n * chore: remove unused --------- Co-authored-by: ubugeeei <ubuge1122@gmail.com> * chore: eslint ignore path * chore: add timetable og image * feat: staff (#930) * feat: add staff grid components and i18n labels * feat: add core staff info * feat: add leader section and fix styles * fix: x link and image paths * feat: add volunteer, minimize images * chore: add staff name to cSpell * fix: linter * chore: optimize image sizes * fix: layout * chore: add feature flags * save * save * save * save * save * save * save --------- Co-authored-by: ubugeeei <ubuge1122@gmail.com> * chore: shuffle on client * chore: aspect-ratio * feat: guiest details (#971) * feat: closing early bird (#972) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: csy19 <43460318+csy19@users.noreply.github.com> Co-authored-by: Hal <102022084+Hal-Spidernight@users.noreply.github.com>
1 parent 832d54f commit 32e7f31

File tree

142 files changed

+3167
-209
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+3167
-209
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
- name: Check Building
3737
run: pnpm build
3838
env:
39+
NODE_OPTIONS: '--max-old-space-size=4096'
3940
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
4041
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
4142
CLOUDFLARE_R2_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ logs
2929
# Local Netlify folder
3030
.netlify
3131

32-
app/components/_i18n
32+
app/components/_i18n
33+
server/i18n/generated

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,80 @@ pnpm install
2121
pnpm dev
2222
```
2323

24+
## Feature Flags Module
25+
26+
This project includes a custom Nuxt module for managing feature flags with full TypeScript support.
27+
28+
### Features
29+
30+
- **Type-safe**: Auto-generates TypeScript definitions for all feature flags
31+
- **Universal**: Works seamlessly in both client and server environments
32+
- **Build-time resolution**: All feature flags are resolved during build step, not at runtime
33+
- **Zero runtime overhead**: Flags are replaced at build time with constants
34+
- **Tree-shaking friendly**: Unused code paths are eliminated from the final bundle
35+
36+
### Configuration
37+
38+
Configure feature flags in `nuxt.config.ts`:
39+
40+
```ts
41+
export default defineNuxtConfig({
42+
featureFlags: {
43+
timetable: true,
44+
soldOutAfterParty: false,
45+
}
46+
})
47+
```
48+
49+
### Usage
50+
51+
#### Dynamic Component Import
52+
53+
Use feature flags with dynamic imports to conditionally load components:
54+
55+
```vue
56+
<script setup lang="ts">
57+
// Conditionally import component based on feature flag
58+
const BetaFeature = import.meta.vfFeatures.betaFeature
59+
? defineAsyncComponent(() => import('~/components/BetaFeature.vue'))
60+
: null;
61+
</script>
62+
63+
<template>
64+
<div>
65+
<BetaFeature v-if="BetaFeature" />
66+
</div>
67+
</template>
68+
```
69+
70+
#### Page-level Feature Flags
71+
72+
Control page generation with Nuxt's `ignore` option:
73+
74+
This approach completely excludes pages from the build, resulting in smaller bundle sizes and true 404s when features are disabled.
75+
76+
See: https://nuxt.com/docs/4.x/api/nuxt-config#ignore
77+
78+
#### Server API Routes
79+
80+
Control API endpoints with feature flags:
81+
82+
```ts
83+
// server/api/timetable.get.ts
84+
export default defineEventHandler(async (event) => {
85+
// Block API if feature is disabled
86+
if (!import.meta.vfFeatures.timetable) {
87+
throw createError({
88+
statusCode: 404,
89+
statusMessage: 'Endpoint not available'
90+
});
91+
}
92+
93+
// Return timetable data
94+
return await getTimetableData();
95+
});
96+
```
97+
2498
## Plans Overview
2599

26100
### Features

app/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const HOME_HEADING_ID = {
1212
access: "access",
1313
studentSupport: "student-support",
1414
store: "store",
15+
staff: "staff",
1516

1617
// NOTE: Be careful as it is hardcoded in MDC
1718
contact: "contact-form",

app/layouts/default.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ const menuItems = computed<MenuItemProps[]>(() =>
3737
{
3838
id: HOME_HEADING_ID.timetable,
3939
label: "Timetable",
40-
// TODO:
41-
routeName: localeRoute({ name: "index" }).name,
42-
disabled: !__FEATURE_TIMETABLE__,
40+
routeName: localeRoute({ name: "timetable" }).name,
41+
disabled: !import.meta.vfFeatures.timetable,
4342
},
4443
{
4544
id: HOME_HEADING_ID.speaker,
@@ -103,6 +102,7 @@ const WIDE_ROUTE_NAMES: RoutesNamesList[] = [
103102
"sponsors",
104103
"sponsors-sponsorId",
105104
"event",
105+
"timetable",
106106
"related-events",
107107
"store",
108108
];
@@ -232,6 +232,7 @@ watch(() => route.hash, async (hash) => {
232232
233233
&.widen-content {
234234
min-width: 960px;
235+
/* min-width: 95%; */
235236
transition: unset;
236237
237238
@media (--mobile) {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<script setup lang="ts">
2+
import { HOME_HEADING_ID } from "~/constant";
3+
import { useI18n, useFetch, useBreakpoint, computed, onMounted } from "#imports";
4+
import { VFSection } from "#components";
5+
6+
import StaffGrid from "./StaffGrid.vue";
7+
import type { Staff } from "~~/server/api/staffs/index.get";
8+
9+
const { t } = useI18n();
10+
const bp = useBreakpoint();
11+
12+
const { data: staffList } = await useFetch("/api/staffs", { deep: true });
13+
14+
const leaderColumns = computed(() => {
15+
return bp.value === "pc" ? 3 : 2;
16+
});
17+
18+
const coreColumns = computed(() => {
19+
return bp.value === "pc" ? 4 : 3;
20+
});
21+
22+
onMounted(() => {
23+
if (!staffList.value) return;
24+
staffList.value.leaders = shuffleNonPinned(staffList.value.leaders);
25+
staffList.value.cores = shuffleNonPinned(staffList.value.cores);
26+
});
27+
function shuffleArray<T>(array: T[]): T[] {
28+
const shuffled = [...array];
29+
for (let i = shuffled.length - 1; i > 0; i--) {
30+
const j = Math.floor(Math.random() * (i + 1));
31+
[shuffled[i]!, shuffled[j]!] = [shuffled[j]!, shuffled[i]!];
32+
}
33+
return shuffled;
34+
}
35+
36+
function shuffleNonPinned(staffArray: Staff[]): Staff[] {
37+
const pinned = staffArray.filter(staff => staff.pinned);
38+
const nonPinned = staffArray.filter(staff => !staff.pinned);
39+
const shuffledNonPinned = shuffleArray(nonPinned);
40+
return [...pinned, ...shuffledNonPinned];
41+
}
42+
</script>
43+
44+
<template>
45+
<VFSection :id="HOME_HEADING_ID.staff" :title="t('staff.title')">
46+
<p class="staff-description">
47+
{{ t('staff.description') }}
48+
</p>
49+
<template v-if="staffList">
50+
<StaffGrid
51+
:staff-list="staffList.leaders"
52+
grid-mode="leader"
53+
:columns="leaderColumns"
54+
/>
55+
<StaffGrid
56+
:staff-list="staffList.cores"
57+
grid-mode="core"
58+
:columns="coreColumns"
59+
/>
60+
61+
<VFHeading id="volunteer-staff">
62+
{{ t('staff.volunteer') }}
63+
</VFHeading>
64+
65+
<StaffGrid
66+
:staff-list="staffList.volunteers"
67+
grid-mode="volunteer"
68+
/>
69+
</template>
70+
</VFSection>
71+
</template>
72+
73+
<style scoped>
74+
.staff-description {
75+
margin: 2rem 0;
76+
word-break: break-all;
77+
}
78+
</style>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<script setup lang="ts">
2+
import { computed } from "vue";
3+
4+
import StaffGridItem, { type StaffItemProps } from "./StaffGridItem.vue";
5+
6+
const {
7+
staffList,
8+
gridMode,
9+
columns,
10+
} = defineProps<{
11+
staffList: StaffItemProps[];
12+
gridMode: "leader" | "core" | "volunteer";
13+
columns?: number;
14+
}>();
15+
16+
const gridClass = computed(() => `staff-grid staff-grid-${gridMode}`);
17+
18+
const gridStyle = computed(() =>
19+
columns && (gridMode === "leader" || gridMode === "core")
20+
? { gridTemplateColumns: `repeat(${columns}, 1fr)` }
21+
: {},
22+
);
23+
</script>
24+
25+
<template>
26+
<div :class="gridClass" :style="gridStyle">
27+
<StaffGridItem
28+
v-for="(staff, idx) in staffList" :key="idx" class="staff-member"
29+
v-bind="{ ...staff, gridMode }"
30+
/>
31+
</div>
32+
</template>
33+
34+
<style scoped>
35+
@import "~/assets/styles/custom-media-query.css";
36+
37+
.staff-grid {
38+
display: grid;
39+
margin-bottom: 1.5rem;
40+
}
41+
42+
.staff-grid-leader {
43+
gap: 1rem;
44+
}
45+
46+
.staff-grid-core {
47+
gap: 0.9rem;
48+
}
49+
50+
.staff-grid-volunteer {
51+
display: flex;
52+
flex-wrap: wrap;
53+
align-items: left;
54+
margin: 1.5rem 0 0 0;
55+
}
56+
</style>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script lang="ts">
2+
import { computed } from "vue";
3+
4+
export type StaffItemProps = {
5+
name: string;
6+
avatarUrl: string;
7+
socialUrls?: {
8+
x?: string;
9+
github?: string;
10+
};
11+
};
12+
</script>
13+
14+
<script setup lang="ts">
15+
const {
16+
name,
17+
avatarUrl,
18+
socialUrls,
19+
gridMode,
20+
} = defineProps<StaffItemProps & { gridMode: "leader" | "core" | "volunteer" }>();
21+
22+
const gridItemClass = computed(() => `staff-link-${gridMode}`);
23+
24+
const linkComp = computed(() => socialUrls?.x ? socialUrls.x : socialUrls?.github);
25+
</script>
26+
27+
<template>
28+
<NuxtLink
29+
v-if="linkComp"
30+
:to="linkComp"
31+
external
32+
target="_blank"
33+
class="staff"
34+
:class="gridItemClass"
35+
>
36+
<img :src="avatarUrl" :alt="name" />
37+
<p class="font-bold">{{ name }}</p>
38+
</NuxtLink>
39+
40+
<div v-else class="staff" :class="gridItemClass">
41+
<img v-if="avatarUrl" :src="avatarUrl" :alt="name" loading="lazy" />
42+
<p class="font-bold">
43+
{{ name }}
44+
</p>
45+
</div>
46+
</template>
47+
48+
<style scoped>
49+
@import "~/assets/styles/custom-media-query.css";
50+
51+
.staff {
52+
display: flex;
53+
flex-direction: column;
54+
align-items: center;
55+
text-decoration: none;
56+
color: #444;
57+
margin-bottom: 1rem;
58+
59+
p {
60+
width: 100%;
61+
text-align: left;
62+
overflow-wrap: break-word;
63+
color: var(--color-text-default);
64+
margin-top: 1rem;
65+
66+
@media (--mobile) {
67+
margin-top: 0.75rem;
68+
}
69+
}
70+
71+
&.staff-link-leader,
72+
&.staff-link-core {
73+
p {
74+
font-size: 18px;
75+
font-family: IBMPlexSansJP-Bold;
76+
77+
@media (--mobile) {
78+
font-size: 16px;
79+
}
80+
}
81+
82+
img {
83+
width: 100%;
84+
height: auto;
85+
border-radius: 8%;
86+
border: 0.5px solid #ddd;
87+
aspect-ratio: 1 / 1;
88+
}
89+
}
90+
91+
&.staff-link-volunteer {
92+
margin-bottom: 0;
93+
94+
p {
95+
margin: 0.3rem 0.6rem;
96+
text-align: left;
97+
98+
@media (--mobile) {
99+
font-size: 14px;
100+
margin: 0 0.6rem;
101+
}
102+
}
103+
}
104+
}
105+
</style>

0 commit comments

Comments
 (0)