1- import { BiTableSchema , TableCellLocation , TableCellUser } from 'mobx-lark' ;
1+ import { BiTableSchema , TableCellLocation , TableCellUser , TableFormView } from 'mobx-lark' ;
22import { observer } from 'mobx-react' ;
33import Link from 'next/link' ;
44import { cache , compose , errorLogger } from 'next-ssr-middleware' ;
@@ -67,18 +67,15 @@ interface HackathonDetailProps {
6767 } ;
6868}
6969
70- type FormGroupKey = 'Person' | 'Project' | 'Product' | 'Evaluation' ;
70+ const FormButtonBar = [ 'Person' , 'Project' , 'Product' , 'Evaluation' ] as const ;
7171
72- interface FormLink {
73- name : string ;
74- shared_limit ?: string ;
75- shared_url : string ;
76- }
72+ type FormGroupKey = ( typeof FormButtonBar ) [ number ] ;
73+ type FormLink = TableFormView ;
7774
7875interface FormGroupMeta {
79- description : string ;
80- eyebrow : string ;
81- title : string ;
76+ description : I18nKey ;
77+ eyebrow : I18nKey ;
78+ title : I18nKey ;
8279}
8380
8481interface FormGroup {
@@ -87,32 +84,31 @@ interface FormGroup {
8784 meta : FormGroupMeta ;
8885}
8986
90- const FormButtonBar : FormGroupKey [ ] = [ 'Person' , 'Project' , 'Product' , 'Evaluation' ] ;
91-
9287const FormSectionMeta : Record < FormGroupKey , FormGroupMeta > = {
9388 Person : {
94- eyebrow : 'Participants ' ,
95- title : '参与者登记 ' ,
96- description : '收集报名成员、建立参赛者池,并为后续组队和通知打底。 ' ,
89+ eyebrow : 'participants ' ,
90+ title : 'hackathon_participant_registration ' ,
91+ description : 'hackathon_participant_registration_description ' ,
9792 } ,
9893 Project : {
99- eyebrow : 'Team Lead ' ,
100- title : '项目注册 ' ,
101- description : '由队长登记项目名称、成员、赛道和一句话介绍,完成队伍锁定。 ' ,
94+ eyebrow : 'hackathon_team_lead ' ,
95+ title : 'hackathon_project_registration ' ,
96+ description : 'hackathon_project_registration_description ' ,
10297 } ,
10398 Product : {
104- eyebrow : 'Submission ' ,
105- title : '作品提交 ' ,
106- description : '比赛截止前统一提交最终作品、演示链接和补充说明。 ' ,
99+ eyebrow : 'hackathon_submission ' ,
100+ title : 'product_submission ' ,
101+ description : 'hackathon_product_submission_description ' ,
107102 } ,
108103 Evaluation : {
109- eyebrow : 'Review ' ,
110- title : '评审入口 ' ,
111- description : '评委或导师在评审阶段使用,用于评分、复核与结果整理。 ' ,
104+ eyebrow : 'hackathon_review ' ,
105+ title : 'hackathon_evaluation_entry ' ,
106+ description : 'hackathon_evaluation_entry_description ' ,
112107 } ,
113108} ;
114109
115- const isPublicForm = ( { shared_limit } : FormLink ) => shared_limit === 'anyone_editable' ;
110+ const isPublicForm = ( { shared, shared_limit } : FormLink ) =>
111+ shared || [ 'tenant_editable' , 'anyone_editable' ] . includes ( shared_limit as string ) ;
116112
117113const HackathonDetail : FC < HackathonDetailProps > = observer ( ( { activity, hackathon } ) => {
118114 const { t } = useContext ( I18nContext ) ;
@@ -150,7 +146,7 @@ const HackathonDetail: FC<HackathonDetailProps> = observer(({ activity, hackatho
150146 { label : t ( 'templates' ) , value : templates . length } ,
151147 { label : t ( 'organizations' ) , value : organizations . length } ,
152148 ] ;
153- const hostTags = ( ( host as string [ ] | undefined ) || [ ] ) . slice ( 0 , 3 ) ;
149+ const hostTags = ( host as string [ ] | undefined ) ? .slice ( 0 , 3 ) || [ ] ;
154150 const agendaPreview = agenda . slice ( 0 , 3 ) ;
155151
156152 return (
@@ -161,30 +157,30 @@ const HackathonDetail: FC<HackathonDetailProps> = observer(({ activity, hackatho
161157 < Container >
162158 < Row className = "align-items-center g-4" >
163159 < Col lg = { 7 } >
164- < div className = { styles . heroEyebrow } >
165- < span className = { styles . heroTag } > { ( activityType as string ) || t ( 'hackathon' ) } </ span >
160+ < ul className = "list-unstyled d-flex flex-wrap gap-3 mb-3" >
161+ < li className = { styles . heroTag } > { ( activityType as string ) || t ( 'hackathon' ) } </ li >
166162 { hostTags . map ( tag => (
167- < span key = { tag } className = { styles . heroTag } >
163+ < li key = { tag } className = { styles . heroTag } >
168164 { tag }
169- </ span >
165+ </ li >
170166 ) ) }
171- </ div >
167+ </ ul >
172168
173169 < h1 className = { styles . title } > { name as string } </ h1 >
174170 < p className = { styles . description } > { summary as string } </ p >
175171
176- < div className = { styles . heroStats } >
172+ < ul className = "list-unstyled d-flex flex-wrap gap-3 mt-4 mb-0" >
177173 { heroStats . map ( ( { label, value } ) => (
178- < span key = { label } className = { styles . statChip } >
174+ < li key = { label } className = { styles . statChip } >
179175 { value } { label }
180- </ span >
176+ </ li >
181177 ) ) }
182- </ div >
178+ </ ul >
183179
184- < div className = { styles . heroActions } >
180+ < nav className = "d-flex flex-wrap gap-3 mt-4" >
185181 { primaryForm && (
186182 < Button href = { primaryForm . list [ 0 ] . shared_url } target = "_blank" rel = "noreferrer" >
187- { primaryForm . meta . title }
183+ { t ( primaryForm . meta . title ) }
188184 </ Button >
189185 ) }
190186 { secondaryForm && (
@@ -194,35 +190,32 @@ const HackathonDetail: FC<HackathonDetailProps> = observer(({ activity, hackatho
194190 rel = "noreferrer"
195191 variant = "light"
196192 >
197- { secondaryForm . meta . title }
193+ { t ( secondaryForm . meta . title ) }
198194 </ Button >
199195 ) }
200196 { formGroups [ 0 ] && (
201197 < Button href = "#entry-hub" variant = "outline-light" >
202- 查看全部入口
198+ { t ( 'hackathon_view_all_entries' ) }
203199 </ Button >
204200 ) }
205- </ div >
206-
207- < Row className = "mt-4 g-3" >
208- < Col md = { 6 } >
209- < Card className = { styles . infoCard } >
210- < Card . Body >
211- < h5 className = "text-white mb-2" > 📍 { t ( 'event_location' ) } </ h5 >
212- < p className = "text-white-50 mb-0" >
213- { ( location as TableCellLocation ) ?. full_address }
214- </ p >
215- </ Card . Body >
201+ </ nav >
202+
203+ < Row className = "mt-4 g-3" md = { 2 } >
204+ < Col >
205+ < Card className = { styles . infoCard } body >
206+ < h5 className = "text-white mb-2" > 📍 { t ( 'event_location' ) } </ h5 >
207+ < address className = "text-white-50 mb-0 fst-normal" >
208+ { ( location as TableCellLocation ) ?. full_address }
209+ </ address >
216210 </ Card >
217211 </ Col >
218- < Col md = { 6 } >
219- < Card className = { styles . infoCard } >
220- < Card . Body >
221- < h5 className = "text-white mb-2" > ⏰ { t ( 'event_duration' ) } </ h5 >
222- < p className = "text-white-50 mb-0" >
223- { formatDate ( startTime as string ) } - { formatDate ( endTime as string ) }
224- </ p >
225- </ Card . Body >
212+ < Col >
213+ < Card className = { styles . infoCard } body >
214+ < h5 className = "text-white mb-2" > ⏰ { t ( 'event_duration' ) } </ h5 >
215+ < p className = "text-white-50 mb-0" >
216+ < time dateTime = { startTime as string } > { formatDate ( startTime as string ) } </ time > { ' ' }
217+ - < time dateTime = { endTime as string } > { formatDate ( endTime as string ) } </ time >
218+ </ p >
226219 </ Card >
227220 </ Col >
228221 </ Row >
@@ -236,24 +229,32 @@ const HackathonDetail: FC<HackathonDetailProps> = observer(({ activity, hackatho
236229 </ div >
237230 ) }
238231 < Card . Body >
239- < div className = { styles . heroVisualHead } >
240- < span className = { styles . heroVisualKicker } > Agenda Preview</ span >
241- < strong > { agendaPreview [ 0 ] ?. name as string } </ strong >
242- </ div >
243-
244- < div className = { styles . heroVisualList } >
245- { agendaPreview . map ( ( { name, startedAt, endedAt } ) => (
246- < div
247- key = { `${ name as string } -${ startedAt as string } ` }
248- className = { styles . heroVisualItem }
249- >
250- < strong > { name as string } </ strong >
251- < span >
252- { formatDate ( startedAt as string ) } - { formatDate ( endedAt as string ) }
253- </ span >
232+ { agendaPreview [ 0 ] && (
233+ < dl className = "d-grid gap-3 mb-0" >
234+ < div className = { `${ styles . heroVisualHead } ${ styles . heroVisualItem } ` } >
235+ < dt className = { styles . heroVisualKicker } > { t ( 'hackathon_agenda_preview' ) } </ dt >
236+ < dd className = "fw-semibold" > { agendaPreview [ 0 ] . name as string } </ dd >
254237 </ div >
255- ) ) }
256- </ div >
238+
239+ { agendaPreview . map ( ( { name, startedAt, endedAt } ) => (
240+ < div
241+ key = { `${ name as string } -${ startedAt as string } ` }
242+ className = { styles . heroVisualItem }
243+ >
244+ < dt className = "fw-semibold" > { name as string } </ dt >
245+ < dd >
246+ < time dateTime = { startedAt as string } >
247+ { formatDate ( startedAt as string ) }
248+ </ time > { ' ' }
249+ -{ ' ' }
250+ < time dateTime = { endedAt as string } >
251+ { formatDate ( endedAt as string ) }
252+ </ time >
253+ </ dd >
254+ </ div >
255+ ) ) }
256+ </ dl >
257+ ) }
257258 </ Card . Body >
258259 </ Card >
259260 </ Col >
@@ -264,22 +265,23 @@ const HackathonDetail: FC<HackathonDetailProps> = observer(({ activity, hackatho
264265 < Container className = "my-5" >
265266 { formGroups [ 0 ] && (
266267 < section id = "entry-hub" className = { styles . section } >
267- < p className = { styles . sectionEyebrow } > Action Hub · Forms </ p >
268- < h2 className = { styles . sectionTitle } > 报名与提交流程 </ h2 >
269- < p className = { styles . sectionLead } >
270- 入口根据活动当前配置自动生成。不同活动可以复用同一套页面结构,而不是为每次活动单开专页。
271- </ p >
268+ < hgroup className = "mb-0" >
269+ < p className = { styles . sectionEyebrow } > { t ( 'hackathon_action_hub' ) } </ p >
270+ < h2 className = { styles . sectionTitle } > { t ( 'hackathon_entry_flow' ) } </ h2 >
271+ < p className = { styles . sectionLead } > { t ( 'hackathon_entry_flow_description' ) } </ p >
272+ </ hgroup >
272273
273- < Row className = "mt-4 g-3" md = { 2 } xl = { 4 } >
274+ < Row as = "ol" className = "list-unstyled mt-4 g-3" md = { 2 } xl = { 4 } >
274275 { formGroups . map ( ( { key, list, meta } , index ) => (
275- < Col key = { key } >
276+ < Col as = "li" key = { key } >
276277 < Card className = { styles . entryCard } body >
277278 < span className = { styles . entryStep } >
278- Step { String ( index + 1 ) . padStart ( 2 , '0' ) } · { meta . eyebrow }
279+ { t ( 'hackathon_step' ) } { String ( index + 1 ) . padStart ( 2 , '0' ) } ·{ ' ' }
280+ { t ( meta . eyebrow ) }
279281 </ span >
280- < h3 className = "h5 text-white mt-2" > { meta . title } </ h3 >
281- < p className = "text-white-50 mb-3" > { meta . description } </ p >
282- < div className = "d-grid gap-2" >
282+ < h3 className = "h5 text-white mt-2" > { t ( meta . title ) } </ h3 >
283+ < p className = "text-white-50 mb-3" > { t ( meta . description ) } </ p >
284+ < nav className = "d-grid gap-2" >
283285 { list . map ( ( { name, shared_url } ) => (
284286 < Button
285287 key = { name }
@@ -291,7 +293,7 @@ const HackathonDetail: FC<HackathonDetailProps> = observer(({ activity, hackatho
291293 { name }
292294 </ Button >
293295 ) ) }
294- </ div >
296+ </ nav >
295297 </ Card >
296298 </ Col >
297299 ) ) }
0 commit comments