From 229b360e3c9c5a63e019fe3764f4edf6a88fefca Mon Sep 17 00:00:00 2001 From: SURESH CHOUKSEY Date: Fri, 29 May 2026 20:42:12 +0530 Subject: [PATCH 1/3] feat: refactor open collaborator award page with nomination list and submission form --- pages/award/index.tsx | 435 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 430 insertions(+), 5 deletions(-) diff --git a/pages/award/index.tsx b/pages/award/index.tsx index a0e22bd..2cc07ce 100644 --- a/pages/award/index.tsx +++ b/pages/award/index.tsx @@ -1,16 +1,441 @@ +import { observer } from 'mobx-react'; import { cache, compose, errorLogger } from 'next-ssr-middleware'; -import { FC } from 'react'; +import { FC, useContext, useState } from 'react'; +import { Alert, Badge, Button, Card, Col, Container, Form, Row, Spinner } from 'react-bootstrap'; +import { PageHead } from '../../components/Layout/PageHead'; import { Award, AwardModel } from '../../models/Award'; +import { I18nContext } from '../../models/Translation'; export const getServerSideProps = compose(cache(), errorLogger, async () => { const awards = await new AwardModel().getAll(); - return { props: { awards } }; + // Sort awards by votes desc (handling undefined or null votes) + const sortedAwards = awards.sort((a, b) => { + const votesA = typeof a.votes === 'number' ? a.votes : parseInt((a.votes as any) || '0', 10); + const votesB = typeof b.votes === 'number' ? b.votes : parseInt((b.votes as any) || '0', 10); + return votesB - votesA; + }); + + return { props: { awards: sortedAwards } }; }); -const AwardPage: FC<{ awards: Award[] }> = ({ awards }) => { - return <>; -}; +const AwardPage: FC<{ awards: (Award & { id: string })[] }> = observer(({ awards }) => { + const { t } = useContext(I18nContext); + + // Form states + const [nominator, setNominator] = useState(''); + const [nomineeName, setNomineeName] = useState(''); + const [nomineeDesc, setNomineeDesc] = useState(''); + const [reason, setReason] = useState(''); + const [videoUrl, setVideoUrl] = useState(''); + + // Status states + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(false); + const [votingId, setVotingId] = useState(null); + + // Submit nomination + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!nominator.trim() || !nomineeName.trim() || !reason.trim()) { + setError('请填写所有必填字段(提名人、被提名人、提名理由)'); + return; + } + + setLoading(true); + setError(''); + setSuccess(false); + + try { + await new AwardModel().updateOne({ + awardName: '开放协作人奖', + nomineeName: nomineeName.trim(), + nomineeDesc: nomineeDesc.trim(), + videoUrl: videoUrl.trim(), + reason: reason.trim(), + nominator: nominator.trim(), + votes: 0, + createdAt: Date.now(), + } as any); + + setSuccess(true); + setNominator(''); + setNomineeName(''); + setNomineeDesc(''); + setReason(''); + setVideoUrl(''); + + // Auto-reload after a brief delay to show the new nomination + setTimeout(() => { + window.location.reload(); + }, 1500); + } catch (err: any) { + console.error(err); + setError(err.message || '提交提名失败,请检查网络或稍后重试'); + } finally { + setLoading(false); + } + }; + + // Upvote candidate + const handleVote = async (awardId: string, currentVotes: any) => { + if (votingId) return; + setVotingId(awardId); + try { + const parsedVotes = typeof currentVotes === 'number' ? currentVotes : parseInt(currentVotes || '0', 10); + await new AwardModel().updateOne({ + votes: parsedVotes + 1, + } as any, awardId); + + // Auto-reload to fetch the latest votes + window.location.reload(); + } catch (err: any) { + console.error(err); + alert(err.message || '投票失败,请重试'); + } finally { + setVotingId(null); + } + }; + + return ( +
+ + + {/* Hero Banner Section */} + + + + + ✨ 社区荣誉 · 激励同行 + +

+ 开放协作人奖 +

+

+ 发掘身边的开源之星,记录每一次不凡付出。开放协作人奖旨在表彰在开源协作、文档、布道及社区生态建设中做出突出贡献的优秀个人。 +

+
+ + +
+ + + +
+