Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions components/Activity/Hackathon/ActionHub.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FC, PropsWithChildren } from 'react';
import type { FC, PropsWithChildren, ReactNode } from 'react';
import { Col, Container, Row } from 'react-bootstrap';

import { HackathonHeroAction } from './Hero';
Expand All @@ -14,7 +14,7 @@ export interface HackathonActionHubEntry {

export interface HackathonActionHubProps {
entries: HackathonActionHubEntry[];
facts: string[];
facts: ReactNode[];
primaryAction?: HackathonHeroAction;
primaryDescription: string;
primaryTitle: string;
Expand Down Expand Up @@ -90,8 +90,8 @@ export const HackathonActionHub: FC<PropsWithChildren<HackathonActionHubProps>>
</nav>

<ul className={`list-unstyled ${styles.regFacts} d-flex flex-wrap gap-2 mt-4`}>
{facts.map(fact => (
<li key={fact}>{fact}</li>
{facts.map((fact, index) => (
<li key={index}>{fact}</li>
))}
</ul>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/Activity/Hackathon/AgendaCountdown.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.8rem;

li {
.item {
gap: 0.7rem;
box-shadow:
Comment on lines +23 to 25
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The countdown item styling was switched from li to .item, but the mobile override in the @media (max-width: 767px) block still targets li. To avoid relying on the underlying DOM tag (especially after switching to itemClassName), update the media-query selector to target .item as well so the responsive styles keep applying.

Copilot uses AI. Check for mistakes.
inset 0 0 0 1px rgba(255, 255, 255, 0.03),
Expand Down
6 changes: 4 additions & 2 deletions components/Activity/Hackathon/AgendaCountdown.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { TableCellValue } from 'mobx-lark';
import { observer } from 'mobx-react';
import { FC, useContext, useState } from 'react';
import { Countdown, TimeUnit } from 'idea-react';

import { Agenda } from '../../../models/Hackathon';
import { I18nContext } from '../../../models/Translation';
import { Countdown, TimeUnit } from '../../Base/Countdown';
import styles from './AgendaCountdown.module.less';
import { agendaTypeLabelOf, resolveCountdownState } from './utility';

Expand All @@ -15,7 +15,7 @@ export interface AgendaCountdownProps {
units: TimeUnit[];
}

export const AgendaCountdown: FC<AgendaCountdownProps> = observer(
const AgendaCountdown: FC<AgendaCountdownProps> = observer(
({ agendaItems, endTime, startTime, units }) => {
const { t } = useContext(I18nContext);
const [referenceTime, setReferenceTime] = useState(Date.now());
Expand All @@ -38,6 +38,7 @@ export const AgendaCountdown: FC<AgendaCountdownProps> = observer(

<Countdown
className={styles.grid}
itemClassName={`${styles.item} d-flex flex-column align-items-center justify-content-center`}
endTime={countdownTo}
onEnd={() => setReferenceTime(Date.now())}
units={units}
Expand All @@ -46,3 +47,4 @@ export const AgendaCountdown: FC<AgendaCountdownProps> = observer(
);
},
);
export default AgendaCountdown;
18 changes: 10 additions & 8 deletions components/Activity/Hackathon/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { TableCellValue } from 'mobx-lark';
import { FC } from 'react';
import dynamic from 'next/dynamic';
import { FC, ReactNode } from 'react';
import { Container } from 'react-bootstrap';
import { TimeUnit } from 'idea-react';

import { Agenda } from '../../../models/Hackathon';
import { LarkImage } from '../../LarkImage';
import { AgendaCountdown } from './AgendaCountdown';
import { TimeUnit } from '../../Base/Countdown';
import styles from './Hero.module.less';

const AgendaCountdown = dynamic(() => import('./AgendaCountdown'), { ssr: false });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

给倒计时补一个首屏占位,避免 hydration 后推挤 hero 内容。

这里把 AgendaCountdown 切成了客户端懒加载,首屏会先少掉一块高度,CTA 和视觉区块会在 chunk 到达后下移。建议保留一个固定高度的 loading shell,至少把布局占位先稳住。

建议修复
 const AgendaCountdown = dynamic(() => import('./AgendaCountdown'), {
   ssr: false,
+  loading: () => <div style={{ minHeight: 120 }} />,
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/Activity/Hackathon/Hero.tsx` at line 11, Hero 的客户端懒加载
AgendaCountdown 会导致首屏缺少高度、内容被下移;在动态导入(const AgendaCountdown = dynamic(...))
时提供一个固定高度的 loading shell 或在 Hero 里渲染占位元素以稳住布局,确保加载完成前 CTA/视觉区块不会位移;定位到
AgendaCountdown 动态导入和 Hero 组件,将 loading 占位返回一个与实际倒计时高度相同的静态占位
DOM(或样式化容器)以保持布局稳定。


Comment on lines +11 to +12
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic(() => import('./AgendaCountdown')) loses the component’s prop typing (it becomes ComponentType<any>), which can hide incorrect props being passed from this file. Consider exporting/importing AgendaCountdownProps and providing it as the generic to dynamic<AgendaCountdownProps>(...) (or otherwise typing the dynamic component) to keep type-safety.

Copilot uses AI. Check for mistakes.
export type HackathonHeroNavItem = Record<'label' | 'href', string>;

export interface HackathonHeroAction extends HackathonHeroNavItem {
Expand All @@ -26,9 +28,9 @@ export interface HackathonHeroProps extends Record<
string
> {
agendaItems: Agenda[];
badges: string[];
badges: ReactNode[];
bottomCard?: HackathonHeroCard;
chips?: string[];
chips?: ReactNode[];
countdownUnits: TimeUnit[];
endTime?: TableCellValue;
image?: TableCellValue;
Expand Down Expand Up @@ -148,7 +150,7 @@ export const HackathonHero: FC<HackathonHeroProps> = ({
<ul className={`list-unstyled ${styles.heroEyebrow} d-flex flex-wrap gap-3 m-0`}>
{badges.map((badge, index) => (
<li
key={`${badge}-${index}`}
key={index}
className={`${styles.heroBadge} d-inline-flex align-items-center ${BadgeToneClass[index % BadgeToneClass.length]}`}
>
{badge}
Expand Down Expand Up @@ -180,8 +182,8 @@ export const HackathonHero: FC<HackathonHeroProps> = ({
<ul
className={`list-unstyled ${styles.heroStats} d-flex flex-wrap gap-2 gap-md-3 m-0`}
>
{chips.map(chip => (
<li key={chip} className={`${styles.statChip} d-inline-flex align-items-center`}>
{chips.map((chip, index) => (
<li key={index} className={`${styles.statChip} d-inline-flex align-items-center`}>
{chip}
</li>
))}
Expand Down
35 changes: 24 additions & 11 deletions components/Activity/Hackathon/Schedule.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FC } from 'react';
import { FC, ReactNode } from 'react';
import { Container } from 'react-bootstrap';
import { TimeData } from 'web-utility';

import { TimeRange } from '../../TimeRange';
import type { HackathonAwardsMeta } from './Awards';
import styles from './Schedule.module.less';

Expand All @@ -16,11 +18,13 @@ export interface HackathonScheduleFact extends HackathonAwardsMeta {
}

export interface HackathonScheduleItem extends Record<
'id' | 'phase' | 'title' | 'dateText' | 'description',
'id' | 'phase' | 'title' | 'description',
string
> {
facts: HackathonScheduleFact[];

endedAt?: TimeData;
startedAt?: TimeData;
stageGoal?: string;
tone: HackathonScheduleTone;
}
Expand All @@ -30,16 +34,21 @@ export interface HackathonScheduleProps extends Record<
string
> {
items: HackathonScheduleItem[];
keyDates?: Record<'date' | 'label', string>[];
overviewPills: string[];
keyDates?: {
label: string;
startedAt?: TimeData;
endedAt?: TimeData;
}[];
overviewPills: ReactNode[];
}

const ScheduleCard: FC<HackathonScheduleItem & Record<'phaseLabel' | 'stageGoalLabel', string>> = ({
dateText,
description,
endedAt,
facts,
phase,
phaseLabel,
startedAt,
stageGoal,
stageGoalLabel,
title,
Expand All @@ -52,7 +61,9 @@ const ScheduleCard: FC<HackathonScheduleItem & Record<'phaseLabel' | 'stageGoalL
<span className={`${styles.dayNo} d-inline-flex align-items-center`}>
{phaseLabel} {phase}
</span>
<time className={styles.dayDate}>{dateText}</time>
<span className={styles.dayDate}>
<TimeRange start={startedAt} end={endedAt} />
</span>
</div>

<h3 className={`${styles.dayTitle} mt-3 mb-2`}>{title}</h3>
Expand Down Expand Up @@ -108,8 +119,8 @@ export const HackathonSchedule: FC<HackathonScheduleProps & { phaseLabel: string
<ul
className={`list-unstyled ${styles.scheduleOverview} d-flex flex-wrap justify-content-center gap-3 mb-4`}
>
{overviewPills.map(pill => (
<li key={pill} className={styles.schedulePill}>
{overviewPills.map((pill, index) => (
<li key={index} className={styles.schedulePill}>
{pill}
</li>
))}
Expand All @@ -128,12 +139,14 @@ export const HackathonSchedule: FC<HackathonScheduleProps & { phaseLabel: string

{keyDates?.[0] && (
<ul className={`list-unstyled ${styles.keyDates} mt-4`}>
{keyDates.map(({ date, label }, index) => (
{keyDates.map(({ startedAt, endedAt, label }, index) => (
<li
key={`${date}-${label}`}
key={`${startedAt || ''}-${endedAt || ''}-${label}`}
className={`${styles.keyDateCard} d-grid gap-1 ${index % 2 ? styles.keyDateCardWarn : ''}`}
>
<strong className={styles.keyDateValue}>{date}</strong>
<strong className={styles.keyDateValue}>
<TimeRange start={startedAt} end={endedAt} format="MM-DD" />
</strong>
<span className={styles.keyDateLabel}>{label}</span>
</li>
))}
Expand Down
6 changes: 5 additions & 1 deletion components/Activity/Hackathon/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
formatPeriod,
normalizeAgendaType,
previewText,
timeOf,
} from './utility';

export const RequiredTableKeys = [
Expand Down Expand Up @@ -194,13 +195,16 @@ export const buildScheduleItems = (
const description = compactSummaryOf((summary as string) || reasonText, reasonText, 120);
const windowValue = formatPeriod(startedAt, endedAt) || '-';
const focusValue = compactSummaryOf((summary as string) || focusText, focusText, 92);
const startedAtTime = timeOf(startedAt);
const endedAtTime = timeOf(endedAt);

return {
id: id as string,
phase: String(index + 1).padStart(2, '0'),
dateText: formatPeriod(startedAt, endedAt),
title: name as string,
description,
startedAt: Number.isFinite(startedAtTime) ? startedAtTime : undefined,
endedAt: Number.isFinite(endedAtTime) ? endedAtTime : undefined,
stageGoal: stageGoalText,
tone: agendaToneClassOf(type, index),
facts: [
Expand Down
12 changes: 4 additions & 8 deletions components/Activity/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { observer } from 'mobx-react';
import { FilePreview } from 'mobx-restful-table';
import { FC } from 'react';
import { CardProps, Card, Button } from 'react-bootstrap';
import { formatDate } from 'web-utility';
import { Time } from 'idea-react';

import { Product } from '../../models/Hackathon';
import styles from '../../styles/Hackathon.module.less';
Expand Down Expand Up @@ -68,13 +68,9 @@ export const ProductCard: FC<ProductCardProps> = observer(
</div>
)}

<time
suppressHydrationWarning
className="text-dark opacity-75 small"
dateTime={new Date(createdAt as number).toJSON()}
>
📅 {formatDate(createdAt as number)}
</time>
<span className="text-dark opacity-75 small">
📅 <Time dateTime={createdAt as number} format="YYYY-MM-DD" />
</span>
</Card.Body>
</Card>
),
Expand Down
105 changes: 0 additions & 105 deletions components/Base/Countdown.tsx

This file was deleted.

Loading
Loading