From ed65b36b7365437a8b0aa5b658bda32c158bbf19 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:31:09 +0100 Subject: [PATCH 01/12] Pr 486 (#488) * feat: Implement comprehensive hackathon detail page with new header, sidebar, and tabbed content sections including teams, announcements, winners, participants, and resources. * fix: update coderabit * feat: Enhance hackathon team invitation flow, recruitment post management, and dynamic tab visibility. * feat: Integrate AuthModalProvider for authentication handling and enhance messaging features across hackathon components * fix: fix pagination issues in particiapants and submissions page for organizers * fix: remove any type --------- Co-authored-by: Collins Ikechukwu --- .../[hackathonId]/participants/page.tsx | 46 ++++++++- .../[hackathonId]/submissions/page.tsx | 96 +++++++++++++++---- .../submissions/SubmissionDetailModal.tsx | 10 +- components/organization/cards/Participant.tsx | 2 +- .../hackathons/ParticipantsGrid.tsx | 4 +- .../hackathons/ParticipantsTable.tsx | 23 +++-- .../submissions/SubmissionsManagement.tsx | 63 +++--------- hooks/hackathon/use-organizer-submissions.ts | 69 +++++++++---- hooks/hackathon/use-register-hackathon.ts | 2 +- hooks/use-hackathons.ts | 24 +++-- hooks/use-submission-actions.ts | 4 +- lib/api/hackathons.ts | 9 +- 12 files changed, 230 insertions(+), 122 deletions(-) diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx index b5a17ef1..e475a6a9 100644 --- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx @@ -43,8 +43,8 @@ interface FilterState { const mapFiltersToParams = (filters: FilterState, searchOverride?: string) => ({ search: searchOverride !== undefined ? searchOverride : filters.search, - status: filters.status === 'all' ? undefined : filters.status, - type: filters.type === 'all' ? undefined : filters.type, + status: filters.status === 'all' ? undefined : filters.status.toUpperCase(), + type: filters.type === 'all' ? undefined : filters.type.toUpperCase(), }); const ParticipantsPage: React.FC = () => { @@ -228,9 +228,45 @@ const ParticipantsPage: React.FC = () => { } }; + // Frontend-side filtering as per requirement + const filteredParticipants = useMemo(() => { + return participants.filter(participant => { + // Search: filter by name or username + const search = filters.search.toLowerCase(); + const matchesSearch = search + ? (participant.user?.profile?.name || '') + .toLowerCase() + .includes(search) || + (participant.user?.profile?.username || '') + .toLowerCase() + .includes(search) + : true; + + // Status: filter by participant.submission.status + // Filter values are 'submitted', 'not_submitted', etc. + // ParticipantSubmission.status values are 'submitted', 'shortlisted', etc. + const matchesStatus = + filters.status === 'all' + ? true + : filters.status === 'not_submitted' + ? !participant.submission + : participant.submission?.status?.toLowerCase() === + filters.status.toLowerCase(); + + // Type: filter by participant.participationType + const matchesType = + filters.type === 'all' + ? true + : participant.participationType?.toLowerCase() === + filters.type.toLowerCase(); + + return matchesSearch && matchesStatus && matchesType; + }); + }, [participants, filters.search, filters.status, filters.type]); + // Mock table instance for DataTablePagination const table = useReactTable({ - data: participants, + data: filteredParticipants, columns: [], // Not used for rendering here getCoreRowModel: getCoreRowModel(), manualPagination: true, @@ -316,7 +352,7 @@ const ParticipantsPage: React.FC = () => {
{view === 'table' ? ( { /> ) : ( (null); @@ -34,7 +41,6 @@ export default function SubmissionsPage() { useEffect(() => { if (hackathonId) { - fetchSubmissions(); const fetchHackathonDetails = async () => { try { const res = await getHackathon(hackathonId); @@ -50,7 +56,7 @@ export default function SubmissionsPage() { }; fetchHackathonDetails(); } - }, [hackathonId, fetchSubmissions]); + }, [hackathonId]); useEffect(() => { const fetchSession = async () => { @@ -66,6 +72,52 @@ export default function SubmissionsPage() { fetchSession(); }, []); + // Frontend-side filtering + const filteredSubmissions = useMemo(() => { + return allSubmissions.filter(sub => { + const search = filters.search?.toLowerCase() || ''; + const matchesSearch = search + ? (sub.projectName || '').toLowerCase().includes(search) || + (sub.participant?.name || '').toLowerCase().includes(search) || + (sub.participant?.username || '').toLowerCase().includes(search) + : true; + + const matchesStatus = !filters.status || sub.status === filters.status; + + const matchesType = + !filters.type || sub.participationType === filters.type; + + return matchesSearch && matchesStatus && matchesType; + }); + }, [allSubmissions, filters.search, filters.status, filters.type]); + + const table = useReactTable({ + data: filteredSubmissions, + columns: [], + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + pageCount: pagination.totalPages, + state: { + pagination: { + pageIndex: pagination.page - 1, + pageSize: pagination.limit, + }, + }, + onPaginationChange: updater => { + if (typeof updater === 'function') { + const newState = updater({ + pageIndex: pagination.page - 1, + pageSize: pagination.limit, + }); + if (newState.pageSize !== pagination.limit) { + updateLimit(newState.pageSize); + } else { + goToPage(newState.pageIndex + 1); + } + } + }, + }); + if (error) { return (
@@ -100,7 +152,7 @@ export default function SubmissionsPage() { {/* Main Content */}
- {loading && submissions.length === 0 ? ( + {loading && allSubmissions.length === 0 ? (
@@ -108,19 +160,25 @@ export default function SubmissionsPage() {
) : ( - +
+ + +
+ +
+
)}
diff --git a/components/hackathons/submissions/SubmissionDetailModal.tsx b/components/hackathons/submissions/SubmissionDetailModal.tsx index 3cb2bf64..99413574 100644 --- a/components/hackathons/submissions/SubmissionDetailModal.tsx +++ b/components/hackathons/submissions/SubmissionDetailModal.tsx @@ -166,16 +166,16 @@ export function SubmissionDetailModal({
- {submission.status === 'shortlisted' + {submission.status === 'SHORTLISTED' ? 'Shortlisted' - : submission.status === 'disqualified' + : submission.status === 'DISQUALIFIED' ? 'Disqualified' : 'Submitted'} @@ -295,7 +295,7 @@ export function SubmissionDetailModal({
{/* Disqualification Reason */} - {submission.status === 'disqualified' && + {submission.status === 'DISQUALIFIED' && submission.disqualificationReason && (

diff --git a/components/organization/cards/Participant.tsx b/components/organization/cards/Participant.tsx index 8d527083..c62bf112 100644 --- a/components/organization/cards/Participant.tsx +++ b/components/organization/cards/Participant.tsx @@ -45,7 +45,7 @@ const Participant = ({ // Check if submission is shortlisted const isShortlisted = useMemo(() => { - return participant.submission?.status === 'shortlisted'; + return participant.submission?.status === 'SHORTLISTED'; }, [participant.submission?.status]); // Fetch criteria when opening judge modal diff --git a/components/organization/hackathons/ParticipantsGrid.tsx b/components/organization/hackathons/ParticipantsGrid.tsx index 42d3a4ed..72281d88 100644 --- a/components/organization/hackathons/ParticipantsGrid.tsx +++ b/components/organization/hackathons/ParticipantsGrid.tsx @@ -67,8 +67,8 @@ export const ParticipantsGrid: React.FC = ({ const user = participant.user; const submission = participant.submission; const hasSubmission = !!submission; - const isShortlisted = submission?.status === 'shortlisted'; - const isDisqualified = submission?.status === 'disqualified'; + const isShortlisted = submission?.status === 'SHORTLISTED'; + const isDisqualified = submission?.status === 'DISQUALIFIED'; const isTeam = participant.participationType === 'team'; const votesCount = Array.isArray(submission?.votes) diff --git a/components/organization/hackathons/ParticipantsTable.tsx b/components/organization/hackathons/ParticipantsTable.tsx index 1ab5a3ee..c1e141b8 100644 --- a/components/organization/hackathons/ParticipantsTable.tsx +++ b/components/organization/hackathons/ParticipantsTable.tsx @@ -110,22 +110,25 @@ export function ParticipantsTable({ ); } - const status = submission.status; + const status = submission.status as + | 'SHORTLISTED' + | 'DISQUALIFIED' + | 'SUBMITTED'; return ( - {status === 'shortlisted' + {status === 'SHORTLISTED' ? 'Shortlisted' - : status === 'disqualified' + : status === 'DISQUALIFIED' ? 'Disqualified' : 'Submitted'} @@ -150,7 +153,7 @@ export function ParticipantsTable({ const participant = row.original; const hasSubmission = !!participant.submission; const isShortlisted = - participant.submission?.status === 'shortlisted'; + participant.submission?.status === 'SHORTLISTED'; return ( diff --git a/components/organization/hackathons/submissions/SubmissionsManagement.tsx b/components/organization/hackathons/submissions/SubmissionsManagement.tsx index f75edeab..3a55e8a2 100644 --- a/components/organization/hackathons/submissions/SubmissionsManagement.tsx +++ b/components/organization/hackathons/submissions/SubmissionsManagement.tsx @@ -32,11 +32,7 @@ import { useUpdateRank } from '@/hooks/hackathon/use-update-rank'; /* Types */ /* -------------------------------------------------------------------------- */ -type SubmissionStatus = - | 'SUBMITTED' - | 'SHORTLISTED' - | 'DISQUALIFIED' - | 'WITHDRAWN'; +type SubmissionStatus = 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED'; type SubmissionType = 'INDIVIDUAL' | 'TEAM'; @@ -58,6 +54,7 @@ interface SubmissionsManagementProps { loading: boolean; onFilterChange: (filters: SubmissionFilters) => void; onPageChange: (page: number) => void; + onPageSizeChange?: (size: number) => void; onRefresh: () => void; organizationId?: string; hackathonId?: string; @@ -83,7 +80,6 @@ export function SubmissionsManagement({ hackathon, }: SubmissionsManagementProps) { const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid'); - const [searchTerm, setSearchTerm] = useState(filters.search ?? ''); const [selectedIds, setSelectedIds] = useState([]); const [showBulkDisqualifyDialog, setShowBulkDisqualifyDialog] = useState(false); @@ -143,9 +139,8 @@ export function SubmissionsManagement({ onRefresh(); }; - const handleSearchSubmit = (e: FormEvent) => { - e.preventDefault(); - onFilterChange({ ...filters, search: searchTerm }); + const handleSearchTermChange = (value: string) => { + onFilterChange({ ...filters, search: value }); }; const handleStatusChange = (value: string) => { @@ -214,18 +209,16 @@ export function SubmissionsManagement({

) : ( -
-
- - setSearchTerm(e.target.value)} - className='focus:border-primary focus:ring-primary border-gray-700/50 bg-gray-900/50 pl-10 text-white placeholder:text-gray-500' - /> -
-
+
+ + handleSearchTermChange(e.target.value)} + className='focus:border-primary focus:ring-primary border-gray-700/50 bg-gray-900/50 pl-10 text-white placeholder:text-gray-500' + /> +
)} @@ -244,7 +237,6 @@ export function SubmissionsManagement({ Submitted Shortlisted Disqualified - Withdrawn @@ -338,32 +330,7 @@ export function SubmissionsManagement({ isSubmitting={isBulkLoading} /> - {/* Pagination */} - {pagination.totalPages > 1 && ( -
- - - Page {pagination.page} of {pagination.totalPages} - - -
- )} + {/* Pagination removed - controlled externally by page.tsx */} ); } diff --git a/hooks/hackathon/use-organizer-submissions.ts b/hooks/hackathon/use-organizer-submissions.ts index 504b6eb0..f0b402fd 100644 --- a/hooks/hackathon/use-organizer-submissions.ts +++ b/hooks/hackathon/use-organizer-submissions.ts @@ -1,9 +1,10 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { getHackathonSubmissions } from '@/lib/api/hackathons'; import type { ParticipantSubmission } from '@/lib/api/hackathons'; +import { useDebounce } from '@/hooks/use-debounce'; export type OrganizerSubmissionFilters = { - status?: 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED' | 'WITHDRAWN'; + status?: 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED'; type?: 'INDIVIDUAL' | 'TEAM'; search?: string; }; @@ -33,6 +34,7 @@ export interface UseOrganizerSubmissionsReturn { ) => Promise; goToPage: (page: number) => void; refresh: () => void; + updateLimit: (limit: number) => void; } export function useOrganizerSubmissions( @@ -47,6 +49,8 @@ export function useOrganizerSubmissions( const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const debouncedSearch = useDebounce(filters.search, 500); + const fetchSubmissions = useCallback( async (page = 1, filterOverrides?: OrganizerSubmissionFilters) => { if (!hackathonId) return; @@ -55,24 +59,32 @@ export function useOrganizerSubmissions( setError(null); try { - const appliedFilters = filterOverrides ?? filters; + const appliedFilters = filterOverrides ?? { + ...filters, + search: debouncedSearch, + }; const res = await getHackathonSubmissions( hackathonId, page, - initialLimit, + pagination.limit || initialLimit, appliedFilters ); const list = res.data?.submissions ?? []; - const pag = res.data?.pagination ?? { - page: 1, - limit: initialLimit, - total: 0, - totalPages: 0, - }; + const rawPag = res.data?.pagination; + + const currentPage = rawPag?.page || page; + const limit = rawPag?.limit || pagination.limit || initialLimit; + const total = rawPag?.total || 0; + const totalPages = rawPag?.totalPages || Math.ceil(total / limit) || 1; setSubmissions(list); - setPagination(pag); + setPagination({ + page: currentPage, + limit, + total, + totalPages, + }); } catch (err) { setError( err instanceof Error ? err.message : 'Failed to fetch submissions' @@ -83,16 +95,30 @@ export function useOrganizerSubmissions( setLoading(false); } }, - [hackathonId, initialLimit, filters] + [ + hackathonId, + initialLimit, + debouncedSearch, + pagination.limit, + filters.status, + filters.type, + ] ); - const updateFilters = useCallback( - (next: OrganizerSubmissionFilters) => { - setFilters(next); - fetchSubmissions(1, next); - }, - [fetchSubmissions] - ); + // Sync with backend on filter/pagination changes + useEffect(() => { + fetchSubmissions(1); + }, [ + fetchSubmissions, + debouncedSearch, + filters.status, + filters.type, + pagination.limit, + ]); + + const updateFilters = useCallback((next: OrganizerSubmissionFilters) => { + setFilters(next); + }, []); const goToPage = useCallback( (page: number) => { @@ -105,6 +131,10 @@ export function useOrganizerSubmissions( fetchSubmissions(pagination.page); }, [fetchSubmissions, pagination.page]); + const updateLimit = useCallback((nextLimit: number) => { + setPagination(prev => ({ ...prev, limit: nextLimit })); + }, []); + return { submissions, pagination, @@ -115,5 +145,6 @@ export function useOrganizerSubmissions( fetchSubmissions, goToPage, refresh, + updateLimit, }; } diff --git a/hooks/hackathon/use-register-hackathon.ts b/hooks/hackathon/use-register-hackathon.ts index f15a93b9..4e7cb921 100644 --- a/hooks/hackathon/use-register-hackathon.ts +++ b/hooks/hackathon/use-register-hackathon.ts @@ -122,6 +122,6 @@ export function useRegisterHackathon({ hasCheckedInitially, // Expose setters for immediate updates setParticipant, - hasSubmitted: participant?.submission?.status === 'submitted', + hasSubmitted: participant?.submission?.status === 'SUBMITTED', }; } diff --git a/hooks/use-hackathons.ts b/hooks/use-hackathons.ts index 4b56eff7..f6a79a58 100644 --- a/hooks/use-hackathons.ts +++ b/hooks/use-hackathons.ts @@ -31,8 +31,8 @@ export interface UseHackathonsOptions { search?: string; }; participantFilters?: { - status?: 'submitted' | 'not_submitted' | 'shortlisted' | 'disqualified'; - type?: 'individual' | 'team'; + status?: string; + type?: string; search?: string; }; } @@ -579,16 +579,22 @@ export function useHackathons( const pagination = (response.data?.pagination || response.meta?.pagination) as any; + const responsePage = pagination?.page || 1; + const totalItems = pagination?.total || 0; + const itemsPerPage = pagination?.limit || pageSize; + const totalPages = + pagination?.totalPages || Math.ceil(totalItems / itemsPerPage) || 1; + setParticipants(response.data?.participants || []); setParticipantsPagination({ - currentPage: pagination?.page || 1, - totalPages: pagination?.totalPages || 1, - totalItems: pagination?.total || 0, - itemsPerPage: pagination?.limit || pageSize, - hasNext: !!pagination?.hasNext, - hasPrev: !!pagination?.hasPrev, + currentPage: responsePage, + totalPages, + totalItems, + itemsPerPage, + hasNext: !!pagination?.hasNext || responsePage < totalPages, + hasPrev: !!pagination?.hasPrev || responsePage > 1, }); - participantsPageRef.current = pagination?.page || 1; + participantsPageRef.current = responsePage; } catch (error) { const errorMessage = error instanceof Error diff --git a/hooks/use-submission-actions.ts b/hooks/use-submission-actions.ts index 64216884..fd43b9d2 100644 --- a/hooks/use-submission-actions.ts +++ b/hooks/use-submission-actions.ts @@ -35,7 +35,7 @@ export const useSubmissionActions = ({ if (response.success && response.data) { const newStatus = response.data.submission?.status; - const isShortlisted = newStatus === 'shortlisted'; + const isShortlisted = newStatus === 'SHORTLISTED'; toast.success( isShortlisted @@ -84,7 +84,7 @@ export const useSubmissionActions = ({ if (response.success && response.data) { const newStatus = response.data.submission?.status; - const isDisqualified = newStatus === 'disqualified'; + const isDisqualified = newStatus === 'DISQUALIFIED'; toast.success( isDisqualified diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts index dc223a57..a105755c 100644 --- a/lib/api/hackathons.ts +++ b/lib/api/hackathons.ts @@ -692,7 +692,14 @@ export interface ParticipantSubmission { comments?: number | ParticipantComment[]; submissionDate?: string; submittedAt?: string; - status: 'submitted' | 'shortlisted' | 'disqualified' | string; + status: SubmissionStatus; + participationType?: 'INDIVIDUAL' | 'TEAM' | 'TEAM_OR_INDIVIDUAL'; + participant?: { + id: string; + name: string; + username: string; + image?: string; + }; disqualificationReason?: string | null; reviewedBy?: { id: string; From d536c43373d6dc81e955b4d0b15176c6e63d9dac Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:07:58 +0100 Subject: [PATCH 02/12] Pr 486 (#490) * feat: Implement comprehensive hackathon detail page with new header, sidebar, and tabbed content sections including teams, announcements, winners, participants, and resources. * fix: update coderabit * feat: Enhance hackathon team invitation flow, recruitment post management, and dynamic tab visibility. * feat: Integrate AuthModalProvider for authentication handling and enhance messaging features across hackathon components * fix: fix pagination issues in particiapants and submissions page for organizers * fix: remove any type * fix: enhanced filtering for organizations * fix: fix coderabbit corrections --------- Co-authored-by: Collins Ikechukwu --- .../components/header/ActionButtons.tsx | 3 +- .../components/tabs/contents/Participants.tsx | 75 ++++++++++++++++--- .../[hackathonId]/participants/page.tsx | 42 +---------- .../[hackathonId]/submissions/page.tsx | 23 +----- .../hackathons/submissions/submissionCard.tsx | 2 +- .../submissions/submissionCard2.tsx | 2 +- .../organization/cards/ParticipantInfo.tsx | 2 +- .../SubmissionVotesTab.tsx | 7 +- .../ReviewSubmissionModal/TeamSection.tsx | 7 +- .../cards/ReviewSubmissionModal/types.ts | 6 +- components/organization/cards/TeamModal.tsx | 7 +- .../hackathons/ParticipantsTable.tsx | 5 +- hooks/hackathon/use-hackathon-queries.ts | 3 + hooks/hackathon/use-organizer-submissions.ts | 2 +- hooks/hackathon/use-register-hackathon.ts | 5 +- hooks/use-hackathons.ts | 3 +- hooks/use-participant-submission.ts | 6 +- lib/api/hackathon.ts | 20 ++++- lib/api/hackathons.ts | 8 +- types/hackathon/participant.ts | 14 ++-- 20 files changed, 139 insertions(+), 103 deletions(-) diff --git a/app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx b/app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx index e422e1f6..236d204a 100644 --- a/app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx +++ b/app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx @@ -38,7 +38,8 @@ const ActionButtons = () => { const isParticipant = user ? !!hackathon?.isParticipant || (hackathon?.participants || []).some( - (p: Participant) => p.userId === user.id + (p: Participant) => + (p as any).userId === user.id || p.user?.id === user.id ) : false; diff --git a/app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx b/app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx index e935ee23..f82da490 100644 --- a/app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx +++ b/app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx @@ -6,6 +6,7 @@ import { useHackathon, useHackathonParticipants, } from '@/hooks/hackathon/use-hackathon-queries'; +import { useDebounce } from '@/hooks/use-debounce'; import { TabsContent } from '@/components/ui/tabs'; import { ChevronDown, Search, Filter, Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -53,6 +54,12 @@ const Participants = () => { 'all' | 'submitted' | 'in_progress' >('all'); const [skillFilter, setSkillFilter] = useState('all'); + const [participationType, setParticipationType] = useState< + 'all' | 'individual' | 'team' + >('all'); + const [searchQuery, setSearchQuery] = useState(''); + const debouncedSearch = useDebounce(searchQuery, 300); + const [page, setPage] = useState(1); const [accumulatedParticipants, setAccumulatedParticipants] = useState< Participant[] @@ -68,13 +75,15 @@ const Participants = () => { limit, status: statusFilter === 'all' ? undefined : statusFilter, skill: skillFilter === 'all' ? undefined : skillFilter, + type: participationType === 'all' ? undefined : participationType, + search: debouncedSearch || undefined, }); // Reset page and list when filters change React.useEffect(() => { setPage(1); setAccumulatedParticipants([]); - }, [statusFilter, skillFilter]); + }, [statusFilter, skillFilter, participationType, debouncedSearch]); // Accumulate participants as they are fetched React.useEffect(() => { @@ -133,14 +142,28 @@ const Participants = () => { > {/* Header with Count and Filters */}
-
-

Participants

-

- - {totalBuilders.toLocaleString()} - {' '} - builders competing in {hackathon.name} -

+
+
+

Participants

+

+ + {totalBuilders.toLocaleString()} + {' '} + builders competing in {hackathon.name} +

+
+ + {/* Search Bar */} +
+ setSearchQuery(e.target.value)} + className='hover:border-primary/20 focus:border-primary/30 h-10 w-full rounded-xl border border-white/5 bg-[#141517] pr-4 pl-10 text-sm text-white transition-all outline-none placeholder:text-gray-500' + /> + +
@@ -175,6 +198,38 @@ const Participants = () => { + {/* Type Filter */} + + + + + + setParticipationType('all')}> + All Types + + setParticipationType('individual')} + > + Individual + + setParticipationType('team')}> + Team + + + + {/* Status Filter */} @@ -222,7 +277,7 @@ const Participants = () => { key={p.id} name={p.user.profile.name} username={p.user.profile.username} - image={p.user.profile.image} + image={p.user.profile.image || undefined} submitted={!!p.submittedAt} skills={p.user.profile.skills} userId={p.userId ?? p.user?.id} diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx index e475a6a9..ce74910f 100644 --- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx @@ -228,45 +228,11 @@ const ParticipantsPage: React.FC = () => { } }; - // Frontend-side filtering as per requirement - const filteredParticipants = useMemo(() => { - return participants.filter(participant => { - // Search: filter by name or username - const search = filters.search.toLowerCase(); - const matchesSearch = search - ? (participant.user?.profile?.name || '') - .toLowerCase() - .includes(search) || - (participant.user?.profile?.username || '') - .toLowerCase() - .includes(search) - : true; - - // Status: filter by participant.submission.status - // Filter values are 'submitted', 'not_submitted', etc. - // ParticipantSubmission.status values are 'submitted', 'shortlisted', etc. - const matchesStatus = - filters.status === 'all' - ? true - : filters.status === 'not_submitted' - ? !participant.submission - : participant.submission?.status?.toLowerCase() === - filters.status.toLowerCase(); - - // Type: filter by participant.participationType - const matchesType = - filters.type === 'all' - ? true - : participant.participationType?.toLowerCase() === - filters.type.toLowerCase(); - - return matchesSearch && matchesStatus && matchesType; - }); - }, [participants, filters.search, filters.status, filters.type]); + // Removed redundant frontend-side filtering as backend now handles it. // Mock table instance for DataTablePagination const table = useReactTable({ - data: filteredParticipants, + data: participants, columns: [], // Not used for rendering here getCoreRowModel: getCoreRowModel(), manualPagination: true, @@ -352,7 +318,7 @@ const ParticipantsPage: React.FC = () => {
{view === 'table' ? ( { /> ) : ( { - return allSubmissions.filter(sub => { - const search = filters.search?.toLowerCase() || ''; - const matchesSearch = search - ? (sub.projectName || '').toLowerCase().includes(search) || - (sub.participant?.name || '').toLowerCase().includes(search) || - (sub.participant?.username || '').toLowerCase().includes(search) - : true; - - const matchesStatus = !filters.status || sub.status === filters.status; - - const matchesType = - !filters.type || sub.participationType === filters.type; - - return matchesSearch && matchesStatus && matchesType; - }); - }, [allSubmissions, filters.search, filters.status, filters.type]); - const table = useReactTable({ - data: filteredSubmissions, + data: allSubmissions, columns: [], getCoreRowModel: getCoreRowModel(), manualPagination: true, @@ -162,7 +143,7 @@ export default function SubmissionsPage() { ) : (
= ({
- + {userName.charAt(0).toUpperCase()}

{userName}

diff --git a/components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx b/components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx index fd67bbe8..b1105c1f 100644 --- a/components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx +++ b/components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx @@ -11,7 +11,7 @@ interface Voter { id: string; name: string; username: string; - avatar?: string; + avatar?: string | null; votedAt?: string; voteType?: 'positive' | 'negative'; } @@ -42,7 +42,10 @@ export const SubmissionVotesTab: React.FC = ({
- + {voter.name.charAt(0).toUpperCase()} diff --git a/components/organization/cards/ReviewSubmissionModal/TeamSection.tsx b/components/organization/cards/ReviewSubmissionModal/TeamSection.tsx index 627dffb8..99f3d6aa 100644 --- a/components/organization/cards/ReviewSubmissionModal/TeamSection.tsx +++ b/components/organization/cards/ReviewSubmissionModal/TeamSection.tsx @@ -11,7 +11,7 @@ interface TeamMember { id: string; name: string; role: string; - avatar?: string; + avatar?: string | null; username?: string; } @@ -37,7 +37,10 @@ export const TeamSection: React.FC = ({ teamMembers }) => { className='group bg-background-card/20 hover:bg-background-card/40 flex items-center gap-4 rounded-xl border border-gray-900/60 p-4 transition-all hover:border-gray-800' > - + {member.name.charAt(0).toUpperCase()} diff --git a/components/organization/cards/ReviewSubmissionModal/types.ts b/components/organization/cards/ReviewSubmissionModal/types.ts index 76f80aa9..f7e3752e 100644 --- a/components/organization/cards/ReviewSubmissionModal/types.ts +++ b/components/organization/cards/ReviewSubmissionModal/types.ts @@ -2,7 +2,7 @@ export interface TeamMember { id: string; name: string; role: string; - avatar?: string; + avatar?: string | null; username?: string; } @@ -10,7 +10,7 @@ export interface Voter { id: string; name: string; username: string; - avatar?: string; + avatar?: string | null; votedAt?: string; voteType?: 'positive' | 'negative'; } @@ -21,7 +21,7 @@ export interface Comment { author: { name: string; username: string; - avatar?: string; + avatar?: string | null; }; createdAt: string; reactions?: { diff --git a/components/organization/cards/TeamModal.tsx b/components/organization/cards/TeamModal.tsx index 21adea3e..f3524345 100644 --- a/components/organization/cards/TeamModal.tsx +++ b/components/organization/cards/TeamModal.tsx @@ -19,7 +19,7 @@ interface TeamMember { id: string; name: string; role: string; - avatar?: string; + avatar?: string | null; } type ParticipationType = 'team' | 'individual' | 'no-submission'; @@ -168,7 +168,10 @@ export default function TeamModal({ className='group flex cursor-pointer items-center gap-3 rounded-lg p-3 transition-colors hover:bg-gray-900/50' > - + {member.name.charAt(0).toUpperCase()} diff --git a/components/organization/hackathons/ParticipantsTable.tsx b/components/organization/hackathons/ParticipantsTable.tsx index c1e141b8..69591e93 100644 --- a/components/organization/hackathons/ParticipantsTable.tsx +++ b/components/organization/hackathons/ParticipantsTable.tsx @@ -57,7 +57,10 @@ export function ParticipantsTable({ return (
- + {user.profile.name?.substring(0, 2).toUpperCase()} diff --git a/hooks/hackathon/use-hackathon-queries.ts b/hooks/hackathon/use-hackathon-queries.ts index e8972915..bc7589e2 100644 --- a/hooks/hackathon/use-hackathon-queries.ts +++ b/hooks/hackathon/use-hackathon-queries.ts @@ -43,6 +43,8 @@ export interface ParticipantsQueryParams { page?: number; limit?: number; status?: string; + search?: string; + type?: string; skill?: string; } @@ -50,6 +52,7 @@ export interface SubmissionsQueryParams { page?: number; limit?: number; status?: string; + search?: string; sort?: string; } diff --git a/hooks/hackathon/use-organizer-submissions.ts b/hooks/hackathon/use-organizer-submissions.ts index f0b402fd..7030cef8 100644 --- a/hooks/hackathon/use-organizer-submissions.ts +++ b/hooks/hackathon/use-organizer-submissions.ts @@ -76,7 +76,7 @@ export function useOrganizerSubmissions( const currentPage = rawPag?.page || page; const limit = rawPag?.limit || pagination.limit || initialLimit; const total = rawPag?.total || 0; - const totalPages = rawPag?.totalPages || Math.ceil(total / limit) || 1; + const totalPages = Math.ceil(total / limit) || 1; setSubmissions(list); setPagination({ diff --git a/hooks/hackathon/use-register-hackathon.ts b/hooks/hackathon/use-register-hackathon.ts index 4e7cb921..75ddc226 100644 --- a/hooks/hackathon/use-register-hackathon.ts +++ b/hooks/hackathon/use-register-hackathon.ts @@ -122,6 +122,9 @@ export function useRegisterHackathon({ hasCheckedInitially, // Expose setters for immediate updates setParticipant, - hasSubmitted: participant?.submission?.status === 'SUBMITTED', + hasSubmitted: + participant?.submission?.status === 'SUBMITTED' || + participant?.submission?.status === 'SHORTLISTED' || + participant?.submission?.status === 'DISQUALIFIED', }; } diff --git a/hooks/use-hackathons.ts b/hooks/use-hackathons.ts index f6a79a58..f3c6709e 100644 --- a/hooks/use-hackathons.ts +++ b/hooks/use-hackathons.ts @@ -582,8 +582,7 @@ export function useHackathons( const responsePage = pagination?.page || 1; const totalItems = pagination?.total || 0; const itemsPerPage = pagination?.limit || pageSize; - const totalPages = - pagination?.totalPages || Math.ceil(totalItems / itemsPerPage) || 1; + const totalPages = Math.ceil(totalItems / itemsPerPage) || 1; setParticipants(response.data?.participants || []); setParticipantsPagination({ diff --git a/hooks/use-participant-submission.ts b/hooks/use-participant-submission.ts index 37fc420b..930f08d3 100644 --- a/hooks/use-participant-submission.ts +++ b/hooks/use-participant-submission.ts @@ -16,7 +16,7 @@ export interface SubmissionData { id: string; name: string; role: string; - avatar?: string; + avatar?: string | null; username?: string; }>; links?: Array<{ type: string; url: string }>; @@ -24,7 +24,7 @@ export interface SubmissionData { id: string; name: string; username: string; - avatar?: string; + avatar?: string | null; votedAt?: string; voteType: 'positive' | 'negative'; }>; @@ -34,7 +34,7 @@ export interface SubmissionData { author: { name: string; username: string; - avatar?: string; + avatar?: string | null; }; createdAt: string; reactions?: { diff --git a/lib/api/hackathon.ts b/lib/api/hackathon.ts index 2501f4df..e7b77408 100644 --- a/lib/api/hackathon.ts +++ b/lib/api/hackathon.ts @@ -71,13 +71,22 @@ export const getFeaturedHackathons = // Get participants for a hackathon export const getHackathonParticipants = async ( slug: string, - params?: { page?: number; limit?: number; status?: string; skill?: string } + params?: { + page?: number; + limit?: number; + status?: string; + skill?: string; + search?: string; + type?: string; + } ): Promise => { const queryParams = new URLSearchParams(); if (params?.page) queryParams.append('page', params.page.toString()); if (params?.limit) queryParams.append('limit', params.limit.toString()); if (params?.status) queryParams.append('status', params.status); if (params?.skill) queryParams.append('skill', params.skill); + if (params?.search) queryParams.append('search', params.search); + if (params?.type) queryParams.append('type', params.type); const response = await api.get( `/hackathons/${slug}/participants?${queryParams.toString()}` @@ -97,12 +106,19 @@ export const getHackathonAnalytics = async ( // Get submissions for a hackathon export const getHackathonSubmissions = async ( slug: string, - params?: { page?: number; limit?: number; status?: string; sort?: string } + params?: { + page?: number; + limit?: number; + status?: string; + search?: string; + sort?: string; + } ): Promise => { const queryParams = new URLSearchParams(); if (params?.page) queryParams.append('page', params.page.toString()); if (params?.limit) queryParams.append('limit', params.limit.toString()); if (params?.status) queryParams.append('status', params.status); + if (params?.search) queryParams.append('search', params.search); if (params?.sort) queryParams.append('sort', params.sort); const response = await api.get( diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts index a105755c..ec935a13 100644 --- a/lib/api/hackathons.ts +++ b/lib/api/hackathons.ts @@ -636,7 +636,7 @@ export interface ParticipantTeamMember { name: string; username: string; role: string; - avatar?: string; + avatar?: string | null; } export interface ParticipantVote { @@ -698,7 +698,7 @@ export interface ParticipantSubmission { id: string; name: string; username: string; - image?: string; + image?: string | null; }; disqualificationReason?: string | null; reviewedBy?: { @@ -772,7 +772,7 @@ export interface Participant { profile: { name: string; username: string; - image?: string; + image?: string | null; }; email: string; }; @@ -2403,7 +2403,7 @@ export interface TeamMember { username: string; name: string; role: string; - image?: string; + image?: string | null; joinedAt: string; } diff --git a/types/hackathon/participant.ts b/types/hackathon/participant.ts index 0346f572..cda60e9f 100644 --- a/types/hackathon/participant.ts +++ b/types/hackathon/participant.ts @@ -26,7 +26,7 @@ export interface ParticipantTeamMember { name: string; username: string; role: string; - avatar?: string; + avatar?: string | null; } export interface ParticipantVote { @@ -38,7 +38,7 @@ export interface ParticipantVote { firstName: string; lastName: string; username: string; - avatar?: string; + avatar?: string | null; }; email: string; }; @@ -55,7 +55,7 @@ export interface ParticipantComment { firstName: string; lastName: string; username: string; - avatar?: string; + avatar?: string | null; }; email: string; }; @@ -89,7 +89,7 @@ export interface ParticipantSubmission { firstName: string; lastName: string; username: string; - avatar?: string; + avatar?: string | null; }; email: string; } | null; @@ -106,7 +106,7 @@ export interface Participant { profile: { name: string; username: string; - image?: string; + image?: string | null; skills?: string[]; }; email: string; @@ -150,7 +150,7 @@ export interface CreateSubmissionRequest { name: string; username?: string; role: string; - avatar?: string; + avatar?: string | null; }>; projectName: string; category: string; @@ -181,7 +181,7 @@ export interface SubmissionCardProps { projectName: string; description: string; submitterName: string; - submitterAvatar?: string; + submitterAvatar?: string | null; category?: string; categories?: string[]; status?: 'Pending' | 'Approved' | 'Rejected'; From 1a712ffd0a542c211b59a54b5e4640ae8d7485b0 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Tue, 17 Mar 2026 14:22:37 +0100 Subject: [PATCH 03/12] feat: new card for hackathon (#495) * feat: new card for hackathon * fix: changed to correct status * fix: update the ogr button --- .../preview/[orgId]/[draftId]/page.tsx | 4 +- components/avatars/GroupAvatar.tsx | 7 +- components/hackathons/HackathonsPage.tsx | 2 +- components/landing-page/Explore.tsx | 2 +- components/landing-page/Hero2.tsx | 2 +- .../hackathon/AnimatedUsersButton.tsx | 62 ++ .../landing-page/hackathon/HackathonCard.tsx | 533 +++++++----------- components/layout/sidebar.tsx | 6 +- .../CreateProjectModal/SuccessScreen.tsx | 2 +- lib/api/hackathons.ts | 1 + lib/api/organization.ts | 1 + 11 files changed, 273 insertions(+), 349 deletions(-) create mode 100644 components/landing-page/hackathon/AnimatedUsersButton.tsx diff --git a/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx b/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx index 2bfa90b0..d5ece3a8 100644 --- a/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx +++ b/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx @@ -95,13 +95,14 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) { resolvedParams.orgId, resolvedParams.draftId ); - let organizationData = { name: '', logo: '' }; + let organizationData = { name: '', logo: '', slug: '' }; try { const orgRes = await getOrganization(resolvedParams.orgId); if (orgRes) { organizationData = { name: orgRes.name, logo: orgRes.logo || '', + slug: orgRes.slug || '', }; } } catch (orgErr) { @@ -130,6 +131,7 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) { id: resolvedParams.orgId, name: organizationData.name, logo: organizationData.logo, + slug: organizationData.slug, }, status: 'DRAFT', diff --git a/components/avatars/GroupAvatar.tsx b/components/avatars/GroupAvatar.tsx index 006d2b2a..7ba22fd8 100644 --- a/components/avatars/GroupAvatar.tsx +++ b/components/avatars/GroupAvatar.tsx @@ -19,10 +19,7 @@ const GroupAvatar = ({ members }: GroupAvatarProps) => { return ( {visibleMembers.map((member, index) => ( - + {member.slice(0, 2).toUpperCase()} @@ -30,7 +27,7 @@ const GroupAvatar = ({ members }: GroupAvatarProps) => { ))} {remainingCount > 0 && ( - + +{remainingCount} )} diff --git a/components/hackathons/HackathonsPage.tsx b/components/hackathons/HackathonsPage.tsx index 34f6dbff..9a5c345a 100644 --- a/components/hackathons/HackathonsPage.tsx +++ b/components/hackathons/HackathonsPage.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useState } from 'react'; -import HackathonCard from '@/components/landing-page/hackathon/HackathonCard'; +import { HackathonCard } from '@/components/landing-page/hackathon/HackathonCard'; import HackathonsFiltersHeader from '@/components/hackathons/HackathonsFiltersHeader'; import LoadingSpinner from '@/components/LoadingSpinner'; import { useHackathonFilters } from '@/hooks/hackathon/use-hackathon-filters'; diff --git a/components/landing-page/Explore.tsx b/components/landing-page/Explore.tsx index d784ffda..d7659ef2 100644 --- a/components/landing-page/Explore.tsx +++ b/components/landing-page/Explore.tsx @@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'; import { ArrowRight } from 'lucide-react'; import Image from 'next/image'; import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; -import HackathonCard from './hackathon/HackathonCard'; +import { HackathonCard } from './hackathon/HackathonCard'; import Link from 'next/link'; import { getPublicHackathonsList } from '@/lib/api/hackathons'; import type { Hackathon } from '@/lib/api/hackathons'; diff --git a/components/landing-page/Hero2.tsx b/components/landing-page/Hero2.tsx index 537b9051..e72a2eba 100644 --- a/components/landing-page/Hero2.tsx +++ b/components/landing-page/Hero2.tsx @@ -10,7 +10,7 @@ import { } from '../ui/shadcn-io/cursor'; import Image from 'next/image'; import { BoundlessButton } from '../buttons'; -import HackathonCard from '@/components/landing-page/hackathon/HackathonCard'; +import { HackathonCard } from '@/components/landing-page/hackathon/HackathonCard'; import ProjectCard from '@/features/projects/components/ProjectCard'; import { Crowdfunding } from '@/features/projects/types'; diff --git a/components/landing-page/hackathon/AnimatedUsersButton.tsx b/components/landing-page/hackathon/AnimatedUsersButton.tsx new file mode 100644 index 00000000..cb54ea6a --- /dev/null +++ b/components/landing-page/hackathon/AnimatedUsersButton.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useRef } from 'react'; +import { gsap } from 'gsap'; +import { useGSAP } from '@gsap/react'; +import { Users } from 'lucide-react'; + +// Register the hook to prevent React strict-mode double-firing issues +gsap.registerPlugin(useGSAP); + +export default function AnimatedUsersButton() { + const usersIconRef = useRef(null); + + // 1. Mount Animation: Slides the background user in when the page loads + useGSAP( + () => { + if (!usersIconRef.current) return; + + // Selects elements inside this specific SVG + const q = gsap.utils.selector(usersIconRef); + + // Target the elements making up the background person + // (Lucide draws the foreground person first, so the last path/circle belong to the background) + gsap.from(q('path:last-of-type, circle:last-of-type'), { + x: 10, + opacity: 0, + duration: 0.6, + ease: 'back.out(1.5)', + delay: 0.1, // Slight delay so it feels natural after the page loads + }); + }, + { scope: usersIconRef } + ); + + // 2. Hover Animation: Makes both users "bounce" sequentially + const handleMouseEnter = () => { + if (!usersIconRef.current) return; + + // Grab all the individual paths and circles inside the SVG + const svgParts = usersIconRef.current.children; + + gsap.to(svgParts, { + y: -3, + duration: 0.2, + stagger: 0.05, + yoyo: true, + repeat: 1, + ease: 'power2.out', + transformOrigin: 'bottom center', + }); + }; + + return ( + + ); +} diff --git a/components/landing-page/hackathon/HackathonCard.tsx b/components/landing-page/hackathon/HackathonCard.tsx index 305124ac..105792d7 100644 --- a/components/landing-page/hackathon/HackathonCard.tsx +++ b/components/landing-page/hackathon/HackathonCard.tsx @@ -1,124 +1,26 @@ 'use client'; + import Link from 'next/link'; import Image from 'next/image'; -import { MapPinIcon } from 'lucide-react'; -import { useEffect, useState, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import { + MapPin, + Users, + Trophy, + Clock, + Lock, + ArrowUpRight, + AlertCircle, +} from 'lucide-react'; +import { useEffect, useState, useCallback, useMemo } from 'react'; import { Hackathon } from '@/lib/api/hackathons'; import { cn } from '@/lib/utils'; - -// type HackathonCardProps = { -// id: string; -// name: string; -// slug: string; -// tagline: string; -// description: string; - -// banner: string; - -// organizationId: string; -// organization: { -// id: string; -// name: string; -// logo: string; -// }; - -// status: "DRAFT" | "PUBLISHED" | "ARCHIVED"; -// isActive: boolean; - -// venueType: "VIRTUAL" | "PHYSICAL" | "HYBRID"; -// venueName: string; -// venueAddress: string; -// city: string; -// state: string; -// country: string; -// timezone: string; - -// startDate: string; // ISO date -// endDate: string; // ISO date -// submissionDeadline: string; // ISO date -// registrationDeadline: string; // ISO date -// customRegistrationDeadline: string | null; - -// registrationOpen: boolean; -// registrationDeadlinePolicy: "BEFORE_SUBMISSION_DEADLINE" | "CUSTOM"; - -// daysUntilStart: number; -// daysUntilEnd: number; - -// participantType: "INDIVIDUAL" | "TEAM"; -// teamMin: number; -// teamMax: number; - -// categories: string[]; - -// enabledTabs: Array< -// | "detailsTab" -// | "participantsTab" -// | "resourcesTab" -// | "submissionTab" -// | "announcementsTab" -// | "discussionTab" -// | "winnersTab" -// | "sponsorsTab" -// | "joinATeamTab" -// | "rulesTab" -// >; - -// judgingCriteria: Array<{ -// id?: string; -// title?: string; -// description?: string; -// weight?: number; -// }>; - -// prizeTiers: Array<{ -// id?: string; -// title?: string; -// prizeAmount?: number; -// description?: string; -// }>; - -// phases: Array<{ -// id?: string; -// name?: string; -// startDate?: string; -// endDate?: string; -// }>; - -// resources: any[]; - -// sponsorsPartners: any[]; - -// submissions: any[]; -// followers: any[]; - -// requireGithub: boolean; -// requireDemoVideo: boolean; -// requireOtherLinks: boolean; - -// contactEmail: string; -// discord: string; -// telegram: string; -// socialLinks: string[]; - -// publishedAt: string; -// createdAt: string; -// updatedAt: string; - -// _count: { -// submissions: number; -// followers: number; -// }; -// isFullWidth?: boolean; -// isListView?: boolean; -// className?: string; - -// }; +import GroupAvatar from '@/components/avatars/GroupAvatar'; const formatFullNumber = (num: number): string => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(num); -const MAX_VISIBLE_CATEGORIES = 3; +const MAX_VISIBLE_CATEGORIES = 2; interface CategoriesDisplayProps { categoriesList?: string[]; @@ -128,24 +30,23 @@ const CategoriesDisplay = ({ categoriesList = [] }: CategoriesDisplayProps) => { const visible = categoriesList.slice(0, MAX_VISIBLE_CATEGORIES); const remainingCount = categoriesList.length - MAX_VISIBLE_CATEGORIES; + if (!categoriesList.length) return null; + return ( -
-
- {visible.map((cat, i) => ( - - {cat} - - ))} - - {remainingCount > 0 && ( - - +{remainingCount} - - )} -
+
+ {visible.map((cat, i) => ( + + {cat} + + ))} + {remainingCount > 0 && ( + + +{remainingCount} + + )}
); }; @@ -176,24 +77,26 @@ function calculateTimeRemaining(targetDate: string): TimeRemaining { }; } -// function formatCountdown(time: TimeRemaining): string { -// if (time.total <= 0) return 'Ended'; - -// if (time.days > 0) { -// return `${time.days} day${time.days !== 1 ? 's' : ''} left`; -// } else if (time.hours > 0) { -// return `${time.hours} hour${time.hours !== 1 ? 's' : ''} left`; -// } else if (time.minutes > 0) { -// return `${time.minutes} minute${time.minutes !== 1 ? 's' : ''} left`; -// } else { -// return `${time.seconds} second${time.seconds !== 1 ? 's' : ''} left`; -// } -// } - -interface HackathonCardProps extends Hackathon { +interface HackathonCardProps extends Omit< + Hackathon, + 'organization' | '_count' +> { + organization: { + id: string; + name: string; + logo: string; + slug?: string; + }; isFullWidth?: boolean; className?: string; target?: string; + isPrivate?: boolean; + isExtended?: boolean; + _count?: { + participants: number; + submissions: number; + followers: number; + }; } export const HackathonCard = ({ @@ -202,22 +105,26 @@ export const HackathonCard = ({ name, tagline, banner, - organization, status, - venueName, - startDate, - submissionDeadline, - categories, prizeTiers, + participants: participantMembers, isFullWidth = false, className, target, + isPrivate = false, + isExtended = false, + _count: { participants: participantsCount, submissions, followers } = { + participants: 0, + submissions: 0, + followers: 0, + }, }: HackathonCardProps) => { + const router = useRouter(); const [timeRemaining, setTimeRemaining] = useState({ days: 0, hours: 0, @@ -226,12 +133,20 @@ export const HackathonCard = ({ total: 0, }); - // Determine top badge status using raw dates — memoised so it can safely - // appear in the useEffect dependency array without triggering infinite loops. + const participantAvatars = useMemo(() => { + return (participantMembers || []) + .map(p => p.user?.profile?.image || '') + .filter(img => img !== ''); + }, [participantMembers]); + + const totalPrize = + prizeTiers?.reduce((acc, tier) => { + const amount = Number(tier.prizeAmount); + return acc + (Number.isFinite(amount) ? amount : 0); + }, 0) || 0; + const getTopBadgeStatus = useCallback(() => { - if (status === 'ARCHIVED') { - return 'Archived'; - } + if (status === 'ARCHIVED') return 'Archived'; const now = new Date().getTime(); const start = startDate ? new Date(startDate).getTime() : null; @@ -239,255 +154,201 @@ export const HackathonCard = ({ ? new Date(submissionDeadline).getTime() : null; - // Check if ended (submission deadline passed) - if (deadline && now > deadline) { - return 'Ended'; - } - - // Check if ongoing (started but submission deadline not passed) - if (start && now >= start) { - return 'Ongoing'; - } - - // Otherwise it's upcoming + if (deadline && now > deadline) return 'Ended'; + if (start && now >= start) return 'Ongoing'; return 'Upcoming'; }, [status, startDate, submissionDeadline]); const getTopBadgeColor = () => { - const badgeStatus = getTopBadgeStatus(); - switch (badgeStatus) { + switch (getTopBadgeStatus()) { case 'Ongoing': - return 'text-green-400 bg-green-400/10'; + return 'text-success-400 bg-success-400/10 border-success-400/20'; case 'Upcoming': - return 'text-blue-400 bg-blue-400/10'; + return 'text-secondary-400 bg-secondary-400/10 border-secondary-400/20'; case 'Ended': - return 'text-gray-400 bg-gray-800/20'; case 'Archived': - return 'text-red-400 bg-red-400/10'; default: - return 'text-gray-400 bg-gray-800/20'; + return 'text-muted-foreground bg-muted border-border'; } }; - // Determine bottom status text using real-time countdown - const getBottomStatusInfo = () => { - if (status === 'ARCHIVED') { - return { text: 'Archived', className: 'text-red-400' }; - } - - const badgeStatus = getTopBadgeStatus(); - - if (badgeStatus === 'Ended') { - return { text: 'Ended', className: 'text-gray-500' }; - } - - if (badgeStatus === 'Ongoing' && submissionDeadline) { - // Ongoing hackathon - show time until submission deadline - if (timeRemaining.total <= 0) { - return { text: 'Ended', className: 'text-gray-500' }; - } - if (timeRemaining.days === 0) { - return { text: 'Ending today', className: 'text-red-400' }; - } - if (timeRemaining.days === 1) { - return { text: 'Ending tomorrow', className: 'text-red-400' }; - } - if (timeRemaining.days <= 3) { - return { - text: `Ending in ${timeRemaining.days} days`, - className: 'text-red-400', - }; - } - if (timeRemaining.days <= 7) { - return { - text: `Ending in ${timeRemaining.days} days`, - className: 'text-yellow-400', - }; - } - return { - text: `Ending in ${timeRemaining.days} days`, - className: 'text-green-400', - }; - } - - if (badgeStatus === 'Upcoming' && startDate) { - // Upcoming hackathon - show time until start - if (timeRemaining.total <= 0) { - return { text: 'Starting soon', className: 'text-gray-400' }; - } - if (timeRemaining.days === 0) { - return { text: 'Starting today', className: 'text-red-400' }; - } - if (timeRemaining.days === 1) { - return { text: 'Starting tomorrow', className: 'text-red-400' }; - } - if (timeRemaining.days <= 3) { - return { - text: `Starting in ${timeRemaining.days} days`, - className: 'text-red-400', - }; - } - if (timeRemaining.days <= 7) { - return { - text: `Starting in ${timeRemaining.days} days`, - className: 'text-yellow-400', - }; - } - return { - text: `Starting in ${timeRemaining.days} days`, - className: 'text-blue-400', - }; - } - - return { text: 'Starting soon', className: 'text-gray-400' }; - }; - - // Update time remaining based on current status useEffect(() => { - let targetDate: string | null = null; const badgeStatus = getTopBadgeStatus(); + let targetDate: string | null = null; - if (badgeStatus === 'Ongoing' && submissionDeadline) { + if (badgeStatus === 'Ongoing' && submissionDeadline) targetDate = submissionDeadline; - } else if (badgeStatus === 'Upcoming' && startDate) { - targetDate = startDate; - } else if (badgeStatus === 'Ended' && submissionDeadline) { + else if (badgeStatus === 'Upcoming' && startDate) targetDate = startDate; + else if (badgeStatus === 'Ended' && submissionDeadline) targetDate = submissionDeadline; - } if (!targetDate) return; setTimeRemaining(calculateTimeRemaining(targetDate)); - // Update every second for ongoing/upcoming hackathons if (badgeStatus === 'Ongoing' || badgeStatus === 'Upcoming') { const interval = setInterval(() => { setTimeRemaining(calculateTimeRemaining(targetDate!)); }, 1000); - return () => clearInterval(interval); } }, [startDate, submissionDeadline, getTopBadgeStatus]); - const bottomStatusInfo = getBottomStatusInfo(); const topBadgeStatus = getTopBadgeStatus(); - const topBadgeColor = getTopBadgeColor(); - - // const locationText = (() => { - // if (location) { - // return location; - // } - // if (venueType === 'VIRTUAL') { - // return 'Virtual'; - // } - // if (venueType === 'PHYSICAL') { - // return 'Physical'; - // } - // return undefined; - // })(); - const href = `/hackathons/${slug || id || ''}`; + const handleOrganizerClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const orgSlug = organization?.slug; + if (orgSlug) { + router.push(`/org/${orgSlug}`); + } + }; + return ( - {/* Image */} -
+
{name} -
+
-
+
- - {topBadgeStatus} - -
-
- {organization?.logo && ( -
- )} - {organization?.name && ( - - {organization.name} +
+ {isPrivate && ( + + Private + + )} + + {topBadgeStatus} - )} +
- {/* Body */} -
-
-

+
+ {organization && + (organization.slug ? ( + + ) : ( +
+ {organization.logo ? ( +
+ ) : ( +
+ )} + + {organization.name} + +
+ ))} + +
+

{name}

-

{tagline}

+

+ {tagline} +

-
- {prizeTiers && ( -
- - $ - {formatFullNumber( - prizeTiers.reduce((acc, tier) => { - const amount = Number(tier.prizeAmount); - return acc + (Number.isFinite(amount) ? amount : 0); - }, 0) - )} - - Prize Pool -
- )} - {/* {participantsCount && ( -
- - {formatNumber(participantsCount)} - {participantsGoal && `/${formatNumber(participantsGoal)}`} - - Participants + {totalPrize > 0 && ( +
+ + + Prize Pool + + + ${formatFullNumber(totalPrize)} + +
+ )} + +
+
+
+
+ + + {formatFullNumber(participantsCount)} Participants + +
- )} */} +
+ {venueName && venueName !== 'TBD' && ( -
- - {venueName} +
+ + + + {venueName} + +
)}
-
- - {bottomStatusInfo.text} - - {/* {participants?.goal && ( - - )} */} +
+
+ + + {timeRemaining.days > 0 + ? `${topBadgeStatus === 'Upcoming' ? 'Starts' : 'Ends'} in ${timeRemaining.days}d ${timeRemaining.hours}h` + : topBadgeStatus} + +
+ + {isExtended && ( + + + Extended + + )}
diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx index 5b3b7fe0..8df480ea 100644 --- a/components/layout/sidebar.tsx +++ b/components/layout/sidebar.tsx @@ -187,7 +187,7 @@ const SidebarLayout: React.FC = () => { > { > @@ -301,7 +301,7 @@ const SidebarLayout: React.FC = () => { : 'text-gray-400 hover:bg-[#2A2A2A]/50 hover:text-white' )} > - + {item.label} diff --git a/features/projects/components/CreateProjectModal/SuccessScreen.tsx b/features/projects/components/CreateProjectModal/SuccessScreen.tsx index d50fbac4..43a397c5 100644 --- a/features/projects/components/CreateProjectModal/SuccessScreen.tsx +++ b/features/projects/components/CreateProjectModal/SuccessScreen.tsx @@ -4,7 +4,7 @@ import { motion } from 'motion/react'; const SuccessScreen = ({ onContinue }: { onContinue: () => void }) => { return ( -
+
{/* Date: Tue, 17 Mar 2026 17:34:28 +0100 Subject: [PATCH 04/12] UI fixes (#497) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * fix: fix auto refresh ib submission page * fix: hackathon submission fixes * fix: fix coderabbit corrections * fix: fix coderabbit corrections * chore: write boundless on x challenge blog * fix: remove blog * fix: my project dashbaord count and extend hackathon deadline * fix: my project dashbaord count and extend hackathon deadline * fix: fix auto validate wallet address and user nav * fix: fix notification badge * fix: fix coderabbit corrections * fix: fix pagination in organization participants page From d8667de47f7e689ec022cab9a1ff3f626945b158 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:34:53 +0100 Subject: [PATCH 05/12] Pr 486 (#498) * feat: Implement comprehensive hackathon detail page with new header, sidebar, and tabbed content sections including teams, announcements, winners, participants, and resources. * fix: update coderabit * feat: Enhance hackathon team invitation flow, recruitment post management, and dynamic tab visibility. * feat: Integrate AuthModalProvider for authentication handling and enhance messaging features across hackathon components * fix: fix pagination issues in particiapants and submissions page for organizers * fix: remove any type * fix: enhanced filtering for organizations * fix: fix coderabbit corrections --------- Co-authored-by: Collins Ikechukwu From 17292e2ec04b6558eed5fc5803599f7d11e6dab7 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:02:21 +0100 Subject: [PATCH 06/12] fix: fix hackathon submission count (#499) --- app/(landing)/hackathons/[slug]/components/tabs/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/(landing)/hackathons/[slug]/components/tabs/index.tsx b/app/(landing)/hackathons/[slug]/components/tabs/index.tsx index 2360f4e5..2e0ad370 100644 --- a/app/(landing)/hackathons/[slug]/components/tabs/index.tsx +++ b/app/(landing)/hackathons/[slug]/components/tabs/index.tsx @@ -31,7 +31,6 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => { const { currentHackathon, winners, - submissions, loading: generalLoading, } = useHackathonData(); const { data: announcements = [], isLoading: announcementsLoading } = @@ -91,7 +90,7 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => { { id: 'submissions', label: 'Submissions', - badge: submissions.filter(p => p.status === 'Approved').length, + badge: currentHackathon._count?.submissions ?? 0, }, { id: 'discussions', @@ -140,7 +139,6 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => { }, [ currentHackathon, winners, - submissions, discussionComments.pagination.totalItems, announcements, announcementsLoading, From 79bac8690a7c915e8d4960765e47252ad0e4f895 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Wed, 18 Mar 2026 01:45:43 +0100 Subject: [PATCH 07/12] refactor: Standardize submission status values to 'SHORTLISTED', 'DISQUALIFIED', and 'SUBMITTED' across types, labels, and filters. --- app/(landing)/hackathons/[slug]/HackathonPageClient.tsx | 2 +- app/(landing)/hackathons/[slug]/components/tabs/index.tsx | 2 +- components/hackathons/submissions/submissionTab.tsx | 2 +- hooks/hackathon/use-hackathon-queries.ts | 6 +++--- lib/providers/hackathonProvider.tsx | 6 +++--- types/hackathon/participant.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx index c8951f4b..b0fc368e 100644 --- a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx +++ b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx @@ -132,7 +132,7 @@ export default function HackathonPageClient({ { id: 'submission', label: 'Submissions', - badge: submissions.filter(p => p.status === 'Approved').length, + badge: submissions.filter(p => p.status === 'SHORTLISTED').length, }, { id: 'discussions', diff --git a/app/(landing)/hackathons/[slug]/components/tabs/index.tsx b/app/(landing)/hackathons/[slug]/components/tabs/index.tsx index 2360f4e5..11e1f03d 100644 --- a/app/(landing)/hackathons/[slug]/components/tabs/index.tsx +++ b/app/(landing)/hackathons/[slug]/components/tabs/index.tsx @@ -91,7 +91,7 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => { { id: 'submissions', label: 'Submissions', - badge: submissions.filter(p => p.status === 'Approved').length, + badge: submissions.filter(p => p.status === 'SHORTLISTED').length, }, { id: 'discussions', diff --git a/components/hackathons/submissions/submissionTab.tsx b/components/hackathons/submissions/submissionTab.tsx index 59e8ee2c..80445c1b 100644 --- a/components/hackathons/submissions/submissionTab.tsx +++ b/components/hackathons/submissions/submissionTab.tsx @@ -155,7 +155,7 @@ const SubmissionTabContent: React.FC = ({ submissions.filter( p => p.status?.toLowerCase() === 'shortlisted' || - p.status === 'Approved' + p.status === 'SHORTLISTED' ).length } {' '} diff --git a/hooks/hackathon/use-hackathon-queries.ts b/hooks/hackathon/use-hackathon-queries.ts index bc7589e2..f449c537 100644 --- a/hooks/hackathon/use-hackathon-queries.ts +++ b/hooks/hackathon/use-hackathon-queries.ts @@ -96,13 +96,13 @@ function mapSubmissionStatus(apiStatus: string): SubmissionCardProps['status'] { const normalized = apiStatus?.toUpperCase(); switch (normalized) { case 'SHORTLISTED': - return 'Approved'; + return 'SHORTLISTED'; case 'DISQUALIFIED': case 'WITHDRAWN': - return 'Rejected'; + return 'DISQUALIFIED'; case 'SUBMITTED': default: - return 'Pending'; + return 'SUBMITTED'; } } diff --git a/lib/providers/hackathonProvider.tsx b/lib/providers/hackathonProvider.tsx index 9ad18c05..c71481b3 100644 --- a/lib/providers/hackathonProvider.tsx +++ b/lib/providers/hackathonProvider.tsx @@ -28,9 +28,9 @@ import { function mapSubmissionStatus(raw?: string): SubmissionCardProps['status'] { const status = raw?.toUpperCase(); - if (status === 'SHORTLISTED') return 'Approved'; - if (status === 'DISQUALIFIED') return 'Rejected'; - return 'Pending'; + if (status === 'SHORTLISTED') return 'SHORTLISTED'; + if (status === 'DISQUALIFIED') return 'DISQUALIFIED'; + return 'SUBMITTED'; } // ─── Types ──────────────────────────────────────────────────────────────────── diff --git a/types/hackathon/participant.ts b/types/hackathon/participant.ts index cda60e9f..df8cbebf 100644 --- a/types/hackathon/participant.ts +++ b/types/hackathon/participant.ts @@ -184,7 +184,7 @@ export interface SubmissionCardProps { submitterAvatar?: string | null; category?: string; categories?: string[]; - status?: 'Pending' | 'Approved' | 'Rejected'; + status?: 'SUBMITTED' | 'SHORTLISTED' | 'DISQUALIFIED'; upvotes?: number; votes?: { current: number; total: number }; comments?: number; From 3682d102da5b77461cb1c91855e87fd4d4dbaeb9 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Fri, 20 Mar 2026 09:18:21 +0100 Subject: [PATCH 08/12] Feat/improved rewards tab (#503) * feat: enhance RewardsTab with preset confirmation and wallet warning alerts * fix: ran audit --- .../hackathons/new/tabs/RewardsTab.tsx | 657 +++++++++++++++--- package-lock.json | 247 +++---- 2 files changed, 701 insertions(+), 203 deletions(-) diff --git a/components/organization/hackathons/new/tabs/RewardsTab.tsx b/components/organization/hackathons/new/tabs/RewardsTab.tsx index d6c02af0..c343cf77 100644 --- a/components/organization/hackathons/new/tabs/RewardsTab.tsx +++ b/components/organization/hackathons/new/tabs/RewardsTab.tsx @@ -14,10 +14,16 @@ import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Button } from '@/components/ui/button'; import { - rewardsSchema, - RewardsFormData, - PrizeTier, -} from './schemas/rewardsSchema'; + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { rewardsSchema, RewardsFormData } from './schemas/rewardsSchema'; import { getFeeEstimate, type FeeEstimateData, @@ -35,6 +41,11 @@ import { Sparkles, ChevronDown, Loader2, + Wallet, + ArrowUpCircle, + ArrowDownCircle, + TriangleAlert, + X, } from 'lucide-react'; import { DndContext, @@ -59,16 +70,64 @@ interface RewardsTabProps { } const PRIZE_PRESETS = { - standard: { name: 'Standard', tiers: [50, 30, 20] }, - topHeavy: { name: 'Winner Takes Most', tiers: [70, 20, 10] }, - even: { name: 'Equal Split', tiers: [33.33, 33.33, 33.34] }, - fiveWay: { name: 'Top 5', tiers: [40, 25, 20, 10, 5] }, + standard: { + name: 'Standard', + description: 'Classic podium split', + tiers: [50, 30, 20], + }, + topHeavy: { + name: 'Winner Takes Most', + description: 'Heavily rewards 1st place', + tiers: [70, 20, 10], + }, + even: { + name: 'Equal Split', + description: 'Fair distribution for all', + tiers: [33.33, 33.33, 33.34], + }, + fiveWay: { + name: 'Top 5', + description: 'Rewards top 5 finishers', + tiers: [40, 25, 20, 10, 5], + }, }; -const RANK_EMOJIS = ['🥇', '🥈', '🥉', '🏅', '🏅']; +const PLACE_LABELS = [ + '1st', + '2nd', + '3rd', + '4th', + '5th', + '6th', + '7th', + '8th', + '9th', + '10th', +]; +const RANK_EMOJIS = [ + '🥇', + '🥈', + '🥉', + '🏅', + '🏅', + '🏅', + '🏅', + '🏅', + '🏅', + '🏅', +]; + +/** Build the initial funded amount from initialData so we can compute the top-up delta */ +function computeInitialFundedAmount(initialData?: RewardsFormData): number { + if (!initialData?.prizeTiers) return 0; + return initialData.prizeTiers.reduce((sum, tier) => { + const amount = parseFloat(String(tier.prizeAmount || '0')); + return sum + (isNaN(amount) || amount < 0 ? 0 : amount); + }, 0); +} interface PrizeTierProps { - tier: any; // Using any for simplicity with FieldArray types + tier: any; index: number; onRemove: (id: string) => void; canRemove: boolean; @@ -76,7 +135,7 @@ interface PrizeTierProps { totalTiers: number; } -// Sortable Prize Tier Component +// ─── Sortable Prize Tier ───────────────────────────────────────────────────── const PrizeTierComponent = ({ tier, index, @@ -129,7 +188,7 @@ const PrizeTierComponent = ({ @@ -153,11 +212,16 @@ const PrizeTierComponent = ({ {...field} type='number' placeholder='0' + min='0' value={field.value || '0'} onChange={e => { - const value = e.target.value; - // Update field value immediately for real-time calculation - field.onChange(value === '' ? '0' : value); + const raw = e.target.value; + // Block negatives — clamp to 0 + const value = + raw === '' + ? '0' + : String(Math.max(0, parseFloat(raw) || 0)); + field.onChange(value); }} onBlur={field.onBlur} className='h-11 border-zinc-800 bg-zinc-900/50 pr-16 pl-7 text-right font-medium text-white placeholder:text-zinc-600' @@ -210,20 +274,28 @@ const PrizeTierComponent = ({ ); }; +// ─── Prize Summary (with top-up delta) ─────────────────────────────────────── interface PrizeSummaryProps { totalPool: number; + initialFundedAmount: number; feeEstimate: FeeEstimateData | null; feeEstimateLoading: boolean; } -// Summary Card Component – fee breakdown from backend const PrizeSummary = ({ totalPool, + initialFundedAmount, feeEstimate, feeEstimateLoading, }: PrizeSummaryProps) => { const formatCurrency = (amount: number) => - amount.toLocaleString('en-US', { minimumFractionDigits: 0 }); + amount.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }); + + const delta = totalPool - initialFundedAmount; + const hasExistingFunds = initialFundedAmount > 0; return (
@@ -242,6 +314,16 @@ const PrizeSummary = ({
+ {/* Existing funds row */} + {hasExistingFunds && ( +
+ Currently Funded + + ${formatCurrency(initialFundedAmount)} + +
+ )} +
{feeEstimateLoading @@ -274,10 +356,47 @@ const PrizeSummary = ({
+ {/* ── Top-Up / Refund Delta ── */} + {hasExistingFunds && delta !== 0 && ( +
0 + ? 'border border-amber-500/20 bg-amber-500/10 text-amber-400' + : 'border border-blue-500/20 bg-blue-500/10 text-blue-400' + )} + > + + {delta > 0 ? ( + + ) : ( + + )} + {delta > 0 ? 'Top-Up Required' : 'Reduction'} + + + {delta > 0 ? '+' : ''}${formatCurrency(Math.abs(delta))} USDC + +
+ )} + + {/* New hackathon — show full amount going to escrow */} + {!hasExistingFunds && totalPool > 0 && ( +
+ + + Sending to Escrow + + + ${formatCurrency(feeEstimate?.totalFunds ?? totalPool)} USDC + +
+ )} +

- Funds locked in escrow until winners announced + Funds are locked in escrow until winners are announced

@@ -285,7 +404,7 @@ const PrizeSummary = ({ ); }; -// Validation Alert Component +// ─── Validation Alert ───────────────────────────────────────────────────────── const ValidationAlert = ({ totalPool }: { totalPool: number }) => { const minPool = 1000; const isValid = totalPool >= minPool; @@ -314,7 +433,7 @@ const ValidationAlert = ({ totalPool }: { totalPool: number }) => {

{isValid - ? 'Your prize pool meets the minimum threshold.' + ? 'Your prize pool meets the recommended threshold.' : `We recommend at least $${minPool.toLocaleString()} to attract quality participants.`}

@@ -322,6 +441,155 @@ const ValidationAlert = ({ totalPool }: { totalPool: number }) => { ); }; +// ─── Wallet Debit Warning ───────────────────────────────────────────────────── +// Shows only the NEW funds leaving the wallet: +// • New hackathon → full fee-adjusted total +// • Existing hackathon top-up → delta only (totalPool - initialFundedAmount) +// • Reduction / unchanged → hidden (no new funds leaving) +const WalletWarningAlert = ({ + totalPool, + initialFundedAmount, + feeEstimate, +}: { + totalPool: number; + initialFundedAmount: number; + feeEstimate: FeeEstimateData | null; +}) => { + const formatAmt = (n: number) => + n.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }); + + const hasExistingFunds = initialFundedAmount > 0; + const delta = totalPool - initialFundedAmount; + + // Nothing new is leaving the wallet — hide the warning + if (hasExistingFunds && delta <= 0) return null; + if (!hasExistingFunds && totalPool <= 0) return null; + + // For a top-up on an existing hackathon, only the delta leaves the wallet. + // For a new hackathon, the full fee-adjusted amount leaves. + const outgoingAmount = hasExistingFunds + ? formatAmt(delta) // just the extra being added + : feeEstimate + ? formatAmt(feeEstimate.totalFunds) + : formatAmt(totalPool); + + const isTopUp = hasExistingFunds; + + return ( +
+ +
+

+ ⚠️{' '} + {isTopUp + ? 'Additional funds will be moved from your wallet' + : 'Funds will be moved from your wallet'} +

+

+ By clicking {isTopUp ? 'Save Rewards' : 'Continue'},{' '} + {isTopUp ? ( + <> + an additional{' '} + + ${outgoingAmount} USDC + {' '} + (the top-up amount) will be transferred + + ) : ( + <> + exactly{' '} + + ${outgoingAmount} USDC + {' '} + will be transferred + + )}{' '} + from your connected wallet into a secure escrow smart contract. This + action is irreversible without contacting support. + Please ensure your wallet has sufficient balance before proceeding. +

+
+
+ ); +}; + +// ─── Preset Confirmation Banner ─────────────────────────────────────────────── +interface PresetConfirmBannerProps { + presetKey: keyof typeof PRIZE_PRESETS; + totalPool: number; + onConfirm: () => void; + onCancel: () => void; +} + +const PresetConfirmBanner = ({ + presetKey, + totalPool, + onConfirm, + onCancel, +}: PresetConfirmBannerProps) => { + const preset = PRIZE_PRESETS[presetKey]; + const formatCurrency = (n: number) => + n.toLocaleString('en-US', { minimumFractionDigits: 0 }); + + return ( +
+
+
+ +
+

+ Apply "{preset.name}" Preset? +

+

+ This will replace your current prize tiers with{' '} + {preset.tiers.length} new tiers using the{' '} + {preset.tiers.join(' / ')}% split. + {totalPool > 0 && ( + <> + {' '} + Your current total of{' '} + ${formatCurrency(totalPool)} will be + redistributed proportionally across the new tiers. + + )} +

+
+
+ +
+
+ + +
+
+ ); +}; + +// ─── Main Component ─────────────────────────────────────────────────────────── export default function RewardsTab({ onSave, onContinue, @@ -329,11 +597,42 @@ export default function RewardsTab({ isLoading = false, }: RewardsTabProps) { const [showPresets, setShowPresets] = useState(false); + const [pendingPreset, setPendingPreset] = useState< + keyof typeof PRIZE_PRESETS | null + >(null); + const [confirmOpen, setConfirmOpen] = useState(false); - const form = useForm({ - resolver: zodResolver(rewardsSchema), - mode: 'onChange', - defaultValues: initialData || { + /** The amount that was already funded when this form was opened (for top-up delta). */ + const initialFundedAmount = useMemo( + () => computeInitialFundedAmount(initialData), + // intentionally only computed once on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + /** + * Normalize initialData from the API into the shapes that Zod and the form expect: + * - prizeAmount → always a string (API returns numbers like 30) + * - rank → always a number + * - passMark → always a number (default 0 if absent) + * - id → always present (generate if the API tier lacks one) + */ + const normalizedDefaultValues = useMemo(() => { + if (initialData?.prizeTiers?.length) { + return { + ...initialData, + prizeTiers: initialData.prizeTiers.map((tier, idx) => ({ + id: (tier as any).id || `tier-init-${idx}-${Date.now()}`, + place: tier.place || `${PLACE_LABELS[idx] || `${idx + 1}th`} Place`, + prizeAmount: String(tier.prizeAmount ?? '0'), + description: tier.description || '', + currency: tier.currency || 'USDC', + rank: Number(tier.rank ?? idx + 1), + passMark: Number((tier as any).passMark ?? 0), + })), + }; + } + return { prizeTiers: [ { id: `tier-${Date.now()}-1`, @@ -363,7 +662,16 @@ export default function RewardsTab({ passMark: 50, }, ], - }, + }; + // Intentionally run only once on mount — initialData reference may change on + // parent re-renders but we don't want to reset the user's in-progress edits. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const form = useForm({ + resolver: zodResolver(rewardsSchema), + mode: 'onChange', + defaultValues: normalizedDefaultValues, }); const { fields, append, remove, move, replace } = useFieldArray({ @@ -371,14 +679,12 @@ export default function RewardsTab({ name: 'prizeTiers', }); - // Watch prizeTiers in real-time for immediate updates using useWatch hook const prizeTiers = useWatch({ control: form.control, name: 'prizeTiers', defaultValue: form.getValues('prizeTiers') || [], }); - // Total prize pool from tiers (client-side sum) const totalPool = useMemo(() => { return prizeTiers.reduce((sum, tier) => { const amount = parseFloat( @@ -431,21 +737,40 @@ export default function RewardsTab({ useSensor(PointerSensor, { activationConstraint: { distance: 8 } }) ); - const applyPreset = (presetKey: keyof typeof PRIZE_PRESETS) => { - const preset = PRIZE_PRESETS[presetKey]; + /** Step 1: User clicks a preset card → show the confirmation banner */ + const handlePresetClick = (presetKey: keyof typeof PRIZE_PRESETS) => { + setPendingPreset(presetKey); + setShowPresets(false); // collapse the grid while confirming + }; + + /** Step 2: User confirms → actually apply the preset */ + const confirmApplyPreset = () => { + if (!pendingPreset) return; + const preset = PRIZE_PRESETS[pendingPreset]; const baseAmount = totalPool || 0; + + // Preserve descriptions from existing tiers where possible + const existingTiers = form.getValues('prizeTiers'); + const newTiers = preset.tiers.map((percentage, idx) => ({ id: `tier-${Date.now()}-${idx}`, - place: `${['1st', '2nd', '3rd', '4th', '5th'][idx]} Place`, + place: `${PLACE_LABELS[idx] || `${idx + 1}th`} Place`, prizeAmount: String(Math.round((baseAmount * percentage) / 100)), - description: '', + // preserve description if same index existed + description: existingTiers[idx]?.description || '', currency: 'USDC', rank: idx + 1, - passMark: 80 - idx * 10, + passMark: Math.max(0, 80 - idx * 10), })); + replace(newTiers); - toast.success(`Applied ${preset.name} preset`); - setShowPresets(false); + toast.success(`Applied "${preset.name}" preset`); + setPendingPreset(null); + }; + + const cancelPreset = () => { + setPendingPreset(null); + setShowPresets(true); // re-open grid so user can pick another }; const handleRemove = (id: string) => { @@ -457,15 +782,15 @@ export default function RewardsTab({ }; const handleAdd = () => { - const placeLabels = ['1st', '2nd', '3rd', '4th', '5th']; - const nextPlace = `${placeLabels[fields.length] || `${fields.length + 1}th`} Place`; + const nextIdx = fields.length; + const nextPlace = `${PLACE_LABELS[nextIdx] || `${nextIdx + 1}th`} Place`; append({ id: `tier-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, place: nextPlace, prizeAmount: '0', description: '', currency: 'USDC', - rank: fields.length + 1, + rank: nextIdx + 1, passMark: 0, }); toast.success('Prize tier added'); @@ -476,7 +801,19 @@ export default function RewardsTab({ if (over && active.id !== over.id) { const oldIndex = fields.findIndex(tier => tier.id === active.id); const newIndex = fields.findIndex(tier => tier.id === over.id); - if (oldIndex !== -1 && newIndex !== -1) move(oldIndex, newIndex); + if (oldIndex !== -1 && newIndex !== -1) { + move(oldIndex, newIndex); + // Re-sync rank values after reorder so the backend receives correct positions + // setTimeout allows useFieldArray to settle before we read the new order + setTimeout(() => { + const current = form.getValues('prizeTiers'); + current.forEach((_, idx) => { + form.setValue(`prizeTiers.${idx}.rank`, idx + 1, { + shouldDirty: true, + }); + }); + }, 0); + } } }; @@ -497,9 +834,30 @@ export default function RewardsTab({ } }; + /** + * Called when Continue is clicked. + * Runs form validation; if it passes, opens the confirm dialog. + * If validation fails, react-hook-form will display inline errors as usual. + */ + const handleContinueClick = async () => { + const isValid = await form.trigger(); + if (isValid) { + setConfirmOpen(true); + } + }; + + /** Called only when the user clicks "Yes, lock funds" inside the AlertDialog. */ + const handleConfirmedSubmit = form.handleSubmit(onSubmit); + + // Derive root-level tier array error message (true schema failures only) + const tierArrayError = + form.formState.errors.prizeTiers?.message || + (form.formState.errors.prizeTiers?.root as any)?.message; + return (
- + {/* onSubmit is intentionally preventDefault — submission goes through handleContinueClick → AlertDialog confirm */} + e.preventDefault()} className='space-y-6'> {/* Header */}

Prize Distribution

@@ -508,9 +866,10 @@ export default function RewardsTab({

- {/* Summary Card – fee from backend GET /api/hackathons/fee-estimate */} + {/* Summary Card */} @@ -520,21 +879,47 @@ export default function RewardsTab({ {/* Presets */}
- +
+ +
+ + {/* Info callout about what presets do */} + {showPresets && ( +
+ +

+ Presets redistribute your current total prize pool ( + + ${totalPool.toLocaleString()} + + ) across tiers using a percentage split. You can still adjust + individual amounts after applying.{' '} + {totalPool === 0 && ( + + Tip: enter prize amounts first so the preset can split them + correctly. + + )} +

+
+ )} {showPresets && (
@@ -542,19 +927,34 @@ export default function RewardsTab({ ))}
)} + + {/* Preset confirmation banner */} + {pendingPreset && ( + + )}
{/* Prize Tiers */} @@ -593,58 +993,153 @@ export default function RewardsTab({ Add Prize Tier - {form.formState.errors.prizeTiers && ( + {/* Generic schema validation error (not a "must use preset" message) */} + {tierArrayError && (

- {form.formState.errors.prizeTiers.message || - 'Validation error in prize tiers'} + {tierArrayError}

-
-

- To proceed, you must use a Prize Preset to - configure your rewards. Presets automatically apply the - required ranking and score thresholds needed for the system. -

-
-

- Action Required: -

-

- Scroll up and select one of the{' '} - Prize Presets (Standard, Winner Takes Most, - etc.). You can still adjust the total prize pool amounts - after selecting a preset. -

-
-
)}
+ {/* ── Wallet Debit Warning – shown before submit ── */} + + {/* Submit */}

{fields.length} prize tier{fields.length !== 1 ? 's' : ''}{' '} configured

+ {isLoading ? ( <> Saving... - ) : ( + ) : onContinue ? ( 'Continue' + ) : ( + 'Save Rewards' )}
+ + {/* ── Confirmation AlertDialog ── */} + + + +
+ +
+ + Confirm Fund Transfer + + +
+

+ You are about to lock{' '} + + {(() => { + const hasExisting = initialFundedAmount > 0; + const delta = totalPool - initialFundedAmount; + const amt = hasExisting + ? delta + : (feeEstimate?.totalFunds ?? totalPool); + return `$${amt.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2 })} USDC`; + })()} + {' '} + {initialFundedAmount > 0 ? '(top-up amount) ' : ''}into an + escrow smart contract for this hackathon's prize pool. +

+
+

+ ⚠️ This action is irreversible +

+

+ Once confirmed, funds cannot be withdrawn without contacting + support. Ensure your wallet has sufficient balance. +

+
+
+
+ Prize tiers + {fields.length} +
+
+ Total prize pool + + $ + {totalPool.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}{' '} + USDC + +
+ {feeEstimate && ( +
+ + {feeEstimate.feeLabel ?? 'Platform fee'} + + + $ + {feeEstimate.feeAmount.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}{' '} + USDC + +
+ )} + {initialFundedAmount > 0 && ( +
+ Already in escrow + + $ + {initialFundedAmount.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}{' '} + USDC + +
+ )} +
+
+
+
+ + + Cancel + + { + e.preventDefault(); + setConfirmOpen(false); + handleConfirmedSubmit(); + }} + className='bg-orange-500 text-white hover:bg-orange-600' + > + Yes, lock funds + + +
+
); } diff --git a/package-lock.json b/package-lock.json index ed1c8f94..909d137f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -243,6 +243,7 @@ "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", @@ -470,6 +471,7 @@ "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" @@ -499,12 +501,14 @@ "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" + "license": "MIT", + "peer": true }, "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==" + "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==", + "peer": true }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", @@ -595,6 +599,7 @@ "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", @@ -890,6 +895,7 @@ "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" @@ -1505,7 +1511,6 @@ "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" @@ -1633,15 +1638,15 @@ } }, "node_modules/@next/env": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", - "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.0.tgz", + "integrity": "sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", - "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.0.tgz", + "integrity": "sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==", "cpu": [ "arm64" ], @@ -1655,9 +1660,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", - "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.0.tgz", + "integrity": "sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==", "cpu": [ "x64" ], @@ -1671,9 +1676,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", - "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.0.tgz", + "integrity": "sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==", "cpu": [ "arm64" ], @@ -1687,9 +1692,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", - "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.0.tgz", + "integrity": "sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==", "cpu": [ "arm64" ], @@ -1703,9 +1708,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", - "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.0.tgz", + "integrity": "sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==", "cpu": [ "x64" ], @@ -1719,9 +1724,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", - "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.0.tgz", + "integrity": "sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==", "cpu": [ "x64" ], @@ -1735,9 +1740,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", - "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.0.tgz", + "integrity": "sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==", "cpu": [ "arm64" ], @@ -1751,9 +1756,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", - "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.0.tgz", + "integrity": "sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==", "cpu": [ "x64" ], @@ -1783,6 +1788,7 @@ "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" } @@ -1804,6 +1810,7 @@ "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" }, @@ -1816,6 +1823,7 @@ "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" }, @@ -2239,6 +2247,7 @@ "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" @@ -2255,6 +2264,7 @@ "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", @@ -2272,6 +2282,7 @@ "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" } @@ -4232,6 +4243,7 @@ "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": "*", @@ -4335,6 +4347,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5572,6 +5585,7 @@ "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" }, @@ -5638,6 +5652,7 @@ "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" @@ -5873,6 +5888,7 @@ "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" @@ -5978,6 +5994,7 @@ "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" @@ -5992,6 +6009,7 @@ "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", @@ -6381,7 +6399,6 @@ "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": "*" @@ -6392,7 +6409,6 @@ "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": "*" @@ -6572,6 +6588,7 @@ "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" } @@ -6581,6 +6598,7 @@ "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" } @@ -6623,6 +6641,7 @@ "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", @@ -6724,6 +6743,7 @@ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", @@ -7156,7 +7176,6 @@ "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" @@ -7166,29 +7185,25 @@ "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", - "peer": true + "license": "MIT" }, "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", - "peer": true + "license": "MIT" }, "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", - "peer": true + "license": "MIT" }, "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", @@ -7199,15 +7214,13 @@ "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", - "peer": true + "license": "MIT" }, "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", @@ -7220,7 +7233,6 @@ "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" } @@ -7230,7 +7242,6 @@ "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" } @@ -7239,15 +7250,13 @@ "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", - "peer": true + "license": "MIT" }, "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", @@ -7264,7 +7273,6 @@ "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", @@ -7278,7 +7286,6 @@ "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", @@ -7291,7 +7298,6 @@ "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", @@ -7306,7 +7312,6 @@ "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" @@ -7322,21 +7327,20 @@ "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", - "peer": true + "license": "BSD-3-Clause" }, "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", - "peer": true + "license": "Apache-2.0" }, "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" }, @@ -7358,7 +7362,6 @@ "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" }, @@ -7397,7 +7400,6 @@ "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" }, @@ -7415,7 +7417,6 @@ "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", @@ -7431,8 +7432,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/ansi-escapes": { "version": "7.3.0", @@ -7918,6 +7918,7 @@ "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", @@ -8000,6 +8001,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8042,8 +8044,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/call-bind": { "version": "1.0.8", @@ -8199,6 +8200,7 @@ "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", @@ -8225,7 +8227,6 @@ "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" } @@ -8492,6 +8493,7 @@ "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" } @@ -8901,6 +8903,7 @@ "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" } @@ -9054,6 +9057,7 @@ "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" @@ -9318,7 +9322,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-autoplay": { "version": "8.6.0", @@ -9551,8 +9556,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -9671,6 +9675,7 @@ "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", @@ -9731,6 +9736,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -10102,7 +10108,6 @@ "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" } @@ -10184,8 +10189,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/feaxios": { "version": "0.0.23", @@ -10271,9 +10275,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -10551,8 +10555,7 @@ "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", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/balanced-match": { "version": "4.0.4", @@ -10685,7 +10688,8 @@ "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." + "license": "Standard 'no charge' license: https://gsap.com/standard-license.", + "peer": true }, "node_modules/hachure-fill": { "version": "0.5.2", @@ -11826,7 +11830,6 @@ "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", @@ -11841,7 +11844,6 @@ "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" }, @@ -11866,6 +11868,7 @@ "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" } @@ -11921,8 +11924,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -12016,10 +12018,11 @@ } }, "node_modules/kysely": { - "version": "0.28.11", - "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.11.tgz", - "integrity": "sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==", + "version": "0.28.13", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.13.tgz", + "integrity": "sha512-jCkYDvlfzOyHaVsrvR4vnNZxG30oNv2jbbFBjTQAUG8n0h07HW0sZJHk4KAQIRyu9ay+Rg+L8qGa3lwt8Gve9w==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" } @@ -12382,7 +12385,6 @@ "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" }, @@ -12888,8 +12890,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/mermaid": { "version": "11.12.3", @@ -13841,6 +13842,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^20.0.0 || >=22.0.0" } @@ -13856,18 +13858,18 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/next": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", - "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "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.1.6", + "@next/env": "16.2.0", "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.8.3", + "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -13879,15 +13881,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.6", - "@next/swc-darwin-x64": "16.1.6", - "@next/swc-linux-arm64-gnu": "16.1.6", - "@next/swc-linux-arm64-musl": "16.1.6", - "@next/swc-linux-x64-gnu": "16.1.6", - "@next/swc-linux-x64-musl": "16.1.6", - "@next/swc-win32-arm64-msvc": "16.1.6", - "@next/swc-win32-x64-msvc": "16.1.6", - "sharp": "^0.34.4" + "@next/swc-darwin-arm64": "16.2.0", + "@next/swc-darwin-x64": "16.2.0", + "@next/swc-linux-arm64-gnu": "16.2.0", + "@next/swc-linux-arm64-musl": "16.2.0", + "@next/swc-linux-x64-gnu": "16.2.0", + "@next/swc-linux-x64-musl": "16.2.0", + "@next/swc-win32-arm64-msvc": "16.2.0", + "@next/swc-win32-x64-msvc": "16.2.0", + "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -14659,6 +14661,7 @@ "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" @@ -14689,6 +14692,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14951,6 +14955,7 @@ "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" } @@ -14980,6 +14985,7 @@ "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", @@ -15028,6 +15034,7 @@ "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", @@ -15297,6 +15304,7 @@ "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" } @@ -15327,6 +15335,7 @@ "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" }, @@ -15356,6 +15365,7 @@ "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" }, @@ -16094,6 +16104,7 @@ "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" }, @@ -16255,7 +16266,6 @@ "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", @@ -16292,7 +16302,6 @@ "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" }, @@ -16304,8 +16313,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/section-matter": { "version": "1.0.0", @@ -16594,9 +16602,9 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", - "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -16649,7 +16657,6 @@ "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" @@ -16660,7 +16667,6 @@ "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" } @@ -17022,7 +17028,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -17042,7 +17049,6 @@ "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", @@ -17061,7 +17067,6 @@ "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", @@ -17094,14 +17099,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", - "peer": true + "license": "MIT" }, "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" + "license": "MIT", + "peer": true }, "node_modules/three-mesh-bvh": { "version": "0.8.3", @@ -17191,6 +17196,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17481,6 +17487,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17949,7 +17956,6 @@ "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" @@ -17990,7 +17996,6 @@ "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", @@ -18039,7 +18044,6 @@ "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" } @@ -18049,7 +18053,6 @@ "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" @@ -18063,7 +18066,6 @@ "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" } @@ -18289,6 +18291,7 @@ "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" } From bf05979bdbc78077c16a38055ac8cdb7f5cbdb32 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Fri, 20 Mar 2026 09:45:51 +0100 Subject: [PATCH 09/12] Feat/improved rewards tab (#505) * feat: enhance RewardsTab with preset confirmation and wallet warning alerts * fix: ran audit * fix: update typescript version to use caret (^) for compatibility --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 909d137f..19b0fa88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,7 +155,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", - "typescript": "5.9.2" + "typescript": "^5.9.2" }, "optionalDependencies": { "usb": "*" diff --git a/package.json b/package.json index c210cdb5..189355f4 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", - "typescript": "5.9.2" + "typescript": "^5.9.2" }, "optionalDependencies": { "usb": "*" From 050845426e1bab4ab25149e311f721d652017137 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Fri, 20 Mar 2026 10:27:28 +0100 Subject: [PATCH 10/12] chore: ensure typescript in devDependencies --- package-lock.json | 155 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 77 insertions(+), 80 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19b0fa88..fd3dec1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,7 +155,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "optionalDependencies": { "usb": "*" @@ -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", @@ -12022,7 +12020,6 @@ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.13.tgz", "integrity": "sha512-jCkYDvlfzOyHaVsrvR4vnNZxG30oNv2jbbFBjTQAUG8n0h07HW0sZJHk4KAQIRyu9ay+Rg+L8qGa3lwt8Gve9w==", "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" }, @@ -17482,12 +17476,11 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "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" } diff --git a/package.json b/package.json index 189355f4..784bb75a 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "optionalDependencies": { "usb": "*" From cd8285225477f0857cd623aa731fb449cb3d9745 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Fri, 20 Mar 2026 10:32:23 +0100 Subject: [PATCH 11/12] fix: add --include=dev to vercel install command so typescript is not pruned --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 4eef6d13..6f4c3311 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,7 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "npm run build", - "installCommand": "npm install && npm rebuild lightningcss", + "installCommand": "npm install --include=dev && npm rebuild lightningcss", "framework": "nextjs", "env": { "npm_config_platform": "linux", From b2e71506fa7247c4af37b6ddea3d6d3be165b6c0 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Fri, 20 Mar 2026 11:31:54 +0100 Subject: [PATCH 12/12] feat: display financial preview in rewards confirmation dialog --- .../hackathons/new/tabs/RewardsTab.tsx | 329 ++++++++++++++---- .../settings/RewardsSettingsTab.tsx | 4 + lib/api/hackathons/rewards.ts | 64 +++- 3 files changed, 323 insertions(+), 74 deletions(-) diff --git a/components/organization/hackathons/new/tabs/RewardsTab.tsx b/components/organization/hackathons/new/tabs/RewardsTab.tsx index c343cf77..0e3aeea8 100644 --- a/components/organization/hackathons/new/tabs/RewardsTab.tsx +++ b/components/organization/hackathons/new/tabs/RewardsTab.tsx @@ -26,7 +26,9 @@ import { import { rewardsSchema, RewardsFormData } from './schemas/rewardsSchema'; import { getFeeEstimate, + getFinancialPreview, type FeeEstimateData, + type FinancialPreviewData, } from '@/lib/api/hackathons/rewards'; import { cn } from '@/lib/utils'; import type { Control } from 'react-hook-form'; @@ -67,6 +69,9 @@ interface RewardsTabProps { onSave?: (data: RewardsFormData) => Promise; initialData?: RewardsFormData; isLoading?: boolean; + /** Required to call the financial preview dry-run endpoint */ + organizationId?: string; + hackathonId?: string; } const PRIZE_PRESETS = { @@ -595,12 +600,19 @@ export default function RewardsTab({ onContinue, initialData, isLoading = false, + organizationId, + hackathonId, }: RewardsTabProps) { const [showPresets, setShowPresets] = useState(false); const [pendingPreset, setPendingPreset] = useState< keyof typeof PRIZE_PRESETS | null >(null); const [confirmOpen, setConfirmOpen] = useState(false); + const [previewData, setPreviewData] = useState( + null + ); + const [previewLoading, setPreviewLoading] = useState(false); + const [previewError, setPreviewError] = useState(null); /** The amount that was already funded when this form was opened (for top-up delta). */ const initialFundedAmount = useMemo( @@ -835,14 +847,37 @@ export default function RewardsTab({ }; /** - * Called when Continue is clicked. - * Runs form validation; if it passes, opens the confirm dialog. - * If validation fails, react-hook-form will display inline errors as usual. + * Called when the action button is clicked. + * 1. Runs Zod validation — shows inline errors if invalid. + * 2. If valid and org/hackathon IDs are present, calls the dry-run preview endpoint. + * 3. Opens the confirm dialog (with live preview data if available). */ const handleContinueClick = async () => { const isValid = await form.trigger(); - if (isValid) { - setConfirmOpen(true); + if (!isValid) return; + + // Reset preview state for this dialog open + setPreviewData(null); + setPreviewError(null); + setConfirmOpen(true); + + if (organizationId && hackathonId) { + setPreviewLoading(true); + try { + const tiers = form.getValues('prizeTiers'); + const data = await getFinancialPreview(organizationId, hackathonId, { + prizeTiers: tiers, + }); + setPreviewData(data); + } catch (err: any) { + setPreviewError( + err?.response?.data?.message || + err?.message || + 'Could not load cost preview. You can still proceed.' + ); + } finally { + setPreviewLoading(false); + } } }; @@ -1041,7 +1076,7 @@ export default function RewardsTab({ {/* ── Confirmation AlertDialog ── */} - +
@@ -1051,75 +1086,217 @@ export default function RewardsTab({
-

- You are about to lock{' '} - - {(() => { - const hasExisting = initialFundedAmount > 0; - const delta = totalPool - initialFundedAmount; - const amt = hasExisting - ? delta - : (feeEstimate?.totalFunds ?? totalPool); - return `$${amt.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2 })} USDC`; - })()} - {' '} - {initialFundedAmount > 0 ? '(top-up amount) ' : ''}into an - escrow smart contract for this hackathon's prize pool. -

-
-

- ⚠️ This action is irreversible -

-

- Once confirmed, funds cannot be withdrawn without contacting - support. Ensure your wallet has sufficient balance. -

-
-
-
- Prize tiers - {fields.length} + {/* Loading state */} + {previewLoading && ( +
+ + Calculating exact cost…
-
- Total prize pool - - $ - {totalPool.toLocaleString('en-US', { + )} + + {/* Error fallback — still lets user proceed */} + {previewError && !previewLoading && ( +
+

Preview unavailable

+

+ {previewError} +

+
+ )} + + {/* Rich preview from dry-run endpoint */} + {previewData && + !previewLoading && + (() => { + const fmt = (n: number) => + n.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2, - })}{' '} - USDC -
-
- {feeEstimate && ( -
- - {feeEstimate.feeLabel ?? 'Platform fee'} - - - $ - {feeEstimate.feeAmount.toLocaleString('en-US', { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - })}{' '} - USDC - + }); + return ( + <> +

+ You are about to{' '} + {previewData.additionalFundingRequired > 0 + ? 'top up' + : 'update'}{' '} + your escrow by{' '} + + ${fmt(previewData.additionalFundingRequired)} USDC + + . +

+ + {/* Wallet sufficiency */} +
+
+ Wallet balance + + ${fmt(previewData.walletBalance)} USDC + +
+ {!previewData.sufficient && ( +

+ ⚠️ Shortfall of ${fmt(previewData.shortfall)} USDC + — please top up your wallet before proceeding. +

+ )} +
+ + {/* Irreversibility warning */} +
+

+ ⚠️ This action is irreversible +

+

+ Once confirmed, funds move on-chain into escrow and + cannot be withdrawn without contacting support. +

+
+ + {/* Cost summary */} +
+
+ Current pool + + ${fmt(previewData.currentPrizePool)} USDC + +
+
+ New pool + + ${fmt(previewData.newPrizePool)} USDC + +
+
+ + New platform fee + + + ${fmt(previewData.newPlatformFee)} USDC + +
+
+ + You'll pay now + + + ${fmt(previewData.additionalFundingRequired)} USDC + +
+
+ + {/* Per-tier breakdown */} + {previewData.breakdown.length > 0 && ( +
+

+ Per-tier breakdown +

+
+ {previewData.breakdown.map((tier, i) => ( +
+ + {tier.place} + + + ${fmt(tier.amount)} + ${fmt(tier.fee)} fee ={' '} + ${fmt(tier.total)} + +
+ ))} +
+
+ )} + + ); + })()} + + {/* Fallback when no org/hackathon IDs (creation flow) */} + {!organizationId && !previewLoading && !previewData && ( + <> +

+ You are about to lock{' '} + + {(() => { + const hasExisting = initialFundedAmount > 0; + const delta = totalPool - initialFundedAmount; + const amt = hasExisting + ? delta + : (feeEstimate?.totalFunds ?? totalPool); + return `$${amt.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2 })} USDC`; + })()} + {' '} + {initialFundedAmount > 0 ? '(top-up) ' : ''}into escrow. +

+
+

+ ⚠️ This action is irreversible +

+

+ Once confirmed, funds move on-chain and cannot be + withdrawn without contacting support. Ensure your wallet + has sufficient balance. +

- )} - {initialFundedAmount > 0 && ( -
- Already in escrow - - $ - {initialFundedAmount.toLocaleString('en-US', { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - })}{' '} - USDC - +
+
+ Prize tiers + {fields.length} +
+
+ Total prize pool + + $ + {totalPool.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}{' '} + USDC + +
+ {feeEstimate && ( +
+ + {feeEstimate.feeLabel ?? 'Platform fee'} + + + $ + {feeEstimate.feeAmount.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}{' '} + USDC + +
+ )} + {initialFundedAmount > 0 && ( +
+ + Already in escrow + + + $ + {initialFundedAmount.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + })}{' '} + USDC + +
+ )}
- )} -
+ + )}
@@ -1128,14 +1305,22 @@ export default function RewardsTab({ Cancel { e.preventDefault(); setConfirmOpen(false); handleConfirmedSubmit(); }} - className='bg-orange-500 text-white hover:bg-orange-600' + className={cn( + 'text-white', + previewData && !previewData.sufficient + ? 'cursor-not-allowed bg-zinc-600 opacity-50' + : 'bg-orange-500 hover:bg-orange-600' + )} > - Yes, lock funds + {previewData && !previewData.sufficient + ? 'Insufficient balance' + : 'Yes, lock funds'} diff --git a/components/organization/hackathons/settings/RewardsSettingsTab.tsx b/components/organization/hackathons/settings/RewardsSettingsTab.tsx index 237c832c..25ce74e4 100644 --- a/components/organization/hackathons/settings/RewardsSettingsTab.tsx +++ b/components/organization/hackathons/settings/RewardsSettingsTab.tsx @@ -16,6 +16,8 @@ export default function RewardsSettingsTab({ initialData, onSave, isLoading = false, + organizationId, + hackathonId, }: RewardsSettingsTabProps) { return (
@@ -30,6 +32,8 @@ export default function RewardsSettingsTab({ initialData={initialData} onSave={onSave} isLoading={isLoading} + organizationId={organizationId} + hackathonId={hackathonId} />
); diff --git a/lib/api/hackathons/rewards.ts b/lib/api/hackathons/rewards.ts index 54e3cbe7..705c34db 100644 --- a/lib/api/hackathons/rewards.ts +++ b/lib/api/hackathons/rewards.ts @@ -1,7 +1,9 @@ import api from '../api'; import { ApiResponse } from '../types'; -// Fee estimate (GET /api/hackathons/fee-estimate?totalPool=...) +// ── Fee Estimate ───────────────────────────────────────────────────────────── +// GET /api/hackathons/fee-estimate?totalPool=... + export interface FeeEstimateData { totalPool: number; feeRate: number; @@ -36,7 +38,65 @@ export const getFeeEstimate = async ( return res.data.data; }; -// Rewards API Request/Response Types +// ── Financial Preview (dry-run) ────────────────────────────────────────────── +// POST /api/organizations/:orgId/hackathons/:id/financial/preview + +export interface FinancialPreviewTier { + place: string; + amount: number; + fee: number; + total: number; +} + +export interface FinancialPreviewData { + currentPrizePool: number; + newPrizePool: number; + currentPlatformFee: number; + newPlatformFee: number; + currentTotalRequired: number; + newTotalRequired: number; + additionalFundingRequired: number; + walletBalance: number; + sufficient: boolean; + shortfall: number; + escrowStatus: string; + breakdown: FinancialPreviewTier[]; +} + +export interface GetFinancialPreviewResponse { + success: boolean; + message: string; + data: FinancialPreviewData; +} + +/** + * Dry-run preview: calculates the exact USDC cost of a proposed reward update + * without touching escrow state or the wallet. + * + * Call this before PATCH /financial to power the confirmation dialog and avoid + * surprising the organizer with an insufficient-balance error. + */ +export const getFinancialPreview = async ( + organizationId: string, + hackathonId: string, + rewards: { + prizeTiers: Array<{ + place: string; + prizeAmount: string | number; + [key: string]: unknown; + }>; + } +): Promise => { + const res = await api.post( + `/organizations/${organizationId}/hackathons/${hackathonId}/financial/preview`, + { rewards } + ); + if (!res.data?.data) throw new Error('Invalid financial preview response'); + return res.data.data; +}; + +// ── Rewards: Assign Ranks ──────────────────────────────────────────────────── + export interface AssignRanksRequest { ranks: Array<{ participantId: string;