Skip to content

Commit 7759941

Browse files
committed
refactor: focus hackathon team page on team content
1 parent f5c705c commit 7759941

1 file changed

Lines changed: 93 additions & 145 deletions

File tree

pages/hackathon/[id]/team/[tid].tsx

Lines changed: 93 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,26 @@ import { FC, useContext, useMemo, useState } from 'react';
66
import { Breadcrumb, Button, Card, Col, Container, Modal, Ratio, Row } from 'react-bootstrap';
77

88
import { CommentBox } from '../../../../components/Activity/CommentBox';
9-
import { buildCountdownUnitLabels } from '../../../../components/Activity/Hackathon/constant';
10-
import { HackathonHero } from '../../../../components/Activity/Hackathon/Hero';
11-
import { useLiveCountdownState } from '../../../../components/Activity/Hackathon/useLiveCountdownState';
129
import {
13-
agendaTypeLabelOf,
14-
compactDateKeyOf,
1510
compactSummaryOf,
1611
firstTextOf,
17-
formatMoment,
18-
formatPeriod,
1912
isPublicForm,
2013
relationNameOf,
2114
textListOf,
22-
timeOf,
2315
userOf,
2416
} from '../../../../components/Activity/Hackathon/utility';
2517
import { ProductCard } from '../../../../components/Activity/ProductCard';
2618
import { PageHead } from '../../../../components/Layout/PageHead';
2719
import { Activity, ActivityModel } from '../../../../models/Activity';
2820
import {
29-
Agenda,
30-
AgendaModel,
3121
Member,
3222
MemberModel,
3323
Product,
3424
ProductModel,
3525
Project,
3626
ProjectModel,
3727
} from '../../../../models/Hackathon';
38-
import { i18n, I18nContext } from '../../../../models/Translation';
28+
import { I18nContext } from '../../../../models/Translation';
3929
import styles from '../../../../styles/HackathonTeam.module.less';
4030

4131
export const getServerSideProps = compose<Record<'id' | 'tid', string>>(
@@ -50,7 +40,6 @@ export const getServerSideProps = compose<Record<'id' | 'tid', string>>(
5040
if (
5141
!appId ||
5242
!tableIdMap?.Project ||
53-
!tableIdMap?.Agenda ||
5443
!tableIdMap?.Member ||
5544
!tableIdMap?.Product
5645
)
@@ -59,8 +48,7 @@ export const getServerSideProps = compose<Record<'id' | 'tid', string>>(
5948
const project = await new ProjectModel(appId, tableIdMap.Project).getOne(params!.tid);
6049

6150
// Get approved members for this project
62-
const [agenda, members, products] = await Promise.all([
63-
new AgendaModel(appId, tableIdMap.Agenda).getAll(),
51+
const [members, products] = await Promise.all([
6452
new MemberModel(appId, tableIdMap.Member).getAll({
6553
project: project.name as string,
6654
status: 'approved',
@@ -73,7 +61,6 @@ export const getServerSideProps = compose<Record<'id' | 'tid', string>>(
7361
props: {
7462
activity,
7563
project,
76-
agenda,
7764
members,
7865
products,
7966
},
@@ -83,24 +70,20 @@ export const getServerSideProps = compose<Record<'id' | 'tid', string>>(
8370

8471
interface ProjectPageProps {
8572
activity: Activity;
86-
agenda: Agenda[];
8773
project: Project;
8874
members: Member[];
8975
products: Product[];
9076
}
9177

9278
const ProjectPage: FC<ProjectPageProps> = observer(
93-
({ activity, agenda, project, members, products }) => {
79+
({ activity, project, members, products }) => {
9480
const { t } = useContext(I18nContext);
9581
const [showScoreModal, setShowScoreModal] = useState(false);
9682

9783
const {
9884
name: activityName,
9985
databaseSchema,
100-
image,
10186
location,
102-
startTime,
103-
endTime,
10487
summary: activitySummary,
10588
type: activityType,
10689
} = activity;
@@ -123,35 +106,10 @@ const ProjectPage: FC<ProjectPageProps> = observer(
123106
140,
124107
);
125108
const locationText = (location as TableCellLocation | undefined)?.full_address || '-';
126-
const eventRange = formatPeriod(startTime, endTime) || locationText;
127109
const groupName = relationNameOf(group);
128110
const scoreText = firstTextOf(score);
129111
const rankText = firstTextOf(rank);
130112
const prizeText = firstTextOf(prize);
131-
const agendaItems = [...agenda].sort(({ startedAt: left }, { startedAt: right }) => {
132-
const leftTime = timeOf(left);
133-
const rightTime = timeOf(right);
134-
135-
if (!Number.isFinite(leftTime) && !Number.isFinite(rightTime)) return 0;
136-
if (!Number.isFinite(leftTime)) return 1;
137-
if (!Number.isFinite(rightTime)) return -1;
138-
139-
return leftTime - rightTime;
140-
});
141-
const phaseBadges = agendaItems
142-
.slice(0, 4)
143-
.map(({ type, startedAt, endedAt }) => {
144-
const phase = agendaTypeLabelOf(type, t, t('agenda'));
145-
const start = compactDateKeyOf(startedAt);
146-
const end = compactDateKeyOf(endedAt);
147-
const period =
148-
start && end && start !== end
149-
? `${start} - ${end}`
150-
: start || end || formatMoment(startedAt);
151-
152-
return [phase, period].filter(Boolean).join(' ');
153-
})
154-
.filter(Boolean);
155113
const publicForms = useMemo(
156114
() =>
157115
Object.values(forms || {})
@@ -177,128 +135,118 @@ const ProjectPage: FC<ProjectPageProps> = observer(
177135
{ href: '#works', label: t('team_works') },
178136
{ href: '#creator', label: t('created_by') },
179137
];
180-
const { nextItem: nextAgendaItem, countdownTo } = useLiveCountdownState(
181-
agendaItems,
182-
startTime,
183-
endTime,
184-
);
185-
const countdownLabel = nextAgendaItem
186-
? agendaTypeLabelOf(nextAgendaItem.type, t, t('agenda'))
187-
: t('event_duration');
188138
const heroChips = [
189139
`${t('participants')} · ${members.length}`,
190140
`${t('products')} · ${products.length}`,
191141
groupName,
192142
rankText ? `#${rankText}` : '',
193143
scoreText ? `${t('score')} · ${scoreText}` : '',
194144
].filter(Boolean);
195-
const creatorText = creator?.name || '';
196145
const heroPrimaryAction = primaryForm
197146
? {
198147
label: t('hackathon_register_now'),
199148
href: primaryForm.shared_url,
200149
external: true as const,
201150
}
202151
: { label: t('hackathon_detail'), href: ActivityModel.getLink(activity) };
152+
const quickMetrics = [
153+
{
154+
label: t('participants'),
155+
value: String(members.length),
156+
meta: t('team_members'),
157+
},
158+
{
159+
label: t('products'),
160+
value: String(products.length),
161+
meta: t('team_works'),
162+
},
163+
{
164+
label: t('created_by'),
165+
value: creator?.name || '-',
166+
meta: groupName || displayTitle,
167+
},
168+
{
169+
label: t('score'),
170+
value: scoreText || '--',
171+
meta: [prizeText, rankText ? `#${rankText}` : ''].filter(Boolean).join(' · ') || '--',
172+
},
173+
];
203174

204175
return (
205176
<>
206177
<PageHead title={`${displayTitle} - ${activityName}`} />
207178

208179
<div className={styles.page}>
209-
<HackathonHero
210-
badges={phaseBadges}
211-
bottomCard={
212-
nextAgendaItem
213-
? {
214-
eyebrow: t('event_duration'),
215-
title: eventRange,
216-
description: agendaTypeLabelOf(nextAgendaItem.type, t, t('agenda')),
217-
}
218-
: undefined
219-
}
220-
countdownLabel={countdownLabel}
221-
countdownUnitLabels={buildCountdownUnitLabels({ t } as typeof i18n)}
222-
countdownTo={countdownTo}
223-
description={projectSummary}
224-
image={image}
225-
imageFallback={(activityType as string) || t('hackathon_detail')}
226-
locationText={locationText}
227-
name={`${displayTitle} ${t('hackathon_team_showcase')}`}
228-
navigation={navigation}
229-
primaryAction={heroPrimaryAction}
230-
secondaryAction={{ label: t('team_works'), href: '#works' }}
231-
chips={heroChips}
232-
subtitle={activityName as string}
233-
topCard={{
234-
eyebrow: t('event_description'),
235-
title: compactSummaryOf(projectSummary, displayTitle, 40),
236-
description: creatorText || locationText,
237-
}}
238-
visualChip={groupName || (activityType as string) || t('projects')}
239-
visualCopy={eventRange || locationText}
240-
visualKicker={t('main_visual')}
241-
visualTitle={compactSummaryOf(projectSummary, displayTitle, 52)}
242-
/>
243-
244-
<section id="overview" className={styles.section}>
180+
<section id="overview" className={styles.heroSection}>
245181
<Container>
246-
<header className={styles.sectionHeader}>
247-
<h2 className={styles.sectionTitle}>{t('event_description')}</h2>
248-
<p className={styles.sectionSubtitle}>{displayTitle}</p>
249-
<div className={styles.accentLine} />
250-
</header>
251-
252-
<article className={styles.introPanel}>
253-
<Breadcrumb aria-label={t('breadcrumb')} className={styles.breadcrumb}>
254-
{currentRoute.map(({ title, href }, index, { length }) => (
255-
<Breadcrumb.Item
256-
key={`${title}-${index}`}
257-
href={index === length - 1 ? undefined : href}
258-
active={index === length - 1}
182+
<div className={styles.heroShell}>
183+
<div className={styles.heroMain}>
184+
<Breadcrumb aria-label={t('breadcrumb')} className={styles.breadcrumb}>
185+
{currentRoute.map(({ title, href }, index, { length }) => (
186+
<Breadcrumb.Item
187+
key={`${title}-${index}`}
188+
href={index === length - 1 ? undefined : href}
189+
active={index === length - 1}
190+
>
191+
{title}
192+
</Breadcrumb.Item>
193+
))}
194+
</Breadcrumb>
195+
196+
<nav className={styles.heroNav} aria-label={displayTitle}>
197+
{navigation.map(({ href, label }) => (
198+
<a key={`${href}-${label}`} className={styles.heroNavLink} href={href}>
199+
{label}
200+
</a>
201+
))}
202+
</nav>
203+
204+
<div className={styles.heroTagRow}>
205+
<span className={styles.heroEyebrow}>
206+
{(activityType as string) || t('hackathon_detail')}
207+
</span>
208+
{groupName && <span className={styles.heroTag}>{groupName}</span>}
209+
{prizeText && <span className={styles.heroTag}>{prizeText}</span>}
210+
{rankText && <span className={styles.heroTag}>#{rankText}</span>}
211+
</div>
212+
213+
<h1 className={styles.introTitle}>{displayTitle}</h1>
214+
<p className={styles.introText}>{projectSummary}</p>
215+
216+
<div className={styles.heroActionRow}>
217+
<a
218+
className={styles.primaryAction}
219+
href={heroPrimaryAction.href}
220+
{...(heroPrimaryAction.external && {
221+
target: '_blank',
222+
rel: 'noreferrer',
223+
})}
259224
>
260-
{title}
261-
</Breadcrumb.Item>
262-
))}
263-
</Breadcrumb>
264-
265-
<h3 className={styles.introTitle}>{displayTitle}</h3>
266-
<p className={styles.introText}>{projectSummary}</p>
267-
268-
<ul className={styles.metaList}>
269-
{groupName && <li className={styles.metaItem}>{groupName}</li>}
270-
{prizeText && <li className={styles.metaItem}>{prizeText}</li>}
271-
{rankText && <li className={styles.metaItem}>#{rankText}</li>}
272-
{scoreText && (
273-
<li className={styles.metaItem}>{`${t('score')} · ${scoreText}`}</li>
274-
)}
275-
<li className={styles.metaItem}>{locationText}</li>
276-
</ul>
277-
</article>
278-
279-
<div className={styles.metricGrid}>
280-
<article className={styles.metricCard}>
281-
<span className={styles.metricLabel}>{t('participants')}</span>
282-
<strong className={styles.metricValue}>{members.length}</strong>
283-
<span className={styles.metricMeta}>{t('team_members')}</span>
284-
</article>
285-
<article className={styles.metricCard}>
286-
<span className={styles.metricLabel}>{t('products')}</span>
287-
<strong className={styles.metricValue}>{products.length}</strong>
288-
<span className={styles.metricMeta}>{t('team_works')}</span>
289-
</article>
290-
<article className={styles.metricCard}>
291-
<span className={styles.metricLabel}>{t('event_location')}</span>
292-
<strong className={styles.metricValue}>
293-
{(location as TableCellLocation | undefined)?.name || '-'}
294-
</strong>
295-
<span className={styles.metricMeta}>{locationText}</span>
296-
</article>
297-
<article className={styles.metricCard}>
298-
<span className={styles.metricLabel}>{t('score')}</span>
299-
<strong className={styles.metricValue}>{scoreText || '--'}</strong>
300-
<span className={styles.metricMeta}>{eventRange}</span>
301-
</article>
225+
{heroPrimaryAction.label}
226+
</a>
227+
<a className={styles.secondaryAction} href="#works">
228+
{t('team_works')}
229+
</a>
230+
</div>
231+
232+
<ul className={styles.metaList}>
233+
{heroChips.map(item => (
234+
<li key={item} className={styles.metaItem}>
235+
{item}
236+
</li>
237+
))}
238+
</ul>
239+
240+
<div className={styles.metricGridCompact}>
241+
{quickMetrics.map(({ label, value, meta }) => (
242+
<article key={`${label}-${value}`} className={styles.metricCard}>
243+
<span className={styles.metricLabel}>{label}</span>
244+
<strong className={styles.metricValue}>{value}</strong>
245+
<span className={styles.metricMeta}>{meta}</span>
246+
</article>
247+
))}
248+
</div>
249+
</div>
302250
</div>
303251
</Container>
304252
</section>

0 commit comments

Comments
 (0)