Skip to content

Commit 27a008e

Browse files
committed
feat: enhance submission grading modal with improved UI, detailed score breakdown, and submission-centric scoring.
1 parent c964bf0 commit 27a008e

14 files changed

Lines changed: 1434 additions & 589 deletions

File tree

app/(landing)/organizations/[id]/hackathons/[hackathonId]/judging/page.tsx

Lines changed: 625 additions & 294 deletions
Large diffs are not rendered by default.

app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ const ParticipantsPage: React.FC = () => {
408408
onOpenChange={setIsJudgeModalOpen}
409409
organizationId={organizationId}
410410
hackathonId={hackathonId}
411+
submissionId={selectedParticipant.submission.id}
411412
participantId={selectedParticipant.id}
412413
judgingCriteria={criteria}
413414
submission={{

components/organization/cards/GradeSubmissionModal/ModalFooter.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export const ModalFooter = ({
6767
isLoading || isFetching || isFetchingCriteria || !hasCriteria
6868
}
6969
className={cn(
70-
'rounded-lg px-6 py-2.5 font-medium text-white transition-all',
71-
'from-success-500 to-success-600 hover:from-success-600 hover:to-success-700 bg-gradient-to-r',
70+
'text-background rounded-lg px-6 py-2.5 font-medium transition-all',
71+
'hover:bg-primary/95 bg-primary',
7272
'disabled:cursor-not-allowed disabled:from-gray-700 disabled:to-gray-700 disabled:text-gray-500',
73-
'shadow-success-500/20 hover:shadow-success-500/30 shadow-lg'
73+
'shadow-primary/20 hover:shadow-primary/70 shadow-lg'
7474
)}
7575
>
7676
{isLoading ? (

components/organization/cards/GradeSubmissionModal/index.tsx

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ interface SubmissionData {
3131
votes: number;
3232
comments: number;
3333
logo?: string;
34+
videoUrl?: string;
3435
}
3536

3637
interface GradeSubmissionModalProps {
3738
open: boolean;
3839
onOpenChange: (open: boolean) => void;
3940
organizationId: string;
4041
hackathonId: string;
42+
submissionId: string;
4143
participantId: string;
4244
judgingCriteria?: JudgingCriterion[];
4345
submission: SubmissionData;
@@ -51,6 +53,7 @@ interface GradeSubmissionModalProps {
5153
image?: string;
5254
role?: string;
5355
}>;
56+
initialScore?: any;
5457
onSuccess?: () => void;
5558
}
5659

@@ -59,12 +62,14 @@ export default function GradeSubmissionModal({
5962
onOpenChange,
6063
organizationId,
6164
hackathonId,
65+
submissionId,
6266
participantId,
6367
judgingCriteria,
6468
submission,
6569
mode = 'judge',
6670
overrideJudgeId,
6771
judges = [],
72+
initialScore,
6873
onSuccess,
6974
}: GradeSubmissionModalProps) {
7075
const isOverride = mode === 'organizer-override';
@@ -118,8 +123,11 @@ export default function GradeSubmissionModal({
118123
open,
119124
organizationId,
120125
hackathonId,
121-
participantId: submission.id,
126+
submissionId,
127+
participantId,
122128
criteria,
129+
targetJudgeId: creditJudge ? selectedJudgeId : undefined,
130+
initialScore,
123131
});
124132

125133
const {
@@ -143,7 +151,8 @@ export default function GradeSubmissionModal({
143151
criteria,
144152
organizationId,
145153
hackathonId,
146-
participantId: submission.id,
154+
submissionId,
155+
participantId,
147156
existingScore,
148157
mode,
149158
overrideJudgeId: creditJudge ? selectedJudgeId : undefined,
@@ -175,10 +184,21 @@ export default function GradeSubmissionModal({
175184
<div className='mx-auto max-w-6xl'>
176185
<ProjectHeader submission={submission} />
177186
{isOverride && (
178-
<div className='mb-6 space-y-4 rounded-lg border border-amber-500/20 bg-amber-500/10 px-4 py-3 text-xs text-amber-300'>
179-
<div>
180-
Organizer override: this action directly assigns scores and
181-
bypasses judge assignment checks.
187+
<div className='mb-6 space-y-4 rounded-xl border border-amber-500/30 bg-amber-500/5 px-5 py-4 text-xs text-amber-200/90 shadow-sm'>
188+
<div className='flex items-start gap-3'>
189+
<div className='mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-amber-500/20 text-amber-500'>
190+
<span className='text-[10px] font-bold'>!</span>
191+
</div>
192+
<div className='leading-relaxed'>
193+
<strong className='text-amber-400'>
194+
Organizer scoring override
195+
</strong>
196+
<p className='mt-1 text-amber-200/60'>
197+
Directly assign results to this submission. These values
198+
will bypass standard judge assignment and
199+
conflict-of-interest checks.
200+
</p>
201+
</div>
182202
</div>
183203
<div className='flex flex-wrap items-center gap-3 text-[11px] text-amber-200'>
184204
<div className='flex items-center gap-2'>
@@ -260,7 +280,7 @@ export default function GradeSubmissionModal({
260280
getScoreColor={getScoreColor}
261281
overallComment={overallComment}
262282
onOverallCommentChange={setOverallComment}
263-
showComments={!isOverride}
283+
showComments={true}
264284
/>
265285
</div>
266286

@@ -288,23 +308,28 @@ export default function GradeSubmissionModal({
288308
/ {criteria.length}
289309
</span>
290310
</div>
291-
<div className='flex justify-between text-sm'>
292-
<span className='text-gray-400'>Comments Added</span>
293-
<span className='font-medium text-white'>
311+
<div className='flex items-center justify-between'>
312+
<span className='font-sans text-xs font-semibold text-gray-400'>
313+
Insights
314+
</span>
315+
<span className='rounded-full bg-gray-800 px-2 py-0.5 text-[10px] font-bold text-gray-300'>
294316
{
295317
Object.values(comments).filter(
296318
c => c.trim().length > 0
297319
).length
298-
}
320+
}{' '}
321+
Comments
299322
</span>
300323
</div>
301324

302-
<div className='border-t border-gray-800 pt-4'>
303-
<p className='text-[11px] leading-relaxed text-gray-500 italic'>
304-
Your scores and comments are saved automatically
305-
when you submit. You can return later to update
306-
them.
307-
</p>
325+
<div className='mt-6 border-t border-gray-800/60 pt-6'>
326+
<div className='flex items-start gap-2'>
327+
<div className='bg-primary mt-1.5 h-1 w-1 rounded-full' />
328+
<p className='font-sans text-[10px] leading-relaxed font-medium text-gray-500'>
329+
Changes are staged in real-time. Finalize by
330+
clicking the primary action below.
331+
</p>
332+
</div>
308333
</div>
309334
</div>
310335
</div>
@@ -315,7 +340,7 @@ export default function GradeSubmissionModal({
315340
)}
316341
</div>
317342

318-
<div className='bg-background-main-bg/80 fixed right-0 bottom-0 left-0 z-50 border-t border-white/5 p-4 backdrop-blur-md'>
343+
<div className='fixed right-0 bottom-0 left-0 z-50 border-t border-gray-900 bg-black/80 p-4 backdrop-blur-xl'>
319344
<div className='mx-auto max-w-6xl'>
320345
<ModalFooter
321346
isLoading={isSubmitting}

components/organization/cards/GradeSubmissionModal/useScoreCalculation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ export const useScoreCalculation = ({
3030
const percentage = useMemo(() => Math.round(totalScore), [totalScore]);
3131

3232
const getScoreColor = (percentage: number): string => {
33-
if (percentage >= 80) return 'bg-success-500';
34-
if (percentage >= 60) return 'bg-primary';
35-
if (percentage >= 40) return 'bg-warning-500';
36-
return 'bg-error-500';
33+
if (percentage >= 80) return 'bg-primary text-background';
34+
if (percentage >= 60) return 'bg-chart-2 text-white';
35+
if (percentage >= 40) return 'bg-warning-500 text-background';
36+
return 'bg-error-500 text-white';
3737
};
3838

3939
return {

components/organization/cards/GradeSubmissionModal/useScoreForm.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface UseScoreFormProps {
1919
criteria: JudgingCriterion[];
2020
organizationId: string;
2121
hackathonId: string;
22+
submissionId: string;
2223
participantId: string;
2324
existingScore: { scores: CriterionScore[]; notes?: string } | null;
2425
mode?: 'judge' | 'organizer-override';
@@ -37,6 +38,7 @@ export const useScoreForm = ({
3738
criteria,
3839
organizationId,
3940
hackathonId,
41+
submissionId,
4042
participantId,
4143
existingScore,
4244
mode = 'judge',
@@ -141,7 +143,7 @@ export const useScoreForm = ({
141143
setIsLoading(true);
142144

143145
try {
144-
const includeComments = mode !== 'organizer-override';
146+
const includeComments = true;
145147
const scoreData = criteria.map(criterion => {
146148
const key = getCriterionKey(criterion);
147149
const payload: {
@@ -163,14 +165,15 @@ export const useScoreForm = ({
163165
? await overrideSubmissionScore(
164166
organizationId,
165167
hackathonId,
166-
participantId,
168+
submissionId,
167169
{
168170
criteriaScores: scoreData,
169171
judgeId: overrideJudgeId,
172+
notes: overallComment,
170173
}
171174
)
172175
: await submitJudgingScore({
173-
submissionId: participantId,
176+
submissionId,
174177
criteriaScores: scoreData,
175178
comment: overallComment,
176179
});
@@ -207,11 +210,21 @@ export const useScoreForm = ({
207210
}
208211
} catch (error: any) {
209212
// Handle network or unexpected errors
210-
const errorMessage =
213+
const status = error?.response?.status;
214+
let errorMessage =
211215
error?.response?.data?.message ||
212216
error?.message ||
213217
'Failed to submit grade. Please try again.';
214-
toast.error(errorMessage);
218+
219+
// Explicitly handle Conflict of Interest
220+
if (status === 403) {
221+
errorMessage =
222+
'Conflict of Interest: You are not permitted to score this submission (e.g., self-submission or team member).';
223+
}
224+
225+
toast.error(errorMessage, {
226+
description: status === 403 ? 'Rules of Competition' : undefined,
227+
});
215228
} finally {
216229
setIsLoading(false);
217230
}

0 commit comments

Comments
 (0)