From 6f9df1c3e4731b16c9bf9a2fc404af9aaf4fcbec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 20:58:15 +0000 Subject: [PATCH 01/12] Initial plan From a7d2168168acb3953edcb81ed7bea5faf00cf3be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:07:40 +0000 Subject: [PATCH 02/12] Add hackathon detail page with mock data and tech-themed styling Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- models/Hackathon.ts | 330 +++++++++++++++++++++++++++++++++++ pages/hackathon/[id].tsx | 326 ++++++++++++++++++++++++++++++++++ styles/Hackathon.module.scss | 263 ++++++++++++++++++++++++++++ translation/en-US.ts | 45 +++++ translation/zh-CN.ts | 45 +++++ translation/zh-TW.ts | 45 +++++ 6 files changed, 1054 insertions(+) create mode 100644 models/Hackathon.ts create mode 100644 pages/hackathon/[id].tsx create mode 100644 styles/Hackathon.module.scss diff --git a/models/Hackathon.ts b/models/Hackathon.ts new file mode 100644 index 0000000..9aadd14 --- /dev/null +++ b/models/Hackathon.ts @@ -0,0 +1,330 @@ +// Hackathon data types and mock data generator + +export interface Agenda { + summary: string; + name: string; + type: 'workshop' | 'presentation' | 'coding' | 'break' | 'ceremony'; + startedAt: Date; + endedAt: Date; +} + +export interface Person { + name: string; + avatar: string; + gender: 'male' | 'female' | 'other'; + age: number; + address: string; + organizations: string[]; + skills: string[]; + githubLink: string; + githubAccount: string; + createdBy: string; +} + +export interface Organization { + name: string; + logo: string; + link: string; + members: string[]; + prizes: string[]; +} + +export interface Prize { + name: string; + image: string; + price: number; + amount: number; + level: 'gold' | 'silver' | 'bronze' | 'special'; + sponsor: string; +} + +export interface Template { + name: string; + summary: string; + sourceLink: string; + previewLink: string; +} + +export interface Project { + name: string; + summary: string; + createdBy: string; + group: string[]; + members: string[]; + products: string[]; + score: number; +} + +export interface Hackathon { + id: string; + title: string; + description: string; + startDate: Date; + endDate: Date; + location: string; + agenda: Agenda[]; + people: Person[]; + organizations: Organization[]; + prizes: Prize[]; + templates: Template[]; + projects: Project[]; +} + +// Mock data generator +export function generateMockHackathon(id: string): Hackathon { + return { + id, + title: 'Open Source Innovation Hackathon 2026', + description: + 'A 48-hour coding marathon bringing together developers, designers, and innovators to build the future of open source software.', + startDate: new Date('2026-03-15T09:00:00'), + endDate: new Date('2026-03-17T18:00:00'), + location: 'Virtual & On-site (Beijing)', + agenda: [ + { + summary: 'Welcome participants and introduce the hackathon theme', + name: 'Opening Ceremony', + type: 'ceremony', + startedAt: new Date('2026-03-15T09:00:00'), + endedAt: new Date('2026-03-15T10:00:00'), + }, + { + summary: 'Introduction to modern web development frameworks', + name: 'Web Development Workshop', + type: 'workshop', + startedAt: new Date('2026-03-15T10:30:00'), + endedAt: new Date('2026-03-15T12:00:00'), + }, + { + summary: 'Lunch break and networking', + name: 'Lunch & Networking', + type: 'break', + startedAt: new Date('2026-03-15T12:00:00'), + endedAt: new Date('2026-03-15T13:30:00'), + }, + { + summary: 'Teams start working on their projects', + name: 'Coding Session - Day 1', + type: 'coding', + startedAt: new Date('2026-03-15T13:30:00'), + endedAt: new Date('2026-03-15T22:00:00'), + }, + { + summary: 'Continue development and team collaboration', + name: 'Coding Session - Day 2', + type: 'coding', + startedAt: new Date('2026-03-16T09:00:00'), + endedAt: new Date('2026-03-16T22:00:00'), + }, + { + summary: 'Final sprint for project completion', + name: 'Coding Session - Day 3', + type: 'coding', + startedAt: new Date('2026-03-17T09:00:00'), + endedAt: new Date('2026-03-17T14:00:00'), + }, + { + summary: 'Teams present their projects to judges', + name: 'Project Presentations', + type: 'presentation', + startedAt: new Date('2026-03-17T14:30:00'), + endedAt: new Date('2026-03-17T17:00:00'), + }, + { + summary: 'Announce winners and distribute prizes', + name: 'Awards Ceremony', + type: 'ceremony', + startedAt: new Date('2026-03-17T17:00:00'), + endedAt: new Date('2026-03-17T18:00:00'), + }, + ], + people: [ + { + name: 'Li Wei', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=liwei', + gender: 'male', + age: 28, + address: 'Beijing, China', + organizations: ['Tech Innovators', 'Open Source Alliance'], + skills: ['React', 'Node.js', 'TypeScript', 'GraphQL'], + githubLink: 'https://github.com/liwei-dev', + githubAccount: 'liwei-dev', + createdBy: 'admin', + }, + { + name: 'Zhang Mei', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhangmei', + gender: 'female', + age: 26, + address: 'Shanghai, China', + organizations: ['Cloud Computing Society'], + skills: ['Python', 'Django', 'Machine Learning', 'Docker'], + githubLink: 'https://github.com/zhangmei-ml', + githubAccount: 'zhangmei-ml', + createdBy: 'admin', + }, + { + name: 'Wang Jun', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=wangjun', + gender: 'male', + age: 30, + address: 'Shenzhen, China', + organizations: ['DevOps Guild', 'Open Source Alliance'], + skills: ['Kubernetes', 'Go', 'AWS', 'CI/CD'], + githubLink: 'https://github.com/wangjun-ops', + githubAccount: 'wangjun-ops', + createdBy: 'admin', + }, + { + name: 'Chen Xiao', + avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=chenxiao', + gender: 'female', + age: 24, + address: 'Hangzhou, China', + organizations: ['UI/UX Designers Hub'], + skills: ['Figma', 'React', 'CSS', 'Design Systems'], + githubLink: 'https://github.com/chenxiao-design', + githubAccount: 'chenxiao-design', + createdBy: 'admin', + }, + ], + organizations: [ + { + name: 'Tech Innovators', + logo: 'https://api.dicebear.com/7.x/identicon/svg?seed=techinnovators', + link: 'https://techinnovators.example.com', + members: ['Li Wei', 'Wang Jun'], + prizes: ['Best Innovation Award'], + }, + { + name: 'Cloud Computing Society', + logo: 'https://api.dicebear.com/7.x/identicon/svg?seed=cloudcomputing', + link: 'https://cloudcomputing.example.com', + members: ['Zhang Mei'], + prizes: ['Best Cloud Solution'], + }, + { + name: 'Open Source Alliance', + logo: 'https://api.dicebear.com/7.x/identicon/svg?seed=opensource', + link: 'https://opensource.example.com', + members: ['Li Wei', 'Wang Jun'], + prizes: ['Community Choice Award', 'Best Open Source Project'], + }, + { + name: 'UI/UX Designers Hub', + logo: 'https://api.dicebear.com/7.x/identicon/svg?seed=uiuxhub', + link: 'https://uiuxhub.example.com', + members: ['Chen Xiao'], + prizes: ['Best Design Award'], + }, + ], + prizes: [ + { + name: 'Best Innovation Award', + image: 'https://api.dicebear.com/7.x/shapes/svg?seed=gold', + price: 10000, + amount: 1, + level: 'gold', + sponsor: 'Tech Innovators', + }, + { + name: 'Best Cloud Solution', + image: 'https://api.dicebear.com/7.x/shapes/svg?seed=silver', + price: 7000, + amount: 1, + level: 'silver', + sponsor: 'Cloud Computing Society', + }, + { + name: 'Best Design Award', + image: 'https://api.dicebear.com/7.x/shapes/svg?seed=bronze', + price: 5000, + amount: 1, + level: 'bronze', + sponsor: 'UI/UX Designers Hub', + }, + { + name: 'Community Choice Award', + image: 'https://api.dicebear.com/7.x/shapes/svg?seed=special1', + price: 3000, + amount: 2, + level: 'special', + sponsor: 'Open Source Alliance', + }, + { + name: 'Best Open Source Project', + image: 'https://api.dicebear.com/7.x/shapes/svg?seed=special2', + price: 5000, + amount: 1, + level: 'special', + sponsor: 'Open Source Alliance', + }, + ], + templates: [ + { + name: 'React TypeScript Starter', + summary: 'A modern React template with TypeScript, ESLint, and Prettier pre-configured', + sourceLink: 'https://github.com/templates/react-typescript-starter', + previewLink: 'https://react-ts-starter.demo.com', + }, + { + name: 'Next.js Full-stack Template', + summary: 'Complete Next.js setup with API routes, authentication, and database integration', + sourceLink: 'https://github.com/templates/nextjs-fullstack', + previewLink: 'https://nextjs-fullstack.demo.com', + }, + { + name: 'Python FastAPI Starter', + summary: 'FastAPI template with PostgreSQL, Docker, and comprehensive testing setup', + sourceLink: 'https://github.com/templates/fastapi-starter', + previewLink: 'https://fastapi-starter.demo.com', + }, + { + name: 'Kubernetes Microservices', + summary: 'Production-ready microservices architecture with Kubernetes manifests', + sourceLink: 'https://github.com/templates/k8s-microservices', + previewLink: 'https://k8s-microservices.demo.com', + }, + ], + projects: [ + { + name: 'EcoTracker', + summary: + 'A mobile app for tracking personal carbon footprint and suggesting eco-friendly alternatives', + createdBy: 'Li Wei', + group: ['Team Green'], + members: ['Li Wei', 'Chen Xiao'], + products: ['Mobile App', 'API Backend'], + score: 95, + }, + { + name: 'CodeMentor AI', + summary: + 'AI-powered code review assistant that provides real-time suggestions and best practices', + createdBy: 'Zhang Mei', + group: ['AI Innovators'], + members: ['Zhang Mei'], + products: ['VS Code Extension', 'Web Dashboard'], + score: 92, + }, + { + name: 'CloudOps Dashboard', + summary: 'Unified monitoring dashboard for multi-cloud infrastructure management', + createdBy: 'Wang Jun', + group: ['DevOps Masters'], + members: ['Wang Jun'], + products: ['Web Application', 'CLI Tool'], + score: 88, + }, + { + name: 'DesignSync', + summary: 'Collaborative design tool that syncs Figma designs with development code', + createdBy: 'Chen Xiao', + group: ['Design Tech'], + members: ['Chen Xiao', 'Li Wei'], + products: ['Figma Plugin', 'React Component Library'], + score: 90, + }, + ], + }; +} diff --git a/pages/hackathon/[id].tsx b/pages/hackathon/[id].tsx new file mode 100644 index 0000000..34b2165 --- /dev/null +++ b/pages/hackathon/[id].tsx @@ -0,0 +1,326 @@ +import { observer } from 'mobx-react'; +import { GetServerSideProps } from 'next'; +import { FC, useContext } from 'react'; +import { Badge, Button, Card, Col, Container, Row } from 'react-bootstrap'; + +import { PageHead } from '../../components/Layout/PageHead'; +import { generateMockHackathon, Hackathon } from '../../models/Hackathon'; +import { I18nContext } from '../../models/Translation'; +import styles from '../../styles/Hackathon.module.scss'; + +export const getServerSideProps: GetServerSideProps = async ({ params }) => { + const id = params?.id as string; + const hackathon = generateMockHackathon(id); + + return { + props: { + hackathon: { + ...hackathon, + startDate: hackathon.startDate.toISOString(), + endDate: hackathon.endDate.toISOString(), + agenda: hackathon.agenda.map(item => ({ + ...item, + startedAt: item.startedAt.toISOString(), + endedAt: item.endedAt.toISOString(), + })), + }, + }, + }; +}; + +interface HackathonDetailProps { + hackathon: Omit & { + startDate: string; + endDate: string; + agenda: Array<{ + summary: string; + name: string; + type: string; + startedAt: string; + endedAt: string; + }>; + }; +} + +const HackathonDetail: FC = observer(({ hackathon }) => { + const { t } = useContext(I18nContext); + + const formatDateTime = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + const getTypeColor = (type: string) => { + const colors: Record = { + workshop: 'info', + presentation: 'danger', + coding: 'success', + break: 'warning', + ceremony: 'primary', + }; + return colors[type] || 'secondary'; + }; + + const getLevelColor = (level: string) => { + const colors: Record = { + gold: 'warning', + silver: 'secondary', + bronze: 'dark', + special: 'info', + }; + return colors[level] || 'primary'; + }; + + return ( + <> + + + {/* Hero Section */} +
+ +

{hackathon.title}

+

{hackathon.description}

+ + + + + +
📍 {t('event_location')}
+

{hackathon.location}

+
+
+ + + + +
⏰ {t('event_duration')}
+

+ {formatDateTime(hackathon.startDate)} - {formatDateTime(hackathon.endDate)} +

+
+
+ +
+
+
+ + + {/* Agenda Section */} +
+

📅 {t('agenda')}

+
+ {hackathon.agenda.map((item, index) => ( +
+ + +

{item.name}

+

{item.summary}

+ + + + {t(item.type)} + +
+ {formatDateTime(item.startedAt)} - {formatDateTime(item.endedAt)} +
+ +
+
+ ))} +
+
+ + {/* Participants Section */} +
+

👥 {t('participants')}

+ + {hackathon.people.map((person, index) => ( + + + +
+ {person.name} +
+
{person.name}
+
+ {t('gender')}: {t(person.gender)} +
+
+ {t('age')}: {person.age} +
+
+ {t('address')}: {person.address} +
+
+
+ {t('skills')}: +
+ {person.skills.map((skill, idx) => ( + + {skill} + + ))} +
+ +
+
+ + ))} +
+
+ + {/* Organizations Section */} +
+

🏢 {t('organizations')}

+ + {hackathon.organizations.map((org, index) => ( + + + + + + {org.name} + + +
{org.name}
+ + {org.link} + + +
+
+
+ {t('members')}: {org.members.join(', ')} +
+
+ {t('prizes')}: {org.prizes.join(', ')} +
+
+
+ + ))} +
+
+ + {/* Prizes Section */} +
+

🏆 {t('prizes')}

+ + {hackathon.prizes.map((prize, index) => ( + + + + {prize.name} +
{prize.name}
+
+ {t(prize.level)} + ¥{prize.price.toLocaleString()} +
+
+ {t('amount')}: {prize.amount} +
+
+ {t('sponsor')}: {prize.sponsor} +
+
+
+ + ))} +
+
+ + {/* Templates Section */} +
+

🛠️ {t('templates')}

+ + {hackathon.templates.map((template, index) => ( + + + +
{template.name}
+

{template.summary}

+
+ + +
+
+
+ + ))} +
+
+ + {/* Projects Section */} +
+

💡 {t('projects')}

+
+ {hackathon.projects.map((project, index) => ( + + + + +

{project.name}

+

{project.summary}

+ + + {t('created_by')}: {project.createdBy} + + + {t('group')}: {project.group.join(', ')} + + + {t('members')}: {project.members.join(', ')} + + + {t('products')}: {project.products.join(', ')} + + + + +
{project.score}
+
{t('score')}
+ +
+
+
+ ))} +
+
+
+ + ); +}); + +export default HackathonDetail; diff --git a/styles/Hackathon.module.scss b/styles/Hackathon.module.scss new file mode 100644 index 0000000..ee22b71 --- /dev/null +++ b/styles/Hackathon.module.scss @@ -0,0 +1,263 @@ +// Tech-themed hackathon styling with dark/neon aesthetic + +.hero { + position: relative; + background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); + padding: 4rem 0; + overflow: hidden; + color: #fff; + + &::before { + position: absolute; + top: -50%; + left: -50%; + animation: grid-animation 20s linear infinite; + background: radial-gradient(circle, rgba(0, 255, 255, 0.1) 1px, transparent 1px); + background-size: 50px 50px; + width: 200%; + height: 200%; + pointer-events: none; + content: ''; + } +} + +@keyframes grid-animation { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(50px, 50px); + } +} + +.title { + margin-bottom: 1rem; + font-weight: 700; + font-size: 3rem; + text-shadow: 0 0 20px rgba(0, 255, 255, 0.5); +} + +.description { + opacity: 0.9; + margin: 0 auto; + max-width: 800px; + font-size: 1.2rem; +} + +.infoCard { + backdrop-filter: blur(10px); + transition: all 0.3s ease; + margin-bottom: 1rem; + border: 1px solid rgba(0, 255, 255, 0.3); + border-radius: 12px; + background: rgba(255, 255, 255, 0.05); + padding: 1.5rem; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 0 20px rgba(0, 255, 255, 0.2); + border-color: rgba(0, 255, 255, 0.6); + } +} + +.section { + position: relative; + padding: 3rem 0; +} + +.sectionTitle { + display: inline-block; + margin-bottom: 2rem; + border-bottom: 3px solid; + border-image: linear-gradient(90deg, #00ffff, #0080ff) 1; + padding-bottom: 0.5rem; + font-weight: 700; + font-size: 2rem; +} + +.agendaItem { + transition: all 0.3s ease; + margin-bottom: 1rem; + border-left: 4px solid; + border-radius: 8px; + background: linear-gradient(135deg, rgba(15, 12, 41, 0.8) 0%, rgba(48, 43, 99, 0.6) 100%); + padding: 1.5rem; + + &:hover { + transform: translateX(5px); + box-shadow: 0 4px 15px rgba(0, 255, 255, 0.2); + } + + &.workshop { + border-left-color: #00ffff; + } + + &.presentation { + border-left-color: #ff6b6b; + } + + &.coding { + border-left-color: #51cf66; + } + + &.break { + border-left-color: #ffd43b; + } + + &.ceremony { + border-left-color: #a78bfa; + } +} + +.personCard { + transition: all 0.3s ease; + margin-bottom: 1.5rem; + border: 1px solid rgba(0, 255, 255, 0.2); + border-radius: 12px; + background: linear-gradient(135deg, rgba(15, 12, 41, 0.9) 0%, rgba(48, 43, 99, 0.7) 100%); + padding: 1.5rem; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 8px 25px rgba(0, 255, 255, 0.15); + border-color: rgba(0, 255, 255, 0.5); + } +} + +.avatar { + box-shadow: 0 0 15px rgba(0, 255, 255, 0.3); + border: 3px solid rgba(0, 255, 255, 0.5); + border-radius: 50%; + width: 100px; + height: 100px; +} + +.skillBadge { + display: inline-block; + margin: 0.25rem; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); + border-radius: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 0.25rem 0.75rem; + color: #fff; + font-size: 0.85rem; +} + +.orgCard { + transition: all 0.3s ease; + margin-bottom: 1.5rem; + border: 2px solid rgba(0, 255, 255, 0.3); + border-radius: 12px; + background: linear-gradient(135deg, rgba(15, 12, 41, 0.95) 0%, rgba(36, 36, 62, 0.8) 100%); + padding: 2rem; + + &:hover { + box-shadow: 0 0 30px rgba(0, 255, 255, 0.2); + border-color: rgba(0, 255, 255, 0.6); + } +} + +.logo { + box-shadow: 0 0 15px rgba(0, 255, 255, 0.2); + border: 2px solid rgba(0, 255, 255, 0.4); + border-radius: 12px; + width: 80px; + height: 80px; +} + +.prizeCard { + transition: all 0.3s ease; + margin-bottom: 1.5rem; + border: 2px solid; + border-radius: 12px; + background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, rgba(255, 165, 0, 0.05) 100%); + padding: 1.5rem; + + &.gold { + box-shadow: 0 0 20px rgba(255, 215, 0, 0.3); + border-color: #ffd700; + } + + &.silver { + box-shadow: 0 0 20px rgba(192, 192, 192, 0.3); + border-color: #c0c0c0; + } + + &.bronze { + box-shadow: 0 0 20px rgba(205, 127, 50, 0.3); + border-color: #cd7f32; + } + + &.special { + box-shadow: 0 0 20px rgba(167, 139, 250, 0.3); + border-color: #a78bfa; + } + + &:hover { + transform: scale(1.02); + } +} + +.prizeImage { + margin-bottom: 1rem; + border-radius: 8px; + width: 100%; + height: 150px; + object-fit: contain; +} + +.templateCard { + transition: all 0.3s ease; + margin-bottom: 1.5rem; + border: 1px solid rgba(0, 255, 255, 0.3); + border-radius: 12px; + background: linear-gradient(135deg, rgba(15, 12, 41, 0.9) 0%, rgba(48, 43, 99, 0.7) 100%); + padding: 1.5rem; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 8px 25px rgba(0, 255, 255, 0.2); + border-color: rgba(0, 255, 255, 0.6); + } +} + +.projectCard { + transition: all 0.3s ease; + margin-bottom: 2rem; + border: 2px solid rgba(81, 207, 102, 0.3); + border-radius: 12px; + background: linear-gradient(135deg, rgba(15, 12, 41, 0.95) 0%, rgba(48, 43, 99, 0.8) 100%); + padding: 2rem; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 0 30px rgba(81, 207, 102, 0.2); + border-color: rgba(81, 207, 102, 0.6); + } +} + +.scoreCircle { + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 0 20px rgba(81, 207, 102, 0.4); + border-radius: 50%; + background: linear-gradient(135deg, #51cf66 0%, #37b24d 100%); + width: 80px; + height: 80px; + color: #fff; + font-weight: 700; + font-size: 1.5rem; +} + +.techGradient { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 700; +} + +.glowText { + text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); +} diff --git a/translation/en-US.ts b/translation/en-US.ts index b7fc156..5f4732d 100644 --- a/translation/en-US.ts +++ b/translation/en-US.ts @@ -186,4 +186,49 @@ export default { finance_edu_next_2: 'Find corresponding ETFs or index funds, paying attention to fees and fund size.', finance_edu_next_3: 'Set up a recurring investment plan and track valuation and drawdown.', + + // Hackathon + hackathon_detail: 'Hackathon Details', + event_info: 'Event Information', + event_description: 'Event Description', + event_location: 'Location', + event_duration: 'Duration', + agenda: 'Agenda', + participants: 'Participants', + organizations: 'Organizations', + prizes: 'Prizes', + templates: 'Templates', + projects: 'Projects', + type: 'Type', + start_time: 'Start Time', + end_time: 'End Time', + workshop: 'Workshop', + presentation: 'Presentation', + coding: 'Coding', + break: 'Break', + ceremony: 'Ceremony', + gender: 'Gender', + age: 'Age', + address: 'Address', + skills: 'Skills', + github_account: 'GitHub Account', + view_github: 'View GitHub', + members: 'Members', + sponsor: 'Sponsor', + price: 'Prize Money', + amount: 'Amount', + level: 'Level', + gold: 'Gold', + silver: 'Silver', + bronze: 'Bronze', + special: 'Special', + source_code: 'Source Code', + preview: 'Preview', + created_by: 'Created By', + group: 'Team', + products: 'Products', + score: 'Score', + male: 'Male', + female: 'Female', + other: 'Other', }; diff --git a/translation/zh-CN.ts b/translation/zh-CN.ts index 7fe7629..84ec574 100644 --- a/translation/zh-CN.ts +++ b/translation/zh-CN.ts @@ -182,4 +182,49 @@ export default { finance_edu_next_1: '挑选 2-3 支指数,加入收藏/关注列表。', finance_edu_next_2: '了解对应的 ETF 或联接基金,关注费率与规模。', finance_edu_next_3: '设定定投计划并持续跟踪估值、回撤。', + + // Hackathon + hackathon_detail: '黑客松详情', + event_info: '活动信息', + event_description: '活动描述', + event_location: '活动地点', + event_duration: '活动时间', + agenda: '日程安排', + participants: '参与者', + organizations: '组织方', + prizes: '奖项', + templates: '项目模板', + projects: '参赛项目', + type: '类型', + start_time: '开始时间', + end_time: '结束时间', + workshop: '工作坊', + presentation: '展示', + coding: '编程', + break: '休息', + ceremony: '仪式', + gender: '性别', + age: '年龄', + address: '地址', + skills: '技能', + github_account: 'GitHub 账号', + view_github: '查看 GitHub', + members: '成员', + sponsor: '赞助商', + price: '奖金', + amount: '数量', + level: '等级', + gold: '金奖', + silver: '银奖', + bronze: '铜奖', + special: '特别奖', + source_code: '源代码', + preview: '预览', + created_by: '创建者', + group: '团队', + products: '产品', + score: '评分', + male: '男', + female: '女', + other: '其他', }; diff --git a/translation/zh-TW.ts b/translation/zh-TW.ts index bfabbad..ca9922c 100644 --- a/translation/zh-TW.ts +++ b/translation/zh-TW.ts @@ -182,4 +182,49 @@ export default { finance_edu_next_1: '挑選 2-3 支指數,加入收藏/關注列表。', finance_edu_next_2: '了解對應的 ETF 或聯接基金,關注費率與規模。', finance_edu_next_3: '設定定投計畫並持續跟蹤估值、回撤。', + + // Hackathon + hackathon_detail: '黑客松詳情', + event_info: '活動資訊', + event_description: '活動描述', + event_location: '活動地點', + event_duration: '活動時間', + agenda: '日程安排', + participants: '參與者', + organizations: '組織方', + prizes: '獎項', + templates: '項目模板', + projects: '參賽項目', + type: '類型', + start_time: '開始時間', + end_time: '結束時間', + workshop: '工作坊', + presentation: '展示', + coding: '編程', + break: '休息', + ceremony: '儀式', + gender: '性別', + age: '年齡', + address: '地址', + skills: '技能', + github_account: 'GitHub 帳號', + view_github: '查看 GitHub', + members: '成員', + sponsor: '贊助商', + price: '獎金', + amount: '數量', + level: '等級', + gold: '金獎', + silver: '銀獎', + bronze: '銅獎', + special: '特別獎', + source_code: '源代碼', + preview: '預覽', + created_by: '創建者', + group: '團隊', + products: '產品', + score: '評分', + male: '男', + female: '女', + other: '其他', }; From 7f14036e8d9296c211506cd0a4af44c8ba9cd0db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:12:16 +0000 Subject: [PATCH 03/12] Fix TypeScript type issues in hackathon page Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- .env | 2 ++ pages/hackathon/[id].tsx | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.env b/.env index dc5c88f..eef0760 100644 --- a/.env +++ b/.env @@ -7,3 +7,5 @@ NEXT_PUBLIC_LARK_APP_ID = cli_a8094a652022900d NEXT_PUBLIC_LARK_WIKI_URL = https://open-source-bazaar.feishu.cn/wiki/space/7052192153363054596 NEXT_PUBLIC_STRAPI_API_HOST = https://china-ngo-db.onrender.com/api/ + +JWT_SECRET=dummy_secret_for_build_only diff --git a/pages/hackathon/[id].tsx b/pages/hackathon/[id].tsx index 34b2165..739989e 100644 --- a/pages/hackathon/[id].tsx +++ b/pages/hackathon/[id].tsx @@ -123,7 +123,7 @@ const HackathonDetail: FC = observer(({ hackathon }) => { - {t(item.type)} + {t(item.type as any)}
{formatDateTime(item.startedAt)} - {formatDateTime(item.endedAt)} @@ -148,7 +148,7 @@ const HackathonDetail: FC = observer(({ hackathon }) => {
{person.name}
- {t('gender')}: {t(person.gender)} + {t('gender')}: {t(person.gender as any)}
{t('age')}: {person.age} @@ -231,7 +231,7 @@ const HackathonDetail: FC = observer(({ hackathon }) => { {prize.name}
{prize.name}
- {t(prize.level)} + {t(prize.level as any)} ¥{prize.price.toLocaleString()}
From 42636ae44a364f19ce60dfc460e06370e0d96268 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:13:16 +0000 Subject: [PATCH 04/12] Remove JWT_SECRET from .env (not needed for hackathon page) --- .env | 2 -- 1 file changed, 2 deletions(-) diff --git a/.env b/.env index eef0760..dc5c88f 100644 --- a/.env +++ b/.env @@ -7,5 +7,3 @@ NEXT_PUBLIC_LARK_APP_ID = cli_a8094a652022900d NEXT_PUBLIC_LARK_WIKI_URL = https://open-source-bazaar.feishu.cn/wiki/space/7052192153363054596 NEXT_PUBLIC_STRAPI_API_HOST = https://china-ngo-db.onrender.com/api/ - -JWT_SECRET=dummy_secret_for_build_only From ef55436e35749acc03ff5659d8f699b519835276 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:34:08 +0000 Subject: [PATCH 05/12] Update color scheme to blue-purple gradient with better contrast Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- styles/Hackathon.module.scss | 85 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/styles/Hackathon.module.scss b/styles/Hackathon.module.scss index ee22b71..1549fa1 100644 --- a/styles/Hackathon.module.scss +++ b/styles/Hackathon.module.scss @@ -1,8 +1,8 @@ -// Tech-themed hackathon styling with dark/neon aesthetic +// Tech-themed hackathon styling with blue-purple gradient .hero { position: relative; - background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); + background: radial-gradient(circle at top left, #312e81, #1e1b4b 55%, #0f172a); padding: 4rem 0; overflow: hidden; color: #fff; @@ -12,7 +12,7 @@ top: -50%; left: -50%; animation: grid-animation 20s linear infinite; - background: radial-gradient(circle, rgba(0, 255, 255, 0.1) 1px, transparent 1px); + background: radial-gradient(circle, rgba(99, 102, 241, 0.08) 1px, transparent 1px); background-size: 50px 50px; width: 200%; height: 200%; @@ -34,7 +34,7 @@ margin-bottom: 1rem; font-weight: 700; font-size: 3rem; - text-shadow: 0 0 20px rgba(0, 255, 255, 0.5); + text-shadow: 0 0 20px rgba(99, 102, 241, 0.4); } .description { @@ -48,15 +48,15 @@ backdrop-filter: blur(10px); transition: all 0.3s ease; margin-bottom: 1rem; - border: 1px solid rgba(0, 255, 255, 0.3); + border: 1px solid rgba(99, 102, 241, 0.3); border-radius: 12px; - background: rgba(255, 255, 255, 0.05); + background: rgba(15, 23, 42, 0.45); padding: 1.5rem; &:hover { transform: translateY(-2px); - box-shadow: 0 0 20px rgba(0, 255, 255, 0.2); - border-color: rgba(0, 255, 255, 0.6); + box-shadow: 0 0 20px rgba(99, 102, 241, 0.25); + border-color: rgba(99, 102, 241, 0.5); } } @@ -69,7 +69,7 @@ display: inline-block; margin-bottom: 2rem; border-bottom: 3px solid; - border-image: linear-gradient(90deg, #00ffff, #0080ff) 1; + border-image: linear-gradient(90deg, #6366f1, #4f46e5) 1; padding-bottom: 0.5rem; font-weight: 700; font-size: 2rem; @@ -80,53 +80,54 @@ margin-bottom: 1rem; border-left: 4px solid; border-radius: 8px; - background: linear-gradient(135deg, rgba(15, 12, 41, 0.8) 0%, rgba(48, 43, 99, 0.6) 100%); + background: rgba(30, 27, 75, 0.5); padding: 1.5rem; &:hover { transform: translateX(5px); - box-shadow: 0 4px 15px rgba(0, 255, 255, 0.2); + box-shadow: 0 4px 15px rgba(99, 102, 241, 0.15); + background: rgba(30, 27, 75, 0.7); } &.workshop { - border-left-color: #00ffff; + border-left-color: #3b82f6; } &.presentation { - border-left-color: #ff6b6b; + border-left-color: #ef4444; } &.coding { - border-left-color: #51cf66; + border-left-color: #10b981; } &.break { - border-left-color: #ffd43b; + border-left-color: #f59e0b; } &.ceremony { - border-left-color: #a78bfa; + border-left-color: #8b5cf6; } } .personCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgba(0, 255, 255, 0.2); + border: 1px solid rgba(99, 102, 241, 0.25); border-radius: 12px; - background: linear-gradient(135deg, rgba(15, 12, 41, 0.9) 0%, rgba(48, 43, 99, 0.7) 100%); + background: rgba(30, 27, 75, 0.6); padding: 1.5rem; &:hover { transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(0, 255, 255, 0.15); - border-color: rgba(0, 255, 255, 0.5); + box-shadow: 0 8px 25px rgba(99, 102, 241, 0.2); + border-color: rgba(99, 102, 241, 0.4); } } .avatar { - box-shadow: 0 0 15px rgba(0, 255, 255, 0.3); - border: 3px solid rgba(0, 255, 255, 0.5); + box-shadow: 0 0 15px rgba(99, 102, 241, 0.25); + border: 3px solid rgba(99, 102, 241, 0.4); border-radius: 50%; width: 100px; height: 100px; @@ -135,9 +136,9 @@ .skillBadge { display: inline-block; margin: 0.25rem; - box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25); border-radius: 20px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); padding: 0.25rem 0.75rem; color: #fff; font-size: 0.85rem; @@ -146,20 +147,20 @@ .orgCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 2px solid rgba(0, 255, 255, 0.3); + border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; - background: linear-gradient(135deg, rgba(15, 12, 41, 0.95) 0%, rgba(36, 36, 62, 0.8) 100%); + background: rgba(30, 27, 75, 0.65); padding: 2rem; &:hover { - box-shadow: 0 0 30px rgba(0, 255, 255, 0.2); - border-color: rgba(0, 255, 255, 0.6); + box-shadow: 0 0 30px rgba(99, 102, 241, 0.25); + border-color: rgba(99, 102, 241, 0.5); } } .logo { - box-shadow: 0 0 15px rgba(0, 255, 255, 0.2); - border: 2px solid rgba(0, 255, 255, 0.4); + box-shadow: 0 0 15px rgba(99, 102, 241, 0.2); + border: 2px solid rgba(99, 102, 241, 0.35); border-radius: 12px; width: 80px; height: 80px; @@ -209,30 +210,30 @@ .templateCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgba(0, 255, 255, 0.3); + border: 1px solid rgba(99, 102, 241, 0.25); border-radius: 12px; - background: linear-gradient(135deg, rgba(15, 12, 41, 0.9) 0%, rgba(48, 43, 99, 0.7) 100%); + background: rgba(30, 27, 75, 0.6); padding: 1.5rem; &:hover { transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(0, 255, 255, 0.2); - border-color: rgba(0, 255, 255, 0.6); + box-shadow: 0 8px 25px rgba(99, 102, 241, 0.2); + border-color: rgba(99, 102, 241, 0.4); } } .projectCard { transition: all 0.3s ease; margin-bottom: 2rem; - border: 2px solid rgba(81, 207, 102, 0.3); + border: 2px solid rgba(16, 185, 129, 0.3); border-radius: 12px; - background: linear-gradient(135deg, rgba(15, 12, 41, 0.95) 0%, rgba(48, 43, 99, 0.8) 100%); + background: rgba(30, 27, 75, 0.65); padding: 2rem; &:hover { transform: translateY(-5px); - box-shadow: 0 0 30px rgba(81, 207, 102, 0.2); - border-color: rgba(81, 207, 102, 0.6); + box-shadow: 0 0 30px rgba(16, 185, 129, 0.25); + border-color: rgba(16, 185, 129, 0.5); } } @@ -240,9 +241,9 @@ display: flex; justify-content: center; align-items: center; - box-shadow: 0 0 20px rgba(81, 207, 102, 0.4); + box-shadow: 0 0 20px rgba(16, 185, 129, 0.35); border-radius: 50%; - background: linear-gradient(135deg, #51cf66 0%, #37b24d 100%); + background: linear-gradient(135deg, #10b981 0%, #059669 100%); width: 80px; height: 80px; color: #fff; @@ -251,7 +252,7 @@ } .techGradient { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -259,5 +260,5 @@ } .glowText { - text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); + text-shadow: 0 0 10px rgba(99, 102, 241, 0.4); } From a167b04d6ce76c6011dc4c23ea857250e0069b15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:52:23 +0000 Subject: [PATCH 06/12] Reorganize hackathon page with pink gradient cards and LESS styling Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- pages/hackathon/[id].tsx | 272 ++++++++---------- ...thon.module.scss => Hackathon.module.less} | 165 +++++------ 2 files changed, 191 insertions(+), 246 deletions(-) rename styles/{Hackathon.module.scss => Hackathon.module.less} (61%) diff --git a/pages/hackathon/[id].tsx b/pages/hackathon/[id].tsx index 739989e..f8d90f9 100644 --- a/pages/hackathon/[id].tsx +++ b/pages/hackathon/[id].tsx @@ -6,7 +6,7 @@ import { Badge, Button, Card, Col, Container, Row } from 'react-bootstrap'; import { PageHead } from '../../components/Layout/PageHead'; import { generateMockHackathon, Hackathon } from '../../models/Hackathon'; import { I18nContext } from '../../models/Translation'; -import styles from '../../styles/Hackathon.module.scss'; +import styles from '../../styles/Hackathon.module.less'; export const getServerSideProps: GetServerSideProps = async ({ params }) => { const id = params?.id as string; @@ -110,154 +110,101 @@ const HackathonDetail: FC = observer(({ hackathon }) => { - {/* Agenda Section */} -
-

📅 {t('agenda')}

-
- {hackathon.agenda.map((item, index) => ( -
- - -

{item.name}

-

{item.summary}

- - - - {t(item.type as any)} - -
- {formatDateTime(item.startedAt)} - {formatDateTime(item.endedAt)} -
- -
-
- ))} -
-
- - {/* Participants Section */} -
-

👥 {t('participants')}

- - {hackathon.people.map((person, index) => ( - - - -
- {person.name} -
-
{person.name}
-
- {t('gender')}: {t(person.gender as any)} -
-
- {t('age')}: {person.age} -
-
- {t('address')}: {person.address} -
-
-
- {t('skills')}: + {/* Header: Agenda and Prizes side by side */} + + {/* Agenda Section */} + +
+

📅 {t('agenda')}

+
+ {hackathon.agenda.map((item, index) => ( +
+
{item.name}
+

{item.summary}

+
+ + {t(item.type as any)} + +
+ {formatDateTime(item.startedAt)} - {formatDateTime(item.endedAt)}
- {person.skills.map((skill, idx) => ( - - {skill} - - ))}
- - - - - ))} - -
+
+ ))} +
+
+ - {/* Organizations Section */} -
-

🏢 {t('organizations')}

- - {hackathon.organizations.map((org, index) => ( - - - + {/* Prizes Section - Frameless vertical cards */} + +
+

🏆 {t('prizes')}

+
+ {hackathon.prizes.map((prize, index) => ( +
- {org.name} + {prize.name} -
{org.name}
- - {org.link} - +
{prize.name}
+
+ {t(prize.level as any)} + + ¥{prize.price.toLocaleString()} + +
+
+ {t('sponsor')}: {prize.sponsor} • {t('amount')}: {prize.amount} +
-
-
- {t('members')}: {org.members.join(', ')} -
-
- {t('prizes')}: {org.prizes.join(', ')} -
- - - - ))} - -
+
+ ))} +
+ + + - {/* Prizes Section */} + {/* Mid-front: Organizations - Frameless vertical cards */}
-

🏆 {t('prizes')}

- - {hackathon.prizes.map((prize, index) => ( - - - - {prize.name} -
{prize.name}
-
- {t(prize.level as any)} - ¥{prize.price.toLocaleString()} -
-
- {t('amount')}: {prize.amount} -
-
- {t('sponsor')}: {prize.sponsor} -
-
-
- +

🏢 {t('organizations')}

+
+ {hackathon.organizations.map((org, index) => ( +
+ + + {org.name} + + +
{org.name}
+ + {org.link} + + +
+
+ {t('members')}: {org.members.join(', ')} +
+
+ {t('prizes')}: {org.prizes.join(', ')} +
+
))} - +
- {/* Templates Section */} + {/* Mid-back: Templates - Narrow cards, 3-4 per row */}

🛠️ {t('templates')}

{hackathon.templates.map((template, index) => ( - + -
{template.name}
-

{template.summary}

-
+
{template.name}
+

{template.summary}

+
- {/* Projects Section */} + {/* Mid-back: Projects - Narrow cards, 3-4 per row */}

💡 {t('projects')}

-
+ {hackathon.projects.map((project, index) => ( - - - - -

{project.name}

-

{project.summary}

- - - {t('created_by')}: {project.createdBy} - - - {t('group')}: {project.group.join(', ')} - - - {t('members')}: {project.members.join(', ')} - - - {t('products')}: {project.products.join(', ')} - - - - + + + +
+
{project.name}
{project.score}
-
{t('score')}
- - - - +
+

{project.summary}

+
+ {t('created_by')}: {project.createdBy} +
+
+ {t('group')}: {project.group.join(', ')} +
+
+ {t('members')}: {project.members.join(', ')} +
+
+
+ + ))} +
+
+ + {/* Footer: Participants - Circular avatars only */} +
+

👥 {t('participants')}

+
+ {hackathon.people.map((person, index) => ( +
+ {person.name} +
))}
diff --git a/styles/Hackathon.module.scss b/styles/Hackathon.module.less similarity index 61% rename from styles/Hackathon.module.scss rename to styles/Hackathon.module.less index 1549fa1..8dbbd63 100644 --- a/styles/Hackathon.module.scss +++ b/styles/Hackathon.module.less @@ -1,4 +1,4 @@ -// Tech-themed hackathon styling with blue-purple gradient +// Tech-themed hackathon styling with blue-purple gradient and pink cards .hero { position: relative; @@ -75,18 +75,19 @@ font-size: 2rem; } +// Agenda items with gradient pink background .agendaItem { transition: all 0.3s ease; margin-bottom: 1rem; border-left: 4px solid; border-radius: 8px; - background: rgba(30, 27, 75, 0.5); + background: linear-gradient(135deg, rgba(236, 72, 153, 0.15), rgba(219, 39, 119, 0.1)); padding: 1.5rem; &:hover { transform: translateX(5px); - box-shadow: 0 4px 15px rgba(99, 102, 241, 0.15); - background: rgba(30, 27, 75, 0.7); + box-shadow: 0 4px 15px rgba(236, 72, 153, 0.2); + background: linear-gradient(135deg, rgba(236, 72, 153, 0.2), rgba(219, 39, 119, 0.15)); } &.workshop { @@ -110,130 +111,82 @@ } } -.personCard { +// Frameless prize cards with gradient pink +.prizeCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgba(99, 102, 241, 0.25); - border-radius: 12px; - background: rgba(30, 27, 75, 0.6); + border: none; + border-radius: 0; + background: linear-gradient(135deg, rgba(244, 114, 182, 0.12), rgba(236, 72, 153, 0.08)); padding: 1.5rem; &:hover { - transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(99, 102, 241, 0.2); - border-color: rgba(99, 102, 241, 0.4); + transform: translateY(-3px); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.18), rgba(236, 72, 153, 0.12)); } } -.avatar { - box-shadow: 0 0 15px rgba(99, 102, 241, 0.25); - border: 3px solid rgba(99, 102, 241, 0.4); - border-radius: 50%; - width: 100px; - height: 100px; -} - -.skillBadge { - display: inline-block; - margin: 0.25rem; - box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25); - border-radius: 20px; - background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); - padding: 0.25rem 0.75rem; - color: #fff; - font-size: 0.85rem; +.prizeImage { + margin-bottom: 1rem; + border-radius: 8px; + width: 100%; + height: 120px; + object-fit: contain; } +// Frameless organization cards with gradient pink .orgCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 2px solid rgba(99, 102, 241, 0.3); - border-radius: 12px; - background: rgba(30, 27, 75, 0.65); + border: none; + border-radius: 0; + background: linear-gradient(135deg, rgba(244, 114, 182, 0.12), rgba(236, 72, 153, 0.08)); padding: 2rem; &:hover { - box-shadow: 0 0 30px rgba(99, 102, 241, 0.25); - border-color: rgba(99, 102, 241, 0.5); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.18), rgba(236, 72, 153, 0.12)); } } .logo { - box-shadow: 0 0 15px rgba(99, 102, 241, 0.2); - border: 2px solid rgba(99, 102, 241, 0.35); + box-shadow: 0 0 15px rgba(236, 72, 153, 0.2); + border: 2px solid rgba(236, 72, 153, 0.3); border-radius: 12px; width: 80px; height: 80px; } -.prizeCard { - transition: all 0.3s ease; - margin-bottom: 1.5rem; - border: 2px solid; - border-radius: 12px; - background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, rgba(255, 165, 0, 0.05) 100%); - padding: 1.5rem; - - &.gold { - box-shadow: 0 0 20px rgba(255, 215, 0, 0.3); - border-color: #ffd700; - } - - &.silver { - box-shadow: 0 0 20px rgba(192, 192, 192, 0.3); - border-color: #c0c0c0; - } - - &.bronze { - box-shadow: 0 0 20px rgba(205, 127, 50, 0.3); - border-color: #cd7f32; - } - - &.special { - box-shadow: 0 0 20px rgba(167, 139, 250, 0.3); - border-color: #a78bfa; - } - - &:hover { - transform: scale(1.02); - } -} - -.prizeImage { - margin-bottom: 1rem; - border-radius: 8px; - width: 100%; - height: 150px; - object-fit: contain; -} - +// Narrow template cards with gradient pink .templateCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgba(99, 102, 241, 0.25); + border: 1px solid rgba(236, 72, 153, 0.2); border-radius: 12px; - background: rgba(30, 27, 75, 0.6); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.1), rgba(236, 72, 153, 0.05)); padding: 1.5rem; &:hover { transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(99, 102, 241, 0.2); - border-color: rgba(99, 102, 241, 0.4); + box-shadow: 0 8px 25px rgba(236, 72, 153, 0.15); + border-color: rgba(236, 72, 153, 0.35); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); } } +// Narrow project cards with gradient pink .projectCard { transition: all 0.3s ease; - margin-bottom: 2rem; - border: 2px solid rgba(16, 185, 129, 0.3); + margin-bottom: 1.5rem; + border: 1px solid rgba(236, 72, 153, 0.2); border-radius: 12px; - background: rgba(30, 27, 75, 0.65); - padding: 2rem; + background: linear-gradient(135deg, rgba(244, 114, 182, 0.1), rgba(236, 72, 153, 0.05)); + padding: 1.5rem; &:hover { transform: translateY(-5px); - box-shadow: 0 0 30px rgba(16, 185, 129, 0.25); - border-color: rgba(16, 185, 129, 0.5); + box-shadow: 0 8px 25px rgba(236, 72, 153, 0.15); + border-color: rgba(236, 72, 153, 0.35); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); } } @@ -244,11 +197,38 @@ box-shadow: 0 0 20px rgba(16, 185, 129, 0.35); border-radius: 50%; background: linear-gradient(135deg, #10b981 0%, #059669 100%); + width: 60px; + height: 60px; + color: #fff; + font-weight: 700; + font-size: 1.2rem; +} + +// Circular avatars only for participants +.avatar { + box-shadow: 0 0 15px rgba(236, 72, 153, 0.25); + border: 3px solid rgba(236, 72, 153, 0.4); + border-radius: 50%; width: 80px; height: 80px; + transition: all 0.3s ease; + cursor: pointer; + + &:hover { + transform: scale(1.1); + box-shadow: 0 0 20px rgba(236, 72, 153, 0.4); + } +} + +.skillBadge { + display: inline-block; + margin: 0.25rem; + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25); + border-radius: 20px; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + padding: 0.25rem 0.75rem; color: #fff; - font-weight: 700; - font-size: 1.5rem; + font-size: 0.85rem; } .techGradient { @@ -262,3 +242,12 @@ .glowText { text-shadow: 0 0 10px rgba(99, 102, 241, 0.4); } + +.participantCloud { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + justify-content: center; + align-items: center; + padding: 2rem 0; +} From e9f2f7dfe0c0cc91b87346caf32d9428b6f881ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 22:32:52 +0000 Subject: [PATCH 07/12] Use UserRankView for prizes, GitCard for templates, and improve card contrast Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- pages/hackathon/[id].tsx | 105 ++++++++++------------------- styles/Hackathon.module.less | 124 ++++++++++++++++++----------------- translation/en-US.ts | 1 + translation/zh-CN.ts | 1 + translation/zh-TW.ts | 1 + 5 files changed, 102 insertions(+), 130 deletions(-) diff --git a/pages/hackathon/[id].tsx b/pages/hackathon/[id].tsx index f8d90f9..b41be80 100644 --- a/pages/hackathon/[id].tsx +++ b/pages/hackathon/[id].tsx @@ -2,8 +2,10 @@ import { observer } from 'mobx-react'; import { GetServerSideProps } from 'next'; import { FC, useContext } from 'react'; import { Badge, Button, Card, Col, Container, Row } from 'react-bootstrap'; +import { UserRankView } from 'idea-react'; import { PageHead } from '../../components/Layout/PageHead'; +import { GitCard } from '../../components/Git/Card'; import { generateMockHackathon, Hackathon } from '../../models/Hackathon'; import { I18nContext } from '../../models/Translation'; import styles from '../../styles/Hackathon.module.less'; @@ -135,95 +137,58 @@ const HackathonDetail: FC = observer(({ hackathon }) => { - {/* Prizes Section - Frameless vertical cards */} + {/* Prizes Section - Using UserRankView */} -
+

🏆 {t('prizes')}

- {hackathon.prizes.map((prize, index) => ( -
- - - {prize.name} - - -
{prize.name}
-
- {t(prize.level as any)} - - ¥{prize.price.toLocaleString()} - -
-
- {t('sponsor')}: {prize.sponsor} • {t('amount')}: {prize.amount} -
- -
-
- ))} + ({ + id: `prize-${index}`, + name: prize.name, + avatar: prize.image, + score: prize.price, + email: prize.sponsor, + }))} + />
- {/* Mid-front: Organizations - Frameless vertical cards */} + {/* Mid-front: Organizations - Horizontal logo layout */}

🏢 {t('organizations')}

-
+
{hackathon.organizations.map((org, index) => ( -
- - - {org.name} - - -
{org.name}
- - {org.link} - - -
-
- {t('members')}: {org.members.join(', ')} -
-
- {t('prizes')}: {org.prizes.join(', ')} -
-
+ + {org.name} + ))}
- {/* Mid-back: Templates - Narrow cards, 3-4 per row */} -
+ {/* Mid-back: Templates - Using GitCard, 3-4 per row */} +

🛠️ {t('templates')}

{hackathon.templates.map((template, index) => ( - - -
{template.name}
-

{template.summary}

-
- - -
-
-
+ ))}
diff --git a/styles/Hackathon.module.less b/styles/Hackathon.module.less index 8dbbd63..f6dca81 100644 --- a/styles/Hackathon.module.less +++ b/styles/Hackathon.module.less @@ -75,19 +75,51 @@ font-size: 2rem; } -// Agenda items with gradient pink background +// Dark cards - Blue/Purple gradient background +.darkCard { + transition: all 0.3s ease; + margin-bottom: 1rem; + border-radius: 8px; + background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.15)); + padding: 1.5rem; + color: #fff; + + &:hover { + transform: translateY(-3px); + box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3); + background: linear-gradient(135deg, rgba(99, 102, 241, 0.25), rgba(139, 92, 246, 0.2)); + } +} + +// Light cards - Pink gradient background +.lightCard { + transition: all 0.3s ease; + margin-bottom: 1rem; + border-radius: 8px; + background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); + padding: 1.5rem; + color: #1f2937; + + &:hover { + transform: translateY(-3px); + box-shadow: 0 8px 20px rgba(244, 114, 182, 0.25); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.2), rgba(236, 72, 153, 0.15)); + } +} + +// Agenda items with dark background .agendaItem { transition: all 0.3s ease; margin-bottom: 1rem; border-left: 4px solid; border-radius: 8px; - background: linear-gradient(135deg, rgba(236, 72, 153, 0.15), rgba(219, 39, 119, 0.1)); + background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.15)); padding: 1.5rem; &:hover { transform: translateX(5px); - box-shadow: 0 4px 15px rgba(236, 72, 153, 0.2); - background: linear-gradient(135deg, rgba(236, 72, 153, 0.2), rgba(219, 39, 119, 0.15)); + box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3); + background: linear-gradient(135deg, rgba(99, 102, 241, 0.25), rgba(139, 92, 246, 0.2)); } &.workshop { @@ -111,82 +143,54 @@ } } -// Frameless prize cards with gradient pink -.prizeCard { - transition: all 0.3s ease; - margin-bottom: 1.5rem; - border: none; - border-radius: 0; - background: linear-gradient(135deg, rgba(244, 114, 182, 0.12), rgba(236, 72, 153, 0.08)); - padding: 1.5rem; - - &:hover { - transform: translateY(-3px); - background: linear-gradient(135deg, rgba(244, 114, 182, 0.18), rgba(236, 72, 153, 0.12)); - } +// Prize section using UserRankView +.prizeSection { + margin-bottom: 2rem; } -.prizeImage { - margin-bottom: 1rem; - border-radius: 8px; - width: 100%; - height: 120px; - object-fit: contain; +// Organization horizontal layout +.orgContainer { + display: flex; + flex-wrap: wrap; + gap: 2rem; + justify-content: center; + align-items: center; + padding: 2rem 0; } -// Frameless organization cards with gradient pink -.orgCard { +.orgLogo { transition: all 0.3s ease; - margin-bottom: 1.5rem; - border: none; - border-radius: 0; - background: linear-gradient(135deg, rgba(244, 114, 182, 0.12), rgba(236, 72, 153, 0.08)); - padding: 2rem; + border-radius: 12px; + width: 100px; + height: 100px; + object-fit: contain; + filter: grayscale(0.3); &:hover { - background: linear-gradient(135deg, rgba(244, 114, 182, 0.18), rgba(236, 72, 153, 0.12)); + transform: scale(1.1); + filter: grayscale(0); } } -.logo { - box-shadow: 0 0 15px rgba(236, 72, 153, 0.2); - border: 2px solid rgba(236, 72, 153, 0.3); - border-radius: 12px; - width: 80px; - height: 80px; -} - -// Narrow template cards with gradient pink -.templateCard { - transition: all 0.3s ease; - margin-bottom: 1.5rem; - border: 1px solid rgba(236, 72, 153, 0.2); - border-radius: 12px; - background: linear-gradient(135deg, rgba(244, 114, 182, 0.1), rgba(236, 72, 153, 0.05)); - padding: 1.5rem; - - &:hover { - transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(236, 72, 153, 0.15); - border-color: rgba(236, 72, 153, 0.35); - background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); - } +// Template section using GitCard +.templateSection { + margin-bottom: 2rem; } -// Narrow project cards with gradient pink +// Project cards with light pink gradient .projectCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgba(236, 72, 153, 0.2); + border: 1px solid rgba(244, 114, 182, 0.3); border-radius: 12px; - background: linear-gradient(135deg, rgba(244, 114, 182, 0.1), rgba(236, 72, 153, 0.05)); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); padding: 1.5rem; &:hover { transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(236, 72, 153, 0.15); - border-color: rgba(236, 72, 153, 0.35); - background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); + box-shadow: 0 8px 25px rgba(244, 114, 182, 0.25); + border-color: rgba(244, 114, 182, 0.5); + background: linear-gradient(135deg, rgba(244, 114, 182, 0.2), rgba(236, 72, 153, 0.15)); } } diff --git a/translation/en-US.ts b/translation/en-US.ts index 5f4732d..0df7851 100644 --- a/translation/en-US.ts +++ b/translation/en-US.ts @@ -197,6 +197,7 @@ export default { participants: 'Participants', organizations: 'Organizations', prizes: 'Prizes', + hackathon_prizes: 'Hackathon Prizes', templates: 'Templates', projects: 'Projects', type: 'Type', diff --git a/translation/zh-CN.ts b/translation/zh-CN.ts index 84ec574..30df230 100644 --- a/translation/zh-CN.ts +++ b/translation/zh-CN.ts @@ -193,6 +193,7 @@ export default { participants: '参与者', organizations: '组织方', prizes: '奖项', + hackathon_prizes: '黑客松奖项', templates: '项目模板', projects: '参赛项目', type: '类型', diff --git a/translation/zh-TW.ts b/translation/zh-TW.ts index ca9922c..a0fd11b 100644 --- a/translation/zh-TW.ts +++ b/translation/zh-TW.ts @@ -193,6 +193,7 @@ export default { participants: '參與者', organizations: '組織方', prizes: '獎項', + hackathon_prizes: '黑客松獎項', templates: '項目模板', projects: '參賽項目', type: '類型', From 95dbbd7d25ca0a2fc7404040ba2b30c1724f5cb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 23:42:09 +0000 Subject: [PATCH 08/12] Replace all rgba() with rgb() for solid colors and high contrast Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- styles/Hackathon.module.less | 52 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/styles/Hackathon.module.less b/styles/Hackathon.module.less index f6dca81..004e221 100644 --- a/styles/Hackathon.module.less +++ b/styles/Hackathon.module.less @@ -12,7 +12,7 @@ top: -50%; left: -50%; animation: grid-animation 20s linear infinite; - background: radial-gradient(circle, rgba(99, 102, 241, 0.08) 1px, transparent 1px); + background: radial-gradient(circle, rgb(99, 102, 241) 1px, transparent 1px); background-size: 50px 50px; width: 200%; height: 200%; @@ -34,7 +34,7 @@ margin-bottom: 1rem; font-weight: 700; font-size: 3rem; - text-shadow: 0 0 20px rgba(99, 102, 241, 0.4); + text-shadow: 0 0 20px rgb(99, 102, 241); } .description { @@ -48,15 +48,15 @@ backdrop-filter: blur(10px); transition: all 0.3s ease; margin-bottom: 1rem; - border: 1px solid rgba(99, 102, 241, 0.3); + border: 1px solid rgb(99, 102, 241); border-radius: 12px; - background: rgba(15, 23, 42, 0.45); + background: rgb(15, 23, 42); padding: 1.5rem; &:hover { transform: translateY(-2px); - box-shadow: 0 0 20px rgba(99, 102, 241, 0.25); - border-color: rgba(99, 102, 241, 0.5); + box-shadow: 0 0 20px rgb(99, 102, 241); + border-color: rgb(119, 122, 245); } } @@ -80,14 +80,14 @@ transition: all 0.3s ease; margin-bottom: 1rem; border-radius: 8px; - background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.15)); + background: linear-gradient(135deg, rgb(99, 102, 241), rgb(139, 92, 246)); padding: 1.5rem; color: #fff; &:hover { transform: translateY(-3px); - box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3); - background: linear-gradient(135deg, rgba(99, 102, 241, 0.25), rgba(139, 92, 246, 0.2)); + box-shadow: 0 8px 20px rgb(99, 102, 241); + background: linear-gradient(135deg, rgb(119, 122, 245), rgb(159, 112, 250)); } } @@ -96,14 +96,14 @@ transition: all 0.3s ease; margin-bottom: 1rem; border-radius: 8px; - background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); + background: linear-gradient(135deg, rgb(244, 114, 182), rgb(236, 72, 153)); padding: 1.5rem; color: #1f2937; &:hover { transform: translateY(-3px); - box-shadow: 0 8px 20px rgba(244, 114, 182, 0.25); - background: linear-gradient(135deg, rgba(244, 114, 182, 0.2), rgba(236, 72, 153, 0.15)); + box-shadow: 0 8px 20px rgb(244, 114, 182); + background: linear-gradient(135deg, rgb(248, 134, 196), rgb(240, 92, 167)); } } @@ -113,13 +113,13 @@ margin-bottom: 1rem; border-left: 4px solid; border-radius: 8px; - background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.15)); + background: linear-gradient(135deg, rgb(99, 102, 241), rgb(139, 92, 246)); padding: 1.5rem; &:hover { transform: translateX(5px); - box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3); - background: linear-gradient(135deg, rgba(99, 102, 241, 0.25), rgba(139, 92, 246, 0.2)); + box-shadow: 0 4px 15px rgb(99, 102, 241); + background: linear-gradient(135deg, rgb(119, 122, 245), rgb(159, 112, 250)); } &.workshop { @@ -181,16 +181,16 @@ .projectCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgba(244, 114, 182, 0.3); + border: 1px solid rgb(244, 114, 182); border-radius: 12px; - background: linear-gradient(135deg, rgba(244, 114, 182, 0.15), rgba(236, 72, 153, 0.1)); + background: linear-gradient(135deg, rgb(244, 114, 182), rgb(236, 72, 153)); padding: 1.5rem; &:hover { transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(244, 114, 182, 0.25); - border-color: rgba(244, 114, 182, 0.5); - background: linear-gradient(135deg, rgba(244, 114, 182, 0.2), rgba(236, 72, 153, 0.15)); + box-shadow: 0 8px 25px rgb(244, 114, 182); + border-color: rgb(248, 134, 196); + background: linear-gradient(135deg, rgb(248, 134, 196), rgb(240, 92, 167)); } } @@ -198,7 +198,7 @@ display: flex; justify-content: center; align-items: center; - box-shadow: 0 0 20px rgba(16, 185, 129, 0.35); + box-shadow: 0 0 20px rgb(16, 185, 129); border-radius: 50%; background: linear-gradient(135deg, #10b981 0%, #059669 100%); width: 60px; @@ -210,8 +210,8 @@ // Circular avatars only for participants .avatar { - box-shadow: 0 0 15px rgba(236, 72, 153, 0.25); - border: 3px solid rgba(236, 72, 153, 0.4); + box-shadow: 0 0 15px rgb(236, 72, 153); + border: 3px solid rgb(236, 72, 153); border-radius: 50%; width: 80px; height: 80px; @@ -220,14 +220,14 @@ &:hover { transform: scale(1.1); - box-shadow: 0 0 20px rgba(236, 72, 153, 0.4); + box-shadow: 0 0 20px rgb(236, 72, 153); } } .skillBadge { display: inline-block; margin: 0.25rem; - box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25); + box-shadow: 0 2px 8px rgb(99, 102, 241); border-radius: 20px; background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); padding: 0.25rem 0.75rem; @@ -244,7 +244,7 @@ } .glowText { - text-shadow: 0 0 10px rgba(99, 102, 241, 0.4); + text-shadow: 0 0 10px rgb(99, 102, 241); } .participantCloud { From 48ff7b8a4c9faa65fc697fd773314222c60c9122 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Fri, 2 Jan 2026 08:05:45 +0800 Subject: [PATCH 09/12] [add] Query API of BI Table schema [optimize] support Ownership Transfer in copying Lark documents [remove] useless codes in Hackathon page --- package.json | 6 +- pages/api/Lark/bitable/schema/[...slug].tsx | 22 +++ pages/api/Lark/document/copy/[...slug].ts | 21 ++- pages/hackathon/[id].tsx | 128 ++++++--------- pnpm-lock.yaml | 172 ++++++++++---------- 5 files changed, 176 insertions(+), 173 deletions(-) create mode 100644 pages/api/Lark/bitable/schema/[...slug].tsx diff --git a/package.json b/package.json index 37f04af..7aeb19e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "mobx": "^6.15.0", "mobx-github": "^0.6.2", "mobx-i18n": "^0.7.2", - "mobx-lark": "^2.6.0", + "mobx-lark": "^2.6.3", "mobx-react": "^9.2.1", "mobx-react-helper": "^0.5.1", "mobx-restful": "^2.1.4", @@ -74,7 +74,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-react": "^7.37.5", "eslint-plugin-simple-import-sort": "^12.1.1", - "globals": "^16.5.0", + "globals": "^17.0.0", "husky": "^9.1.7", "jiti": "^2.6.1", "less": "^4.5.1", @@ -85,7 +85,7 @@ "prettier-plugin-css-order": "^2.1.2", "sass": "^1.97.1", "typescript": "~5.9.3", - "typescript-eslint": "^8.50.1" + "typescript-eslint": "^8.51.0" }, "resolutions": { "mobx-react-helper": "$mobx-react-helper", diff --git a/pages/api/Lark/bitable/schema/[...slug].tsx b/pages/api/Lark/bitable/schema/[...slug].tsx new file mode 100644 index 0000000..6bd4143 --- /dev/null +++ b/pages/api/Lark/bitable/schema/[...slug].tsx @@ -0,0 +1,22 @@ +import { Context } from 'koa'; +import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware'; + +import { safeAPI, verifyJWT } from '../../../core'; +import { lark } from '../../core'; + +export const config = { api: { bodyParser: false } }; + +const router = createKoaRouter(import.meta.url); + +router.get('/:id', safeAPI, verifyJWT, async (context: Context) => { + const { id } = context.params, + { type } = context.query; + + await lark.getAccessToken(); + + const documentId = type !== 'wiki' ? id : (await lark.wiki2drive(id)).obj_token; + + context.body = await lark.getBiTableSchema(documentId); +}); + +export default withKoaRouter(router); diff --git a/pages/api/Lark/document/copy/[...slug].ts b/pages/api/Lark/document/copy/[...slug].ts index 5b64fd2..dd9f453 100644 --- a/pages/api/Lark/document/copy/[...slug].ts +++ b/pages/api/Lark/document/copy/[...slug].ts @@ -1,4 +1,5 @@ import { Context } from 'koa'; +import { LarkDocumentPathType } from 'mobx-lark'; import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware'; import { safeAPI, verifyJWT } from '../../../core'; @@ -10,9 +11,25 @@ const router = createKoaRouter(import.meta.url); router.post('/:type/:id', safeAPI, verifyJWT, async (context: Context) => { const { type, id } = context.params, - { name, parentToken } = Reflect.get(context.request, 'body'); + { name, parentToken, ownerType, ownerId } = Reflect.get(context.request, 'body'); - context.body = await lark.copyFile(`${type as 'wiki'}/${id}`, name, parentToken); + const copiedFile = + type === 'wiki' + ? await lark.copyFile(`${type as 'wiki'}/${id}`, name, parentToken) + : await lark.copyFile(`${type as LarkDocumentPathType}/${id}`, name, parentToken); + + const newId = 'token' in copiedFile ? copiedFile.token : copiedFile.obj_token; + + if (ownerType && ownerId) + try { + await lark.driveFileStore.transferOwner(type, newId, { + member_type: ownerType, + member_id: ownerId, + }); + } catch (error) { + console.error(JSON.stringify(error, null, 2)); + } + context.body = copiedFile; }); export default withKoaRouter(router); diff --git a/pages/hackathon/[id].tsx b/pages/hackathon/[id].tsx index b41be80..670ae60 100644 --- a/pages/hackathon/[id].tsx +++ b/pages/hackathon/[id].tsx @@ -1,8 +1,8 @@ import { observer } from 'mobx-react'; import { GetServerSideProps } from 'next'; import { FC, useContext } from 'react'; -import { Badge, Button, Card, Col, Container, Row } from 'react-bootstrap'; -import { UserRankView } from 'idea-react'; +import { Badge, Card, Col, Container, Row } from 'react-bootstrap'; +import { text2color, UserRankView } from 'idea-react'; import { PageHead } from '../../components/Layout/PageHead'; import { GitCard } from '../../components/Git/Card'; @@ -44,40 +44,19 @@ interface HackathonDetailProps { }; } +const formatDateTime = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +}; + const HackathonDetail: FC = observer(({ hackathon }) => { const { t } = useContext(I18nContext); - const formatDateTime = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleString('zh-CN', { - month: 'numeric', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); - }; - - const getTypeColor = (type: string) => { - const colors: Record = { - workshop: 'info', - presentation: 'danger', - coding: 'success', - break: 'warning', - ceremony: 'primary', - }; - return colors[type] || 'secondary'; - }; - - const getLevelColor = (level: string) => { - const colors: Record = { - gold: 'warning', - silver: 'secondary', - bronze: 'dark', - special: 'info', - }; - return colors[level] || 'primary'; - }; - return ( <> @@ -112,63 +91,48 @@ const HackathonDetail: FC = observer(({ hackathon }) => {
- {/* Header: Agenda and Prizes side by side */} - - {/* Agenda Section */} - -
-

📅 {t('agenda')}

-
- {hackathon.agenda.map((item, index) => ( -
-
{item.name}
-

{item.summary}

-
- - {t(item.type as any)} - -
- {formatDateTime(item.startedAt)} - {formatDateTime(item.endedAt)} -
-
+
+

🏆 {t('prizes')}

+
+ ({ + id: `prize-${index}`, + name: prize.name, + avatar: prize.image, + score: prize.price, + email: prize.sponsor, + }))} + /> +
+
+ +
+

📅 {t('agenda')}

+
+ {hackathon.agenda.map((item, index) => ( +
+
{item.name}
+

{item.summary}

+
+ + {t(item.type as any)} + +
+ {formatDateTime(item.startedAt)} - {formatDateTime(item.endedAt)}
- ))} +
-
- - - {/* Prizes Section - Using UserRankView */} - -
-

🏆 {t('prizes')}

-
- ({ - id: `prize-${index}`, - name: prize.name, - avatar: prize.image, - score: prize.price, - email: prize.sponsor, - }))} - /> -
-
- - + ))} +
+
{/* Mid-front: Organizations - Horizontal logo layout */}

🏢 {t('organizations')}

{hackathon.organizations.map((org, index) => ( - + {org.name} ))} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2855af..cc4dfab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,8 +67,8 @@ importers: specifier: ^0.7.2 version: 0.7.2(mobx@6.15.0)(typescript@5.9.3) mobx-lark: - specifier: ^2.6.0 - version: 2.6.0(core-js@3.47.0)(react@19.2.3)(typescript@5.9.3) + specifier: ^2.6.3 + version: 2.6.3(core-js@3.47.0)(react@19.2.3)(typescript@5.9.3) mobx-react: specifier: ^9.2.1 version: 9.2.1(mobx@6.15.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -188,8 +188,8 @@ importers: specifier: ^12.1.1 version: 12.1.1(eslint@9.39.2(jiti@2.6.1)) globals: - specifier: ^16.5.0 - version: 16.5.0 + specifier: ^17.0.0 + version: 17.0.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -221,8 +221,8 @@ importers: specifier: ~5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.50.1 - version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.51.0 + version: 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) packages: @@ -1727,63 +1727,63 @@ packages: '@types/warning@3.0.3': resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==} - '@typescript-eslint/eslint-plugin@8.50.1': - resolution: {integrity: sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==} + '@typescript-eslint/eslint-plugin@8.51.0': + resolution: {integrity: sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.50.1 + '@typescript-eslint/parser': ^8.51.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.50.1': - resolution: {integrity: sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==} + '@typescript-eslint/parser@8.51.0': + resolution: {integrity: sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.50.1': - resolution: {integrity: sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==} + '@typescript-eslint/project-service@8.51.0': + resolution: {integrity: sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.50.1': - resolution: {integrity: sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==} + '@typescript-eslint/scope-manager@8.51.0': + resolution: {integrity: sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.50.1': - resolution: {integrity: sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==} + '@typescript-eslint/tsconfig-utils@8.51.0': + resolution: {integrity: sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.50.1': - resolution: {integrity: sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==} + '@typescript-eslint/type-utils@8.51.0': + resolution: {integrity: sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.50.1': - resolution: {integrity: sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==} + '@typescript-eslint/types@8.51.0': + resolution: {integrity: sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.50.1': - resolution: {integrity: sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==} + '@typescript-eslint/typescript-estree@8.51.0': + resolution: {integrity: sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.50.1': - resolution: {integrity: sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==} + '@typescript-eslint/utils@8.51.0': + resolution: {integrity: sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.50.1': - resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} + '@typescript-eslint/visitor-keys@8.51.0': + resolution: {integrity: sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -2108,8 +2108,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001761: - resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} + caniuse-lite@1.0.30001762: + resolution: {integrity: sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2829,8 +2829,8 @@ packages: resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} engines: {node: '>=18'} - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + globals@17.0.0: + resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} engines: {node: '>=18'} globalthis@1.0.4: @@ -3612,8 +3612,8 @@ packages: peerDependencies: mobx: '>=6.11' - mobx-lark@2.6.0: - resolution: {integrity: sha512-X8we2BfohqcQvYQ0ACh+Bwpme/PLu5di6zmTQ5uUuRy01u0mk6vEt6s0NY1hzuzWfalJOYzJHZUjGeaE71KGbw==} + mobx-lark@2.6.3: + resolution: {integrity: sha512-XLEo5y0dtHBVu0dW6lqaiostz08iWjbHMBxA35L7fqDZzAJJU5HTObFG+aVt1Tza1c7Cm65bMTf9D8WTJSKTtw==} peerDependencies: react: '>=16' @@ -3975,8 +3975,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -4553,8 +4553,8 @@ packages: typed.js@2.1.0: resolution: {integrity: sha512-bDuXEf7YcaKN4g08NMTUM6G90XU25CK3bh6U0THC/Mod/QPKlEt9g/EjvbYB8x2Qwr2p6J6I3NrsoYaVnY6wsQ==} - typescript-eslint@8.50.1: - resolution: {integrity: sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==} + typescript-eslint@8.51.0: + resolution: {integrity: sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -6292,7 +6292,7 @@ snapshots: '@stylistic/eslint-plugin@5.6.1(eslint@9.39.2(jiti@2.6.1))': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/types': 8.51.0 eslint: 9.39.2(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -6505,14 +6505,14 @@ snapshots: '@types/warning@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.50.1 - '@typescript-eslint/type-utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.50.1 + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.51.0 + '@typescript-eslint/type-utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.51.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -6521,41 +6521,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.50.1 - '@typescript-eslint/types': 8.50.1 - '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.50.1 + '@typescript-eslint/scope-manager': 8.51.0 + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.51.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.50.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.51.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3) - '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) + '@typescript-eslint/types': 8.51.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.50.1': + '@typescript-eslint/scope-manager@8.51.0': dependencies: - '@typescript-eslint/types': 8.50.1 - '@typescript-eslint/visitor-keys': 8.50.1 + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/visitor-keys': 8.51.0 - '@typescript-eslint/tsconfig-utils@8.50.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.51.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.50.1 - '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) ts-api-utils: 2.3.0(typescript@5.9.3) @@ -6563,14 +6563,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.50.1': {} + '@typescript-eslint/types@8.51.0': {} - '@typescript-eslint/typescript-estree@8.50.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.51.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.50.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3) - '@typescript-eslint/types': 8.50.1 - '@typescript-eslint/visitor-keys': 8.50.1 + '@typescript-eslint/project-service': 8.51.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/visitor-keys': 8.51.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 @@ -6580,20 +6580,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.50.1 - '@typescript-eslint/types': 8.50.1 - '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.51.0 + '@typescript-eslint/types': 8.51.0 + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.50.1': + '@typescript-eslint/visitor-keys@8.51.0': dependencies: - '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/types': 8.51.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} @@ -6871,7 +6871,7 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.11 - caniuse-lite: 1.0.30001761 + caniuse-lite: 1.0.30001762 electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -6903,7 +6903,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001761: {} + caniuse-lite@1.0.30001762: {} ccount@2.0.1: {} @@ -6958,7 +6958,7 @@ snapshots: dependencies: '@hapi/bourne': 3.0.0 inflation: 2.1.0 - qs: 6.14.0 + qs: 6.14.1 raw-body: 2.5.3 type-is: 1.6.18 @@ -7390,7 +7390,7 @@ snapshots: eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) globals: 16.4.0 - typescript-eslint: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -7810,7 +7810,7 @@ snapshots: globals@16.4.0: {} - globals@16.5.0: {} + globals@17.0.0: {} globalthis@1.0.4: dependencies: @@ -8870,7 +8870,7 @@ snapshots: - element-internals-polyfill - typescript - mobx-lark@2.6.0(core-js@3.47.0)(react@19.2.3)(typescript@5.9.3): + mobx-lark@2.6.3(core-js@3.47.0)(react@19.2.3)(typescript@5.9.3): dependencies: '@swc/helpers': 0.5.18 '@types/react': 19.2.7 @@ -8959,7 +8959,7 @@ snapshots: koajax: 3.1.2(core-js@3.47.0)(typescript@5.9.3) mobx: 6.15.0 mobx-restful: 2.1.4(core-js@3.47.0)(mobx@6.15.0)(typescript@5.9.3) - qs: 6.14.0 + qs: 6.14.1 regenerator-runtime: 0.14.1 web-utility: 4.6.4(typescript@5.9.3) transitivePeerDependencies: @@ -9036,7 +9036,7 @@ snapshots: '@next/env': 16.1.1 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.9.11 - caniuse-lite: 1.0.30001761 + caniuse-lite: 1.0.30001762 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -9292,7 +9292,7 @@ snapshots: punycode@2.3.1: {} - qs@6.14.0: + qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -10019,12 +10019,12 @@ snapshots: typed.js@2.1.0: {} - typescript-eslint@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: From 74321b39ae97b1751d263e49e6d5e06922100479 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:24:10 +0000 Subject: [PATCH 10/12] Make color scheme more vibrant with multiple gradients inspired by Hack Club Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- styles/Hackathon.module.less | 125 ++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/styles/Hackathon.module.less b/styles/Hackathon.module.less index 004e221..f3e6028 100644 --- a/styles/Hackathon.module.less +++ b/styles/Hackathon.module.less @@ -2,7 +2,7 @@ .hero { position: relative; - background: radial-gradient(circle at top left, #312e81, #1e1b4b 55%, #0f172a); + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); padding: 4rem 0; overflow: hidden; color: #fff; @@ -12,7 +12,7 @@ top: -50%; left: -50%; animation: grid-animation 20s linear infinite; - background: radial-gradient(circle, rgb(99, 102, 241) 1px, transparent 1px); + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 1px, transparent 1px); background-size: 50px 50px; width: 200%; height: 200%; @@ -34,7 +34,7 @@ margin-bottom: 1rem; font-weight: 700; font-size: 3rem; - text-shadow: 0 0 20px rgb(99, 102, 241); + text-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } .description { @@ -48,15 +48,15 @@ backdrop-filter: blur(10px); transition: all 0.3s ease; margin-bottom: 1rem; - border: 1px solid rgb(99, 102, 241); - border-radius: 12px; - background: rgb(15, 23, 42); + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 16px; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05)); padding: 1.5rem; &:hover { - transform: translateY(-2px); - box-shadow: 0 0 20px rgb(99, 102, 241); - border-color: rgb(119, 122, 245); + transform: translateY(-5px); + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4); + border-color: rgba(255, 255, 255, 0.5); } } @@ -68,78 +68,92 @@ .sectionTitle { display: inline-block; margin-bottom: 2rem; - border-bottom: 3px solid; - border-image: linear-gradient(90deg, #6366f1, #4f46e5) 1; + border-bottom: 4px solid; + border-image: linear-gradient(90deg, #f093fb, #f5576c, #ffd140) 1; padding-bottom: 0.5rem; font-weight: 700; font-size: 2rem; + background: linear-gradient(90deg, #f093fb, #f5576c, #ffd140); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -// Dark cards - Blue/Purple gradient background +// Vibrant cards - Multicolor gradient background .darkCard { transition: all 0.3s ease; margin-bottom: 1rem; - border-radius: 8px; - background: linear-gradient(135deg, rgb(99, 102, 241), rgb(139, 92, 246)); + border-radius: 16px; + background: linear-gradient(135deg, #667eea, #764ba2); padding: 1.5rem; color: #fff; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); &:hover { - transform: translateY(-3px); - box-shadow: 0 8px 20px rgb(99, 102, 241); - background: linear-gradient(135deg, rgb(119, 122, 245), rgb(159, 112, 250)); + transform: translateY(-5px) scale(1.02); + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.5); + background: linear-gradient(135deg, #7d8ff5, #8a5cb8); } } -// Light cards - Pink gradient background +// Bright cards - Warm gradient background .lightCard { transition: all 0.3s ease; margin-bottom: 1rem; - border-radius: 8px; - background: linear-gradient(135deg, rgb(244, 114, 182), rgb(236, 72, 153)); + border-radius: 16px; + background: linear-gradient(135deg, #fa709a, #fee140); padding: 1.5rem; color: #1f2937; + box-shadow: 0 4px 15px rgba(250, 112, 154, 0.3); &:hover { - transform: translateY(-3px); - box-shadow: 0 8px 20px rgb(244, 114, 182); - background: linear-gradient(135deg, rgb(248, 134, 196), rgb(240, 92, 167)); + transform: translateY(-5px) scale(1.02); + box-shadow: 0 10px 30px rgba(250, 112, 154, 0.5); + background: linear-gradient(135deg, #ff6b9d, #ffa400); } } -// Agenda items with dark background +// Agenda items with vibrant gradient backgrounds .agendaItem { transition: all 0.3s ease; margin-bottom: 1rem; - border-left: 4px solid; - border-radius: 8px; - background: linear-gradient(135deg, rgb(99, 102, 241), rgb(139, 92, 246)); + border-left: 5px solid; + border-radius: 12px; padding: 1.5rem; + color: #fff; &:hover { - transform: translateX(5px); - box-shadow: 0 4px 15px rgb(99, 102, 241); - background: linear-gradient(135deg, rgb(119, 122, 245), rgb(159, 112, 250)); + transform: translateX(8px) scale(1.02); } &.workshop { - border-left-color: #3b82f6; + background: linear-gradient(135deg, #00c9ff, #92fe9d); + border-left-color: #00c9ff; + box-shadow: 0 4px 15px rgba(0, 201, 255, 0.3); } &.presentation { - border-left-color: #ef4444; + background: linear-gradient(135deg, #f5576c, #f093fb); + border-left-color: #f5576c; + box-shadow: 0 4px 15px rgba(245, 87, 108, 0.3); } &.coding { - border-left-color: #10b981; + background: linear-gradient(135deg, #4facfe, #00f2fe); + border-left-color: #4facfe; + box-shadow: 0 4px 15px rgba(79, 172, 254, 0.3); } &.break { - border-left-color: #f59e0b; + background: linear-gradient(135deg, #ffd140, #ff6b6b); + border-left-color: #ffd140; + box-shadow: 0 4px 15px rgba(255, 209, 64, 0.3); } &.ceremony { - border-left-color: #8b5cf6; + background: linear-gradient(135deg, #667eea, #764ba2); + border-left-color: #667eea; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } } @@ -177,20 +191,20 @@ margin-bottom: 2rem; } -// Project cards with light pink gradient +// Project cards with vibrant gradient .projectCard { transition: all 0.3s ease; margin-bottom: 1.5rem; - border: 1px solid rgb(244, 114, 182); - border-radius: 12px; - background: linear-gradient(135deg, rgb(244, 114, 182), rgb(236, 72, 153)); + border: 2px solid transparent; + border-radius: 16px; + background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); padding: 1.5rem; + color: #1f2937; &:hover { - transform: translateY(-5px); - box-shadow: 0 8px 25px rgb(244, 114, 182); - border-color: rgb(248, 134, 196); - background: linear-gradient(135deg, rgb(248, 134, 196), rgb(240, 92, 167)); + transform: translateY(-8px) scale(1.02); + box-shadow: 0 12px 35px rgba(250, 112, 154, 0.4); + background: linear-gradient(135deg, #ff6b9d 0%, #ffa400 100%); } } @@ -198,9 +212,9 @@ display: flex; justify-content: center; align-items: center; - box-shadow: 0 0 20px rgb(16, 185, 129); + box-shadow: 0 0 25px rgba(0, 242, 254, 0.5); border-radius: 50%; - background: linear-gradient(135deg, #10b981 0%, #059669 100%); + background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%); width: 60px; height: 60px; color: #fff; @@ -210,33 +224,36 @@ // Circular avatars only for participants .avatar { - box-shadow: 0 0 15px rgb(236, 72, 153); - border: 3px solid rgb(236, 72, 153); + box-shadow: 0 0 20px rgba(250, 112, 154, 0.5); + border: 4px solid transparent; border-radius: 50%; + background: linear-gradient(white, white) padding-box, + linear-gradient(135deg, #f093fb, #f5576c) border-box; width: 80px; height: 80px; transition: all 0.3s ease; cursor: pointer; &:hover { - transform: scale(1.1); - box-shadow: 0 0 20px rgb(236, 72, 153); + transform: scale(1.15); + box-shadow: 0 0 30px rgba(250, 112, 154, 0.7); } } .skillBadge { display: inline-block; margin: 0.25rem; - box-shadow: 0 2px 8px rgb(99, 102, 241); + box-shadow: 0 3px 10px rgba(102, 126, 234, 0.3); border-radius: 20px; - background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); - padding: 0.25rem 0.75rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 0.35rem 0.85rem; color: #fff; font-size: 0.85rem; + font-weight: 600; } .techGradient { - background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -244,7 +261,7 @@ } .glowText { - text-shadow: 0 0 10px rgb(99, 102, 241); + text-shadow: 0 0 15px rgba(102, 126, 234, 0.5); } .participantCloud { From c6bb7c092ac061a1f8b2dd7ad38ec8bec64028e8 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Sat, 3 Jan 2026 08:43:16 +0800 Subject: [PATCH 11/12] [add] Activity model & card component [migrate] replace SCSS with LESS for CSS [remove] Undici proxy agent --- .env | 3 + components/Activity/Card.tsx | 76 ++++++ components/Navigator/MainNavigator.tsx | 6 +- components/Navigator/SearchBar.tsx | 10 +- ...nt.module.scss => PageContent.module.less} | 0 components/PageContent/index.tsx | 4 +- components/data.ts | 16 ++ models/Activity.ts | 65 +++++ models/Base.ts | 2 + models/System.ts | 12 +- models/configuration.ts | 3 + package.json | 3 +- pages/api/core.ts | 5 - pages/index.tsx | 242 +++++++++--------- pages/search/[model]/index.tsx | 5 +- pnpm-lock.yaml | 9 - styles/{Home.module.scss => Home.module.less} | 0 translation/en-US.ts | 1 + translation/zh-CN.ts | 1 + translation/zh-TW.ts | 1 + 20 files changed, 312 insertions(+), 152 deletions(-) create mode 100644 components/Activity/Card.tsx rename components/PageContent/{PageContent.module.scss => PageContent.module.less} (100%) create mode 100644 components/data.ts create mode 100644 models/Activity.ts rename styles/{Home.module.scss => Home.module.less} (100%) diff --git a/.env b/.env index dc5c88f..6efe19b 100644 --- a/.env +++ b/.env @@ -6,4 +6,7 @@ NEXT_PUBLIC_LARK_API_HOST = https://open.feishu.cn/open-apis/ NEXT_PUBLIC_LARK_APP_ID = cli_a8094a652022900d NEXT_PUBLIC_LARK_WIKI_URL = https://open-source-bazaar.feishu.cn/wiki/space/7052192153363054596 +NEXT_PUBLIC_LARK_BITABLE_ID = PNOGbGqhPacsHOsvJqHctS77nje +NEXT_PUBLIC_ACTIVITY_TABLE_ID = tblREEMxDOECZZrK + NEXT_PUBLIC_STRAPI_API_HOST = https://china-ngo-db.onrender.com/api/ diff --git a/components/Activity/Card.tsx b/components/Activity/Card.tsx new file mode 100644 index 0000000..ee6b122 --- /dev/null +++ b/components/Activity/Card.tsx @@ -0,0 +1,76 @@ +import { TimeDistance } from 'idea-react'; +import { TableCellLocation } from 'mobx-lark'; +import type { FC } from 'react'; +import { Card, Col, Row } from 'react-bootstrap'; + +import { type Activity, ActivityModel } from '../../models/Activity'; +import { LarkImage } from '../LarkImage'; +import { TimeOption } from '../data'; +import { BadgeBar } from 'mobx-restful-table'; + +export interface ActivityCardProps extends Activity { + className?: string; +} + +export const ActivityCard: FC = ({ + className = '', + id, + host, + name, + startTime, + city, + location, + image, + ...activity +}) => ( + +
+
+ +
+
+ + + + {name as string} + + + + + + + {city as string} + + {(location as TableCellLocation)?.full_address} + + + + + + ({ + text, + link: `/search/activity?keywords=${text}`, + }))} + /> + + + + + + +
+); diff --git a/components/Navigator/MainNavigator.tsx b/components/Navigator/MainNavigator.tsx index 8894306..76b0824 100644 --- a/components/Navigator/MainNavigator.tsx +++ b/components/Navigator/MainNavigator.tsx @@ -6,6 +6,7 @@ import { Container, Image, Nav, Navbar, NavDropdown } from 'react-bootstrap'; import { DefaultImage } from '../../models/configuration'; import { i18n, I18nContext } from '../../models/Translation'; +import { SearchBar } from './SearchBar'; const LanguageMenu = dynamic(() => import('./LanguageMenu'), { ssr: false }); @@ -107,7 +108,10 @@ export const MainNavigator: FC = observer(({ menu }) => { )} - +
+ + +
diff --git a/components/Navigator/SearchBar.tsx b/components/Navigator/SearchBar.tsx index e6406aa..dc8727f 100644 --- a/components/Navigator/SearchBar.tsx +++ b/components/Navigator/SearchBar.tsx @@ -13,18 +13,16 @@ import { I18nContext } from '../../models/Translation'; import styles from './SearchBar.module.less'; export interface SearchBarProps - extends Omit, + extends + Omit, Pick, - Pick< - FormControlProps, - 'name' | 'placeholder' | 'defaultValue' | 'value' | 'onChange' - > { + Pick { expanded?: boolean; } export const SearchBar: FC = observer( ({ - action = '/search', + action = '/search/activity', size, name = 'keywords', placeholder, diff --git a/components/PageContent/PageContent.module.scss b/components/PageContent/PageContent.module.less similarity index 100% rename from components/PageContent/PageContent.module.scss rename to components/PageContent/PageContent.module.less diff --git a/components/PageContent/index.tsx b/components/PageContent/index.tsx index f3b4f1b..b9d68cd 100644 --- a/components/PageContent/index.tsx +++ b/components/PageContent/index.tsx @@ -2,8 +2,8 @@ import { MDXProvider } from '@mdx-js/react'; import type { FC, PropsWithChildren } from 'react'; import { Card, Container } from 'react-bootstrap'; -import styles from '../../styles/Home.module.scss'; -import pageContentStyles from './PageContent.module.scss'; +import styles from '../../styles/Home.module.less'; +import pageContentStyles from './PageContent.module.less'; export type PageContentProps = PropsWithChildren<{}>; diff --git a/components/data.ts b/components/data.ts new file mode 100644 index 0000000..748b3ae --- /dev/null +++ b/components/data.ts @@ -0,0 +1,16 @@ +import { TimeDistanceProps } from 'idea-react'; + +export const TimeOption: Pick = { + unitWords: { + ms: '毫秒', + s: '秒', + m: '分', + H: '时', + D: '日', + W: '周', + M: '月', + Y: '年', + }, + beforeWord: '前', + afterWord: '后', +}; diff --git a/models/Activity.ts b/models/Activity.ts new file mode 100644 index 0000000..373dedc --- /dev/null +++ b/models/Activity.ts @@ -0,0 +1,65 @@ +import { + BiDataQueryOptions, + BiDataTable, + BiSearch, + normalizeText, + TableCellLink, + TableCellRelation, + TableCellValue, + TableRecord, +} from 'mobx-lark'; + +import { LarkBase, larkClient } from './Base'; +import { ActivityTableId, LarkBitableId } from './configuration'; + +export type Activity = LarkBase & + Record< + | 'name' + | 'alias' + | 'type' + | 'tags' + | 'summary' + | 'image' + | 'cardImage' + | `${'start' | 'end'}Time` + | 'city' + | 'location' + | 'host' + | 'link' + | 'liveLink' + | `database${'' | 'Schema'}`, + TableCellValue + >; + +export class ActivityModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; + + constructor(appId = LarkBitableId, tableId = ActivityTableId) { + super(appId, tableId); + } + + static getLink = ({ + id, + alias, + link, + database, + }: Pick) => + database ? `/activity/${alias || id}` : link + ''; + + extractFields({ id, fields: { host, city, link, database, ...fields } }: TableRecord) { + return { + ...fields, + id: id!, + host: (host as TableCellRelation[])?.map(normalizeText), + city: (city as TableCellRelation[])?.map(normalizeText), + link: (link as TableCellLink)?.link, + database: (database as TableCellLink)?.link, + }; + } +} + +export class SearchActivityModel extends BiSearch(ActivityModel) { + searchKeys = ['name', 'alias', 'type', 'tags', 'summary', 'city', 'location', 'host']; +} diff --git a/models/Base.ts b/models/Base.ts index 1c5b56b..f67cc40 100644 --- a/models/Base.ts +++ b/models/Base.ts @@ -51,6 +51,8 @@ export const makeGithubSearchCondition = (queryMap: DataObject) => .map(([key, value]) => `${key}:${value}`) .join(' '); +export type LarkBase = Record<'id' | 'createdAt' | 'updatedAt', TableCellValue>; + export const larkClient = new HTTPClient({ baseURI: LARK_API_HOST, responseType: 'json', diff --git a/models/System.ts b/models/System.ts index 3afd3db..a8e2684 100644 --- a/models/System.ts +++ b/models/System.ts @@ -1,13 +1,16 @@ import { observable } from 'mobx'; import { BiSearchModelClass } from 'mobx-lark'; -import { BaseModel, ListModel, toggle } from 'mobx-restful'; -import { Base, SearchableFilter } from 'mobx-strapi'; +import { BaseModel, DataObject, Filter, ListModel, toggle } from 'mobx-restful'; import { Constructor } from 'web-utility'; +import { SearchActivityModel } from './Activity'; import { ownClient } from './Base'; import { OrganizationModel } from './Organization'; -export type SearchModel = ListModel>; +export type SearchableFilter = Filter & { + keywords?: string; +}; +export type SearchModel = ListModel>; export type SearchPageMeta = Pick< InstanceType, @@ -18,8 +21,9 @@ export type CityCoordinateMap = Record; export class SystemModel extends BaseModel { searchMap = { + activity: SearchActivityModel, NGO: OrganizationModel, - } as Record>>; + } as Record>>; @observable accessor screenNarrow = false; diff --git a/models/configuration.ts b/models/configuration.ts index 2eb27e4..cf245b9 100644 --- a/models/configuration.ts +++ b/models/configuration.ts @@ -33,3 +33,6 @@ const { hostname, pathname } = new URL(process.env.NEXT_PUBLIC_LARK_WIKI_URL!); export const LarkWikiDomain = hostname; export const LarkWikiId = pathname.split('/').pop()!; + +export const LarkBitableId = process.env.NEXT_PUBLIC_LARK_BITABLE_ID!, + ActivityTableId = process.env.NEXT_PUBLIC_ACTIVITY_TABLE_ID!; diff --git a/package.json b/package.json index 7aeb19e..4679f60 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "react-typed-component": "^1.0.6", "remark-frontmatter": "^5.0.0", "remark-mdx-frontmatter": "^5.2.0", - "undici": "^7.16.0", "web-utility": "^4.6.4", "yaml": "^2.8.2" }, @@ -111,6 +110,6 @@ ] }, "lint-staged": { - "*.{html,md,scss,json,yml,js,mjs,ts,tsx}": "prettier --write" + "*.{html,md,less,json,yml,js,mjs,ts,tsx}": "prettier --write" } } diff --git a/pages/api/core.ts b/pages/api/core.ts index 0202a5a..dcb71c0 100644 --- a/pages/api/core.ts +++ b/pages/api/core.ts @@ -7,15 +7,10 @@ import { HTTPError } from 'koajax'; import { Content } from 'mobx-github'; import { DataObject } from 'mobx-restful'; import { KoaOption, withKoa } from 'next-ssr-middleware'; -import { ProxyAgent, setGlobalDispatcher } from 'undici'; import { parse } from 'yaml'; import { LarkAppMeta } from '../../models/configuration'; -const { HTTP_PROXY } = process.env; - -if (HTTP_PROXY) setGlobalDispatcher(new ProxyAgent(HTTP_PROXY)); - export type JWTContext = ParameterizedContext< { jwtOriginalError: JsonWebTokenError } | { user: DataObject } >; diff --git a/pages/index.tsx b/pages/index.tsx index ab4c84c..88f9654 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -6,140 +6,138 @@ import ReactTyped from 'react-typed-component'; import { PageHead } from '../components/Layout/PageHead'; import { I18nContext } from '../models/Translation'; -import styles from '../styles/Home.module.scss'; +import styles from '../styles/Home.module.less'; const HomePage: FC = observer(() => { const { t } = useContext(I18nContext); return ( - <> - + <> + -
-

- - 欢迎来到 - +
+

+ + 欢迎来到 + + + 开源市集 + + + , + ), + renderToStaticMarkup( + <> + {t('welcome_open_collaboration')} + {t('open_collaboration')} + , + ), + renderToStaticMarkup( + <> + 欢迎一起评选开放协作人奖 + , + ), + ]} + typeSpeed={100} + /> +

+
+
+

{t('participate')}

+ + + +
{t('code_work')}
+
    +
  • - 开源市集 + 官网开发 - - , - ), - renderToStaticMarkup( - <> - {t('welcome_open_collaboration')}{t('open_collaboration')} - , - ), - renderToStaticMarkup( - <> - 欢迎一起评选开放协作人奖 - , - ), - ]} - typeSpeed={100} - /> -

-
-
-

{t('participate')}

- - - -
{t('code_work')}
-
    -
  • + :研发官网,让更多人了解「开源市集」,了解「开放式协作」…… +
  • +
  • + …… +
  • +
+
+ + + +
{t('non_code_work')}
+
    +
  • + + 开放市集 + + :一群来自不同领域的有趣的朋友通过展示、交流,将“开源”和“开放式协作”的乐趣带给更多人…… +
  • +
  • + + 开放协作人奖 + + :在过去的一年中令你难以忘怀的人,请把 Ta 推荐给更多的人…… +
  • +
  • + …… +
  • +
+
+ + + +

+ 欢迎 + + 成为共创人/方 + + ,更多内容/形式等你来共创…… +

+
+ +
+
+
+

{t('action')}

+ + +
+
+

{t('we_are_organizing_bazaar')}

+
+
+ 即兴三月,开源开放!来都来了,玩就是了!👉 - 官网开发 - - :研发官网,让更多人了解「开源市集」,了解「开放式协作」…… - -
  • - …… -
  • - - - - - -
    {t('non_code_work')}
    -
      -
    • - - 开放市集 - - :一群来自不同领域的有趣的朋友通过展示、交流,将“开源”和“开放式协作”的乐趣带给更多人…… -
    • -
    • - - 开放协作人奖 + 立刻协作 - :在过去的一年中令你难以忘怀的人,请把 Ta 推荐给更多的人…… -
    • -
    • - …… -
    • -
    -
    - - - -

    - 欢迎 - - 成为共创人/方 - - ,更多内容/形式等你来共创…… -

    -
    - - -
    -
    -

    {t('action')}

    - - -
    -
    -

    {t('we_are_organizing_bazaar')}

    -
    -
    - 即兴三月,开源开放!来都来了,玩就是了!👉 - - 立刻协作 - -
    -
    - -
    -
    - + + + + +
    + ); }); diff --git a/pages/search/[model]/index.tsx b/pages/search/[model]/index.tsx index a1ea426..11a99be 100644 --- a/pages/search/[model]/index.tsx +++ b/pages/search/[model]/index.tsx @@ -5,6 +5,7 @@ import { FC, useContext } from 'react'; import { Container, Nav } from 'react-bootstrap'; import { buildURLData } from 'web-utility'; +import { ActivityCard } from '../../../components/Activity/Card'; import { CardPage, CardPageProps } from '../../../components/Layout/CardPage'; import { PageHead } from '../../../components/Layout/PageHead'; import { SearchBar } from '../../../components/Navigator/SearchBar'; @@ -38,10 +39,12 @@ export const getServerSideProps = compose<{ model: string }, SearchModelPageProp ); const SearchNameMap = ({ t }: typeof i18n): Record => ({ + activity: t('activity'), NGO: t('NGO'), }); const SearchCardMap: Record = { + activity: ActivityCard, NGO: OrganizationCard, }; @@ -57,7 +60,7 @@ const SearchModelPage: FC = observer( const title = `${keywords} - ${name} ${t('search_results')}`; return ( - +

    {title}

    diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc4dfab..d181b89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,9 +114,6 @@ importers: remark-mdx-frontmatter: specifier: ^5.2.0 version: 5.2.0 - undici: - specifier: ^7.16.0 - version: 7.16.0 web-utility: specifier: ^4.6.4 version: 4.6.4(typescript@5.9.3) @@ -4586,10 +4583,6 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} - engines: {node: '>=20.18.1'} - unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -10055,8 +10048,6 @@ snapshots: undici-types@6.21.0: {} - undici@7.16.0: {} - unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: diff --git a/styles/Home.module.scss b/styles/Home.module.less similarity index 100% rename from styles/Home.module.scss rename to styles/Home.module.less diff --git a/translation/en-US.ts b/translation/en-US.ts index 0df7851..0c272f2 100644 --- a/translation/en-US.ts +++ b/translation/en-US.ts @@ -5,6 +5,7 @@ export default { code_of_conduct: 'Code of Conduct', join_us: 'Join Us', open_collaborator_award: 'Open Collaborator Award', + activity: 'Activity', hackathon: 'Hackathon', open_source_projects: 'Open Source projects', open_source_bazaar: 'Open Source Bazaar', diff --git a/translation/zh-CN.ts b/translation/zh-CN.ts index 30df230..413eaf4 100644 --- a/translation/zh-CN.ts +++ b/translation/zh-CN.ts @@ -7,6 +7,7 @@ export default { join_us: '参与', open_collaborator_award: '开放协作人奖', open_source_projects: '开源项目', + activity: '活动', hackathon: '黑客马拉松', home_page: '主页', wiki: '知识库', diff --git a/translation/zh-TW.ts b/translation/zh-TW.ts index a0fd11b..2270b9d 100644 --- a/translation/zh-TW.ts +++ b/translation/zh-TW.ts @@ -7,6 +7,7 @@ export default { join_us: '參與', open_collaborator_award: '開放協作人獎', open_source_projects: '開源項目', + activity: '活動', hackathon: '黑客馬拉松', home_page: '主頁', wiki: '知識庫', From 7dc65952fc74f2226fcf7e23e852c861696885fd Mon Sep 17 00:00:00 2001 From: TechQuery Date: Sat, 3 Jan 2026 10:26:43 +0800 Subject: [PATCH 12/12] [add] Hackathon types & models based on MobX-Lark --- models/Activity.ts | 43 +++- models/Base.ts | 5 +- models/Hackathon.ts | 442 ++++++++++------------------------- pages/hackathon/[id].tsx | 235 +++++++++++-------- styles/Hackathon.module.less | 53 ++--- 5 files changed, 336 insertions(+), 442 deletions(-) diff --git a/models/Activity.ts b/models/Activity.ts index 373dedc..e2d8864 100644 --- a/models/Activity.ts +++ b/models/Activity.ts @@ -2,12 +2,17 @@ import { BiDataQueryOptions, BiDataTable, BiSearch, + LarkPageData, + makeSimpleFilter, normalizeText, TableCellLink, TableCellRelation, TableCellValue, TableRecord, } from 'mobx-lark'; +import { toggle } from 'mobx-restful'; +import { HTTPError } from 'koajax'; +import { buildURLData } from 'web-utility'; import { LarkBase, larkClient } from './Base'; import { ActivityTableId, LarkBitableId } from './configuration'; @@ -42,13 +47,17 @@ export class ActivityModel extends BiDataTable() { static getLink = ({ id, + type, alias, link, database, - }: Pick) => - database ? `/activity/${alias || id}` : link + ''; + }: Pick) => + database ? `/${type?.toString().toLowerCase() || 'activity'}/${alias || id}` : link + ''; - extractFields({ id, fields: { host, city, link, database, ...fields } }: TableRecord) { + extractFields({ + id, + fields: { host, city, link, database, databaseSchema, ...fields }, + }: TableRecord) { return { ...fields, id: id!, @@ -56,8 +65,36 @@ export class ActivityModel extends BiDataTable() { city: (city as TableCellRelation[])?.map(normalizeText), link: (link as TableCellLink)?.link, database: (database as TableCellLink)?.link, + databaseSchema: databaseSchema && JSON.parse(databaseSchema as string), }; } + + @toggle('downloading') + async getOneByAlias(alias: string) { + const path = `${this.baseURI}?${buildURLData({ filter: makeSimpleFilter({ alias }, '=') })}`; + + const { body } = await this.client.get>>(path); + + const [item] = body!.data!.items || []; + + if (!item) + throw new HTTPError( + `Activity "${alias}" is not found`, + { method: 'GET', path }, + { status: 404, statusText: 'Not found', headers: {} }, + ); + return (this.currentOne = this.extractFields(item)); + } + + @toggle('downloading') + async getOne(id: string) { + try { + await super.getOne(id); + } catch { + await this.getOneByAlias(id); + } + return this.currentOne; + } } export class SearchActivityModel extends BiSearch(ActivityModel) { diff --git a/models/Base.ts b/models/Base.ts index f67cc40..38ecbe4 100644 --- a/models/Base.ts +++ b/models/Base.ts @@ -51,7 +51,10 @@ export const makeGithubSearchCondition = (queryMap: DataObject) => .map(([key, value]) => `${key}:${value}`) .join(' '); -export type LarkBase = Record<'id' | 'createdAt' | 'updatedAt', TableCellValue>; +export type LarkBase = Record< + 'id' | `created${'At' | 'By'}` | `updated${'At' | 'By'}`, + TableCellValue +>; export const larkClient = new HTTPClient({ baseURI: LARK_API_HOST, diff --git a/models/Hackathon.ts b/models/Hackathon.ts index 9aadd14..63c2048 100644 --- a/models/Hackathon.ts +++ b/models/Hackathon.ts @@ -1,330 +1,142 @@ // Hackathon data types and mock data generator -export interface Agenda { - summary: string; - name: string; - type: 'workshop' | 'presentation' | 'coding' | 'break' | 'ceremony'; - startedAt: Date; - endedAt: Date; -} +import { + BiDataQueryOptions, + BiDataTable, + normalizeText, + TableCellRelation, + TableCellText, + TableCellValue, + TableRecord, +} from 'mobx-lark'; -export interface Person { - name: string; - avatar: string; - gender: 'male' | 'female' | 'other'; - age: number; - address: string; - organizations: string[]; - skills: string[]; - githubLink: string; - githubAccount: string; - createdBy: string; -} +import { LarkBase, larkClient } from './Base'; + +export type Agenda = LarkBase & + Record<'summary' | 'name' | 'type' | 'startedAt' | 'endedAt', TableCellValue>; + +export class AgendaModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; -export interface Organization { - name: string; - logo: string; - link: string; - members: string[]; - prizes: string[]; + extractFields({ fields: { summary, ...fields }, ...meta }: TableRecord) { + return { + ...meta, + ...fields, + summary: normalizeText(summary as TableCellText), + }; + } } -export interface Prize { - name: string; - image: string; - price: number; - amount: number; - level: 'gold' | 'silver' | 'bronze' | 'special'; - sponsor: string; +export type Person = LarkBase & + Record< + | 'name' + | 'avatar' + | 'gender' + | 'age' + | 'address' + | 'organizations' + | 'skills' + | 'githubLink' + | 'githubAccount' + | 'createdBy', + TableCellValue + >; + +export class PersonModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; + + extractFields({ fields: { githubLink, ...fields }, ...meta }: TableRecord) { + return { + ...meta, + ...fields, + githubLink: normalizeText(githubLink as TableCellText), + }; + } } -export interface Template { - name: string; - summary: string; - sourceLink: string; - previewLink: string; +export type Organization = LarkBase & + Record<'name' | 'logo' | 'link' | 'members' | 'prizes', TableCellValue>; + +export class OrganizationModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; + + extractFields({ fields: { link, ...fields }, ...meta }: TableRecord) { + return { + ...meta, + ...fields, + link: normalizeText(link as TableCellText), + }; + } } -export interface Project { - name: string; - summary: string; - createdBy: string; - group: string[]; - members: string[]; - products: string[]; - score: number; +export type Prize = LarkBase & + Record< + | 'summary' + | 'name' + | 'image' + | 'price' + | 'amount' + | 'level' + | `${'start' | 'end'}Rank` + | 'sponsor', + TableCellValue + >; + +export class PrizeModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; } -export interface Hackathon { - id: string; - title: string; - description: string; - startDate: Date; - endDate: Date; - location: string; - agenda: Agenda[]; - people: Person[]; - organizations: Organization[]; - prizes: Prize[]; - templates: Template[]; - projects: Project[]; +export type Template = LarkBase & + Record< + 'name' | 'languages' | 'tags' | 'summary' | `${'source' | 'preview'}Link` | 'products', + TableCellValue + >; + +export class TemplateModel extends BiDataTable