diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/judging/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/judging/page.tsx
index 21cfba35..e10b2629 100644
--- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/judging/page.tsx
+++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/judging/page.tsx
@@ -1,12 +1,12 @@
'use client';
-import { useEffect, useState, useCallback } from 'react';
+import { useEffect, useState, useCallback, useMemo, useRef } from 'react';
import MetricsCard from '@/components/organization/cards/MetricsCard';
import JudgingParticipant from '@/components/organization/cards/JudgingParticipant';
import EmptyState from '@/components/EmptyState';
import { useParams } from 'next/navigation';
import {
- getJudgingSubmissions,
+ getJudgingSubmissionsForJudge,
getJudgingCriteria,
addJudge,
removeJudge,
@@ -24,7 +24,25 @@ import { getOrganizationMembers } from '@/lib/api/organization';
import { getCrowdfundingProject } from '@/features/projects/api';
import { authClient } from '@/lib/auth-client';
import { useOrganization } from '@/lib/providers/OrganizationProvider';
-import { Loader2, Trophy, CheckCircle2 } from 'lucide-react';
+import {
+ Loader2,
+ Trophy,
+ CheckCircle2,
+ Search,
+ ArrowUpRight,
+ ExternalLink,
+ ChevronDown,
+ ChevronUp,
+ Mail,
+ Users,
+ Star,
+ Zap,
+ LayoutGrid,
+ List,
+ ArrowUpDown,
+ ChevronLeft,
+ ChevronRight,
+} from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { AuthGuard } from '@/components/auth/AuthGuard';
@@ -33,6 +51,24 @@ import { reportError, reportMessage } from '@/lib/error-reporting';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { JudgingCriteriaList } from '@/components/organization/hackathons/judging/JudgingCriteriaList';
import JudgingResultsTable from '@/components/organization/hackathons/judging/JudgingResultsTable';
+import { Input } from '@/components/ui/input';
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from '@/components/ui/alert-dialog';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
export default function JudgingPage() {
const params = useParams();
@@ -60,11 +96,36 @@ export default function JudgingPage() {
const [isPublishing, setIsPublishing] = useState(false);
const [isCurrentUserJudge, setIsCurrentUserJudge] = useState(false);
const [currentUserId, setCurrentUserId] = useState
(null);
+ const [memberSearchTerm, setMemberSearchTerm] = useState('');
+ const [submissionSearchTerm, setSubmissionSearchTerm] = useState('');
+ const [searchQuery, setSearchQuery] = useState('');
+ const [sortBy, setSortBy] = useState<'date' | 'name' | 'score' | 'rank'>(
+ 'date'
+ );
+ const [viewMode, setViewMode] = useState<'detailed' | 'compact'>('detailed');
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+ const [isFetchingSubmissions, setIsFetchingSubmissions] = useState(false);
+
+ // Debounce search term
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setSubmissionSearchTerm(searchQuery);
+ }, 500);
+ return () => clearTimeout(timer);
+ }, [searchQuery]);
+
+ // Cache using useRef to prevent redundant fetches on tab switch without triggering re-renders
+ const lastFetchedRef = useRef<{ [key: string]: number }>({});
+ const CACHE_TIMEOUT = 30000; // 30 seconds
+
+ const [isPublishDialogOpen, setIsPublishDialogOpen] = useState(false);
+ const [judgeToRemove, setJudgeToRemove] = useState(null);
const canManageJudges =
currentUserRole === 'owner' || currentUserRole === 'admin';
- const canPublishResults = canManageJudges && isCurrentUserJudge;
- const resultsPublished = winners.length > 0;
+ const canPublishResults = canManageJudges; // Admins can always publish
+ const resultsPublished = !!judgingSummary?.resultsPublished;
const fetchJudges = useCallback(async () => {
// Priority: activeOrgId from context, then params.id
@@ -155,164 +216,155 @@ export default function JudgingPage() {
}
}, [organizationId, hackathonId, activeOrgId]);
- const fetchResults = useCallback(async () => {
- if (!organizationId || !hackathonId) return;
+ const fetchResults = useCallback(
+ async (force = false) => {
+ if (!organizationId || !hackathonId) return;
- setIsFetchingResults(true);
- try {
- const res = await getJudgingResults(organizationId, hackathonId);
+ // Check cache
+ const now = Date.now();
+ if (
+ !force &&
+ lastFetchedRef.current['results'] &&
+ now - lastFetchedRef.current['results'] < CACHE_TIMEOUT
+ ) {
+ return;
+ }
- if (res.success && res.data) {
- setJudgingResults(res.data.results || []);
- setJudgingSummary(res.data);
- } else {
+ setIsFetchingResults(true);
+ try {
+ const res = await getJudgingResults(
+ organizationId,
+ hackathonId,
+ currentPage,
+ 10,
+ submissionSearchTerm,
+ sortBy,
+ 'desc'
+ );
+
+ if (res.success && res.data) {
+ setJudgingResults(res.data.results || []);
+ setJudgingSummary(res.data);
+
+ if (res.data.pagination?.totalPages) {
+ setTotalPages(res.data.pagination.totalPages);
+ }
+
+ lastFetchedRef.current['results'] = now;
+ } else {
+ setJudgingResults([]);
+ setJudgingSummary(null);
+ if (!res.success) {
+ toast.error(
+ (res as any).message || 'Failed to load judging results'
+ );
+ }
+ }
+ } catch (error: any) {
+ reportError(error, {
+ context: 'judging-fetchResults',
+ organizationId,
+ hackathonId,
+ });
setJudgingResults([]);
setJudgingSummary(null);
- if (!res.success) {
- toast.error((res as any).message || 'Failed to load judging results');
- }
+ toast.error(
+ error.response?.data?.message ||
+ error.message ||
+ 'Failed to load judging results'
+ );
+ } finally {
+ setIsFetchingResults(false);
}
- } catch (error: any) {
- reportError(error, {
- context: 'judging-fetchResults',
- organizationId,
- hackathonId,
- });
- setJudgingResults([]);
- setJudgingSummary(null);
- toast.error(
- error.response?.data?.message ||
- error.message ||
- 'Failed to load judging results'
- );
- } finally {
- setIsFetchingResults(false);
- }
- }, [organizationId, hackathonId]);
+ },
+ [organizationId, hackathonId]
+ );
- const fetchWinners = useCallback(async () => {
- if (!organizationId || !hackathonId) return;
- setIsFetchingWinners(true);
- try {
- const res = await getJudgingWinners(organizationId, hackathonId);
- if (res.success && res.data) {
- setWinners(Array.isArray(res.data) ? res.data : []);
+ const fetchWinners = useCallback(
+ async (force = false) => {
+ if (!organizationId || !hackathonId) return;
+
+ // Check cache
+ const now = Date.now();
+ if (
+ !force &&
+ lastFetchedRef.current['winners'] &&
+ now - lastFetchedRef.current['winners'] < CACHE_TIMEOUT
+ ) {
+ return;
}
- } catch (error) {
- reportError(error, {
- context: 'judging-fetchWinners',
- organizationId,
- hackathonId,
- });
- } finally {
- setIsFetchingWinners(false);
- }
- }, [organizationId, hackathonId]);
+
+ setIsFetchingWinners(true);
+ try {
+ const res = await getJudgingWinners(
+ organizationId,
+ hackathonId,
+ 1,
+ 50,
+ submissionSearchTerm,
+ 'score',
+ 'desc'
+ );
+ if (res.success && res.data) {
+ setWinners(Array.isArray(res.data) ? res.data : []);
+ lastFetchedRef.current['winners'] = now;
+ }
+ } catch (error) {
+ reportError(error, {
+ context: 'judging-fetchWinners',
+ organizationId,
+ hackathonId,
+ });
+ } finally {
+ setIsFetchingWinners(false);
+ }
+ },
+ [organizationId, hackathonId]
+ );
const fetchData = useCallback(async () => {
if (!organizationId || !hackathonId) return;
- setIsLoading(true);
+ // Only show global loading on initial fetch
+ if (submissions.length === 0) {
+ setIsLoading(true);
+ }
+ setIsFetchingSubmissions(true);
try {
- // Fetch submissions, criteria, and judges/members
- const [submissionsRes, criteriaRes] = await Promise.all([
- getJudgingSubmissions(organizationId, hackathonId, 1, 50),
- getJudgingCriteria(hackathonId),
- ]);
+ // Use the new optimized endpoint that returns submissions, criteria and myScore in one go
+ const submissionsRes = await getJudgingSubmissionsForJudge(
+ hackathonId,
+ currentPage,
+ 12,
+ submissionSearchTerm,
+ sortBy,
+ 'desc'
+ );
- // Trigger judges, results, and winners fetch in parallel (winners => results published state)
+ // Trigger other data fetches in parallel
fetchJudges();
fetchResults();
fetchWinners();
- let enrichedSubmissions: JudgingSubmission[] = [];
-
- if (submissionsRes.success) {
- // Standard submissions endpoint returns { data: { submissions: [], pagination: {} } }
- const submissionData =
- (submissionsRes.data as any)?.submissions ||
- submissionsRes.data ||
- [];
- const basicSubmissions = Array.isArray(submissionData)
- ? submissionData
- : [];
-
- // 2. Fetch full details for each submission to get user info
- // We do this by fetching the project details, as submission endpoints lack user data
- const detailsPromises = basicSubmissions.map(async (sub: any) => {
- try {
- // Check if we already have sufficient user data
- if (
- sub.participant?.user?.profile?.firstName ||
- sub.participant?.name
- )
- return sub;
-
- // Try fetch project details if we have projectId
- if (sub.projectId) {
- const project = await getCrowdfundingProject(sub.projectId);
- if (project && project.project && project.project.creator) {
- const creator = project.project.creator;
- return {
- ...sub,
- participant: {
- ...sub.participant,
- // Use creator info for participant
- name: creator.name,
- username: creator.username,
- image: creator.image,
- email: creator.email,
- user: {
- ...sub.participant?.user,
- name: creator.name,
- username: creator.username,
- image: creator.image,
- email: creator.email,
- profile: {
- ...sub.participant?.user?.profile,
- firstName: creator.name?.split(' ')[0] || '',
- lastName:
- creator.name?.split(' ').slice(1).join(' ') || '',
- username: creator.username,
- avatar: creator.image,
- },
- },
- },
- };
- }
- }
-
- // Fallback to submission details check if project fail or no projectId
- const detailsRes = await getSubmissionDetails(sub.id);
- if (detailsRes.success && detailsRes.data) {
- const details = detailsRes.data as any;
- return {
- ...sub,
- participant: {
- ...sub.participant,
- ...details.participant,
- user: details.participant?.user || sub.participant?.user,
- },
- };
- }
- return sub;
- } catch (err) {
- reportError(err, {
- context: 'judging-fetchSubmissionDetails',
- submissionId: sub.id,
- });
- return sub;
- }
- });
+ if (submissionsRes.success && submissionsRes.data) {
+ const {
+ submissions: subData,
+ criteria: critData,
+ pagination,
+ } = submissionsRes.data;
+
+ setSubmissions(subData || []);
+ setCriteria(critData || []);
- enrichedSubmissions = await Promise.all(detailsPromises);
- setSubmissions(enrichedSubmissions);
+ if (pagination?.totalPages) {
+ setTotalPages(pagination.totalPages);
+ } else if (pagination?.total) {
+ setTotalPages(Math.ceil(pagination.total / 12));
+ }
} else {
setSubmissions([]);
+ setCriteria([]);
}
-
- // Handle criteria response safely
- setCriteria(Array.isArray(criteriaRes) ? criteriaRes : []);
} catch (error) {
reportError(error, {
context: 'judging-fetchData',
@@ -322,8 +374,18 @@ export default function JudgingPage() {
toast.error('Failed to load judging data');
} finally {
setIsLoading(false);
+ setIsFetchingSubmissions(false);
}
- }, [organizationId, hackathonId, fetchJudges, fetchResults, fetchWinners]);
+ }, [
+ organizationId,
+ hackathonId,
+ currentPage,
+ submissionSearchTerm,
+ fetchJudges,
+ fetchResults,
+ fetchWinners,
+ sortBy,
+ ]);
useEffect(() => {
fetchData();
@@ -377,12 +439,18 @@ export default function JudgingPage() {
context: 'judging-removeJudge',
organizationId,
hackathonId,
+ userId,
});
- toast.error(
- error.response?.data?.message ||
- error.message ||
- 'Failed to remove judge'
- );
+ toast.error(error.message || 'Failed to remove judge');
+ }
+ };
+
+ // Use submissions directly as they are now filtered/sorted by the backend
+ const filteredAndSortedSubmissions = submissions;
+
+ const handlePageChange = (newPage: number) => {
+ if (newPage >= 1 && newPage <= totalPages) {
+ setCurrentPage(newPage);
}
};
@@ -392,8 +460,8 @@ export default function JudgingPage() {
const res = await publishJudgingResults(organizationId, hackathonId);
if (res.success) {
toast.success('Results published successfully!');
- fetchResults();
- fetchWinners();
+ fetchResults(true);
+ fetchWinners(true);
} else {
toast.error(res.message || 'Failed to publish results');
}
@@ -430,28 +498,54 @@ export default function JudgingPage() {
}>
-
-
Judging Dashboard
-
- Manage and grade shortlisted submissions
-
+
+
+
+ Judging Dashboard
+
+
+ Manage and grade shortlisted submissions
+
+
+
+
+ {/* Optional: Add a refresh button here for premium feel */}
+ fetchData()}
+ className='border-gray-800 bg-black text-gray-400 hover:text-white'
+ >
+ Refresh Data
+
+
-
+
+ }
+ className='min-w-[200px] border-gray-900 bg-white/5'
+ />
0 ? Math.round((gradedCount / totalPossibleSubmissions) * 100) : 0}% Completion`}
+ title='Graded / Shortlisted'
+ value={`${gradedCount} / ${submissions.length}`}
+ subtitle={`${submissions.length > 0 ? Math.round((gradedCount / submissions.length) * 100) : 0}% Completion`}
+ // icon={ }
+ className='min-w-[200px] border-gray-900 bg-white/5'
/>
}
+ className='min-w-[200px] border-gray-900 bg-white/5'
/>
}
+ className='min-w-[200px] border-gray-900 bg-white/5'
/>
@@ -461,68 +555,185 @@ export default function JudgingPage() {
onValueChange={value => {
setActiveTab(value);
if (value === 'results') {
- fetchResults();
- fetchWinners();
+ fetchResults(false);
+ fetchWinners(false);
}
}}
className='w-full'
>
-
-
- Overview
-
-
- Criteria
-
-
- Judges
-
-
- Results
-
-
+
+
+
+
+ Overview
+
+
+ Criteria
+
+
+ Judges
+
+
+ Results
+
+
+
+ {/* Global Filters & Search */}
+
+
+ {isFetchingSubmissions || isFetchingResults ? (
+
+ ) : (
+
+ )}
+ ) => {
+ setSearchQuery(e.target.value);
+ setCurrentPage(1);
+ }}
+ />
+
-
- {isLoading ? (
+
+
+ Sort:
+ {
+ setSortBy(value);
+ setCurrentPage(1);
+ }}
+ >
+
+
+
+
+ Recently Added
+ Highest Score
+ Top Ranked
+ Project Name
+
+
+
+
+
+
+
+ setViewMode('detailed')}
+ >
+
+
+ setViewMode('compact')}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {isLoading && submissions.length === 0 ? (
- ) : submissions.length > 0 ? (
-
- {submissions.map(submission => (
-
0}
- judges={currentJudges}
- isJudgesLoading={isRefreshingJudges}
- currentUserId={currentUserId || undefined}
- canOverrideScores={canManageJudges}
- onSuccess={handleSuccess}
- />
- ))}
+ ) : filteredAndSortedSubmissions.length > 0 ? (
+
+
+ {filteredAndSortedSubmissions.map(submission => (
+ 0}
+ criteria={criteria}
+ judges={currentJudges}
+ isJudgesLoading={isRefreshingJudges}
+ currentUserId={currentUserId || undefined}
+ canOverrideScores={canManageJudges}
+ onSuccess={handleSuccess}
+ variant={
+ viewMode === 'compact' ? 'compact' : 'detailed'
+ }
+ />
+ ))}
+
+
+ {/* Pagination Controls */}
+ {totalPages > 1 && (
+
+ handlePageChange(currentPage - 1)}
+ className='border-gray-900 bg-black hover:bg-white/5'
+ >
+
+ Previous
+
+
+ Page {currentPage} of {totalPages}
+
+ handlePageChange(currentPage + 1)}
+ className='border-gray-900 bg-black hover:bg-white/5'
+ >
+ Next
+
+
+
+ )}
) : (
)}
@@ -584,7 +795,7 @@ export default function JudgingPage() {
size='sm'
className='text-red-400 hover:bg-red-400/10 hover:text-red-300'
onClick={() =>
- handleRemoveJudge(judge.userId || judge.id)
+ setJudgeToRemove(judge.userId || judge.id)
}
>
Remove
@@ -599,68 +810,90 @@ export default function JudgingPage() {
{/* Org Members List - Only visible to admin/owner */}
{canManageJudges && (
-
- Add from Organization Members
-
-
- Select members from your organization to assign them as
- judges.
-
-
- {orgMembers.map((member: any) => {
- const isAlreadyJudge = currentJudges.some(
- j => j.id === member.id || j.userId === member.id
- );
- return (
-
-
-
- {member.image ? (
-
- ) : (
-
- {member.name?.[0] ||
- member.username?.[0] ||
- '?'}
-
- )}
-
-
-
- {member.name || member.username}
-
-
- {member.email}
-
+
+
+ Add from Organization Members
+
+
+
+ ) =>
+ setMemberSearchTerm(e.target.value)
+ }
+ />
+
+
+ Select members from your organization to assign them as
+ judges.
+
+
+
+ {orgMembers
+ .filter(m => {
+ const search = memberSearchTerm.toLowerCase();
+ return (
+ m.name?.toLowerCase().includes(search) ||
+ m.email?.toLowerCase().includes(search) ||
+ m.username?.toLowerCase().includes(search)
+ );
+ })
+ .map((member: any) => {
+ const isAlreadyJudge = currentJudges.some(
+ j => j.id === member.id || j.userId === member.id
+ );
+ return (
+
+
+
+ {member.image ? (
+
+ ) : (
+
+ {member.name?.[0] ||
+ member.username?.[0] ||
+ '?'}
+
+ )}
+
+
+
+ {member.name || member.username}
+
+
+ {member.email}
+
+
+
+ handleAddJudge(member.id, member.email)
+ }
+ >
+ {isAlreadyJudge
+ ? 'Already Judge'
+ : 'Add as Judge'}
+
-
- handleAddJudge(member.id, member.email)
- }
- >
- {isAlreadyJudge
- ? 'Already Judge'
- : 'Add as Judge'}
-
-
- );
- })}
+ );
+ })}
{orgMembers.length === 0 && !isRefreshingJudges && (
0 && (
-
-
-
- Finalize Competition
-
-
- Publish the current rankings to name the winners.
-
+
+
+
+
+
+
+
+ Finalize Competition
+
+
+ Publish the current rankings to name the winners.
+
+
setIsPublishDialogOpen(true)}
disabled={isPublishing}
- className='bg-primary text-primary-foreground hover:bg-primary/90 px-8 font-bold'
+ className='bg-primary text-primary-foreground hover:bg-primary/90 px-8 font-bold shadow-lg'
>
{isPublishing ? 'Publishing...' : 'Publish Results'}
@@ -726,6 +964,8 @@ export default function JudgingPage() {
hackathonId={hackathonId}
totalJudges={currentJudges.length}
criteria={criteria}
+ canManage={canManageJudges}
+ winnerOverrides={judgingSummary?.winnerOverrides}
/>
)}
@@ -734,18 +974,53 @@ export default function JudgingPage() {
Current Standings
- {isFetchingResults ? (
+ {isFetchingResults && judgingResults.length === 0 ? (
) : judgingResults.length > 0 ? (
-
+ <>
+
+
+ {/* Pagination Controls for Results */}
+ {totalPages > 1 && (
+
+ handlePageChange(currentPage - 1)}
+ className='border-gray-900 bg-black hover:bg-white/5'
+ >
+
+ Previous
+
+
+ Page {currentPage} of {totalPages}
+
+ handlePageChange(currentPage + 1)}
+ className='border-gray-900 bg-black hover:bg-white/5'
+ >
+ Next
+
+
+
+ )}
+ >
) : (
+ {/* Global Dialogs */}
+
+
+
+ Publish Judging Results?
+
+ This will finalize the rankings and announce the winners. This
+ action is irreversible.
+
+
+
+
+ Cancel
+
+
+ Yes, Publish Results
+
+
+
+
+
+
!open && setJudgeToRemove(null)}
+ >
+
+
+ Remove Judge?
+
+ Are you sure you want to remove this judge? Their existing
+ scores will be preserved but they will no longer have access to
+ the judging panel.
+
+
+
+
+ Cancel
+
+ {
+ if (judgeToRemove) handleRemoveJudge(judgeToRemove);
+ setJudgeToRemove(null);
+ }}
+ className='bg-red-600 text-white hover:bg-red-700'
+ >
+ Remove Judge
+
+
+
+
);
diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
index ce74910f..3423f4af 100644
--- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
+++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
@@ -408,6 +408,7 @@ const ParticipantsPage: React.FC = () => {
onOpenChange={setIsJudgeModalOpen}
organizationId={organizationId}
hackathonId={hackathonId}
+ submissionId={selectedParticipant.submission.id}
participantId={selectedParticipant.id}
judgingCriteria={criteria}
submission={{
diff --git a/components/organization/cards/GradeSubmissionModal/ModalFooter.tsx b/components/organization/cards/GradeSubmissionModal/ModalFooter.tsx
index c9df713c..2fcc1a6b 100644
--- a/components/organization/cards/GradeSubmissionModal/ModalFooter.tsx
+++ b/components/organization/cards/GradeSubmissionModal/ModalFooter.tsx
@@ -67,10 +67,10 @@ export const ModalFooter = ({
isLoading || isFetching || isFetchingCriteria || !hasCriteria
}
className={cn(
- 'rounded-lg px-6 py-2.5 font-medium text-white transition-all',
- 'from-success-500 to-success-600 hover:from-success-600 hover:to-success-700 bg-gradient-to-r',
+ 'text-background rounded-lg px-6 py-2.5 font-medium transition-all',
+ 'hover:bg-primary/95 bg-primary',
'disabled:cursor-not-allowed disabled:from-gray-700 disabled:to-gray-700 disabled:text-gray-500',
- 'shadow-success-500/20 hover:shadow-success-500/30 shadow-lg'
+ 'shadow-primary/20 hover:shadow-primary/70 shadow-lg'
)}
>
{isLoading ? (
diff --git a/components/organization/cards/GradeSubmissionModal/index.tsx b/components/organization/cards/GradeSubmissionModal/index.tsx
index c6daaeb2..db80b55e 100644
--- a/components/organization/cards/GradeSubmissionModal/index.tsx
+++ b/components/organization/cards/GradeSubmissionModal/index.tsx
@@ -31,6 +31,7 @@ interface SubmissionData {
votes: number;
comments: number;
logo?: string;
+ videoUrl?: string;
}
interface GradeSubmissionModalProps {
@@ -38,6 +39,7 @@ interface GradeSubmissionModalProps {
onOpenChange: (open: boolean) => void;
organizationId: string;
hackathonId: string;
+ submissionId: string;
participantId: string;
judgingCriteria?: JudgingCriterion[];
submission: SubmissionData;
@@ -51,6 +53,7 @@ interface GradeSubmissionModalProps {
image?: string;
role?: string;
}>;
+ initialScore?: any;
onSuccess?: () => void;
}
@@ -59,12 +62,14 @@ export default function GradeSubmissionModal({
onOpenChange,
organizationId,
hackathonId,
+ submissionId,
participantId,
judgingCriteria,
submission,
mode = 'judge',
overrideJudgeId,
judges = [],
+ initialScore,
onSuccess,
}: GradeSubmissionModalProps) {
const isOverride = mode === 'organizer-override';
@@ -118,8 +123,11 @@ export default function GradeSubmissionModal({
open,
organizationId,
hackathonId,
- participantId: submission.id,
+ submissionId,
+ participantId,
criteria,
+ targetJudgeId: creditJudge ? selectedJudgeId : undefined,
+ initialScore,
});
const {
@@ -143,7 +151,8 @@ export default function GradeSubmissionModal({
criteria,
organizationId,
hackathonId,
- participantId: submission.id,
+ submissionId,
+ participantId,
existingScore,
mode,
overrideJudgeId: creditJudge ? selectedJudgeId : undefined,
@@ -175,10 +184,21 @@ export default function GradeSubmissionModal({
{isOverride && (
-
-
- Organizer override: this action directly assigns scores and
- bypasses judge assignment checks.
+
+
+
+ !
+
+
+
+ Organizer scoring override
+
+
+ Directly assign results to this submission. These values
+ will bypass standard judge assignment and
+ conflict-of-interest checks.
+
+
@@ -260,7 +280,7 @@ export default function GradeSubmissionModal({
getScoreColor={getScoreColor}
overallComment={overallComment}
onOverallCommentChange={setOverallComment}
- showComments={!isOverride}
+ showComments={true}
/>
@@ -288,23 +308,28 @@ export default function GradeSubmissionModal({
/ {criteria.length}
-
-
Comments Added
-
+
+
+ Insights
+
+
{
Object.values(comments).filter(
c => c.trim().length > 0
).length
- }
+ }{' '}
+ Comments
-
-
- Your scores and comments are saved automatically
- when you submit. You can return later to update
- them.
-
+
+
+
+
+ Changes are staged in real-time. Finalize by
+ clicking the primary action below.
+
+
@@ -315,7 +340,7 @@ export default function GradeSubmissionModal({
)}
-
+
Math.round(totalScore), [totalScore]);
const getScoreColor = (percentage: number): string => {
- if (percentage >= 80) return 'bg-success-500';
- if (percentage >= 60) return 'bg-primary';
- if (percentage >= 40) return 'bg-warning-500';
- return 'bg-error-500';
+ if (percentage >= 80) return 'bg-primary text-background';
+ if (percentage >= 60) return 'bg-chart-2 text-white';
+ if (percentage >= 40) return 'bg-warning-500 text-background';
+ return 'bg-error-500 text-white';
};
return {
diff --git a/components/organization/cards/GradeSubmissionModal/useScoreForm.ts b/components/organization/cards/GradeSubmissionModal/useScoreForm.ts
index ddca07f7..46339b41 100644
--- a/components/organization/cards/GradeSubmissionModal/useScoreForm.ts
+++ b/components/organization/cards/GradeSubmissionModal/useScoreForm.ts
@@ -19,6 +19,7 @@ interface UseScoreFormProps {
criteria: JudgingCriterion[];
organizationId: string;
hackathonId: string;
+ submissionId: string;
participantId: string;
existingScore: { scores: CriterionScore[]; notes?: string } | null;
mode?: 'judge' | 'organizer-override';
@@ -37,6 +38,7 @@ export const useScoreForm = ({
criteria,
organizationId,
hackathonId,
+ submissionId,
participantId,
existingScore,
mode = 'judge',
@@ -141,7 +143,7 @@ export const useScoreForm = ({
setIsLoading(true);
try {
- const includeComments = mode !== 'organizer-override';
+ const includeComments = true;
const scoreData = criteria.map(criterion => {
const key = getCriterionKey(criterion);
const payload: {
@@ -163,14 +165,15 @@ export const useScoreForm = ({
? await overrideSubmissionScore(
organizationId,
hackathonId,
- participantId,
+ submissionId,
{
criteriaScores: scoreData,
judgeId: overrideJudgeId,
+ notes: overallComment,
}
)
: await submitJudgingScore({
- submissionId: participantId,
+ submissionId,
criteriaScores: scoreData,
comment: overallComment,
});
@@ -207,11 +210,21 @@ export const useScoreForm = ({
}
} catch (error: any) {
// Handle network or unexpected errors
- const errorMessage =
+ const status = error?.response?.status;
+ let errorMessage =
error?.response?.data?.message ||
error?.message ||
'Failed to submit grade. Please try again.';
- toast.error(errorMessage);
+
+ // Explicitly handle Conflict of Interest
+ if (status === 403) {
+ errorMessage =
+ 'Conflict of Interest: You are not permitted to score this submission (e.g., self-submission or team member).';
+ }
+
+ toast.error(errorMessage, {
+ description: status === 403 ? 'Rules of Competition' : undefined,
+ });
} finally {
setIsLoading(false);
}
diff --git a/components/organization/cards/GradeSubmissionModal/useSubmissionScores.ts b/components/organization/cards/GradeSubmissionModal/useSubmissionScores.ts
index 35185f67..a1f46977 100644
--- a/components/organization/cards/GradeSubmissionModal/useSubmissionScores.ts
+++ b/components/organization/cards/GradeSubmissionModal/useSubmissionScores.ts
@@ -12,8 +12,11 @@ interface UseSubmissionScoresProps {
open: boolean;
organizationId: string;
hackathonId: string;
+ submissionId: string;
participantId: string;
criteria: JudgingCriterion[];
+ targetJudgeId?: string;
+ initialScore?: IndividualJudgeScore;
}
interface ExistingScore {
@@ -25,8 +28,11 @@ export const useSubmissionScores = ({
open,
organizationId,
hackathonId,
+ submissionId,
participantId,
criteria,
+ targetJudgeId,
+ initialScore,
}: UseSubmissionScoresProps) => {
const [scores, setScores] = useState>({});
const [comments, setComments] = useState>({});
@@ -37,7 +43,8 @@ export const useSubmissionScores = ({
);
// Track which submission we've successfully loaded to avoid resets
- const loadedParticipantIdRef = useRef(null);
+ const loadedSubmissionIdRef = useRef(null);
+ const loadedTargetJudgeIdRef = useRef(null);
const getCriterionKey = (criterion: JudgingCriterion) => {
return criterion.id || criterion.name || (criterion as any).title;
@@ -71,14 +78,62 @@ export const useSubmissionScores = ({
setComments({});
setOverallComment('');
setExistingScore(null);
- loadedParticipantIdRef.current = null;
+ loadedSubmissionIdRef.current = null;
+ loadedTargetJudgeIdRef.current = null;
}
return;
}
- // If we already loaded data for this participant, don't refetch/reset
- // This prevents the "clearing" bug when criteria or other deps change slightly
- if (loadedParticipantIdRef.current === participantId) {
+ if (
+ loadedSubmissionIdRef.current === submissionId &&
+ loadedTargetJudgeIdRef.current === (targetJudgeId || 'session')
+ ) {
+ return;
+ }
+
+ // Optimization: If initialScore is provided and matches the target, use it
+ // but only if we haven't already loaded something for this modal session
+ if (initialScore && !loadedSubmissionIdRef.current) {
+ const initialScores: Record = {};
+ const initialComments: Record = {};
+
+ // Initialize with 0
+ criteria.forEach(c => {
+ const key = getCriterionKey(c);
+ initialScores[key] = 0;
+ initialComments[key] = '';
+ });
+
+ // Map existing scores
+ initialScore.criteriaScores.forEach(cs => {
+ const matchingCriterion = criteria.find(
+ c =>
+ c.id === cs.criterionId ||
+ c.name === cs.criterionId ||
+ c.id === cs.criterionName ||
+ c.name === cs.criterionName ||
+ (c as any).title === cs.criterionName
+ );
+
+ const key = matchingCriterion
+ ? getCriterionKey(matchingCriterion)
+ : cs.criterionId;
+
+ if (key) {
+ initialScores[key] = cs.score;
+ initialComments[key] = cs.comment || '';
+ }
+ });
+
+ setExistingScore({
+ scores: initialScore.criteriaScores,
+ notes: initialScore.comment || '',
+ });
+ setScores(initialScores);
+ setComments(initialComments);
+ setOverallComment(initialScore.comment || '');
+ loadedSubmissionIdRef.current = submissionId;
+ loadedTargetJudgeIdRef.current = targetJudgeId || 'session';
return;
}
@@ -87,7 +142,7 @@ export const useSubmissionScores = ({
try {
const [{ data: sessionData }, response] = await Promise.all([
authClient.getSession(),
- getSubmissionScores(organizationId, hackathonId, participantId),
+ getSubmissionScores(organizationId, hackathonId, submissionId),
]);
if (cancelled) return;
@@ -99,17 +154,18 @@ export const useSubmissionScores = ({
}
if (response.success && Array.isArray(response.data)) {
- // Safer judge identifier matching - prioritize ID and email
- const currentUserScore = (
- response.data as IndividualJudgeScore[]
- ).find(
- s =>
- s.judgeId === user.id ||
- s.judgeEmail === user.email ||
- (s.judgeName === user.name &&
- user.name !== undefined &&
- user.name.trim() !== '')
- );
+ // Identify the correct score based on targetJudgeId or current user
+ const scoreData = response.data as IndividualJudgeScore[];
+ const currentUserScore = targetJudgeId
+ ? scoreData.find(s => s.judgeId === targetJudgeId)
+ : scoreData.find(
+ s =>
+ s.judgeId === user.id ||
+ s.judgeEmail === user.email ||
+ (s.judgeName === user.name &&
+ user.name !== undefined &&
+ user.name.trim() !== '')
+ );
if (currentUserScore) {
setExistingScore({
@@ -157,10 +213,15 @@ export const useSubmissionScores = ({
setScores(initialScores);
setComments(initialComments);
setOverallComment(currentUserScore.comment || '');
- loadedParticipantIdRef.current = participantId;
+ loadedSubmissionIdRef.current = submissionId;
+ loadedTargetJudgeIdRef.current = targetJudgeId || 'session';
}
} else {
- if (!cancelled) initializeForm(criteria);
+ if (!cancelled) {
+ initializeForm(criteria);
+ loadedSubmissionIdRef.current = submissionId;
+ loadedTargetJudgeIdRef.current = targetJudgeId || 'session';
+ }
}
} else {
if (!cancelled) initializeForm(criteria);
@@ -168,7 +229,7 @@ export const useSubmissionScores = ({
} catch (err) {
reportError(err, {
context: 'useSubmissionScores-fetch',
- participantId,
+ submissionId,
});
if (!cancelled) initializeForm(criteria);
} finally {
@@ -181,7 +242,14 @@ export const useSubmissionScores = ({
return () => {
cancelled = true;
};
- }, [open, organizationId, hackathonId, participantId, criteria]);
+ }, [
+ open,
+ organizationId,
+ hackathonId,
+ submissionId,
+ criteria,
+ targetJudgeId,
+ ]);
return {
scores,
diff --git a/components/organization/cards/JudgingParticipant.tsx b/components/organization/cards/JudgingParticipant.tsx
index 82b85d75..e459bb63 100644
--- a/components/organization/cards/JudgingParticipant.tsx
+++ b/components/organization/cards/JudgingParticipant.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { useState, useMemo } from 'react';
+import { useState, useCallback, useMemo, useEffect, memo } from 'react';
import {
ArrowUpRight,
Mail,
@@ -8,6 +8,7 @@ import {
ChevronDown,
ChevronUp,
Loader2,
+ Trophy,
} from 'lucide-react';
import Image from 'next/image';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
@@ -17,6 +18,7 @@ import TeamModal from './TeamModal';
import GradeSubmissionModal from './GradeSubmissionModal';
import {
getJudgingCriteria,
+ getSubmissionScores,
type JudgingSubmission,
type JudgingCriterion,
} from '@/lib/api/hackathons/judging';
@@ -25,6 +27,7 @@ import { reportError } from '@/lib/error-reporting';
import IndividualScoresBreakdown from './JudgingParticipant/IndividualScoresBreakdown';
import { authClient } from '@/lib/auth-client';
+import BasicAvatar from '@/components/avatars/BasicAvatar';
interface JudgingParticipantProps {
submission: JudgingSubmission;
@@ -36,6 +39,8 @@ interface JudgingParticipantProps {
currentUserId?: string;
canOverrideScores?: boolean;
onSuccess?: () => void;
+ variant?: 'detailed' | 'compact';
+ criteria?: JudgingCriterion[];
}
const JudgingParticipant = ({
@@ -48,6 +53,8 @@ const JudgingParticipant = ({
currentUserId,
canOverrideScores = false,
onSuccess,
+ variant = 'detailed',
+ criteria: passedCriteria = [],
}: JudgingParticipantProps) => {
const [isTeamModalOpen, setIsTeamModalOpen] = useState(false);
const [isScoreModalOpen, setIsScoreModalOpen] = useState(false);
@@ -65,61 +72,87 @@ const JudgingParticipant = ({
Array<{ title: string; weight: number; description?: string }>
>([]);
const [isLoadingCriteria, setIsLoadingCriteria] = useState(false);
- const [showBreakdown, setShowBreakdown] = useState(false);
-
- const sub = submission as any;
- const participant = sub.participant || sub;
+ const sub = submission;
+ const participant = sub.participant;
const submissionData = sub.submission || sub;
- const userProfile =
- participant.user?.profile || participant.submitterProfile || {};
- let userName = 'Unknown User';
- if (participant.name) {
- userName = participant.name;
- } else if (participant.user?.name) {
- userName = participant.user.name;
- } else if (userProfile.firstName || userProfile.lastName) {
- userName =
- `${userProfile.firstName || ''} ${userProfile.lastName || ''}`.trim();
- } else if (participant.submitterName) {
- userName = participant.submitterName;
- }
- const userAvatar =
- participant.image ||
- participant.user?.image ||
- userProfile.avatar ||
- userProfile.image ||
- participant.submitterAvatar ||
- '';
+ const [showBreakdown, setShowBreakdown] = useState(false);
+ const [localAverageScore, setLocalAverageScore] = useState(
+ sub.averageScore !== undefined && sub.averageScore !== null
+ ? Number(sub.averageScore)
+ : null
+ );
+
+ const handleScoresLoaded = (avg: number | null) => {
+ if (avg !== null) {
+ setLocalAverageScore(avg);
+ }
+ };
- const username =
- participant.username ||
- participant.user?.username ||
- userProfile.username ||
- participant.submitterUsername ||
- 'anonymous';
+ useEffect(() => {
+ if (sub.averageScore !== undefined && sub.averageScore !== null) {
+ setLocalAverageScore(Number(sub.averageScore));
+ } else if (hasCriteria) {
+ // Proactively fetch scores if backend aggregation is missing
+ const fetchScores = async () => {
+ try {
+ const res = await getSubmissionScores(
+ organizationId,
+ hackathonId,
+ sub.submission.id
+ );
+ if (res.success && Array.isArray(res.data) && res.data.length > 0) {
+ const scores = res.data;
+ const avg =
+ scores.reduce(
+ (sum: number, s: any) =>
+ sum +
+ (s.totalScore ??
+ s.criteriaScores?.reduce(
+ (cSum: number, c: any) => cSum + (c.score || 0),
+ 0
+ ) ??
+ 0),
+ 0
+ ) / scores.length;
+ setLocalAverageScore(avg);
+ }
+ } catch (err) {
+ // Silent fail for proactive fetch
+ }
+ };
+ fetchScores();
+ }
+ }, [
+ sub.averageScore,
+ hasCriteria,
+ organizationId,
+ hackathonId,
+ participant.id,
+ ]);
- // Try to find the email
- const userEmail =
- participant.email ||
- participant.user?.email ||
- participant.submitterEmail ||
- '';
+ // New API response provides name/image/username directly on participant
+ const userName = participant.user?.name || 'Unknown User';
+ const userAvatar = participant.user?.image || '';
+ const username = participant.user?.username || 'anonymous';
+ const userEmail = participant.user?.email || '';
// Determine participation type for TeamModal
const participationType = useMemo(() => {
- const type =
- participant.participationType?.toLowerCase() || sub.type?.toLowerCase();
+ const type = participant.participationType?.toLowerCase();
return type === 'team' ? 'team' : 'individual';
- }, [participant.participationType, sub.type]);
+ }, [participant.participationType]);
// Fetch criteria when opening judge modal
const handleOpenScoreModal = async (mode: 'judge' | 'organizer-override') => {
if (!hasCriteria) return; // Guard clause
setIsLoadingCriteria(true);
try {
- const response = await getJudgingCriteria(hackathonId);
- const criteriaList = Array.isArray(response) ? response : [];
+ let criteriaList = passedCriteria;
+ if (criteriaList.length === 0) {
+ const response = await getJudgingCriteria(hackathonId);
+ criteriaList = Array.isArray(response) ? response : [];
+ }
if (criteriaList.length > 0) {
const validCriteria = criteriaList
@@ -155,188 +188,353 @@ const JudgingParticipant = ({
}
};
- return (
-
- {/* Project Image - Fixed square */}
-
-
+ {/* Project Image - Fixed square */}
+
+
+
+
+ {/* Content Area */}
+
+ {/* Project Info */}
+
+
+
+ {submissionData.projectName || 'Untitled Project'}
+
+
+ {submissionData.category || 'General'}
+
+ {submissionData.rank && (
+
+
+ #{submissionData.rank}
+
+ )}
+ {sub.myScore && (
+
+ You graded
+
+ )}
+
+
+ {submissionData.description || 'No description provided.'}
+
+
+
+ Submitted{' '}
+ {new Date(
+ submissionData.submissionDate || Date.now()
+ ).toLocaleDateString()}
+
+
+
+ setShowBreakdown(!showBreakdown)}
+ className='hover:text-primary flex items-center gap-1 text-xs text-white transition-colors'
+ >
+ {localAverageScore !== null ? (
+ <>Score: {Number(localAverageScore).toFixed(1)}>
+ ) : (
+ Not Graded
+ )}
+ {showBreakdown ? (
+
+ ) : (
+
+ )}
+
+
+ {submissionData.id && (
+ <>
+
+
+ View Details
+
+ >
+ )}
+
+
+
+ {/* User Info & Actions */}
+
+
+
+
+
+ {/* Grade Button */}
+
+ {isJudgesLoading ? (
+
+ ) : (
+ <>
+ {hasCriteria && isAssignedJudge && (
+
handleOpenScoreModal('judge')}
+ disabled={isLoadingCriteria}
+ className='bg-primary text-primary-foreground hover:bg-primary/90 h-8 shrink-0 gap-1.5 px-3 text-xs'
+ >
+ Grade
+
+
+ )}
+ {hasCriteria && canOverrideScores && (
+
handleOpenScoreModal('organizer-override')}
+ disabled={isLoadingCriteria}
+ className='h-8 shrink-0 border-amber-500/40 px-3 text-xs text-amber-300 hover:bg-amber-500/10 hover:text-amber-200'
+ >
+ Override
+
+ )}
+ >
+ )}
+
+
+
+
+ {/* Breakdown & Modals */}
+ {showBreakdown && (
+
+
+
+ )}
+
+
+ );
+ }
- {/* Content Area */}
-
- {/* Project Info */}
-
-
-
- {submissionData.projectName || 'Untitled Project'}
+ // Compact View (New)
+ return (
+
+
+
+
+
+
+
+
+ {submissionData.projectName || 'Untitled'}
-
- {submissionData.category || 'General'}
-
+ •
+
+ {userName}
+
-
- {submissionData.description || 'No description provided.'}
-
-
-
- Submitted{' '}
- {new Date(
- submissionData.submissionDate || participant.registeredAt
- ).toLocaleDateString()}
+
+
+ {submissionData.category || 'General'}
-
-
- setShowBreakdown(!showBreakdown)}
- className='hover:text-primary flex items-center gap-1 text-xs text-white transition-colors'
- >
- {sub.averageScore !== undefined && sub.averageScore !== null ? (
- <>Score: {Number(sub.averageScore).toFixed(1)}>
- ) : (
- Not Graded
- )}
- {showBreakdown ? (
-
- ) : (
-
- )}
-
-
- {/* Link */}
- {submissionData.id && (
- <>
-
-
- View Details
-
- >
+ {sub.myScore && (
+
+ (You graded)
+
)}
+
|
+
setShowBreakdown(!showBreakdown)}
+ className='hover:text-primary text-gray-400 transition-colors'
+ >
+ {localAverageScore !== null ? (
+
+ Score: {Number(localAverageScore).toFixed(1)}
+ {showBreakdown ? (
+
+ ) : (
+
+ )}
+
+ ) : (
+ 'Not Graded'
+ )}
+
+
- {/* User Info & Actions */}
-
-
-
-
-
- {userName.charAt(0).toUpperCase()}
-
-
-
-
- {userName}
-
-
- @{username}
-
-
+
+ {submissionData.rank && (
+
+
+ #{submissionData.rank}
+ )}
- {/* Grade Button - Only for assigned judges */}
-
- {isJudgesLoading ? (
-
- ) : (
- <>
- {hasCriteria && isAssignedJudge && (
-
handleOpenScoreModal('judge')}
- disabled={isLoadingCriteria}
- className='bg-primary text-primary-foreground hover:bg-primary/90 h-8 shrink-0 gap-1.5 px-3 text-xs'
- >
- Grade
-
-
- )}
- {hasCriteria && canOverrideScores && (
-
handleOpenScoreModal('organizer-override')}
- disabled={isLoadingCriteria}
- className='h-8 shrink-0 border-amber-500/40 px-3 text-xs text-amber-300 hover:bg-amber-500/10 hover:text-amber-200'
- >
- Override
-
- )}
- >
+ {isJudgesLoading ? (
+
+ ) : (
+
+ {hasCriteria && isAssignedJudge && (
+ handleOpenScoreModal('judge')}
+ disabled={isLoadingCriteria}
+ className='bg-primary text-primary-foreground hover:bg-primary/90 h-7 px-2 text-[10px]'
+ >
+ Grade
+
+ )}
+ {hasCriteria && canOverrideScores && (
+ handleOpenScoreModal('organizer-override')}
+ disabled={isLoadingCriteria}
+ className='h-7 border-amber-500/40 px-2 text-[10px] text-amber-300'
+ >
+ Override
+
)}
+
+
+
-
+ )}
- {/* Expanded Breakdown */}
+ {/* Breakdown Overlay */}
{showBreakdown && (
-
+
)}
- {/* Team Modal */}
-
{
- // TODO: Navigate to team details page or show team information
- }}
- />
-
- {/* Grade Submission Modal */}
- {
- if (onSuccess) {
- onSuccess();
- }
+
);
};
-export default JudgingParticipant;
+// Helper component to avoid duplication
+const SharedModals = ({
+ isTeamModalOpen,
+ setIsTeamModalOpen,
+ isScoreModalOpen,
+ setIsScoreModalOpen,
+ criteria,
+ scoreMode,
+ judges,
+ organizationId,
+ hackathonId,
+ sub,
+ participant,
+ submissionData,
+ participationType,
+ onSuccess,
+}: any) => (
+ <>
+
{}}
+ />
+
+
+ >
+);
+
+export default memo(JudgingParticipant);
diff --git a/components/organization/cards/JudgingParticipant/IndividualScoresBreakdown.tsx b/components/organization/cards/JudgingParticipant/IndividualScoresBreakdown.tsx
index 8c153a02..6adf8ac0 100644
--- a/components/organization/cards/JudgingParticipant/IndividualScoresBreakdown.tsx
+++ b/components/organization/cards/JudgingParticipant/IndividualScoresBreakdown.tsx
@@ -18,12 +18,13 @@ import {
interface IndividualScoresBreakdownProps {
organizationId: string;
hackathonId: string;
- participantId: string;
+ submissionId: string;
initialScores?: Array<{
judgeId: string;
judgeName: string;
score: number;
}>;
+ onScoresLoaded?: (average: number | null) => void;
}
interface JudgeScore {
@@ -43,8 +44,9 @@ interface JudgeScore {
const IndividualScoresBreakdown = ({
organizationId,
hackathonId,
- participantId,
+ submissionId,
initialScores,
+ onScoresLoaded,
}: IndividualScoresBreakdownProps) => {
const [scores, setScores] = useState([]);
const [isLoading, setIsLoading] = useState(!initialScores);
@@ -74,14 +76,13 @@ const IndividualScoresBreakdown = ({
const res = await getSubmissionScores(
organizationId,
hackathonId,
- participantId
+ submissionId
);
if (res.success && Array.isArray(res.data)) {
// Map API response to internal state shape
const mappedScores: JudgeScore[] = res.data.map((item: any) => ({
judgeId: item.judgeId,
judgeName: item.judgeName,
- // Ensure totalScore is available, fallback to sum of criteria scores if missing
totalScore:
item.totalScore ??
item.criteriaScores?.reduce(
@@ -89,23 +90,50 @@ const IndividualScoresBreakdown = ({
0
) ??
0,
- score: item.totalScore, // Keep score for backward compatibility if needed
+ score: item.totalScore,
comment: item.comment,
criteriaScores: item.criteriaScores,
}));
setScores(mappedScores);
+
+ if (onScoresLoaded) {
+ const avg =
+ mappedScores.length > 0
+ ? mappedScores.reduce(
+ (sum, s) => sum + (s.totalScore ?? 0),
+ 0
+ ) / mappedScores.length
+ : null;
+ onScoresLoaded(avg);
+ }
} else if (initialScores) {
- // Fallback to initialScores if API fails
- setScores(normalizeInitialScores(initialScores));
+ const normalized = normalizeInitialScores(initialScores);
+ setScores(normalized);
+ if (onScoresLoaded) {
+ const avg =
+ normalized.length > 0
+ ? normalized.reduce((sum, s) => sum + (s.totalScore ?? 0), 0) /
+ normalized.length
+ : null;
+ onScoresLoaded(avg);
+ }
}
} catch (err) {
reportError(err, {
context: 'judging-individualScores',
- participantId,
+ submissionId,
});
- // Fallback to initialScores if fetch fails
if (initialScores) {
- setScores(normalizeInitialScores(initialScores));
+ const normalized = normalizeInitialScores(initialScores);
+ setScores(normalized);
+ if (onScoresLoaded) {
+ const avg =
+ normalized.length > 0
+ ? normalized.reduce((sum, s) => sum + (s.totalScore ?? 0), 0) /
+ normalized.length
+ : null;
+ onScoresLoaded(avg);
+ }
}
} finally {
setIsLoading(false);
@@ -114,7 +142,7 @@ const IndividualScoresBreakdown = ({
fetchScores();
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [organizationId, hackathonId, participantId]);
+ }, [organizationId, hackathonId, submissionId]);
const toggleExpand = (judgeId: string) => {
setExpandedJudges(prev => ({
diff --git a/components/organization/cards/MetricsCard.tsx b/components/organization/cards/MetricsCard.tsx
index b8f0e275..1ba7d6d8 100644
--- a/components/organization/cards/MetricsCard.tsx
+++ b/components/organization/cards/MetricsCard.tsx
@@ -7,8 +7,9 @@ interface MetricsCardProps {
title: string;
value: string | number;
subtitle?: string;
- icon?: string;
+ icon?: string | React.ReactNode;
showTrend?: boolean;
+ className?: string;
}
const MetricsCard = ({
@@ -17,6 +18,7 @@ const MetricsCard = ({
subtitle,
icon = '/user-border.svg',
showTrend = false,
+ className,
}: MetricsCardProps) => {
const formattedValue =
typeof value === 'number' ? value.toLocaleString() : value;
@@ -27,13 +29,17 @@ const MetricsCard = ({
style={{ backgroundImage: 'url("/metric-image.svg")' }}
>
-
+ {typeof icon === 'string' ? (
+
+ ) : (
+ icon
+ )}
{title}
diff --git a/components/organization/cards/Participant.tsx b/components/organization/cards/Participant.tsx
index c62bf112..750670bb 100644
--- a/components/organization/cards/Participant.tsx
+++ b/components/organization/cards/Participant.tsx
@@ -150,6 +150,7 @@ const Participant = ({
onOpenChange={setIsJudgeModalOpen}
organizationId={organizationId}
hackathonId={hackathonId}
+ submissionId={participant.submission.id}
participantId={participant.id}
judgingCriteria={criteria}
submission={{
diff --git a/components/organization/hackathons/judging/JudgingResultsTable.tsx b/components/organization/hackathons/judging/JudgingResultsTable.tsx
index 39dc095a..fe8eebf0 100644
--- a/components/organization/hackathons/judging/JudgingResultsTable.tsx
+++ b/components/organization/hackathons/judging/JudgingResultsTable.tsx
@@ -21,6 +21,10 @@ import { cn } from '@/lib/utils';
import IndividualScoresBreakdown from '@/components/organization/cards/JudgingParticipant/IndividualScoresBreakdown';
import AggregatedCriteriaBreakdown from './AggregatedCriteriaBreakdown';
import { JudgingCriterion } from '@/lib/api/hackathons/judging';
+import { updateHackathon, getHackathon } from '@/lib/api/hackathons';
+import { toast } from 'sonner';
+import { Input } from '@/components/ui/input';
+import { Loader2 } from 'lucide-react';
interface JudgingResultsTableProps {
results: JudgingResult[];
@@ -28,6 +32,8 @@ interface JudgingResultsTableProps {
hackathonId: string;
totalJudges?: number;
criteria?: JudgingCriterion[];
+ canManage?: boolean;
+ winnerOverrides?: Record;
}
// Helper function to safely extract score from JudgingResult
@@ -41,10 +47,31 @@ const JudgingResultsTable = ({
hackathonId,
totalJudges,
criteria = [],
+ canManage = false,
+ winnerOverrides: initialWinnerOverrides = {},
}: JudgingResultsTableProps) => {
const [expandedRows, setExpandedRows] = React.useState<
Record
>({});
+ const [winnerOverrides, setWinnerOverrides] = React.useState<
+ Record
+ >({});
+ const [isUpdating, setIsUpdating] = React.useState(null);
+
+ // Initialize overrides from props or results
+ React.useEffect(() => {
+ const overrides: Record = {
+ ...initialWinnerOverrides,
+ };
+
+ // Fallback to results[].rank if not in overrides (for published results)
+ results.forEach(res => {
+ if (res.rank && !overrides[res.submissionId]) {
+ overrides[res.submissionId] = res.rank;
+ }
+ });
+ setWinnerOverrides(overrides);
+ }, [results, initialWinnerOverrides]);
const toggleRow = (id: string) => {
setExpandedRows(prev => ({
@@ -53,11 +80,46 @@ const JudgingResultsTable = ({
}));
};
- const sortedResults = React.useMemo(() => {
- return [...results].sort((a, b) => {
- return getScore(b) - getScore(a);
- });
- }, [results]);
+ const sortedResults = results;
+
+ const handleOverrideRank = async (
+ submissionId: string,
+ rank: number | null
+ ) => {
+ try {
+ setIsUpdating(submissionId);
+ setWinnerOverrides(prev => ({ ...prev, [submissionId]: rank }));
+
+ // Fetch current hackathon to get latest state
+ const hackathonRes = await getHackathon(hackathonId);
+ const hackathonData = hackathonRes.data;
+
+ // Update overrides record
+ const updatedOverrides = {
+ ...(hackathonData.winnerOverrides || {}),
+ };
+
+ if (rank === null || isNaN(rank)) {
+ delete updatedOverrides[submissionId];
+ } else {
+ updatedOverrides[submissionId] = rank;
+ }
+
+ await updateHackathon(hackathonId, {
+ rewards: {
+ prizeTiers: (hackathonData.prizeTiers as any) || [],
+ winnerOverrides: updatedOverrides,
+ },
+ });
+
+ toast.success('Manual rank updated');
+ } catch (error: any) {
+ console.error('Failed to update rank override:', error);
+ toast.error('Failed to update manual rank');
+ } finally {
+ setIsUpdating(null);
+ }
+ };
const getRankIcon = (index: number) => {
switch (index) {
@@ -85,6 +147,11 @@ const JudgingResultsTable = ({
Participation
+ {canManage && (
+
+ Manual Rank
+
+ )}
@@ -200,10 +267,36 @@ const JudgingResultsTable = ({
+ {canManage && (
+
e.stopPropagation()}
+ >
+
+ {isUpdating === result.submissionId ? (
+
+ ) : (
+ {
+ const val = e.target.value
+ ? parseInt(e.target.value)
+ : null;
+ handleOverrideRank(result.submissionId, val);
+ }}
+ />
+ )}
+
+
+ )}
{expandedRows[result.submissionId] && (
-
+
{/* Aggregated Criteria Breakdown */}
{result.criteriaBreakdown &&
@@ -218,7 +311,7 @@ const JudgingResultsTable = ({
@@ -231,7 +324,7 @@ const JudgingResultsTable = ({
{results.length === 0 && (
No judging results available yet. Results appear once judges
diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts
index 57a21c0b..52b95d3b 100644
--- a/lib/api/hackathons.ts
+++ b/lib/api/hackathons.ts
@@ -121,6 +121,7 @@ export interface PrizeTier {
export interface HackathonRewards {
prizeTiers: PrizeTier[];
+ winnerOverrides?: Record; // submissionId -> rank
}
// Judging Tab Types
@@ -432,6 +433,7 @@ export type Hackathon = {
publishedAt: string;
createdAt: string;
updatedAt: string;
+ winnerOverrides?: Record; // Added for manual rank overrides
participants: Participant[];
_count: {
@@ -475,7 +477,9 @@ export interface PublishHackathonRequest extends Hackathon {
escrowDetails?: object;
}
-export type UpdateHackathonRequest = Partial;
+export type UpdateHackathonRequest = Partial & {
+ rewards?: HackathonRewards;
+};
// Response Types
export interface CreateDraftResponse extends ApiResponse {
@@ -2920,6 +2924,7 @@ export const toggleRoleHired = async (
export interface HackathonWinner {
rank: number;
projectName: string;
+ logo: string;
projectId?: string;
teamName: string | null;
participants: Array<{
diff --git a/lib/api/hackathons/judging.ts b/lib/api/hackathons/judging.ts
index 41a27bed..eff483a5 100644
--- a/lib/api/hackathons/judging.ts
+++ b/lib/api/hackathons/judging.ts
@@ -81,6 +81,7 @@ export interface JudgingResult {
isPending: boolean;
hasDisagreement: boolean;
prize?: string;
+ overriddenRank?: number; // Added to track manual overrides
}
export interface AggregatedJudgingResults {
@@ -90,7 +91,9 @@ export interface AggregatedJudgingResults {
submissionsPendingCount: number;
averageScoreAcrossAll: number;
judgesAssigned: number;
+ resultsPublished: boolean;
results: JudgingResult[];
+ winnerOverrides?: Record; // submissionId -> rank
generatedAt: string;
metadata: {
sortedBy: string;
@@ -98,27 +101,29 @@ export interface AggregatedJudgingResults {
includesIndividualScores: boolean;
includesProgressTracking: boolean;
};
+ pagination?: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ };
}
export interface JudgingSubmission {
participant: {
id: string;
userId: string;
- hackathonId: string;
- organizationId: string;
user: {
id: string;
- profile: {
- firstName: string;
- lastName: string;
- username: string;
- avatar?: string;
- };
email: string;
+ name: string;
+ username: string;
+ image?: string;
};
- participationType: 'individual' | 'team';
+ participationType: string;
teamId?: string;
teamName?: string;
+ teamMembers?: any[];
};
submission: {
id: string;
@@ -130,11 +135,11 @@ export interface JudgingSubmission {
introduction?: string;
links?: Array<{ type: string; url: string }>;
submissionDate: string;
- status: 'shortlisted';
+ status: string;
rank?: number;
+ socialLinks?: any;
};
- criteria: JudgingCriterion[];
- scores: JudgeScore[];
+ myScore?: IndividualJudgeScore;
averageScore: number | null;
judgeCount: number;
}
@@ -229,6 +234,7 @@ export interface SubmitJudgingScoreRequest {
export interface OverrideSubmissionScoreRequest {
criteriaScores: CriterionScoreRequest[];
judgeId?: string;
+ notes?: string;
}
export interface OverrideSubmissionScoreResponse extends ApiResponse<{
@@ -363,13 +369,20 @@ export const getJudgingSubmissions = async (
hackathonId: string,
page = 1,
limit = 10,
- status?: string
+ status?: string,
+ search?: string,
+ sortBy?: string,
+ order?: 'asc' | 'desc'
): Promise => {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
});
+ if (search) params.append('search', search);
+ if (sortBy) params.append('sortBy', sortBy);
+ if (order) params.append('order', order);
+
// Default to SHORTLISTED for backwards-compatibility, passing 'all' bypasses it
const filterStatus = status === 'all' ? undefined : status || 'SHORTLISTED';
if (filterStatus) {
@@ -404,12 +417,12 @@ export const submitGrade = async (
export const getSubmissionScores = async (
organizationId: string,
hackathonId: string,
- participantId: string
+ submissionId: string
): Promise => {
const res = await api.get<
IndividualJudgeScore[] | ApiResponse
>(
- `/organizations/${organizationId}/hackathons/${hackathonId}/judging/submissions/${participantId}/scores`
+ `/organizations/${organizationId}/hackathons/${hackathonId}/judging/submissions/${submissionId}/scores`
);
// Handle raw array response format
@@ -469,12 +482,26 @@ export const overrideSubmissionScore = async (
*/
export const getJudgingResults = async (
organizationId: string,
- hackathonId: string
+ hackathonId: string,
+ page = 1,
+ limit = 10,
+ search?: string,
+ sortBy?: string,
+ order?: 'asc' | 'desc'
): Promise => {
+ const params = new URLSearchParams({
+ page: page.toString(),
+ limit: limit.toString(),
+ });
+
+ if (search) params.append('search', search);
+ if (sortBy) params.append('sortBy', sortBy);
+ if (order) params.append('order', order);
+
const res = await api.get<
AggregatedJudgingResults | ApiResponse
>(
- `/organizations/${organizationId}/hackathons/${hackathonId}/judging/results`
+ `/organizations/${organizationId}/hackathons/${hackathonId}/judging/results?${params.toString()}`
);
// Handle case where backend returns AggregatedJudgingResults directly vs ApiResponse wrapped
@@ -547,12 +574,26 @@ export const getHackathonJudges = async (
*/
export const getJudgingWinners = async (
organizationId: string,
- hackathonId: string
+ hackathonId: string,
+ page = 1,
+ limit = 10,
+ search?: string,
+ sortBy?: string,
+ order?: 'asc' | 'desc'
): Promise => {
+ const params = new URLSearchParams({
+ page: page.toString(),
+ limit: limit.toString(),
+ });
+
+ if (search) params.append('search', search);
+ if (sortBy) params.append('sortBy', sortBy);
+ if (order) params.append('order', order);
+
const res = await api.get<{
data: JudgingResult[] | AggregatedJudgingResults;
}>(
- `/organizations/${organizationId}/hackathons/${hackathonId}/judging/winners`
+ `/organizations/${organizationId}/hackathons/${hackathonId}/judging/winners?${params.toString()}`
);
const payload = res.data?.data;
@@ -592,3 +633,39 @@ export const publishJudgingResults = async (
);
return res.data;
};
+
+/**
+ * Optimized endpoint for judges to get their assigned submissions with criteria and scores
+ */
+export const getJudgingSubmissionsForJudge = async (
+ hackathonId: string,
+ page = 1,
+ limit = 10,
+ search?: string,
+ sortBy?: string,
+ order?: 'asc' | 'desc'
+): Promise<
+ ApiResponse<{
+ submissions: JudgingSubmission[];
+ criteria: JudgingCriterion[];
+ pagination: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ };
+ }>
+> => {
+ const params = new URLSearchParams({
+ page: page.toString(),
+ limit: limit.toString(),
+ });
+ if (search) params.append('search', search);
+ if (sortBy) params.append('sortBy', sortBy);
+ if (order) params.append('order', order);
+
+ const res = await api.get(
+ `/hackathons/${hackathonId}/judging/submissions?${params.toString()}`
+ );
+ return res.data;
+};
diff --git a/package-lock.json b/package-lock.json
index d1f366f9..f67fd6df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -243,7 +243,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -471,7 +470,6 @@
"version": "1.4.18",
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.4.18.tgz",
"integrity": "sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg==",
- "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"zod": "^4.3.5"
@@ -501,14 +499,12 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@better-fetch/fetch": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz",
- "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==",
- "peer": true
+ "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="
},
"node_modules/@braintree/sanitize-url": {
"version": "7.1.2",
@@ -599,7 +595,6 @@
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
@@ -895,7 +890,6 @@
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
"integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@floating-ui/core": "^1.7.4",
"@floating-ui/utils": "^0.2.10"
@@ -1511,6 +1505,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@@ -1788,7 +1783,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
- "peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -1810,7 +1804,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz",
"integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==",
"license": "Apache-2.0",
- "peer": true,
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
@@ -1823,7 +1816,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz",
"integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -2247,7 +2239,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz",
"integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@opentelemetry/core": "2.6.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -2264,7 +2255,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz",
"integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@opentelemetry/core": "2.6.0",
"@opentelemetry/resources": "2.6.0",
@@ -2282,7 +2272,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz",
"integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==",
"license": "Apache-2.0",
- "peer": true,
"engines": {
"node": ">=14"
}
@@ -4243,7 +4232,6 @@
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/webxr": "*",
@@ -4347,7 +4335,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -5585,7 +5572,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
"integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@tanstack/query-core": "5.90.20"
},
@@ -5652,7 +5638,6 @@
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz",
"integrity": "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -5888,7 +5873,6 @@
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.19.0.tgz",
"integrity": "sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -5994,7 +5978,6 @@
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.19.0.tgz",
"integrity": "sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -6009,7 +5992,6 @@
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz",
"integrity": "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
@@ -6399,6 +6381,7 @@
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -6409,6 +6392,7 @@
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
@@ -6588,7 +6572,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -6598,7 +6581,6 @@
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -6641,7 +6623,6 @@
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz",
"integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@dimforge/rapier3d-compat": "~0.12.0",
"@tweenjs/tween.js": "~23.1.3",
@@ -6743,7 +6724,6 @@
"integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.0",
"@typescript-eslint/types": "8.56.0",
@@ -7176,6 +7156,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
@@ -7185,25 +7166,29 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -7214,13 +7199,15 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -7233,6 +7220,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@@ -7242,6 +7230,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@@ -7250,13 +7239,15 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -7273,6 +7264,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
@@ -7286,6 +7278,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -7298,6 +7291,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -7312,6 +7306,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
@@ -7327,20 +7322,21 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
- "license": "Apache-2.0"
+ "license": "Apache-2.0",
+ "peer": true
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7362,6 +7358,7 @@
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10.13.0"
},
@@ -7400,6 +7397,7 @@
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
@@ -7417,6 +7415,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -7432,7 +7431,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/ansi-escapes": {
"version": "7.3.0",
@@ -7918,7 +7918,6 @@
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.1.8.tgz",
"integrity": "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@better-auth/utils": "^0.3.0",
"@better-fetch/fetch": "^1.1.4",
@@ -8001,7 +8000,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -8044,7 +8042,8 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/call-bind": {
"version": "1.0.8",
@@ -8200,7 +8199,6 @@
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz",
"integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@chevrotain/cst-dts-gen": "11.1.2",
"@chevrotain/gast": "11.1.2",
@@ -8227,6 +8225,7 @@
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.0"
}
@@ -8493,7 +8492,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10"
}
@@ -8903,7 +8901,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -9057,7 +9054,6 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
@@ -9322,8 +9318,7 @@
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/embla-carousel-autoplay": {
"version": "8.6.0",
@@ -9556,7 +9551,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
@@ -9675,7 +9671,6 @@
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -9736,7 +9731,6 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -10108,6 +10102,7 @@
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.8.x"
}
@@ -10189,7 +10184,8 @@
"url": "https://opencollective.com/fastify"
}
],
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "peer": true
},
"node_modules/feaxios": {
"version": "0.0.23",
@@ -10555,7 +10551,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "license": "BSD-2-Clause"
+ "license": "BSD-2-Clause",
+ "peer": true
},
"node_modules/glob/node_modules/balanced-match": {
"version": "4.0.4",
@@ -10688,8 +10685,7 @@
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
"integrity": "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==",
- "license": "Standard 'no charge' license: https://gsap.com/standard-license.",
- "peer": true
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/hachure-fill": {
"version": "0.5.2",
@@ -11830,6 +11826,7 @@
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -11844,6 +11841,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -11868,7 +11866,6 @@
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
"integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -11924,7 +11921,8 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@@ -12018,11 +12016,10 @@
}
},
"node_modules/kysely": {
- "version": "0.28.13",
- "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.13.tgz",
- "integrity": "sha512-jCkYDvlfzOyHaVsrvR4vnNZxG30oNv2jbbFBjTQAUG8n0h07HW0sZJHk4KAQIRyu9ay+Rg+L8qGa3lwt8Gve9w==",
+ "version": "0.28.14",
+ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.14.tgz",
+ "integrity": "sha512-SU3lgh0rPvq7upc6vvdVrCsSMUG1h3ChvHVOY7wJ2fw4C9QEB7X3d5eyYEyULUX7UQtxZJtZXGuT6U2US72UYA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.0.0"
}
@@ -12385,6 +12382,7 @@
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.11.5"
},
@@ -12890,7 +12888,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/mermaid": {
"version": "11.12.3",
@@ -13842,7 +13841,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": "^20.0.0 || >=22.0.0"
}
@@ -13858,14 +13856,14 @@
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/next": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.0.tgz",
"integrity": "sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@next/env": "16.2.0",
"@swc/helpers": "0.5.15",
@@ -14661,7 +14659,6 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -14692,7 +14689,6 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -14955,7 +14951,6 @@
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"orderedmap": "^2.0.0"
}
@@ -14985,7 +14980,6 @@
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
@@ -15034,7 +15028,6 @@
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz",
"integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
@@ -15304,7 +15297,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -15335,7 +15327,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -15365,7 +15356,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz",
"integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -16104,7 +16094,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -16266,6 +16255,7 @@
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@@ -16302,6 +16292,7 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
@@ -16313,7 +16304,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/section-matter": {
"version": "1.0.0",
@@ -16657,6 +16649,7 @@
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -16667,6 +16660,7 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -17028,8 +17022,7 @@
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@@ -17049,6 +17042,7 @@
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@@ -17067,6 +17061,7 @@
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz",
"integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
@@ -17099,14 +17094,14 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/three": {
"version": "0.180.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
"integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/three-mesh-bvh": {
"version": "0.8.3",
@@ -17196,7 +17191,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -17487,7 +17481,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -17956,6 +17949,7 @@
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
"integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -17996,6 +17990,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz",
"integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -18044,6 +18039,7 @@
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz",
"integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10.13.0"
}
@@ -18053,6 +18049,7 @@
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
@@ -18066,6 +18063,7 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"license": "BSD-2-Clause",
+ "peer": true,
"engines": {
"node": ">=4.0"
}
@@ -18291,7 +18289,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}