From 68cbe2132984d3692bb1c6d97ba320d4d6ff6ea5 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Sat, 28 Feb 2026 19:54:05 +0100 Subject: [PATCH 1/5] feat: implement the participating page --- app/me/layout.tsx | 33 ++- app/me/participating/page.tsx | 189 ++++++++++++++++++ components/EmptyState.tsx | 4 +- components/app-sidebar.tsx | 21 +- components/hackathons/ProgressIndicator.tsx | 57 ++++++ .../landing-page/hackathon/HackathonCard.tsx | 13 +- 6 files changed, 295 insertions(+), 22 deletions(-) create mode 100644 app/me/participating/page.tsx create mode 100644 components/hackathons/ProgressIndicator.tsx diff --git a/app/me/layout.tsx b/app/me/layout.tsx index 865b62341..29e40b42f 100644 --- a/app/me/layout.tsx +++ b/app/me/layout.tsx @@ -4,20 +4,12 @@ import { AppSidebar } from '@/components/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { useAuthStatus } from '@/hooks/use-auth'; -import React from 'react'; +import React, { useMemo } from 'react'; import LoadingSpinner from '@/components/LoadingSpinner'; export default function MeLayout({ children }: { children: React.ReactNode }) { const { user, isLoading } = useAuthStatus(); - if (isLoading) { - return ( -
- -
- ); - } - const { name = '', email = '', profile, image: userImage = '' } = user || {}; const userData = { @@ -26,6 +18,23 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { image: profile?.image || userImage, }; + const hackathonsCount = useMemo(() => { + if (!profile) return 0; + const mergedIds = new Set([ + ...(profile.hackathonsAsParticipant?.map((h: any) => h.id) || []), + ...(profile.userHackathons?.map((h: any) => h.id) || []), + ]); + return mergedIds.size; + }, [profile]); + + if (isLoading) { + return ( +
+ +
+ ); + } + return ( - +
{children}
diff --git a/app/me/participating/page.tsx b/app/me/participating/page.tsx new file mode 100644 index 000000000..a9b27306e --- /dev/null +++ b/app/me/participating/page.tsx @@ -0,0 +1,189 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useAuthStatus } from '@/hooks/use-auth'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { motion, AnimatePresence } from 'framer-motion'; +import HackathonCard from '@/components/landing-page/hackathon/HackathonCard'; +import { + ProgressIndicator, + SubmissionStage, +} from '@/components/hackathons/ProgressIndicator'; +import { cn } from '@/lib/utils'; +import { Hackathon } from '@/lib/api/hackathons'; +import EmptyState from '@/components/EmptyState'; + +interface ExtendedUser { + profile?: { + hackathonsAsParticipant?: Hackathon[]; + userHackathons?: Hackathon[]; + hackathonSubmissionsAsParticipant?: Array<{ + id: string; + hackathonId: string; + status: string; + submittedAt: string; + }>; + }; +} + +export default function ParticipatingPage() { + const { user, isLoading } = useAuthStatus() as { + user: ExtendedUser | null; + isLoading: boolean; + }; + const [activeTab, setActiveTab] = useState<'all' | 'hackathons' | 'projects'>( + 'all' + ); + + const unifiedList = useMemo(() => { + if (!user?.profile) return []; + + const hackathonsAsParticipant = user.profile.hackathonsAsParticipant || []; + const userHackathons = user.profile.userHackathons || []; + + // Merge and deduplicate by ID + const merged = [...hackathonsAsParticipant, ...userHackathons]; + const seen = new Set(); + const deduplicated = merged.filter(h => { + if (seen.has(h.id)) return false; + seen.add(h.id); + return true; + }); + + // Sort logic: active/ongoing first, then upcoming, then completed + return deduplicated.sort((a, b) => { + const getPriority = (h: Hackathon) => { + const now = new Date().getTime(); + const start = new Date(h.startDate).getTime(); + const deadline = new Date(h.submissionDeadline).getTime(); + + if (now >= start && now <= deadline) return 0; // Ongoing + if (now < start) return 1; // Upcoming + return 2; // Completed + }; + + return getPriority(a) - getPriority(b); + }); + }, [user]); + + const filteredList = useMemo(() => { + if (activeTab === 'all') return unifiedList; + if (activeTab === 'hackathons') { + // In this context, "hackathons" might mean ones you are participating in vs "projects" (ones you created/lead) + // But the prompt says "Each tab filters from the unified list". + // Usually "Projects" refers to hackathons where you have a submission. + return unifiedList; // Placeholder for specific filter logic if needed + } + return unifiedList; + }, [unifiedList, activeTab]); + + const getSubmissionStage = (hackathonId: string): SubmissionStage => { + const submission = user?.profile?.hackathonSubmissionsAsParticipant?.find( + s => s.hackathonId === hackathonId + ); + + if (!submission) return 'Not Started'; + + const status = submission.status.toUpperCase(); + if (status === 'DRAFT') return 'In Progress'; + if (status === 'SUBMITTED') return 'Submitted'; + if (status === 'UNDER_REVIEW') return 'Under Review'; + if (status === 'WINNER' || status === 'COMPLETED') return 'Results Pending'; + + return 'In Progress'; + }; + + if (isLoading) { + return ( +
+
+
+ ); + } + + return ( +
+
+
+

+ Participating +

+

+ Track your active hackathons, projects, and pending submissions. +

+
+ + setActiveTab(v as any)} + className='w-full md:w-auto' + > + + {['all', 'hackathons', 'projects'].map(tab => ( + + {tab} + {activeTab === tab && ( + + )} + + ))} + + +
+ + + {filteredList.length > 0 ? ( + + {filteredList.map(hackathon => ( + + +
+ +
+
+ ))} +
+ ) : ( + (window.location.href = '/hackathons')} + /> + )} +
+
+ ); +} diff --git a/components/EmptyState.tsx b/components/EmptyState.tsx index 2796bca45..ba50d8590 100644 --- a/components/EmptyState.tsx +++ b/components/EmptyState.tsx @@ -66,11 +66,11 @@ const EmptyState: React.FC = ({ switch (type) { case 'compact': - return `${baseStyle} px-4 py-2 text-sm bg-[#00D2A4] text-black hover:bg-[#00B894] focus:ring-[#00D2A4] shadow-sm`; + return `${baseStyle} px-4 py-2 text-sm bg-primary text-primary-foreground hover:bg-primary/90 focus:ring-primary shadow-sm`; case 'custom': return `${baseStyle} px-6 py-3 bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 shadow-md`; default: - return `${baseStyle} px-6 py-3 bg-[#00D2A4] text-black hover:bg-[#00B894] focus:ring-[#00D2A4] shadow-[0_2px_8px_rgba(0,210,164,0.2)]`; + return `${baseStyle} px-6 py-3 bg-primary text-primary-foreground hover:bg-primary/90 focus:ring-primary shadow-[0_2px_8px_rgba(167,249,80,0.2)]`; } }; diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index ad72176a8..ba7629520 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -27,7 +27,7 @@ import { import Image from 'next/image'; import Link from 'next/link'; -const navigationData = { +const getNavigationData = (counts?: { participating?: number }) => ({ main: [ { title: 'Overview', @@ -56,9 +56,9 @@ const navigationData = { hackathons: [ { title: 'Participating', - url: '/me/hackathons', + url: '/me/participating', icon: IconShieldCheck, - badge: '2', + badge: counts?.participating?.toString(), }, { title: 'Submissions', @@ -91,16 +91,27 @@ const navigationData = { badge: '5', }, ], -}; +}); + interface userData { name: string; email: string; image: string; } + export function AppSidebar({ user, + counts, ...props -}: { user: userData } & React.ComponentProps) { +}: { + user: userData; + counts?: { participating?: number }; +} & React.ComponentProps) { + const navigationData = React.useMemo( + () => getNavigationData(counts), + [counts] + ); + return (
diff --git a/components/hackathons/ProgressIndicator.tsx b/components/hackathons/ProgressIndicator.tsx new file mode 100644 index 000000000..bf7e5ac34 --- /dev/null +++ b/components/hackathons/ProgressIndicator.tsx @@ -0,0 +1,57 @@ +'use client'; + +import React from 'react'; +import { cn } from '@/lib/utils'; + +export type SubmissionStage = + | 'Not Started' + | 'In Progress' + | 'Submitted' + | 'Under Review' + | 'Results Pending'; + +interface ProgressIndicatorProps { + stage: SubmissionStage; + className?: string; +} + +const STAGE_CONFIG: Record = + { + 'Not Started': { + color: 'bg-zinc-500/20 text-zinc-400', + label: 'Not Started', + }, + 'In Progress': { + color: 'bg-blue-500/20 text-blue-400', + label: 'In Progress', + }, + Submitted: { color: 'bg-green-500/20 text-green-400', label: 'Submitted' }, + 'Under Review': { + color: 'bg-purple-500/20 text-purple-400', + label: 'Under Review', + }, + 'Results Pending': { + color: 'bg-yellow-500/20 text-yellow-400', + label: 'Results Pending', + }, + }; + +export function ProgressIndicator({ + stage, + className, +}: ProgressIndicatorProps) { + const config = STAGE_CONFIG[stage]; + + return ( +
+
+ {config.label} +
+ ); +} diff --git a/components/landing-page/hackathon/HackathonCard.tsx b/components/landing-page/hackathon/HackathonCard.tsx index dd135c30e..8355acf5b 100644 --- a/components/landing-page/hackathon/HackathonCard.tsx +++ b/components/landing-page/hackathon/HackathonCard.tsx @@ -4,6 +4,7 @@ import Image from 'next/image'; import { MapPinIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Hackathon } from '@/lib/api/hackathons'; +import { cn } from '@/lib/utils'; // type HackathonCardProps = { // id: string; @@ -176,8 +177,8 @@ function HackathonCard({ categories, prizeTiers, isFullWidth = false, - // className, -}: Hackathon & { isFullWidth?: boolean }) { + className, +}: Hackathon & { isFullWidth?: boolean; className?: string }) { const router = useRouter(); const [timeRemaining, setTimeRemaining] = useState({ days: 0, @@ -386,9 +387,11 @@ function HackathonCard({ return (
{/* Image */}
From 504a581a42a7a40e8018e265fe1eb36962fe5880 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Sun, 1 Mar 2026 20:30:34 +0100 Subject: [PATCH 2/5] fix: fix participating page and ui refinements --- app/me/layout.tsx | 9 +- app/me/participating/page.tsx | 126 +++++++++++++----- .../landing-page/hackathon/HackathonCard.tsx | 42 ++++-- components/profile/ProfileDataClient.tsx | 4 +- components/profile/ProfileOverview.tsx | 8 +- 5 files changed, 134 insertions(+), 55 deletions(-) diff --git a/app/me/layout.tsx b/app/me/layout.tsx index 29e40b42f..3a3958caf 100644 --- a/app/me/layout.tsx +++ b/app/me/layout.tsx @@ -20,11 +20,10 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { const hackathonsCount = useMemo(() => { if (!profile) return 0; - const mergedIds = new Set([ - ...(profile.hackathonsAsParticipant?.map((h: any) => h.id) || []), - ...(profile.userHackathons?.map((h: any) => h.id) || []), - ]); - return mergedIds.size; + + const joined = (profile as any)?.user?.joinedHackathons || []; + + return joined.length; }, [profile]); if (isLoading) { diff --git a/app/me/participating/page.tsx b/app/me/participating/page.tsx index a9b27306e..7ab48c07b 100644 --- a/app/me/participating/page.tsx +++ b/app/me/participating/page.tsx @@ -36,50 +36,92 @@ export default function ParticipatingPage() { ); const unifiedList = useMemo(() => { - if (!user?.profile) return []; + if (!user?.profile) { + return []; + } + + const profile = user.profile as any; + const joinedHackathons = profile.user?.joinedHackathons || []; + const hackathonsAsParticipant = profile.hackathonsAsParticipant || []; + const submissions = profile.user?.hackathonSubmissionsAsParticipant || []; + + // Map hackathons from joined list + const typedJoinedHackathons = joinedHackathons.map((h: any) => { + const hackathonData = h.hackathon || h; + return { + ...hackathonData, + type: 'hackathon' as const, + }; + }); + + // Map hackathons from participating list (preferred source based on logs) + const typedParticipatingHackathons = hackathonsAsParticipant.map( + (p: any) => { + const hackathonData = p.hackathon; + return { + ...hackathonData, + type: 'hackathon' as const, + }; + } + ); - const hackathonsAsParticipant = user.profile.hackathonsAsParticipant || []; - const userHackathons = user.profile.userHackathons || []; + // Map hackathons from submissions + const typedSubmissionHackathons = submissions + .filter((s: any) => s.hackathon) + .map((s: any) => ({ + ...s.hackathon, + type: 'hackathon' as const, + })); // Merge and deduplicate by ID - const merged = [...hackathonsAsParticipant, ...userHackathons]; + const merged = [ + ...typedParticipatingHackathons, + ...typedJoinedHackathons, + ...typedSubmissionHackathons, + ]; + const seen = new Set(); - const deduplicated = merged.filter(h => { - if (seen.has(h.id)) return false; - seen.add(h.id); + const deduplicated = merged.filter((item: any) => { + if (!item.id || seen.has(item.id)) return false; + seen.add(item.id); return true; }); // Sort logic: active/ongoing first, then upcoming, then completed - return deduplicated.sort((a, b) => { - const getPriority = (h: Hackathon) => { + const sorted = deduplicated.sort((a: any, b: any) => { + const getPriority = (h: any) => { const now = new Date().getTime(); + if (!h.startDate || !h.submissionDeadline) return 1; + const start = new Date(h.startDate).getTime(); const deadline = new Date(h.submissionDeadline).getTime(); - if (now >= start && now <= deadline) return 0; // Ongoing - if (now < start) return 1; // Upcoming - return 2; // Completed + if (now >= start && now <= deadline) return 0; + if (now < start) return 1; + return 2; }; return getPriority(a) - getPriority(b); }); + + return sorted; }, [user]); const filteredList = useMemo(() => { - if (activeTab === 'all') return unifiedList; + if (activeTab === 'projects') return []; // Keep projects tab empty as requested + + let result = unifiedList; if (activeTab === 'hackathons') { - // In this context, "hackathons" might mean ones you are participating in vs "projects" (ones you created/lead) - // But the prompt says "Each tab filters from the unified list". - // Usually "Projects" refers to hackathons where you have a submission. - return unifiedList; // Placeholder for specific filter logic if needed + result = unifiedList.filter((item: any) => item.type === 'hackathon'); } - return unifiedList; + return result; }, [unifiedList, activeTab]); const getSubmissionStage = (hackathonId: string): SubmissionStage => { - const submission = user?.profile?.hackathonSubmissionsAsParticipant?.find( - s => s.hackathonId === hackathonId + const submission = ( + user?.profile as any + )?.user?.hackathonSubmissionsAsParticipant?.find( + (s: any) => s.hackathonId === hackathonId ); if (!submission) return 'Not Started'; @@ -162,25 +204,45 @@ export default function ParticipatingPage() { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className='relative' + className='group relative' > - -
- +
+ +
+ +
))} ) : ( (window.location.href = '/hackathons')} + title={ + activeTab === 'projects' + ? 'No active projects' + : 'No active engagements' + } + description={ + activeTab === 'projects' + ? "You haven't participating in any projects yet. Explore our community projects to get started!" + : "You haven't joined any hackathons yet. Explore our open events to get started!" + } + buttonText={ + activeTab === 'projects' + ? 'Explore Projects' + : 'Explore Hackathons' + } + onAddClick={() => + (window.location.href = + activeTab === 'projects' ? '/projects' : '/hackathons') + } /> )} diff --git a/components/landing-page/hackathon/HackathonCard.tsx b/components/landing-page/hackathon/HackathonCard.tsx index 8355acf5b..0363ef909 100644 --- a/components/landing-page/hackathon/HackathonCard.tsx +++ b/components/landing-page/hackathon/HackathonCard.tsx @@ -158,7 +158,13 @@ function calculateTimeRemaining(targetDate: string): TimeRemaining { // } // } -function HackathonCard({ +interface HackathonCardProps extends Hackathon { + isFullWidth?: boolean; + className?: string; + target?: string; +} + +export const HackathonCard = ({ id, slug, name, @@ -178,7 +184,8 @@ function HackathonCard({ prizeTiers, isFullWidth = false, className, -}: Hackathon & { isFullWidth?: boolean; className?: string }) { + target, +}: HackathonCardProps) => { const router = useRouter(); const [timeRemaining, setTimeRemaining] = useState({ days: 0, @@ -190,7 +197,12 @@ function HackathonCard({ const handleClick = () => { const slugPath = slug || id || ''; - router.push(`/hackathons/${slugPath}`); + const url = `/hackathons/${slugPath}`; + if (target === '_blank') { + window.open(url, '_blank'); + } else { + router.push(url); + } }; // Determine top badge status using raw dates @@ -353,9 +365,9 @@ function HackathonCard({ // })(); const CategoriesDisplay = ({ - categoriesList, + categoriesList = [], }: { - categoriesList: string[]; + categoriesList?: string[]; }) => { const MAX_VISIBLE = 3; @@ -414,13 +426,17 @@ function HackathonCard({
-
- - {organization.name} - + {organization?.logo && ( +
+ )} + {organization?.name && ( + + {organization.name} + + )}
@@ -481,6 +497,6 @@ function HackathonCard({
); -} +}; export default HackathonCard; diff --git a/components/profile/ProfileDataClient.tsx b/components/profile/ProfileDataClient.tsx index 1e2dd6fe8..b5507cf9f 100644 --- a/components/profile/ProfileDataClient.tsx +++ b/components/profile/ProfileDataClient.tsx @@ -46,8 +46,8 @@ export default function ProfileDataClient({ user }: ProfileDataClientProps) { const isOwnProfile = userData.id === currentUser?.id; const organizationsData = userData.members?.map(org => ({ - name: org.organization.name, - avatarUrl: org.organization.logo || '/blog1.jpg', + name: org.organization?.name || 'Unknown Organization', + avatarUrl: org.organization?.logo || '/blog1.jpg', })) || []; return ( diff --git a/components/profile/ProfileOverview.tsx b/components/profile/ProfileOverview.tsx index a966adf79..3113e36f9 100644 --- a/components/profile/ProfileOverview.tsx +++ b/components/profile/ProfileOverview.tsx @@ -21,9 +21,11 @@ export default function ProfileOverview({ isAuthenticated, isOwnProfile, }: ProfileOverviewProps) { + const nameParts = user.name?.split(' ') || []; const profileData: UserProfile = { username: user.username, - displayName: `${user.name?.split(' ')[0] || ''} ${user.name?.split(' ').slice(1).join(' ') || ''}`, + displayName: + `${nameParts[0] || ''} ${nameParts.slice(1).join(' ') || ''}`.trim(), bio: user.profile?.bio || 'No bio available', avatarUrl: user.image || '/', socialLinks: user.profile?.socialLinks || {}, @@ -39,8 +41,8 @@ export default function ProfileOverview({ const organizationsData: Organization[] = user.members?.map(org => { return { - name: org.organization.name, - avatarUrl: org.organization.logo || '/blog1.jpg', + name: org.organization?.name || 'Unknown Organization', + avatarUrl: org.organization?.logo || '/blog1.jpg', id: org.organizationId, }; }) || []; From 8f2010db57d03000c99401d9139fe4ae66aa15c6 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Sun, 1 Mar 2026 21:37:39 +0100 Subject: [PATCH 3/5] fix: fix sidebar active links and rstored earnings page --- app/me/earnings/page.tsx | 271 +++++++++++++++++++++++++++++++++++++ components/app-sidebar.tsx | 6 + components/nav-main.tsx | 3 +- lib/api/user/earnings.ts | 64 +++++++++ 4 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 app/me/earnings/page.tsx create mode 100644 lib/api/user/earnings.ts diff --git a/app/me/earnings/page.tsx b/app/me/earnings/page.tsx new file mode 100644 index 000000000..8cf554340 --- /dev/null +++ b/app/me/earnings/page.tsx @@ -0,0 +1,271 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import { motion } from 'framer-motion'; +import { + IconCurrencyDollar, + IconClock, + IconCheck, + IconTrophy, + IconBriefcase, + IconUsers, + IconTarget, +} from '@tabler/icons-react'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + getUserEarnings, + EarningsData, + EarningActivity, +} from '@/lib/api/user/earnings'; +import { toast } from 'sonner'; + +/** + * Interface for SummaryCard props. + */ +interface SummaryCardProps { + title: string; + value: string; + icon: React.ReactNode; + description: string; +} + +/** + * SummaryCard component for displaying high-level stats. + */ +const SummaryCard: React.FC = ({ + title, + value, + icon, + description, +}) => ( + + + {title} + {icon} + + +
{value}
+

{description}

+
+
+); + +/** + * Interface for BreakdownItem props. + */ +interface BreakdownItemProps { + label: string; + value: number; + icon: React.ReactNode; +} + +/** + * BreakdownItem component for showing source-specific earnings. + */ +const BreakdownItem: React.FC = ({ + label, + value, + icon, +}) => ( +
+
+
+ {icon} +
+ {label} +
+ ${value.toLocaleString()} +
+); + +/** + * Interface for ActivityItem props. + */ +interface ActivityItemProps { + activity: EarningActivity; +} + +/** + * ActivityItem component for displaying a single reward entry. + */ +const ActivityItem: React.FC = ({ activity }) => ( +
+
+
{activity.title}
+
+ {activity.source} + + {new Date(activity.occurredAt).toLocaleDateString()} +
+
+
+

${activity.amount.toLocaleString()}

+ {activity.currency && ( +

{activity.currency}

+ )} +
+
+); + +/** + * EarningsSkeleton component for loading states. + */ +const EarningsSkeleton: React.FC = () => ( +
+
+ + +
+
+ + + +
+
+ + +
+
+); + +/** + * EarningsPage component for managing and tracking user rewards. + */ +const EarningsPage: React.FC = () => { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + const res = await getUserEarnings(); + if (res.success && res.data) { + setData(res.data); + } + } catch (error) { + console.error('Failed to fetch earnings:', error); + toast.error('Failed to load earnings data'); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + + if (loading) { + return ; + } + + if (!data) { + return ( +
+

+ No earnings data found. +

+
+ ); + } + + return ( +
+ +

+ Earnings Dashboard +

+

+ Manage and track your rewards across the platform. +

+
+ + {/* Summary Cards */} +
+ } + description='Lifetime earnings' + /> + } + description='Awaiting processing' + /> + } + description='Successfully cashed out' + /> +
+ +
+ {/* Breakdown */} + + + Source Breakdown + + Earnings categorized by activity type + + + + } + /> + } + /> + } + /> + } + /> + + + + {/* Activity Feed */} + + + Recent Activity + Your latest wins and rewards + + +
+ {data.activities.length === 0 ? ( +

+ No recent activity found. +

+ ) : ( + data.activities.map(activity => ( + + )) + )} +
+
+
+
+
+ ); +}; + +export default EarningsPage; diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index ba7629520..3005e5ff5 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { IconBell, IconChartBar, + IconCurrencyDollar, IconDashboard, IconFileText, IconFolder, @@ -39,6 +40,11 @@ const getNavigationData = (counts?: { participating?: number }) => ({ url: '/me/analytics', icon: IconChartBar, }, + { + title: 'Earnings', + url: '/me/earnings', + icon: IconCurrencyDollar, + }, ], projects: [ { diff --git a/components/nav-main.tsx b/components/nav-main.tsx index 0bec9fcb8..0648e5092 100644 --- a/components/nav-main.tsx +++ b/components/nav-main.tsx @@ -41,7 +41,8 @@ export function NavMain({ {items.map(item => { const isActive = - pathname === item.url || pathname?.startsWith(`${item.url}/`); + pathname === item.url || + (item.url !== '/me' && pathname?.startsWith(`${item.url}/`)); return ( diff --git a/lib/api/user/earnings.ts b/lib/api/user/earnings.ts new file mode 100644 index 000000000..145aaf418 --- /dev/null +++ b/lib/api/user/earnings.ts @@ -0,0 +1,64 @@ +import { api } from '../api'; +import { ApiResponse } from '../types'; + +export interface EarningActivity { + id: string; + source: 'hackathons' | 'grants' | 'crowdfunding' | 'bounties'; + title: string; + amount: number; + currency: string; + occurredAt: string; +} + +export interface EarningsData { + summary: { + totalEarned: number; + pendingWithdrawal: number; + completedWithdrawal: number; + }; + breakdown: { + hackathons: number; + grants: number; + crowdfunding: number; + bounties: number; + }; + activities: EarningActivity[]; +} + +export interface GetEarningsResponse extends ApiResponse { + success: true; + data: EarningsData; +} + +export interface ClaimEarningRequest { + activityId: string; +} + +export interface ClaimEarningResponse extends ApiResponse { + success: boolean; + message: string; + data?: { + transactionHash: string; + }; +} + +/** + * Get user earnings data + */ +export const getUserEarnings = async (): Promise => { + const res = await api.get('/users/earnings'); + return res.data; +}; + +/** + * Claim a specific earning + */ +export const claimEarning = async ( + data: ClaimEarningRequest +): Promise => { + const res = await api.post( + '/users/earnings/claim', + data + ); + return res.data; +}; From e038b15ee5f154b4fc907da76d8ce5a0323e48db Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Sun, 1 Mar 2026 22:06:30 +0100 Subject: [PATCH 4/5] fix: fix coderabbit corrections --- app/me/earnings/page.tsx | 12 +- app/me/layout.tsx | 4 +- app/me/participating/page.tsx | 103 +++++++++--------- components/app-sidebar.tsx | 11 +- components/hackathons/ProgressIndicator.tsx | 2 +- .../landing-page/hackathon/HackathonCard.tsx | 2 +- components/nav-user.tsx | 6 +- hooks/use-auth.ts | 2 + lib/api/types.ts | 16 +++ lib/api/user/earnings.ts | 7 +- 10 files changed, 98 insertions(+), 67 deletions(-) diff --git a/app/me/earnings/page.tsx b/app/me/earnings/page.tsx index 8cf554340..7174a2426 100644 --- a/app/me/earnings/page.tsx +++ b/app/me/earnings/page.tsx @@ -81,7 +81,9 @@ const BreakdownItem: React.FC = ({
{label}
- ${value.toLocaleString()} + + ${(Number(value) || 0).toLocaleString()} +
); @@ -106,7 +108,9 @@ const ActivityItem: React.FC = ({ activity }) => (
-

${activity.amount.toLocaleString()}

+

+ ${(Number(activity.amount) || 0).toLocaleString()} +

{activity.currency && (

{activity.currency}

)} @@ -146,8 +150,10 @@ const EarningsPage: React.FC = () => { const fetchData = async () => { try { const res = await getUserEarnings(); - if (res.success && res.data) { + if (res.success) { setData(res.data); + } else { + toast.error(res.error || 'Failed to load earnings data'); } } catch (error) { console.error('Failed to fetch earnings:', error); diff --git a/app/me/layout.tsx b/app/me/layout.tsx index 3a3958caf..bcb176d32 100644 --- a/app/me/layout.tsx +++ b/app/me/layout.tsx @@ -15,12 +15,14 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { const userData = { name: name || '', email, - image: profile?.image || userImage, + image: + (profile as any)?.user?.image || (profile as any)?.image || userImage, }; const hackathonsCount = useMemo(() => { if (!profile) return 0; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const joined = (profile as any)?.user?.joinedHackathons || []; return joined.length; diff --git a/app/me/participating/page.tsx b/app/me/participating/page.tsx index 7ab48c07b..1c5886a28 100644 --- a/app/me/participating/page.tsx +++ b/app/me/participating/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useMemo, useState } from 'react'; +import { useRouter } from 'next/navigation'; import { useAuthStatus } from '@/hooks/use-auth'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { motion, AnimatePresence } from 'framer-motion'; @@ -13,61 +14,59 @@ import { cn } from '@/lib/utils'; import { Hackathon } from '@/lib/api/hackathons'; import EmptyState from '@/components/EmptyState'; -interface ExtendedUser { - profile?: { - hackathonsAsParticipant?: Hackathon[]; - userHackathons?: Hackathon[]; - hackathonSubmissionsAsParticipant?: Array<{ - id: string; - hackathonId: string; - status: string; - submittedAt: string; - }>; - }; +type TabType = 'all' | 'hackathons' | 'projects'; + +interface UnifiedItem extends Hackathon { + type: 'hackathon'; } export default function ParticipatingPage() { - const { user, isLoading } = useAuthStatus() as { - user: ExtendedUser | null; - isLoading: boolean; + const router = useRouter(); + const { user, isLoading } = useAuthStatus(); + const [activeTab, setActiveTab] = useState('all'); + + const handleTabChange = (value: string) => { + setActiveTab(value as TabType); }; - const [activeTab, setActiveTab] = useState<'all' | 'hackathons' | 'projects'>( - 'all' - ); - const unifiedList = useMemo(() => { - if (!user?.profile) { + const unifiedList = useMemo(() => { + const profile = user?.profile; + if (!profile) { return []; } - const profile = user.profile as any; const joinedHackathons = profile.user?.joinedHackathons || []; const hackathonsAsParticipant = profile.hackathonsAsParticipant || []; const submissions = profile.user?.hackathonSubmissionsAsParticipant || []; // Map hackathons from joined list - const typedJoinedHackathons = joinedHackathons.map((h: any) => { - const hackathonData = h.hackathon || h; - return { - ...hackathonData, - type: 'hackathon' as const, - }; - }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const typedJoinedHackathons: UnifiedItem[] = joinedHackathons.map( + (h: any) => { + const hackathonData = h.hackathon || h; + return { + ...hackathonData, + type: 'hackathon' as const, + }; + } + ); // Map hackathons from participating list (preferred source based on logs) - const typedParticipatingHackathons = hackathonsAsParticipant.map( - (p: any) => { + const typedParticipatingHackathons: UnifiedItem[] = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + hackathonsAsParticipant.map((p: any) => { const hackathonData = p.hackathon; return { ...hackathonData, type: 'hackathon' as const, }; - } - ); + }); // Map hackathons from submissions - const typedSubmissionHackathons = submissions + const typedSubmissionHackathons: UnifiedItem[] = submissions + // eslint-disable-next-line @typescript-eslint/no-explicit-any .filter((s: any) => s.hackathon) + // eslint-disable-next-line @typescript-eslint/no-explicit-any .map((s: any) => ({ ...s.hackathon, type: 'hackathon' as const, @@ -80,16 +79,16 @@ export default function ParticipatingPage() { ...typedSubmissionHackathons, ]; - const seen = new Set(); - const deduplicated = merged.filter((item: any) => { + const seen = new Set(); + const deduplicated = merged.filter(item => { if (!item.id || seen.has(item.id)) return false; seen.add(item.id); return true; }); // Sort logic: active/ongoing first, then upcoming, then completed - const sorted = deduplicated.sort((a: any, b: any) => { - const getPriority = (h: any) => { + const sorted = deduplicated.sort((a, b) => { + const getPriority = (h: UnifiedItem) => { const now = new Date().getTime(); if (!h.startDate || !h.submissionDeadline) return 1; @@ -112,21 +111,24 @@ export default function ParticipatingPage() { let result = unifiedList; if (activeTab === 'hackathons') { - result = unifiedList.filter((item: any) => item.type === 'hackathon'); + result = unifiedList.filter(item => item.type === 'hackathon'); } return result; }, [unifiedList, activeTab]); const getSubmissionStage = (hackathonId: string): SubmissionStage => { - const submission = ( - user?.profile as any - )?.user?.hackathonSubmissionsAsParticipant?.find( - (s: any) => s.hackathonId === hackathonId - ); + const submission = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + user?.profile?.user?.hackathonSubmissionsAsParticipant?.find( + (s: any) => s.hackathonId === hackathonId + ); if (!submission) return 'Not Started'; - const status = submission.status.toUpperCase(); + const statusRaw = submission.status; + if (!statusRaw || typeof statusRaw !== 'string') return 'In Progress'; + + const status = statusRaw.toUpperCase(); if (status === 'DRAFT') return 'In Progress'; if (status === 'SUBMITTED') return 'Submitted'; if (status === 'UNDER_REVIEW') return 'Under Review'; @@ -135,6 +137,10 @@ export default function ParticipatingPage() { return 'In Progress'; }; + const handleEmptyStateClick = () => { + router.push(activeTab === 'projects' ? '/projects' : '/hackathons'); + }; + if (isLoading) { return (
@@ -157,7 +163,7 @@ export default function ParticipatingPage() { setActiveTab(v as any)} + onValueChange={handleTabChange} className='w-full md:w-auto' > @@ -231,18 +237,15 @@ export default function ParticipatingPage() { } description={ activeTab === 'projects' - ? "You haven't participating in any projects yet. Explore our community projects to get started!" - : "You haven't joined any hackathons yet. Explore our open events to get started!" + ? "You haven't participated in any projects yet. Explore our community projects to get started!" + : "You haven't participated in any hackathons yet. Explore our open events to get started!" } buttonText={ activeTab === 'projects' ? 'Explore Projects' : 'Explore Hackathons' } - onAddClick={() => - (window.location.href = - activeTab === 'projects' ? '/projects' : '/hackathons') - } + onAddClick={handleEmptyStateClick} /> )} diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 3005e5ff5..bdc0ce57c 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -64,7 +64,10 @@ const getNavigationData = (counts?: { participating?: number }) => ({ title: 'Participating', url: '/me/participating', icon: IconShieldCheck, - badge: counts?.participating?.toString(), + badge: + counts?.participating && counts.participating > 0 + ? counts.participating.toString() + : undefined, }, { title: 'Submissions', @@ -99,10 +102,10 @@ const getNavigationData = (counts?: { participating?: number }) => ({ ], }); -interface userData { +interface UserData { name: string; email: string; - image: string; + image: string | null; } export function AppSidebar({ @@ -110,7 +113,7 @@ export function AppSidebar({ counts, ...props }: { - user: userData; + user: UserData; counts?: { participating?: number }; } & React.ComponentProps) { const navigationData = React.useMemo( diff --git a/components/hackathons/ProgressIndicator.tsx b/components/hackathons/ProgressIndicator.tsx index bf7e5ac34..98748abd7 100644 --- a/components/hackathons/ProgressIndicator.tsx +++ b/components/hackathons/ProgressIndicator.tsx @@ -50,7 +50,7 @@ export function ProgressIndicator({ className )} > -
+
{config.label}
); diff --git a/components/landing-page/hackathon/HackathonCard.tsx b/components/landing-page/hackathon/HackathonCard.tsx index 0363ef909..f46339dcd 100644 --- a/components/landing-page/hackathon/HackathonCard.tsx +++ b/components/landing-page/hackathon/HackathonCard.tsx @@ -345,7 +345,7 @@ export const HackathonCard = ({ return () => clearInterval(interval); } - }, [status, startDate, submissionDeadline]); + }, [status, startDate, submissionDeadline, getTopBadgeStatus]); const bottomStatusInfo = getBottomStatusInfo(); const topBadgeStatus = getTopBadgeStatus(); diff --git a/components/nav-user.tsx b/components/nav-user.tsx index 9abede733..183f684f9 100644 --- a/components/nav-user.tsx +++ b/components/nav-user.tsx @@ -32,7 +32,7 @@ export function NavUser({ user: { name: string; email: string; - image: string; + image: string | null; }; }) { const { isMobile } = useSidebar(); @@ -47,7 +47,7 @@ export function NavUser({ className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground hover:bg-sidebar-accent/50 transition-colors' > - + {user.name .split(' ') @@ -75,7 +75,7 @@ export function NavUser({
- + {user.name .split(' ') diff --git a/hooks/use-auth.ts b/hooks/use-auth.ts index de50467c7..fa402b301 100644 --- a/hooks/use-auth.ts +++ b/hooks/use-auth.ts @@ -11,6 +11,7 @@ export function useAuth(requireAuth = true) { } = authClient.useSession(); const router = useRouter(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const [userProfile, setUserProfile] = useState(null); const [profileLoading, setProfileLoading] = useState(false); @@ -97,6 +98,7 @@ export function useOptionalAuth() { export function useAuthStatus() { const { data: session, isPending: sessionPending } = authClient.useSession(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const [userProfile, setUserProfile] = useState(null); const [profileLoading, setProfileLoading] = useState(false); diff --git a/lib/api/types.ts b/lib/api/types.ts index 45584207f..3c6d39a2e 100644 --- a/lib/api/types.ts +++ b/lib/api/types.ts @@ -111,6 +111,15 @@ export interface User { status: string; rank?: number | null; submittedAt: string; + hackathonId: string; + hackathon?: any; + }>; + joinedHackathons?: Array<{ + id: string; + userId: string; + hackathonId: string; + registrationDate: string; + hackathon?: any; }>; profile?: Record; stats?: { @@ -252,6 +261,13 @@ export interface GetMeResponse { project?: Record; organization?: Record; }>; + hackathonsAsParticipant?: Array<{ + id: string; + hackathonId: string; + participantId: string; + status: string; + hackathon?: any; + }>; } // Logout diff --git a/lib/api/user/earnings.ts b/lib/api/user/earnings.ts index 145aaf418..31e5a90a7 100644 --- a/lib/api/user/earnings.ts +++ b/lib/api/user/earnings.ts @@ -25,10 +25,9 @@ export interface EarningsData { activities: EarningActivity[]; } -export interface GetEarningsResponse extends ApiResponse { - success: true; - data: EarningsData; -} +export type GetEarningsResponse = + | { success: true; data: EarningsData; message?: string } + | { success: false; error: string; message?: string }; export interface ClaimEarningRequest { activityId: string; From 955b8d3678fb4f9b31336783e01282fb79d16a82 Mon Sep 17 00:00:00 2001 From: Michaelkingsdev Date: Mon, 2 Mar 2026 11:26:54 +0100 Subject: [PATCH 5/5] fix: fix coderabbit corrections --- app/me/participating/page.tsx | 34 ++-- .../landing-page/hackathon/HackathonCard.tsx | 9 +- package-lock.json | 173 +++++++++++++----- 3 files changed, 152 insertions(+), 64 deletions(-) diff --git a/app/me/participating/page.tsx b/app/me/participating/page.tsx index 1c5886a28..142b26131 100644 --- a/app/me/participating/page.tsx +++ b/app/me/participating/page.tsx @@ -39,28 +39,31 @@ export default function ParticipatingPage() { const hackathonsAsParticipant = profile.hackathonsAsParticipant || []; const submissions = profile.user?.hackathonSubmissionsAsParticipant || []; - // Map hackathons from joined list // eslint-disable-next-line @typescript-eslint/no-explicit-any - const typedJoinedHackathons: UnifiedItem[] = joinedHackathons.map( - (h: any) => { + const typedJoinedHackathons: UnifiedItem[] = joinedHackathons + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .filter((h: any) => { + const data = h?.hackathon || h; + return data && data.id; + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((h: any) => { const hackathonData = h.hackathon || h; return { ...hackathonData, type: 'hackathon' as const, }; - } - ); + }); - // Map hackathons from participating list (preferred source based on logs) - const typedParticipatingHackathons: UnifiedItem[] = + // Map hackathons from participating list — filter first to ensure p.hackathon is defined + const typedParticipatingHackathons: UnifiedItem[] = hackathonsAsParticipant // eslint-disable-next-line @typescript-eslint/no-explicit-any - hackathonsAsParticipant.map((p: any) => { - const hackathonData = p.hackathon; - return { - ...hackathonData, - type: 'hackathon' as const, - }; - }); + .filter((p: any) => p && p.hackathon && p.hackathon.id) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((p: any) => ({ + ...p.hackathon, + type: 'hackathon' as const, + })); // Map hackathons from submissions const typedSubmissionHackathons: UnifiedItem[] = submissions @@ -86,7 +89,6 @@ export default function ParticipatingPage() { return true; }); - // Sort logic: active/ongoing first, then upcoming, then completed const sorted = deduplicated.sort((a, b) => { const getPriority = (h: UnifiedItem) => { const now = new Date().getTime(); @@ -107,7 +109,7 @@ export default function ParticipatingPage() { }, [user]); const filteredList = useMemo(() => { - if (activeTab === 'projects') return []; // Keep projects tab empty as requested + if (activeTab === 'projects') return []; let result = unifiedList; if (activeTab === 'hackathons') { diff --git a/components/landing-page/hackathon/HackathonCard.tsx b/components/landing-page/hackathon/HackathonCard.tsx index f46339dcd..e700bfb87 100644 --- a/components/landing-page/hackathon/HackathonCard.tsx +++ b/components/landing-page/hackathon/HackathonCard.tsx @@ -2,7 +2,7 @@ import { useRouter } from 'nextjs-toploader/app'; import Image from 'next/image'; import { MapPinIcon } from 'lucide-react'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { Hackathon } from '@/lib/api/hackathons'; import { cn } from '@/lib/utils'; @@ -205,8 +205,9 @@ export const HackathonCard = ({ } }; - // Determine top badge status using raw dates - const getTopBadgeStatus = () => { + // Determine top badge status using raw dates — memoised so it can safely + // appear in the useEffect dependency array without triggering infinite loops. + const getTopBadgeStatus = useCallback(() => { if (status === 'ARCHIVED') { return 'Archived'; } @@ -229,7 +230,7 @@ export const HackathonCard = ({ // Otherwise it's upcoming return 'Upcoming'; - }; + }, [status, startDate, submissionDeadline]); const getTopBadgeColor = () => { const badgeStatus = getTopBadgeStatus(); diff --git a/package-lock.json b/package-lock.json index 6c07d7376..65287815c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6198,9 +6198,9 @@ } }, "node_modules/@trezor/connect": { - "version": "9.7.1", - "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.7.1.tgz", - "integrity": "sha512-W2ym0bs4FVmXByEr9gANBp+bRErzNcmqqqYzSJLOVkawxikqYXag2aCpdiXU3LlZbFbhFhIsT/fpDLfwiLRySA==", + "version": "9.7.2", + "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.7.2.tgz", + "integrity": "sha512-Sn6F4mNH+yi2vAHy29kwhs50bRLn92drg3znm3pkY+8yEBxI4MmuP8sKYjdgUEJnQflWh80KlcvEDeVa4olVRA==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { @@ -6216,18 +6216,18 @@ "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.3.0", "@trezor/blockchain-link": "2.6.1", - "@trezor/blockchain-link-types": "1.5.0", - "@trezor/blockchain-link-utils": "1.5.1", + "@trezor/blockchain-link-types": "1.5.1", + "@trezor/blockchain-link-utils": "1.5.2", "@trezor/connect-analytics": "1.4.0", - "@trezor/connect-common": "0.5.0", + "@trezor/connect-common": "0.5.1", "@trezor/crypto-utils": "1.2.0", - "@trezor/device-authenticity": "1.1.1", + "@trezor/device-authenticity": "1.1.2", "@trezor/device-utils": "1.2.0", "@trezor/env-utils": "^1.5.0", - "@trezor/protobuf": "1.5.1", + "@trezor/protobuf": "1.5.2", "@trezor/protocol": "1.3.0", "@trezor/schema-utils": "1.4.0", - "@trezor/transport": "1.6.1", + "@trezor/transport": "1.6.2", "@trezor/type-utils": "1.2.0", "@trezor/utils": "9.5.0", "@trezor/utxo-lib": "2.5.0", @@ -6256,9 +6256,9 @@ } }, "node_modules/@trezor/connect-common": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.5.0.tgz", - "integrity": "sha512-WE71iaFcWmfQxDCiTUNynj2DccRgUiLBJ+g3nrqCBJqEYzu+cD6eZ5k/OLtZ3hfh5gyB5EQwXdGvRT07iNdxAA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.5.1.tgz", + "integrity": "sha512-wdpVCwdylBh4SBO5Ys40tB/d59UlfjmxgBHDkkLgaR+JcqkthCfiw5VlUrV9wu65lquejAZhA5KQL4mUUUhCow==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { @@ -6614,6 +6614,74 @@ "base-x": "^5.0.0" } }, + "node_modules/@trezor/connect/node_modules/@stellar/stellar-sdk": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-14.2.0.tgz", + "integrity": "sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@stellar/stellar-base": "^14.0.1", + "axios": "^1.12.2", + "bignumber.js": "^9.3.1", + "eventsource": "^2.0.2", + "feaxios": "^0.0.23", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@trezor/connect/node_modules/@trezor/blockchain-link-types": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-types/-/blockchain-link-types-1.5.1.tgz", + "integrity": "sha512-Idavz6LwLBW8sXc69fh5AJEnl666EDl2Nt3io7updvBgOR0/P12I900DgjNhCKtiWuv66A33/5RE7zLcj3lfnw==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect/node_modules/@trezor/blockchain-link-utils": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-utils/-/blockchain-link-utils-1.5.2.tgz", + "integrity": "sha512-OSS5OEE98FMnYfjoEALPjBt7ebjC/FKnq3HOolHdEWXBpVlXZNN2+Vo1R9J6WbZUU087sHuUTJJy/GJYWY13Tg==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@mobily/ts-belt": "^3.13.1", + "@stellar/stellar-sdk": "14.2.0", + "@trezor/env-utils": "1.5.0", + "@trezor/protobuf": "1.5.2", + "@trezor/utils": "9.5.0", + "xrpl": "4.4.3" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect/node_modules/@trezor/protobuf": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.2.tgz", + "integrity": "sha512-zViaL1jKue8DUTVEDg0C/lMipqNMd/Z3kr29/+MeZOoupjaXIQ2Lqp3WAMe8hvNTKKX8aNQH9JrbapJ6w9FMXw==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/schema-utils": "1.4.0", + "long": "5.2.5", + "protobufjs": "7.4.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, "node_modules/@trezor/connect/node_modules/base-x": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", @@ -6642,15 +6710,15 @@ } }, "node_modules/@trezor/device-authenticity": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@trezor/device-authenticity/-/device-authenticity-1.1.1.tgz", - "integrity": "sha512-WlYbQgc5l0pWUVP9GkMp+Oj3rVAqMKsWF0HyxujoymNjEB7rLTl2hXs+GFjlz7VnldaSslECc6EBex/eQiNOnA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@trezor/device-authenticity/-/device-authenticity-1.1.2.tgz", + "integrity": "sha512-313uSXYR4XKDv3CjtCpgHA+yEe9xxqN7EFl/D68FEn70SPsuWI0+2zUvjPPh6TIOh/EcLv7hCO/QTHUAGd7ZWQ==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { "@noble/curves": "^2.0.1", "@trezor/crypto-utils": "1.2.0", - "@trezor/protobuf": "1.5.1", + "@trezor/protobuf": "1.5.2", "@trezor/schema-utils": "1.4.0", "@trezor/utils": "9.5.0" } @@ -6684,6 +6752,21 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@trezor/device-authenticity/node_modules/@trezor/protobuf": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.2.tgz", + "integrity": "sha512-zViaL1jKue8DUTVEDg0C/lMipqNMd/Z3kr29/+MeZOoupjaXIQ2Lqp3WAMe8hvNTKKX8aNQH9JrbapJ6w9FMXw==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/schema-utils": "1.4.0", + "long": "5.2.5", + "protobufjs": "7.4.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, "node_modules/@trezor/device-utils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-1.2.0.tgz", @@ -6758,13 +6841,13 @@ } }, "node_modules/@trezor/transport": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.6.1.tgz", - "integrity": "sha512-RQNQingZ1TOVKSJu3Av9bmQovsu9n1NkcAYJ64+ZfapORfl/AzmZizRflhxU3FlIujQJK1gbIaW79+L54g7a8w==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.6.2.tgz", + "integrity": "sha512-w0HlD1fU+qTGO3tefBGHF/YS/ts/TWFja9FGIJ4+7+Z9NphvIG06HGvy2HzcD9AhJy9pvDeIsyoM2TTZTiyjkQ==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@trezor/protobuf": "1.5.1", + "@trezor/protobuf": "1.5.2", "@trezor/protocol": "1.3.0", "@trezor/type-utils": "1.2.0", "@trezor/utils": "9.5.0", @@ -6775,6 +6858,21 @@ "tslib": "^2.6.2" } }, + "node_modules/@trezor/transport/node_modules/@trezor/protobuf": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.2.tgz", + "integrity": "sha512-zViaL1jKue8DUTVEDg0C/lMipqNMd/Z3kr29/+MeZOoupjaXIQ2Lqp3WAMe8hvNTKKX8aNQH9JrbapJ6w9FMXw==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/schema-utils": "1.4.0", + "long": "5.2.5", + "protobufjs": "7.4.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, "node_modules/@trezor/type-utils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-1.2.0.tgz", @@ -7588,37 +7686,24 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -15813,9 +15898,9 @@ "license": "MIT" }, "node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": {