Skip to content

Commit be60e1d

Browse files
cnb.b42izPKlQFAcnb
authored andcommitted
活动作品提交与投票系统
* feat(SubmissionDetailPage): implement conditional vote count display based on event stage Added logic to the SubmissionDetailPage to determine when to show exact vote counts based on the event's current stage. This enhancement improves user experience during hackathons by only revealing vote counts when appropriate, aligning with the existing functionality in the EventSubmissions components. * feat(EventSubmissions): enhance vote display logic in gallery and detail components Updated the EventSubmissionsGallery and SubmissionDetail components to conditionally render vote counts based on the `showResults` prop. This change maintains layout stability when votes are hidden, improving the user experience during the results stage of hackathons. * fix(EditSubmissionPage): update fetch logic to build absolute URL for API requests Refactored the submission fetching logic in the EditSubmissionPage to construct an absolute URL using the request's protocol and host. This change ensures compatibility with Node fetch in the current context and maintains session integrity by forwarding relevant headers. * feat(SubmissionsDashboard): integrate locale support for submission links Added locale handling to the submission detail links in the SubmissionsDashboard component, ensuring that links are correctly formatted based on the current locale. This enhancement improves the user experience for multilingual users navigating to submission details. * feat(i18n): add missing translations for awards in English and Chinese Introduced a new script to update missing translations for the awards section under events.hackathon in both English and Chinese language files. The script ensures that existing noAwards and noAwardsDesc entries are preserved while enhancing multilingual support for the hackathon awards. Updated the en.json and zh.json files accordingly. * fix(HackathonManagement): update label for award count to improve clarity Changed the label from "获奖人数" to "获奖数" in the HackathonManagement component to enhance clarity regarding the number of awards. * feat(PublicSubmissionsPage): add back navigation button and improve layout Introduced a back navigation button to the PublicSubmissionsPage, allowing users to return to the event page easily. Enhanced the layout by grouping the button and title in a flex container for better visual alignment. * feat(EventSubmissions): display results stage details in submissions gallery Enhanced the EventSubmissionsGallery to conditionally show ranks and exact vote counts during the results stage of hackathons. Updated the PublicSubmissionsPage to pass the new `showResults` prop, ensuring a tailored user experience based on event status. * feat(SubmissionsDashboard): remove "全部作品" section as per product requirement Eliminated the "全部作品" section from the SubmissionsDashboard component to align with updated product specifications, streamlining the user interface. * feat(NavigationContent): enhance theme handling with client-side rendering Added a new HardDriveIcon to the navigation and implemented client-side rendering for theme-dependent UI to prevent SSR/CSR hydration mismatches. This change improves the user experience by ensuring stable icon and label rendering during initial load. * refactor(EventSubmissions): simplify media source handling in gallery and detail components Removed redundant type attributes from media source elements in EventSubmissionsGallery and SubmissionDetail components, streamlining the code for better readability and maintainability. Ensured that audio and video attachments are displayed correctly while maintaining functionality. * feat(HackathonContent): update submission button to link for registered users Replaced the button for project submission with a link that directs users to the private submission URL, enhancing the user experience for registered participants during the submission window. * feat(i18n): add "Submit My Project" translation to English and Chinese files Enhanced the multilingual support by adding the translation for "Submit My Project" in both English and Chinese language files, improving user experience during event registration. * feat(EventSubmissions): enhance voting logic and media display in submissions gallery Updated the voting logic to prevent team members from voting on their own submissions. Enhanced the media display in the EventSubmissionsGallery and SubmissionDetail components to prioritize video and audio attachments, improving user experience. Added inline media players for better accessibility of submission content. * feat(storage): add S3 client for presigned URLs without Appid header Introduced a new S3 client specifically for generating presigned URLs, ensuring that the Appid header is not included in the SignedHeaders. This change prevents issues with signature mismatches during direct uploads from browsers. Enhanced error handling for missing environment variables related to S3 configuration. * feat(i18n): add translation for "Set as current" in English and Chinese Added the translation for the phrase "Set as current" to both the English and Chinese language files, enhancing the multilingual support for the application. * fix(HackathonManagement): correct API endpoint and enhance error handling Updated the API endpoint for hackathon configuration to remove an incorrect segment, ensuring proper server communication. Added a sanitization function to filter out incomplete resource items in the payload. Improved error handling to provide more meaningful messages based on server responses, enhancing user feedback during configuration updates. * feat(participants): add participant search functionality with server-side filtering Implemented a new route for searching participants in events, allowing for both event-specific and global searches. The search supports exclusion of specific user IDs and requires authentication. Updated the UserSearchCombobox to disable client-side filtering, ensuring results are fetched directly from the server. * feat(AttachmentUploader): implement secure file name generation and enhance error handling Added a function to generate secure server file names for uploaded files, incorporating file type and size. Enhanced error handling during file upload to provide more informative messages based on server responses. Updated allowed content types in the server route to support additional file formats. * refactor(EventSubmissionForm): restructure description field with FormField component Updated the EventSubmissionForm to use the FormField component for the description input, enhancing the structure and readability of the form. This change includes the addition of FormMessage for better user feedback. PR-URL: #3
1 parent 4144845 commit be60e1d

34 files changed

Lines changed: 6020 additions & 892 deletions

File tree

‎.github/workflows/docker-image.yml‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161

6262
# 同时推送到 cnb.cool(可选,需要配置 secrets)
6363
- name: Log in to cnb.cool
64-
if: secrets.CNB_USERNAME != '' && secrets.CNB_PASSWORD != ''
64+
if: ${{ secrets.CNB_USERNAME != '' && secrets.CNB_PASSWORD != '' }}
6565
uses: docker/login-action@v3
6666
with:
6767
registry: cnb.cool
@@ -81,7 +81,7 @@ jobs:
8181
8282
# 提取 cnb.cool 的 metadata(可选)
8383
- name: Extract CNB Docker metadata
84-
if: secrets.CNB_USERNAME != '' && secrets.CNB_PASSWORD != ''
84+
if: ${{ secrets.CNB_USERNAME != '' && secrets.CNB_PASSWORD != '' }}
8585
id: cnb_meta
8686
uses: docker/metadata-action@v5
8787
with:
@@ -108,7 +108,7 @@ jobs:
108108

109109
# 同时推送到 cnb.cool(可选)
110110
- name: Build and push to cnb.cool
111-
if: secrets.CNB_USERNAME != '' && secrets.CNB_PASSWORD != ''
111+
if: ${{ secrets.CNB_USERNAME != '' && secrets.CNB_PASSWORD != '' }}
112112
uses: docker/build-push-action@v5
113113
with:
114114
context: .
@@ -121,4 +121,3 @@ jobs:
121121
NEXT_PUBLIC_S3_ENDPOINT=${{ env.NEXT_PUBLIC_S3_ENDPOINT }}
122122
cache-from: type=gha
123123
cache-to: type=gha,mode=max
124-

‎scripts/update-i18n-awards.mjs‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Update missing translations under events.hackathon.awards for en and zh.
4+
* Keeps existing noAwards/noAwardsDesc if present.
5+
*/
6+
import { readFileSync, writeFileSync } from 'node:fs';
7+
import { fileURLToPath } from 'node:url';
8+
import { dirname, resolve } from 'node:path';
9+
10+
const __dirname = dirname(fileURLToPath(import.meta.url));
11+
12+
const root = resolve(__dirname, '..');
13+
const translationsDir = resolve(root, 'src/lib/i18n/translations');
14+
15+
/**
16+
* Load, update, and save a locale file.
17+
* @param {string} locale
18+
*/
19+
function updateLocale(locale) {
20+
const file = resolve(translationsDir, `${locale}.json`);
21+
const json = JSON.parse(readFileSync(file, 'utf8'));
22+
23+
// Ensure object path exists
24+
json.events ||= {};
25+
json.events.hackathon ||= {};
26+
json.events.hackathon.awards ||= {};
27+
28+
const existing = json.events.hackathon.awards;
29+
30+
/** @type {Record<string,string>} */
31+
const base =
32+
locale === 'zh'
33+
? {
34+
title: '奖项与荣誉',
35+
description: '了解本次黑客松的奖项类别与获奖方式',
36+
categories: '奖项类别',
37+
judgeAward: '评审奖',
38+
publicAward: '人气奖',
39+
winner: '获奖名额',
40+
winners: '获奖名额',
41+
currentWinners: '当前获奖者',
42+
noWinnersYet: '暂无获奖者,评审结束后公布',
43+
judgeAwards: '评审类奖项',
44+
judgeAwardsDesc: '由评审团根据评审标准评选产生',
45+
publicAwards: '公共投票类',
46+
publicAwardsDesc: '由社区用户投票产生',
47+
recognition: '奖项等级',
48+
recognitionDesc: '金/银/铜等不同等级用于表彰优秀项目',
49+
first: '一等奖',
50+
firstDesc: '最高荣誉,授予最优秀的项目',
51+
second: '二等奖',
52+
secondDesc: '表彰表现出色的项目',
53+
third: '三等奖',
54+
thirdDesc: '表彰具有潜力的项目',
55+
noAwards: existing.noAwards ?? '暂无奖项',
56+
noAwardsDesc: existing.noAwardsDesc ?? '暂无奖项设置',
57+
}
58+
: {
59+
title: 'Awards & Prizes',
60+
description: 'Explore the award categories and recognition for this hackathon',
61+
categories: 'Award Categories',
62+
judgeAward: "Judges' Award",
63+
publicAward: "People's Choice",
64+
winner: 'winner',
65+
winners: 'winners',
66+
currentWinners: 'Current Winners',
67+
noWinnersYet: 'Winners will be announced after judging',
68+
judgeAwards: 'Judged Awards',
69+
judgeAwardsDesc: 'Selected by the judging panel based on criteria',
70+
publicAwards: 'Public Awards',
71+
publicAwardsDesc: 'Voted by the community',
72+
recognition: 'Recognition Levels',
73+
recognitionDesc: 'Gold, Silver and Bronze distinctions highlight excellence',
74+
first: 'First Prize',
75+
firstDesc: 'Top recognition for outstanding projects',
76+
second: 'Second Prize',
77+
secondDesc: 'Recognizing excellent work',
78+
third: 'Third Prize',
79+
thirdDesc: 'Honoring promising projects',
80+
noAwards: existing.noAwards ?? 'No awards available',
81+
noAwardsDesc:
82+
existing.noAwardsDesc ?? 'No awards have been configured for this hackathon',
83+
};
84+
85+
json.events.hackathon.awards = {
86+
...base,
87+
};
88+
89+
writeFileSync(file, JSON.stringify(json, null, 2) + '\n', 'utf8');
90+
return file;
91+
}
92+
93+
const updated = [updateLocale('en'), updateLocale('zh')];
94+
console.log('Updated awards translations in:', updated.join(', '));
95+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { notFound } from "next/navigation";
2+
import { headers } from "next/headers";
3+
import { EventSubmissionForm } from "@/modules/dashboard/events/components/submissions/EventSubmissionForm";
4+
import type { EventSubmission } from "@/features/event-submissions/types";
5+
import { Button } from "@/components/ui/button";
6+
import Link from "next/link";
7+
8+
interface PageProps {
9+
params: Promise<{ eventId: string; submissionId: string }>;
10+
}
11+
12+
async function fetchSubmission(
13+
submissionId: string,
14+
): Promise<EventSubmission | null> {
15+
// Build an absolute URL using the current request's proto + host
16+
// Relative URLs are not supported by Node fetch in this context
17+
const headerList = await headers();
18+
const proto = headerList.get("x-forwarded-proto") ?? "http";
19+
const host =
20+
headerList.get("x-forwarded-host") ??
21+
headerList.get("host") ??
22+
`localhost:${process.env.PORT ?? 3000}`;
23+
const absoluteUrl = `${proto}://${host}/api/submissions/${submissionId}`;
24+
// Only forward whitelisted headers to avoid forbidden header issues
25+
const headerObject: Record<string, string> = {};
26+
const cookie = headerList.get("cookie");
27+
if (cookie) headerObject["cookie"] = cookie;
28+
const authz = headerList.get("authorization");
29+
if (authz) headerObject["authorization"] = authz;
30+
const res = await fetch(absoluteUrl, {
31+
cache: "no-store",
32+
// Pass through all incoming request headers (includes cookies) so auth/session stays intact
33+
headers: headerObject,
34+
});
35+
if (!res.ok) {
36+
return null;
37+
}
38+
const payload = await res.json();
39+
return payload?.data ?? null;
40+
}
41+
42+
export default async function EditSubmissionPage({ params }: PageProps) {
43+
const { eventId, submissionId } = await params;
44+
const submission = await fetchSubmission(submissionId);
45+
46+
if (!submission) {
47+
notFound();
48+
}
49+
50+
return (
51+
<div className="container mx-auto max-w-4xl py-10">
52+
<div className="flex items-center justify-between mb-6">
53+
<div>
54+
<p className="text-sm text-muted-foreground">编辑作品</p>
55+
<h1 className="text-2xl font-semibold">
56+
{submission.event.title}
57+
</h1>
58+
</div>
59+
<Button variant="ghost" asChild>
60+
<Link href={`/app/events/${eventId}/submissions`}>
61+
返回列表
62+
</Link>
63+
</Button>
64+
</div>
65+
<EventSubmissionForm
66+
eventId={eventId}
67+
eventTitle={submission.event.title}
68+
initialData={submission}
69+
mode="edit"
70+
/>
71+
</div>
72+
);
73+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { notFound } from "next/navigation";
2+
import { getEventById } from "@/lib/database";
3+
import { EventSubmissionForm } from "@/modules/dashboard/events/components/submissions/EventSubmissionForm";
4+
import { Button } from "@/components/ui/button";
5+
import Link from "next/link";
6+
7+
interface PageProps {
8+
params: Promise<{ eventId: string }>;
9+
}
10+
11+
export default async function NewSubmissionPage({ params }: PageProps) {
12+
const { eventId } = await params;
13+
const event = await getEventById(eventId);
14+
15+
if (!event) {
16+
notFound();
17+
}
18+
19+
return (
20+
<div className="container mx-auto max-w-4xl py-10">
21+
<div className="flex items-center justify-between mb-6">
22+
<div>
23+
<p className="text-sm text-muted-foreground">提交作品</p>
24+
<h1 className="text-2xl font-semibold">{event.title}</h1>
25+
</div>
26+
<Button variant="ghost" asChild>
27+
<Link href={`/app/events/${eventId}/submissions`}>
28+
返回列表
29+
</Link>
30+
</Button>
31+
</div>
32+
<EventSubmissionForm eventId={eventId} eventTitle={event.title} />
33+
</div>
34+
);
35+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { notFound } from "next/navigation";
2+
import { getEventById } from "@/lib/database";
3+
import { SubmissionsDashboard } from "@/modules/dashboard/events/components/submissions/SubmissionsDashboard";
4+
5+
interface PageProps {
6+
params: Promise<{ eventId: string }>;
7+
}
8+
9+
export default async function EventSubmissionsPage({ params }: PageProps) {
10+
const { eventId } = await params;
11+
const event = await getEventById(eventId);
12+
13+
if (!event) {
14+
notFound();
15+
}
16+
17+
return (
18+
<div className="container mx-auto max-w-5xl py-10">
19+
<SubmissionsDashboard eventId={eventId} eventTitle={event.title} />
20+
</div>
21+
);
22+
}

0 commit comments

Comments
 (0)