@@ -6,36 +6,26 @@ import { FC, useContext, useMemo, useState } from 'react';
66import { Breadcrumb , Button , Card , Col , Container , Modal , Ratio , Row } from 'react-bootstrap' ;
77
88import { 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' ;
129import {
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' ;
2517import { ProductCard } from '../../../../components/Activity/ProductCard' ;
2618import { PageHead } from '../../../../components/Layout/PageHead' ;
2719import { Activity , ActivityModel } from '../../../../models/Activity' ;
2820import {
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' ;
3929import styles from '../../../../styles/HackathonTeam.module.less' ;
4030
4131export 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
8471interface ProjectPageProps {
8572 activity : Activity ;
86- agenda : Agenda [ ] ;
8773 project : Project ;
8874 members : Member [ ] ;
8975 products : Product [ ] ;
9076}
9177
9278const 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