From 6b4cdd87f61cd6769182b49421f67141220d4803 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sun, 18 Jan 2026 15:53:21 +0100 Subject: [PATCH 01/10] refactor: code structure for improved readability and maintainability --- app/(landing)/me/layout.tsx | 14 - app/dashboard/data.json | 614 ++++++++++++++ app/dashboard/page copy.tsx | 225 +++++ app/dashboard/page.tsx | 227 +---- app/globals.css | 4 +- app/me/layout.tsx | 44 + app/{(landing) => }/me/loading.tsx | 0 app/{(landing) => }/me/page.tsx | 11 +- app/{(landing) => }/me/profile-data.tsx | 1 - .../me/settings/SettingsContent.tsx | 0 app/{(landing) => }/me/settings/page.tsx | 0 components/app-sidebar.tsx | 149 ++++ components/chart-area-interactive.tsx | 288 +++++++ components/dashboard-content.tsx | 53 ++ components/data-table.tsx | 802 ++++++++++++++++++ components/nav-documents.tsx | 92 ++ components/nav-main.tsx | 85 ++ components/nav-secondary.tsx | 42 + components/nav-user.tsx | 122 +++ components/section-cards.tsx | 102 +++ components/site-header.tsx | 21 + lib/api/auth.ts | 6 +- lib/utils/hackathon-escrow.ts | 2 +- package-lock.json | 110 ++- package.json | 31 +- public/{pattern.ssvg => pattern.svg} | 0 26 files changed, 2767 insertions(+), 278 deletions(-) delete mode 100644 app/(landing)/me/layout.tsx create mode 100644 app/dashboard/data.json create mode 100644 app/dashboard/page copy.tsx create mode 100644 app/me/layout.tsx rename app/{(landing) => }/me/loading.tsx (100%) rename app/{(landing) => }/me/page.tsx (55%) rename app/{(landing) => }/me/profile-data.tsx (98%) rename app/{(landing) => }/me/settings/SettingsContent.tsx (100%) rename app/{(landing) => }/me/settings/page.tsx (100%) create mode 100644 components/app-sidebar.tsx create mode 100644 components/chart-area-interactive.tsx create mode 100644 components/dashboard-content.tsx create mode 100644 components/data-table.tsx create mode 100644 components/nav-documents.tsx create mode 100644 components/nav-main.tsx create mode 100644 components/nav-secondary.tsx create mode 100644 components/nav-user.tsx create mode 100644 components/section-cards.tsx create mode 100644 components/site-header.tsx rename public/{pattern.ssvg => pattern.svg} (100%) diff --git a/app/(landing)/me/layout.tsx b/app/(landing)/me/layout.tsx deleted file mode 100644 index 38a6e1452..000000000 --- a/app/(landing)/me/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'My Profile | Boundless', - description: 'View and manage your profile on Boundless', -}; - -export default function MeLayout({ children }: { children: React.ReactNode }) { - return ( -
- {children} -
- ); -} diff --git a/app/dashboard/data.json b/app/dashboard/data.json new file mode 100644 index 000000000..ec0873641 --- /dev/null +++ b/app/dashboard/data.json @@ -0,0 +1,614 @@ +[ + { + "id": 1, + "header": "Cover page", + "type": "Cover page", + "status": "In Process", + "target": "18", + "limit": "5", + "reviewer": "Eddie Lake" + }, + { + "id": 2, + "header": "Table of contents", + "type": "Table of contents", + "status": "Done", + "target": "29", + "limit": "24", + "reviewer": "Eddie Lake" + }, + { + "id": 3, + "header": "Executive summary", + "type": "Narrative", + "status": "Done", + "target": "10", + "limit": "13", + "reviewer": "Eddie Lake" + }, + { + "id": 4, + "header": "Technical approach", + "type": "Narrative", + "status": "Done", + "target": "27", + "limit": "23", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 5, + "header": "Design", + "type": "Narrative", + "status": "In Process", + "target": "2", + "limit": "16", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 6, + "header": "Capabilities", + "type": "Narrative", + "status": "In Process", + "target": "20", + "limit": "8", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 7, + "header": "Integration with existing systems", + "type": "Narrative", + "status": "In Process", + "target": "19", + "limit": "21", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 8, + "header": "Innovation and Advantages", + "type": "Narrative", + "status": "Done", + "target": "25", + "limit": "26", + "reviewer": "Assign reviewer" + }, + { + "id": 9, + "header": "Overview of EMR's Innovative Solutions", + "type": "Technical content", + "status": "Done", + "target": "7", + "limit": "23", + "reviewer": "Assign reviewer" + }, + { + "id": 10, + "header": "Advanced Algorithms and Machine Learning", + "type": "Narrative", + "status": "Done", + "target": "30", + "limit": "28", + "reviewer": "Assign reviewer" + }, + { + "id": 11, + "header": "Adaptive Communication Protocols", + "type": "Narrative", + "status": "Done", + "target": "9", + "limit": "31", + "reviewer": "Assign reviewer" + }, + { + "id": 12, + "header": "Advantages Over Current Technologies", + "type": "Narrative", + "status": "Done", + "target": "12", + "limit": "0", + "reviewer": "Assign reviewer" + }, + { + "id": 13, + "header": "Past Performance", + "type": "Narrative", + "status": "Done", + "target": "22", + "limit": "33", + "reviewer": "Assign reviewer" + }, + { + "id": 14, + "header": "Customer Feedback and Satisfaction Levels", + "type": "Narrative", + "status": "Done", + "target": "15", + "limit": "34", + "reviewer": "Assign reviewer" + }, + { + "id": 15, + "header": "Implementation Challenges and Solutions", + "type": "Narrative", + "status": "Done", + "target": "3", + "limit": "35", + "reviewer": "Assign reviewer" + }, + { + "id": 16, + "header": "Security Measures and Data Protection Policies", + "type": "Narrative", + "status": "In Process", + "target": "6", + "limit": "36", + "reviewer": "Assign reviewer" + }, + { + "id": 17, + "header": "Scalability and Future Proofing", + "type": "Narrative", + "status": "Done", + "target": "4", + "limit": "37", + "reviewer": "Assign reviewer" + }, + { + "id": 18, + "header": "Cost-Benefit Analysis", + "type": "Plain language", + "status": "Done", + "target": "14", + "limit": "38", + "reviewer": "Assign reviewer" + }, + { + "id": 19, + "header": "User Training and Onboarding Experience", + "type": "Narrative", + "status": "Done", + "target": "17", + "limit": "39", + "reviewer": "Assign reviewer" + }, + { + "id": 20, + "header": "Future Development Roadmap", + "type": "Narrative", + "status": "Done", + "target": "11", + "limit": "40", + "reviewer": "Assign reviewer" + }, + { + "id": 21, + "header": "System Architecture Overview", + "type": "Technical content", + "status": "In Process", + "target": "24", + "limit": "18", + "reviewer": "Maya Johnson" + }, + { + "id": 22, + "header": "Risk Management Plan", + "type": "Narrative", + "status": "Done", + "target": "15", + "limit": "22", + "reviewer": "Carlos Rodriguez" + }, + { + "id": 23, + "header": "Compliance Documentation", + "type": "Legal", + "status": "In Process", + "target": "31", + "limit": "27", + "reviewer": "Sarah Chen" + }, + { + "id": 24, + "header": "API Documentation", + "type": "Technical content", + "status": "Done", + "target": "8", + "limit": "12", + "reviewer": "Raj Patel" + }, + { + "id": 25, + "header": "User Interface Mockups", + "type": "Visual", + "status": "In Process", + "target": "19", + "limit": "25", + "reviewer": "Leila Ahmadi" + }, + { + "id": 26, + "header": "Database Schema", + "type": "Technical content", + "status": "Done", + "target": "22", + "limit": "20", + "reviewer": "Thomas Wilson" + }, + { + "id": 27, + "header": "Testing Methodology", + "type": "Technical content", + "status": "In Process", + "target": "17", + "limit": "14", + "reviewer": "Assign reviewer" + }, + { + "id": 28, + "header": "Deployment Strategy", + "type": "Narrative", + "status": "Done", + "target": "26", + "limit": "30", + "reviewer": "Eddie Lake" + }, + { + "id": 29, + "header": "Budget Breakdown", + "type": "Financial", + "status": "In Process", + "target": "13", + "limit": "16", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 30, + "header": "Market Analysis", + "type": "Research", + "status": "Done", + "target": "29", + "limit": "32", + "reviewer": "Sophia Martinez" + }, + { + "id": 31, + "header": "Competitor Comparison", + "type": "Research", + "status": "In Process", + "target": "21", + "limit": "19", + "reviewer": "Assign reviewer" + }, + { + "id": 32, + "header": "Maintenance Plan", + "type": "Technical content", + "status": "Done", + "target": "16", + "limit": "23", + "reviewer": "Alex Thompson" + }, + { + "id": 33, + "header": "User Personas", + "type": "Research", + "status": "In Process", + "target": "27", + "limit": "24", + "reviewer": "Nina Patel" + }, + { + "id": 34, + "header": "Accessibility Compliance", + "type": "Legal", + "status": "Done", + "target": "18", + "limit": "21", + "reviewer": "Assign reviewer" + }, + { + "id": 35, + "header": "Performance Metrics", + "type": "Technical content", + "status": "In Process", + "target": "23", + "limit": "26", + "reviewer": "David Kim" + }, + { + "id": 36, + "header": "Disaster Recovery Plan", + "type": "Technical content", + "status": "Done", + "target": "14", + "limit": "17", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 37, + "header": "Third-party Integrations", + "type": "Technical content", + "status": "In Process", + "target": "25", + "limit": "28", + "reviewer": "Eddie Lake" + }, + { + "id": 38, + "header": "User Feedback Summary", + "type": "Research", + "status": "Done", + "target": "20", + "limit": "15", + "reviewer": "Assign reviewer" + }, + { + "id": 39, + "header": "Localization Strategy", + "type": "Narrative", + "status": "In Process", + "target": "12", + "limit": "19", + "reviewer": "Maria Garcia" + }, + { + "id": 40, + "header": "Mobile Compatibility", + "type": "Technical content", + "status": "Done", + "target": "28", + "limit": "31", + "reviewer": "James Wilson" + }, + { + "id": 41, + "header": "Data Migration Plan", + "type": "Technical content", + "status": "In Process", + "target": "19", + "limit": "22", + "reviewer": "Assign reviewer" + }, + { + "id": 42, + "header": "Quality Assurance Protocols", + "type": "Technical content", + "status": "Done", + "target": "30", + "limit": "33", + "reviewer": "Priya Singh" + }, + { + "id": 43, + "header": "Stakeholder Analysis", + "type": "Research", + "status": "In Process", + "target": "11", + "limit": "14", + "reviewer": "Eddie Lake" + }, + { + "id": 44, + "header": "Environmental Impact Assessment", + "type": "Research", + "status": "Done", + "target": "24", + "limit": "27", + "reviewer": "Assign reviewer" + }, + { + "id": 45, + "header": "Intellectual Property Rights", + "type": "Legal", + "status": "In Process", + "target": "17", + "limit": "20", + "reviewer": "Sarah Johnson" + }, + { + "id": 46, + "header": "Customer Support Framework", + "type": "Narrative", + "status": "Done", + "target": "22", + "limit": "25", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 47, + "header": "Version Control Strategy", + "type": "Technical content", + "status": "In Process", + "target": "15", + "limit": "18", + "reviewer": "Assign reviewer" + }, + { + "id": 48, + "header": "Continuous Integration Pipeline", + "type": "Technical content", + "status": "Done", + "target": "26", + "limit": "29", + "reviewer": "Michael Chen" + }, + { + "id": 49, + "header": "Regulatory Compliance", + "type": "Legal", + "status": "In Process", + "target": "13", + "limit": "16", + "reviewer": "Assign reviewer" + }, + { + "id": 50, + "header": "User Authentication System", + "type": "Technical content", + "status": "Done", + "target": "28", + "limit": "31", + "reviewer": "Eddie Lake" + }, + { + "id": 51, + "header": "Data Analytics Framework", + "type": "Technical content", + "status": "In Process", + "target": "21", + "limit": "24", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 52, + "header": "Cloud Infrastructure", + "type": "Technical content", + "status": "Done", + "target": "16", + "limit": "19", + "reviewer": "Assign reviewer" + }, + { + "id": 53, + "header": "Network Security Measures", + "type": "Technical content", + "status": "In Process", + "target": "29", + "limit": "32", + "reviewer": "Lisa Wong" + }, + { + "id": 54, + "header": "Project Timeline", + "type": "Planning", + "status": "Done", + "target": "14", + "limit": "17", + "reviewer": "Eddie Lake" + }, + { + "id": 55, + "header": "Resource Allocation", + "type": "Planning", + "status": "In Process", + "target": "27", + "limit": "30", + "reviewer": "Assign reviewer" + }, + { + "id": 56, + "header": "Team Structure and Roles", + "type": "Planning", + "status": "Done", + "target": "20", + "limit": "23", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 57, + "header": "Communication Protocols", + "type": "Planning", + "status": "In Process", + "target": "15", + "limit": "18", + "reviewer": "Assign reviewer" + }, + { + "id": 58, + "header": "Success Metrics", + "type": "Planning", + "status": "Done", + "target": "30", + "limit": "33", + "reviewer": "Eddie Lake" + }, + { + "id": 59, + "header": "Internationalization Support", + "type": "Technical content", + "status": "In Process", + "target": "23", + "limit": "26", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 60, + "header": "Backup and Recovery Procedures", + "type": "Technical content", + "status": "Done", + "target": "18", + "limit": "21", + "reviewer": "Assign reviewer" + }, + { + "id": 61, + "header": "Monitoring and Alerting System", + "type": "Technical content", + "status": "In Process", + "target": "25", + "limit": "28", + "reviewer": "Daniel Park" + }, + { + "id": 62, + "header": "Code Review Guidelines", + "type": "Technical content", + "status": "Done", + "target": "12", + "limit": "15", + "reviewer": "Eddie Lake" + }, + { + "id": 63, + "header": "Documentation Standards", + "type": "Technical content", + "status": "In Process", + "target": "27", + "limit": "30", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 64, + "header": "Release Management Process", + "type": "Planning", + "status": "Done", + "target": "22", + "limit": "25", + "reviewer": "Assign reviewer" + }, + { + "id": 65, + "header": "Feature Prioritization Matrix", + "type": "Planning", + "status": "In Process", + "target": "19", + "limit": "22", + "reviewer": "Emma Davis" + }, + { + "id": 66, + "header": "Technical Debt Assessment", + "type": "Technical content", + "status": "Done", + "target": "24", + "limit": "27", + "reviewer": "Eddie Lake" + }, + { + "id": 67, + "header": "Capacity Planning", + "type": "Planning", + "status": "In Process", + "target": "21", + "limit": "24", + "reviewer": "Jamik Tashpulatov" + }, + { + "id": 68, + "header": "Service Level Agreements", + "type": "Legal", + "status": "Done", + "target": "26", + "limit": "29", + "reviewer": "Assign reviewer" + } +] diff --git a/app/dashboard/page copy.tsx b/app/dashboard/page copy.tsx new file mode 100644 index 000000000..7546289b3 --- /dev/null +++ b/app/dashboard/page copy.tsx @@ -0,0 +1,225 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + Loader2, + LogOut, + User, + Mail, + Shield, + CheckCircle2, +} from 'lucide-react'; +import { authClient } from '@/lib/auth-client'; +import { useAuthActions } from '@/hooks/use-auth'; + +export default function DashboardPage() { + const { data: session, isPending: sessionPending } = authClient.useSession(); + const router = useRouter(); + const { logout } = useAuthActions(); + + useEffect(() => { + if (!sessionPending && !session?.user) { + router.push('/auth?mode=signin'); + } + }, [session, sessionPending, router]); + + const handleSignOut = async () => { + await logout(); + router.push('/'); + }; + + if (sessionPending) { + return ( +
+
+ + Loading... +
+
+ ); + } + + if (!session?.user) { + return null; + } + + return ( +
+
+ {/* Header */} +
+
+

Dashboard

+

+ Welcome back, {session.user.name || session.user.email} +

+
+ +
+ + {/* Stats Cards Grid */} +
+ {/* Profile Card */} + +
+ + +
+ +
+ Profile +
+
+ +
+ + + + {session.user.name?.charAt(0) || + session.user.email.charAt(0)} + + +
+

+ {session.user.name || 'No name'} +

+

+ {session.user.email} +

+
+
+
+
+ + {/* Account Details Card */} + +
+ + +
+ +
+ Account Details +
+
+ +
+ User ID + + {session.user.id} + +
+
+ Email + + {session.user.email} + +
+
+
+ + {/* Status Card */} + +
+ + +
+ +
+ Status & Verification +
+
+ +
+ Email Status +
+ {session.user.emailVerified ? ( + <> + + + Verified + + + ) : ( + + Unverified + + )} +
+
+
+ Account Status +
+
+ + Active + +
+
+ {(session.user as { lastLoginMethod?: string | null }) + ?.lastLoginMethod && ( +
+ + Last Login Method + + + {(() => { + const method = ( + session.user as { lastLoginMethod?: string | null } + ).lastLoginMethod; + return method === 'google' + ? 'Google' + : method === 'email' + ? 'Email' + : method || 'N/A'; + })()} + +
+ )} +
+
+
+ + {/* Welcome Card */} +
+ +
+
+ + + Welcome to Boundless + + + Your platform for crowdfunding and grants + + + +

+ This is your dashboard where you can manage your projects, view + contributions, and access all the features of the platform. The + authentication system is now working properly! +

+
+
+
+
+
+ ); +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 7546289b3..69a6c55dc 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,225 +1,6 @@ -'use client'; +import { DashboardContent } from '@/components/dashboard-content'; +import React from 'react'; -import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { - Loader2, - LogOut, - User, - Mail, - Shield, - CheckCircle2, -} from 'lucide-react'; -import { authClient } from '@/lib/auth-client'; -import { useAuthActions } from '@/hooks/use-auth'; - -export default function DashboardPage() { - const { data: session, isPending: sessionPending } = authClient.useSession(); - const router = useRouter(); - const { logout } = useAuthActions(); - - useEffect(() => { - if (!sessionPending && !session?.user) { - router.push('/auth?mode=signin'); - } - }, [session, sessionPending, router]); - - const handleSignOut = async () => { - await logout(); - router.push('/'); - }; - - if (sessionPending) { - return ( -
-
- - Loading... -
-
- ); - } - - if (!session?.user) { - return null; - } - - return ( -
-
- {/* Header */} -
-
-

Dashboard

-

- Welcome back, {session.user.name || session.user.email} -

-
- -
- - {/* Stats Cards Grid */} -
- {/* Profile Card */} - -
- - -
- -
- Profile -
-
- -
- - - - {session.user.name?.charAt(0) || - session.user.email.charAt(0)} - - -
-

- {session.user.name || 'No name'} -

-

- {session.user.email} -

-
-
-
-
- - {/* Account Details Card */} - -
- - -
- -
- Account Details -
-
- -
- User ID - - {session.user.id} - -
-
- Email - - {session.user.email} - -
-
-
- - {/* Status Card */} - -
- - -
- -
- Status & Verification -
-
- -
- Email Status -
- {session.user.emailVerified ? ( - <> - - - Verified - - - ) : ( - - Unverified - - )} -
-
-
- Account Status -
-
- - Active - -
-
- {(session.user as { lastLoginMethod?: string | null }) - ?.lastLoginMethod && ( -
- - Last Login Method - - - {(() => { - const method = ( - session.user as { lastLoginMethod?: string | null } - ).lastLoginMethod; - return method === 'google' - ? 'Google' - : method === 'email' - ? 'Email' - : method || 'N/A'; - })()} - -
- )} -
-
-
- - {/* Welcome Card */} -
- -
-
- - - Welcome to Boundless - - - Your platform for crowdfunding and grants - - - -

- This is your dashboard where you can manage your projects, view - contributions, and access all the features of the platform. The - authentication system is now working properly! -

-
-
-
-
-
- ); +export default function Page() { + return ; } diff --git a/app/globals.css b/app/globals.css index 83de59016..adeda57c5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -229,13 +229,13 @@ body { --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); + --sidebar: #030303; --stepper-foreground: oklch(0.2046 0 0); --stepper-border: oklch(0.2264 0 0); --stepper-border-active: oklch(0.3501 0.0381 190.35); --stepper-text-inactive: oklch(0.7731 0 0); --placeholder: oklch(0.6567 0 0); - --sidebar-foreground: oklch(0.145 0 0); + --sidebar-foreground: #fff; --sidebar-primary: oklch(0.205 0 0); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.97 0 0); diff --git a/app/me/layout.tsx b/app/me/layout.tsx new file mode 100644 index 000000000..6142748cb --- /dev/null +++ b/app/me/layout.tsx @@ -0,0 +1,44 @@ +'use client'; + +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 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 = { + name: name || '', + email, + image: profile?.image || userImage, + }; + + return ( + + + + +
{children}
+
+
+ ); +} diff --git a/app/(landing)/me/loading.tsx b/app/me/loading.tsx similarity index 100% rename from app/(landing)/me/loading.tsx rename to app/me/loading.tsx diff --git a/app/(landing)/me/page.tsx b/app/me/page.tsx similarity index 55% rename from app/(landing)/me/page.tsx rename to app/me/page.tsx index 2a6d9c4ae..5266ed9ae 100644 --- a/app/(landing)/me/page.tsx +++ b/app/me/page.tsx @@ -1,5 +1,10 @@ +import { Metadata } from 'next'; import { AuthGuard } from '@/components/auth'; -import { ProfileData } from './profile-data'; + +export const metadata: Metadata = { + title: 'My Profile | Boundless', + description: 'View and manage your profile on Boundless', +}; export default async function MePage() { return ( @@ -7,9 +12,7 @@ export default async function MePage() { redirectTo='/auth?mode=signin' fallback={
Authenticating...
} > -
- -
+
); } diff --git a/app/(landing)/me/profile-data.tsx b/app/me/profile-data.tsx similarity index 98% rename from app/(landing)/me/profile-data.tsx rename to app/me/profile-data.tsx index e11d7eb23..695de8dda 100644 --- a/app/(landing)/me/profile-data.tsx +++ b/app/me/profile-data.tsx @@ -13,7 +13,6 @@ export function ProfileData() { const fetchUserData = async () => { try { const user = await getMe(); - console.trace(user); setUserData(user); } catch { setUserData(null); diff --git a/app/(landing)/me/settings/SettingsContent.tsx b/app/me/settings/SettingsContent.tsx similarity index 100% rename from app/(landing)/me/settings/SettingsContent.tsx rename to app/me/settings/SettingsContent.tsx diff --git a/app/(landing)/me/settings/page.tsx b/app/me/settings/page.tsx similarity index 100% rename from app/(landing)/me/settings/page.tsx rename to app/me/settings/page.tsx diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx new file mode 100644 index 000000000..f41d013e2 --- /dev/null +++ b/components/app-sidebar.tsx @@ -0,0 +1,149 @@ +'use client'; + +import * as React from 'react'; +import { + IconBell, + IconChartBar, + IconDashboard, + IconFileText, + IconFolder, + IconSettings, + IconShieldCheck, + IconUserCircle, + IconUsers, +} from '@tabler/icons-react'; + +import { NavMain } from '@/components/nav-main'; +import { NavUser } from '@/components/nav-user'; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from '@/components/ui/sidebar'; +import Image from 'next/image'; +import Link from 'next/link'; + +const navigationData = { + main: [ + { + title: 'Overview', + url: '/me', + icon: IconDashboard, + }, + { + title: 'Analytics', + url: '/me/analytics', + icon: IconChartBar, + }, + ], + projects: [ + { + title: 'My Projects', + url: '/me/projects', + icon: IconFolder, + badge: '3', + }, + { + title: 'Create Project', + url: '/me/projects/create', + icon: IconFileText, + }, + ], + hackathons: [ + { + title: 'Participating', + url: '/me/hackathons', + icon: IconShieldCheck, + badge: '2', + }, + { + title: 'Submissions', + url: '/me/hackathons/submissions', + icon: IconUsers, + }, + ], + account: [ + { + title: 'Profile', + url: '/me/profile', + icon: IconUserCircle, + }, + { + title: 'Settings', + url: '/me/settings', + icon: IconSettings, + }, + { + title: 'Notifications', + url: '/me/notifications', + icon: IconBell, + badge: '5', + }, + ], +}; +interface userData { + name: string; + email: string; + image: string; +} +export function AppSidebar({ + user, + ...props +}: { user: userData } & React.ComponentProps) { + return ( + +
+
+ Background Pattern +
+
+ + {/* Header with Logo */} + + + + + +
+ Boundless Logo +
+ +
+
+
+
+ + {/* Main Content */} + + + + + + + {/* Footer with User */} + + + +
+ ); +} diff --git a/components/chart-area-interactive.tsx b/components/chart-area-interactive.tsx new file mode 100644 index 000000000..15e330b71 --- /dev/null +++ b/components/chart-area-interactive.tsx @@ -0,0 +1,288 @@ +'use client'; + +import * as React from 'react'; +import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'; + +import { useIsMobile } from '@/hooks/use-mobile'; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from '@/components/ui/chart'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; + +export const description = 'An interactive area chart'; + +const chartData = [ + { date: '2024-04-01', desktop: 222, mobile: 150 }, + { date: '2024-04-02', desktop: 97, mobile: 180 }, + { date: '2024-04-03', desktop: 167, mobile: 120 }, + { date: '2024-04-04', desktop: 242, mobile: 260 }, + { date: '2024-04-05', desktop: 373, mobile: 290 }, + { date: '2024-04-06', desktop: 301, mobile: 340 }, + { date: '2024-04-07', desktop: 245, mobile: 180 }, + { date: '2024-04-08', desktop: 409, mobile: 320 }, + { date: '2024-04-09', desktop: 59, mobile: 110 }, + { date: '2024-04-10', desktop: 261, mobile: 190 }, + { date: '2024-04-11', desktop: 327, mobile: 350 }, + { date: '2024-04-12', desktop: 292, mobile: 210 }, + { date: '2024-04-13', desktop: 342, mobile: 380 }, + { date: '2024-04-14', desktop: 137, mobile: 220 }, + { date: '2024-04-15', desktop: 120, mobile: 170 }, + { date: '2024-04-16', desktop: 138, mobile: 190 }, + { date: '2024-04-17', desktop: 446, mobile: 360 }, + { date: '2024-04-18', desktop: 364, mobile: 410 }, + { date: '2024-04-19', desktop: 243, mobile: 180 }, + { date: '2024-04-20', desktop: 89, mobile: 150 }, + { date: '2024-04-21', desktop: 137, mobile: 200 }, + { date: '2024-04-22', desktop: 224, mobile: 170 }, + { date: '2024-04-23', desktop: 138, mobile: 230 }, + { date: '2024-04-24', desktop: 387, mobile: 290 }, + { date: '2024-04-25', desktop: 215, mobile: 250 }, + { date: '2024-04-26', desktop: 75, mobile: 130 }, + { date: '2024-04-27', desktop: 383, mobile: 420 }, + { date: '2024-04-28', desktop: 122, mobile: 180 }, + { date: '2024-04-29', desktop: 315, mobile: 240 }, + { date: '2024-04-30', desktop: 454, mobile: 380 }, + { date: '2024-05-01', desktop: 165, mobile: 220 }, + { date: '2024-05-02', desktop: 293, mobile: 310 }, + { date: '2024-05-03', desktop: 247, mobile: 190 }, + { date: '2024-05-04', desktop: 385, mobile: 420 }, + { date: '2024-05-05', desktop: 481, mobile: 390 }, + { date: '2024-05-06', desktop: 498, mobile: 520 }, + { date: '2024-05-07', desktop: 388, mobile: 300 }, + { date: '2024-05-08', desktop: 149, mobile: 210 }, + { date: '2024-05-09', desktop: 227, mobile: 180 }, + { date: '2024-05-10', desktop: 293, mobile: 330 }, + { date: '2024-05-11', desktop: 335, mobile: 270 }, + { date: '2024-05-12', desktop: 197, mobile: 240 }, + { date: '2024-05-13', desktop: 197, mobile: 160 }, + { date: '2024-05-14', desktop: 448, mobile: 490 }, + { date: '2024-05-15', desktop: 473, mobile: 380 }, + { date: '2024-05-16', desktop: 338, mobile: 400 }, + { date: '2024-05-17', desktop: 499, mobile: 420 }, + { date: '2024-05-18', desktop: 315, mobile: 350 }, + { date: '2024-05-19', desktop: 235, mobile: 180 }, + { date: '2024-05-20', desktop: 177, mobile: 230 }, + { date: '2024-05-21', desktop: 82, mobile: 140 }, + { date: '2024-05-22', desktop: 81, mobile: 120 }, + { date: '2024-05-23', desktop: 252, mobile: 290 }, + { date: '2024-05-24', desktop: 294, mobile: 220 }, + { date: '2024-05-25', desktop: 201, mobile: 250 }, + { date: '2024-05-26', desktop: 213, mobile: 170 }, + { date: '2024-05-27', desktop: 420, mobile: 460 }, + { date: '2024-05-28', desktop: 233, mobile: 190 }, + { date: '2024-05-29', desktop: 78, mobile: 130 }, + { date: '2024-05-30', desktop: 340, mobile: 280 }, + { date: '2024-05-31', desktop: 178, mobile: 230 }, + { date: '2024-06-01', desktop: 178, mobile: 200 }, + { date: '2024-06-02', desktop: 470, mobile: 410 }, + { date: '2024-06-03', desktop: 103, mobile: 160 }, + { date: '2024-06-04', desktop: 439, mobile: 380 }, + { date: '2024-06-05', desktop: 88, mobile: 140 }, + { date: '2024-06-06', desktop: 294, mobile: 250 }, + { date: '2024-06-07', desktop: 323, mobile: 370 }, + { date: '2024-06-08', desktop: 385, mobile: 320 }, + { date: '2024-06-09', desktop: 438, mobile: 480 }, + { date: '2024-06-10', desktop: 155, mobile: 200 }, + { date: '2024-06-11', desktop: 92, mobile: 150 }, + { date: '2024-06-12', desktop: 492, mobile: 420 }, + { date: '2024-06-13', desktop: 81, mobile: 130 }, + { date: '2024-06-14', desktop: 426, mobile: 380 }, + { date: '2024-06-15', desktop: 307, mobile: 350 }, + { date: '2024-06-16', desktop: 371, mobile: 310 }, + { date: '2024-06-17', desktop: 475, mobile: 520 }, + { date: '2024-06-18', desktop: 107, mobile: 170 }, + { date: '2024-06-19', desktop: 341, mobile: 290 }, + { date: '2024-06-20', desktop: 408, mobile: 450 }, + { date: '2024-06-21', desktop: 169, mobile: 210 }, + { date: '2024-06-22', desktop: 317, mobile: 270 }, + { date: '2024-06-23', desktop: 480, mobile: 530 }, + { date: '2024-06-24', desktop: 132, mobile: 180 }, + { date: '2024-06-25', desktop: 141, mobile: 190 }, + { date: '2024-06-26', desktop: 434, mobile: 380 }, + { date: '2024-06-27', desktop: 448, mobile: 490 }, + { date: '2024-06-28', desktop: 149, mobile: 200 }, + { date: '2024-06-29', desktop: 103, mobile: 160 }, + { date: '2024-06-30', desktop: 446, mobile: 400 }, +]; + +const chartConfig = { + visitors: { + label: 'Visitors', + }, + desktop: { + label: 'Desktop', + color: 'var(--primary)', + }, + mobile: { + label: 'Mobile', + color: 'var(--primary)', + }, +} satisfies ChartConfig; + +export function ChartAreaInteractive() { + const isMobile = useIsMobile(); + const [timeRange, setTimeRange] = React.useState('90d'); + + React.useEffect(() => { + if (isMobile) { + setTimeRange('7d'); + } + }, [isMobile]); + + const filteredData = chartData.filter(item => { + const date = new Date(item.date); + const referenceDate = new Date('2024-06-30'); + let daysToSubtract = 90; + if (timeRange === '30d') { + daysToSubtract = 30; + } else if (timeRange === '7d') { + daysToSubtract = 7; + } + const startDate = new Date(referenceDate); + startDate.setDate(startDate.getDate() - daysToSubtract); + return date >= startDate; + }); + + return ( + + + Total Visitors + + + Total for the last 3 months + + Last 3 months + + + + Last 3 months + Last 30 days + Last 7 days + + + + + + + + + + + + + + + + + + + { + const date = new Date(value); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + }); + }} + /> + { + return new Date(value).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + }); + }} + indicator='dot' + /> + } + /> + + + + + + + ); +} diff --git a/components/dashboard-content.tsx b/components/dashboard-content.tsx new file mode 100644 index 000000000..54a4fcfa3 --- /dev/null +++ b/components/dashboard-content.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { AppSidebar } from '@/components/app-sidebar'; +import { ChartAreaInteractive } from '@/components/chart-area-interactive'; +import { DataTable } from '@/components/data-table'; +import { SectionCards } from '@/components/section-cards'; +import { SiteHeader } from '@/components/site-header'; +import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; +import { useAuthStatus } from '@/hooks/use-auth'; +import data from '../app/dashboard/data.json'; +import React from 'react'; + +export function DashboardContent() { + const { user, isLoading } = useAuthStatus(); + + if (isLoading) { + return
Loading...
; + } + + const { name = '', email = '', profile, image: userImage = '' } = user || {}; + const userData = { + name: name || '', + email, + image: profile?.image || userImage, + }; + + return ( + + + + +
+
+
+ +
+ +
+ +
+
+
+
+
+ ); +} diff --git a/components/data-table.tsx b/components/data-table.tsx new file mode 100644 index 000000000..f52ba0499 --- /dev/null +++ b/components/data-table.tsx @@ -0,0 +1,802 @@ +'use client'; + +import * as React from 'react'; +import { + closestCenter, + DndContext, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + type DragEndEvent, + type UniqueIdentifier, +} from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { + IconChevronDown, + IconChevronLeft, + IconChevronRight, + IconChevronsLeft, + IconChevronsRight, + IconCircleCheckFilled, + IconDotsVertical, + IconGripVertical, + IconLayoutColumns, + IconLoader, + IconPlus, + IconTrendingUp, +} from '@tabler/icons-react'; +import { + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + type ColumnDef, + type ColumnFiltersState, + type Row, + type SortingState, + type VisibilityState, +} from '@tanstack/react-table'; +import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'; +import { toast } from 'sonner'; +import { z } from 'zod'; + +import { useIsMobile } from '@/hooks/use-mobile'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from '@/components/ui/chart'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from '@/components/ui/drawer'; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Separator } from '@/components/ui/separator'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; + +export const schema = z.object({ + id: z.number(), + header: z.string(), + type: z.string(), + status: z.string(), + target: z.string(), + limit: z.string(), + reviewer: z.string(), +}); + +// Create a separate component for the drag handle +function DragHandle({ id }: { id: number }) { + const { attributes, listeners } = useSortable({ + id, + }); + + return ( + + ); +} + +const columns: ColumnDef>[] = [ + { + id: 'drag', + header: () => null, + cell: ({ row }) => , + }, + { + id: 'select', + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label='Select all' + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label='Select row' + /> +
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: 'header', + header: 'Header', + cell: ({ row }) => { + return ; + }, + enableHiding: false, + }, + { + accessorKey: 'type', + header: 'Section Type', + cell: ({ row }) => ( +
+ + {row.original.type} + +
+ ), + }, + { + accessorKey: 'status', + header: 'Status', + cell: ({ row }) => ( + + {row.original.status === 'Done' ? ( + + ) : ( + + )} + {row.original.status} + + ), + }, + { + accessorKey: 'target', + header: () =>
Target
, + cell: ({ row }) => ( +
{ + e.preventDefault(); + toast.promise(new Promise(resolve => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: 'Done', + error: 'Error', + }); + }} + > + + +
+ ), + }, + { + accessorKey: 'limit', + header: () =>
Limit
, + cell: ({ row }) => ( +
{ + e.preventDefault(); + toast.promise(new Promise(resolve => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: 'Done', + error: 'Error', + }); + }} + > + + +
+ ), + }, + { + accessorKey: 'reviewer', + header: 'Reviewer', + cell: ({ row }) => { + const isAssigned = row.original.reviewer !== 'Assign reviewer'; + + if (isAssigned) { + return row.original.reviewer; + } + + return ( + <> + + + + ); + }, + }, + { + id: 'actions', + cell: () => ( + + + + + + Edit + Make a copy + Favorite + + Delete + + + ), + }, +]; + +function DraggableRow({ row }: { row: Row> }) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ + id: row.original.id, + }); + + return ( + + {row.getVisibleCells().map(cell => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); +} + +export function DataTable({ + data: initialData, +}: { + data: z.infer[]; +}) { + const [data, setData] = React.useState(() => initialData); + const [rowSelection, setRowSelection] = React.useState({}); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); + const [sorting, setSorting] = React.useState([]); + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }); + const sortableId = React.useId(); + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ); + + const dataIds = React.useMemo( + () => data?.map(({ id }) => id) || [], + [data] + ); + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + pagination, + }, + getRowId: row => row.id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }); + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event; + if (active && over && active.id !== over.id) { + setData(data => { + const oldIndex = dataIds.indexOf(active.id); + const newIndex = dataIds.indexOf(over.id); + return arrayMove(data, oldIndex, newIndex); + }); + } + } + + return ( + +
+ + + + Outline + + Past Performance 3 + + + Key Personnel 2 + + Focus Documents + +
+ + + + + + {table + .getAllColumns() + .filter( + column => + typeof column.accessorFn !== 'undefined' && + column.getCanHide() + ) + .map(column => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + + +
+
+ +
+ + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + + {table.getRowModel().rows.map(row => ( + + ))} + + ) : ( + + + No results. + + + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{' '} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount()} +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ); +} + +const chartData = [ + { month: 'January', desktop: 186, mobile: 80 }, + { month: 'February', desktop: 305, mobile: 200 }, + { month: 'March', desktop: 237, mobile: 120 }, + { month: 'April', desktop: 73, mobile: 190 }, + { month: 'May', desktop: 209, mobile: 130 }, + { month: 'June', desktop: 214, mobile: 140 }, +]; + +const chartConfig = { + desktop: { + label: 'Desktop', + color: 'var(--primary)', + }, + mobile: { + label: 'Mobile', + color: 'var(--primary)', + }, +} satisfies ChartConfig; + +function TableCellViewer({ item }: { item: z.infer }) { + const isMobile = useIsMobile(); + + return ( + + + + + + + {item.header} + + Showing total visitors for the last 6 months + + +
+ {!isMobile && ( + <> + + + + value.slice(0, 3)} + hide + /> + } + /> + + + + + +
+
+ Trending up by 5.2% this month{' '} + +
+
+ Showing total visitors for the last 6 months. This is just + some random text to test the layout. It spans multiple lines + and should wrap around. +
+
+ + + )} +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + + + + + +
+
+ ); +} diff --git a/components/nav-documents.tsx b/components/nav-documents.tsx new file mode 100644 index 000000000..276cea390 --- /dev/null +++ b/components/nav-documents.tsx @@ -0,0 +1,92 @@ +'use client'; + +import { + IconDots, + IconFolder, + IconShare3, + IconTrash, + type Icon, +} from '@tabler/icons-react'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from '@/components/ui/sidebar'; + +export function NavDocuments({ + items, +}: { + items: { + name: string; + url: string; + icon: Icon; + }[]; +}) { + const { isMobile } = useSidebar(); + + return ( + + Documents + + {items.map(item => ( + + + + + {item.name} + + + + + + + More + + + + + + Open + + + + Share + + + + + Delete + + + + + ))} + + + + More + + + + + ); +} diff --git a/components/nav-main.tsx b/components/nav-main.tsx new file mode 100644 index 000000000..0bec9fcb8 --- /dev/null +++ b/components/nav-main.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { type Icon } from '@tabler/icons-react'; + +import { + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from '@/components/ui/sidebar'; +import Link from 'next/link'; +import { Badge } from './ui/badge'; +import { usePathname } from 'next/navigation'; +import { cn } from '@/lib/utils'; + +export function NavMain({ + items, + label, +}: { + items: { + title: string; + url: string; + icon?: Icon; + badge?: string; + badgeVariant?: 'default' | 'destructive' | 'outline'; + }[]; + label?: string; +}) { + const pathname = usePathname(); + + return ( + + {label && ( + + {label} + + )} + + + {items.map(item => { + const isActive = + pathname === item.url || pathname?.startsWith(`${item.url}/`); + + return ( + + + + {item.icon && ( + + )} + {item.title} + {item.badge && ( + + {item.badge} + + )} + + + + ); + })} + + + + ); +} diff --git a/components/nav-secondary.tsx b/components/nav-secondary.tsx new file mode 100644 index 000000000..319a08b79 --- /dev/null +++ b/components/nav-secondary.tsx @@ -0,0 +1,42 @@ +'use client'; + +import * as React from 'react'; +import { type Icon } from '@tabler/icons-react'; + +import { + SidebarGroup, + SidebarGroupContent, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from '@/components/ui/sidebar'; + +export function NavSecondary({ + items, + ...props +}: { + items: { + title: string; + url: string; + icon: Icon; + }[]; +} & React.ComponentPropsWithoutRef) { + return ( + + + + {items.map(item => ( + + + + + {item.title} + + + + ))} + + + + ); +} diff --git a/components/nav-user.tsx b/components/nav-user.tsx new file mode 100644 index 000000000..9abede733 --- /dev/null +++ b/components/nav-user.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { + IconBell, + IconCreditCard, + IconDotsVertical, + IconLogout, + IconUserCircle, +} from '@tabler/icons-react'; + +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from '@/components/ui/sidebar'; +import { Badge } from './ui/badge'; + +export function NavUser({ + user, +}: { + user: { + name: string; + email: string; + image: string; + }; +}) { + const { isMobile } = useSidebar(); + + return ( + + + + + + + + + {user.name + .split(' ') + .map(n => n[0]) + .join('')} + + +
+ + {user.name} + + + {user.email} + +
+ +
+
+ + +
+ + + + {user.name + .split(' ') + .map(n => n[0]) + .join('')} + + +
+ + {user.name} + + + {user.email} + +
+
+
+ + + + + Account Settings + + + + Billing + + + + Notifications + 3 + + + + + + Log out + +
+
+
+
+ ); +} diff --git a/components/section-cards.tsx b/components/section-cards.tsx new file mode 100644 index 000000000..cd2b59681 --- /dev/null +++ b/components/section-cards.tsx @@ -0,0 +1,102 @@ +import { IconTrendingDown, IconTrendingUp } from '@tabler/icons-react'; + +import { Badge } from '@/components/ui/badge'; +import { + Card, + CardAction, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; + +export function SectionCards() { + return ( +
+ + + Total Revenue + + $1,250.00 + + + + + +12.5% + + + + +
+ Trending up this month +
+
+ Visitors for the last 6 months +
+
+
+ + + New Customers + + 1,234 + + + + + -20% + + + + +
+ Down 20% this period +
+
+ Acquisition needs attention +
+
+
+ + + Active Accounts + + 45,678 + + + + + +12.5% + + + + +
+ Strong user retention +
+
Engagement exceed targets
+
+
+ + + Growth Rate + + 4.5% + + + + + +4.5% + + + + +
+ Steady performance increase +
+
Meets growth projections
+
+
+
+ ); +} diff --git a/components/site-header.tsx b/components/site-header.tsx new file mode 100644 index 000000000..9fe9e1c7e --- /dev/null +++ b/components/site-header.tsx @@ -0,0 +1,21 @@ +import { Separator } from '@/components/ui/separator'; +import { SidebarTrigger } from '@/components/ui/sidebar'; +import { UserMenu } from './user/UserMenu'; + +export function SiteHeader() { + return ( +
+
+ + +

Documents

+
+ +
+
+
+ ); +} diff --git a/lib/api/auth.ts b/lib/api/auth.ts index d16611c0f..b7709cc3b 100644 --- a/lib/api/auth.ts +++ b/lib/api/auth.ts @@ -12,8 +12,10 @@ import { PublicUserProfile } from '@/types/project'; * For server-side usage, use getMeServer() from '@/lib/api/auth-server' instead */ export const getMe = async (): Promise => { - console.trace('getMe called from:'); - + const res = await api.get>('/users/me'); + return res.data.data as User; +}; +export const getMeWithStats = async (): Promise => { const res = await api.get>('/users/me'); return res.data.data as User; }; diff --git a/lib/utils/hackathon-escrow.ts b/lib/utils/hackathon-escrow.ts index f04aa7214..139e49ec2 100644 --- a/lib/utils/hackathon-escrow.ts +++ b/lib/utils/hackathon-escrow.ts @@ -24,7 +24,7 @@ const PLACEHOLDER_MILESTONE_AMOUNT = 1; */ const BOUNDLESS_PLATFORM_ADDRESS = process.env.NEXT_PUBLIC_BOUNDLESS_PLATFORM_ADDRESS || - 'GA4XJQJTKVQZXYW5CYM77CD5E7CX7FJLVDSIWZAV6FEIBFZNRKSWWTFN'; // Fallback to empty string if not configured + 'GB56E64MBDKI43NLRKOGXDENXHEEYSSHALOJ4Q5YDLW3TMTLUCHCIJWC'; // Fallback to empty string if not configured /** * Get Boundless platform wallet address diff --git a/package-lock.json b/package-lock.json index 09f7cd524..e29f19f5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@countrystatecity/countries": "^1.0.4", "@creit.tech/stellar-wallets-kit": "^1.7.6", "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@gsap/react": "^2.1.2", @@ -20,34 +21,36 @@ "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", - "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-context-menu": "^2.2.15", - "@radix-ui/react-dialog": "^1.1.14", - "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-hover-card": "^1.1.14", - "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-menubar": "^1.1.15", "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-scroll-area": "^1.2.9", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slider": "^1.3.5", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.5", - "@radix-ui/react-tabs": "^1.1.12", - "@radix-ui/react-toggle": "^1.1.9", - "@radix-ui/react-toggle-group": "^1.1.10", - "@radix-ui/react-tooltip": "^1.2.7", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", "@react-three/drei": "^10.7.6", "@react-three/fiber": "^9.3.0", "@stellar/freighter-api": "^4.1.0", "@stellar/stellar-sdk": "^13.3.0", + "@tabler/icons-react": "^3.36.1", "@tailwindcss/postcss": "^4", + "@tanstack/react-table": "^8.21.3", "@tiptap/core": "^3.7.2", "@tiptap/extension-blockquote": "^3.7.2", "@tiptap/extension-bold": "^3.7.2", @@ -110,12 +113,12 @@ "recharts": "^2.15.4", "remark-gfm": "^4.0.1", "socket.io-client": "^4.8.1", - "sonner": "^2.0.6", + "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "three": "^0.180.0", "tw-animate-css": "^1.3.6", "vaul": "^1.1.2", - "zod": "^4.0.10", + "zod": "^4.3.5", "zustand": "^5.0.6" }, "devDependencies": { @@ -610,6 +613,20 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, "node_modules/@dnd-kit/sortable": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", @@ -5034,6 +5051,32 @@ "tslib": "^2.8.0" } }, + "node_modules/@tabler/icons": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.1.tgz", + "integrity": "sha512-f4Jg3Fof/Vru5ioix/UO4GX+sdDsF9wQo47FbtvG+utIYYVQ/QVAC0QYgcBbAjQGfbdOh2CCf0BgiFOF9Ixtjw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.36.1.tgz", + "integrity": "sha512-/8nOXeNeMoze9xY/QyEKG65wuvRhkT3q9aytaur6Gj8bYU2A98YVJyLc9MRmc5nVvpy+bRlrrwK/Ykr8WGyUWg==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@tailwindcss/node": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", @@ -5290,6 +5333,39 @@ "tailwindcss": "4.1.17" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tiptap/core": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.12.1.tgz", @@ -19204,9 +19280,9 @@ } }, "node_modules/zod": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 084fd8604..f2751c996 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@countrystatecity/countries": "^1.0.4", "@creit.tech/stellar-wallets-kit": "^1.7.6", "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@gsap/react": "^2.1.2", @@ -35,34 +36,36 @@ "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", - "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.11", "@radix-ui/react-context-menu": "^2.2.15", - "@radix-ui/react-dialog": "^1.1.14", - "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-hover-card": "^1.1.14", - "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-menubar": "^1.1.15", "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-scroll-area": "^1.2.9", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slider": "^1.3.5", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.5", - "@radix-ui/react-tabs": "^1.1.12", - "@radix-ui/react-toggle": "^1.1.9", - "@radix-ui/react-toggle-group": "^1.1.10", - "@radix-ui/react-tooltip": "^1.2.7", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", "@react-three/drei": "^10.7.6", "@react-three/fiber": "^9.3.0", "@stellar/freighter-api": "^4.1.0", "@stellar/stellar-sdk": "^13.3.0", + "@tabler/icons-react": "^3.36.1", "@tailwindcss/postcss": "^4", + "@tanstack/react-table": "^8.21.3", "@tiptap/core": "^3.7.2", "@tiptap/extension-blockquote": "^3.7.2", "@tiptap/extension-bold": "^3.7.2", @@ -125,12 +128,12 @@ "recharts": "^2.15.4", "remark-gfm": "^4.0.1", "socket.io-client": "^4.8.1", - "sonner": "^2.0.6", + "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "three": "^0.180.0", "tw-animate-css": "^1.3.6", "vaul": "^1.1.2", - "zod": "^4.0.10", + "zod": "^4.3.5", "zustand": "^5.0.6" }, "devDependencies": { diff --git a/public/pattern.ssvg b/public/pattern.svg similarity index 100% rename from public/pattern.ssvg rename to public/pattern.svg From d411742375c26688465e1b0db17ee5bdd4cd9a49 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sun, 18 Jan 2026 15:56:45 +0100 Subject: [PATCH 02/10] chore: update dependencies for @trezor packages and related modules --- package-lock.json | 355 ++++++++++++++++++++++++++++++---------------- 1 file changed, 235 insertions(+), 120 deletions(-) diff --git a/package-lock.json b/package-lock.json index e29f19f5e..9b65871c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5814,36 +5814,23 @@ } }, "node_modules/@trezor/analytics": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@trezor/analytics/-/analytics-1.4.3.tgz", - "integrity": "sha512-0o7gp7nfip8yjhAwP3R/Hcy5S8RfmZmYwpCcN0PbydWa5U5VMQ+T/iB/OpbpeV+8j13bf6i7++38nTCUNas0GA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/analytics/-/analytics-1.5.0.tgz", + "integrity": "sha512-evILW5XJEmfPlf0TY1duOLtGJ47pdGeSKVE3P75ODEUsRNxtPVqlkOUBPmYpCxPnzS8XDmkatT8lf9/DF0G6nA==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { - "@trezor/env-utils": "1.4.3", - "@trezor/utils": "9.4.3" - }, - "peerDependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@trezor/analytics/node_modules/@trezor/utils": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.4.3.tgz", - "integrity": "sha512-QkLHpGTF3W3wNGj6OCdcMog7MhAAdlUmpjjmMjMqE0JSoi1Yjr0m7k7xN0iIHSqcgrhYteZDeJZAGlAf/b7gqw==", - "license": "SEE LICENSE IN LICENSE.md", - "peer": true, - "dependencies": { - "bignumber.js": "^9.3.1" + "@trezor/env-utils": "1.5.0", + "@trezor/utils": "9.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/blockchain-link": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@trezor/blockchain-link/-/blockchain-link-2.5.4.tgz", - "integrity": "sha512-3Xki/2Vmr1/rKa5LF+Eb2/Qd5N9LqwyRL76+ycqe1KwqV7xK3XGMsqTH9FUUReRvGxrzAFonbOgADAJczhx81w==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link/-/blockchain-link-2.6.1.tgz", + "integrity": "sha512-SPwxkihOMI0o79BOy0RkfgVL2meuJhIe1yWHCeR8uoqf5KGblUyeXxvNCy6w8ckJ9LRpM1+bZhsUODuNs3083Q==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { @@ -5853,64 +5840,106 @@ "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.3.0", "@solana/rpc-types": "^2.3.0", - "@stellar/stellar-sdk": "^13.3.0", - "@trezor/blockchain-link-types": "1.4.4", - "@trezor/blockchain-link-utils": "1.4.4", - "@trezor/env-utils": "1.4.3", - "@trezor/utils": "9.4.4", - "@trezor/utxo-lib": "2.4.4", - "@trezor/websocket-client": "1.2.4", + "@stellar/stellar-sdk": "14.2.0", + "@trezor/blockchain-link-types": "1.5.0", + "@trezor/blockchain-link-utils": "1.5.1", + "@trezor/env-utils": "1.5.0", + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0", + "@trezor/websocket-client": "1.3.0", "@types/web": "^0.0.197", "crypto-browserify": "3.12.0", "socks-proxy-agent": "8.0.5", "stream-browserify": "^3.0.0", - "xrpl": "^4.4.0" + "xrpl": "4.4.3" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/blockchain-link-types": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-types/-/blockchain-link-types-1.4.4.tgz", - "integrity": "sha512-B38LH4aniZ7gKbpSdMRiA4YriauoYPHgjhKEd+ybR0ca+liNlPAvMwSHJyMhL1eDIYEs16oOjTgT53WjRRZbMg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-types/-/blockchain-link-types-1.5.0.tgz", + "integrity": "sha512-wD6FKKxNr89MTWYL+NikRkBcWXhiWNFR0AuDHW6GHmlCEHhKu/hAvQtcER8X5jt/Wd0hSKNZqtHBXJ1ZkpJ6rg==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { - "@trezor/utils": "9.4.4", - "@trezor/utxo-lib": "2.4.4" + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/blockchain-link-utils": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-utils/-/blockchain-link-utils-1.4.4.tgz", - "integrity": "sha512-8BZD6h5gs3ETPOG2Ri+GOyH44D38YQVeQj8n7oVOVQi21zx93JOpdL3fWS9RytkfmvB84WwVzYoHGlTs3T80Gw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-utils/-/blockchain-link-utils-1.5.1.tgz", + "integrity": "sha512-2tDGLEj5jzydjsJQONGTWVmCDDy6FTZ4ytr1/2gE6anyYEJU8MbaR+liTt3UvcP5jwZTNutwYLvZixRfrb8JpA==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { "@mobily/ts-belt": "^3.13.1", - "@stellar/stellar-sdk": "^13.3.0", - "@trezor/env-utils": "1.4.3", - "@trezor/protobuf": "1.4.4", - "@trezor/utils": "9.4.4", - "xrpl": "^4.4.0" + "@stellar/stellar-sdk": "14.2.0", + "@trezor/env-utils": "1.5.0", + "@trezor/protobuf": "1.5.1", + "@trezor/utils": "9.5.0", + "xrpl": "4.4.3" }, "peerDependencies": { "tslib": "^2.6.2" } }, + "node_modules/@trezor/blockchain-link-utils/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/blockchain-link/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": { - "version": "9.6.4", - "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.6.4.tgz", - "integrity": "sha512-/N3hhOFIIhufvihCx92wvxd15Wy9XAJOSbTiV8rYG2N9uBvzejctNO2+LpwCRl/cBKle9rsp4S7C/zz++iDuOg==", + "version": "9.7.1", + "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.7.1.tgz", + "integrity": "sha512-W2ym0bs4FVmXByEr9gANBp+bRErzNcmqqqYzSJLOVkawxikqYXag2aCpdiXU3LlZbFbhFhIsT/fpDLfwiLRySA==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@ethereumjs/common": "^10.0.0", - "@ethereumjs/tx": "^10.0.0", + "@ethereumjs/common": "^10.1.0", + "@ethereumjs/tx": "^10.1.0", "@fivebinaries/coin-selection": "3.0.0", "@mobily/ts-belt": "^3.13.1", "@noble/hashes": "^1.6.1", @@ -5920,21 +5949,22 @@ "@solana-program/token": "^0.5.1", "@solana-program/token-2022": "^0.4.2", "@solana/kit": "^2.3.0", - "@trezor/blockchain-link": "2.5.4", - "@trezor/blockchain-link-types": "1.4.4", - "@trezor/blockchain-link-utils": "1.4.4", - "@trezor/connect-analytics": "1.3.6", - "@trezor/connect-common": "0.4.4", - "@trezor/crypto-utils": "1.1.5", - "@trezor/device-utils": "1.1.4", - "@trezor/env-utils": "^1.4.3", - "@trezor/protobuf": "1.4.4", - "@trezor/protocol": "1.2.10", - "@trezor/schema-utils": "1.3.4", - "@trezor/transport": "1.5.4", - "@trezor/type-utils": "1.1.9", - "@trezor/utils": "9.4.4", - "@trezor/utxo-lib": "2.4.4", + "@trezor/blockchain-link": "2.6.1", + "@trezor/blockchain-link-types": "1.5.0", + "@trezor/blockchain-link-utils": "1.5.1", + "@trezor/connect-analytics": "1.4.0", + "@trezor/connect-common": "0.5.0", + "@trezor/crypto-utils": "1.2.0", + "@trezor/device-authenticity": "1.1.1", + "@trezor/device-utils": "1.2.0", + "@trezor/env-utils": "^1.5.0", + "@trezor/protobuf": "1.5.1", + "@trezor/protocol": "1.3.0", + "@trezor/schema-utils": "1.4.0", + "@trezor/transport": "1.6.1", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0", "blakejs": "^1.2.1", "bs58": "^6.0.0", "bs58check": "^4.0.0", @@ -5947,28 +5977,28 @@ } }, "node_modules/@trezor/connect-analytics": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@trezor/connect-analytics/-/connect-analytics-1.3.6.tgz", - "integrity": "sha512-Skya46inItcjaahaqpeSsQmB2Xle70f/l+6eTTJYxKQdpMtuW5LRsRRiyMAQTp5RBycL2ngnsVtY+/83Bt5lUw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/connect-analytics/-/connect-analytics-1.4.0.tgz", + "integrity": "sha512-hy2J2oeIhRC/e1bOWXo5dsVMVnDwO2UKnxhR6FD8PINR3jgM6PWAXc6k33WJsBcyiTzwMP7/xPysLcgNJH5o4w==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { - "@trezor/analytics": "1.4.3" + "@trezor/analytics": "1.5.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, "node_modules/@trezor/connect-common": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.4.4.tgz", - "integrity": "sha512-xG2CoPjgcldtO6HU0ZjNCvFdQ4hpl56qzU1VEF1/A1BL2zj2TwLGLmyr4E878go1mmfksNGY5a1tqnzAZ7pRLw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.5.0.tgz", + "integrity": "sha512-WE71iaFcWmfQxDCiTUNynj2DccRgUiLBJ+g3nrqCBJqEYzu+cD6eZ5k/OLtZ3hfh5gyB5EQwXdGvRT07iNdxAA==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@trezor/env-utils": "1.4.3", - "@trezor/type-utils": "1.1.9", - "@trezor/utils": "9.4.4" + "@trezor/env-utils": "1.5.0", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0" }, "peerDependencies": { "tslib": "^2.6.2" @@ -6305,6 +6335,20 @@ "base-x": "^5.0.0" } }, + "node_modules/@trezor/connect/node_modules/@trezor/schema-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-1.4.0.tgz", + "integrity": "sha512-K7upSeh7VDrORaIC4KAxYVW93XNlohmUnH5if/5GKYmTdQSRp1nBkO6Jm+Z4hzIthdnz/1aLgnbeN3bDxWLRxA==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.33.7", + "ts-mixer": "^6.0.3" + }, + "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", @@ -6323,26 +6367,83 @@ } }, "node_modules/@trezor/crypto-utils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@trezor/crypto-utils/-/crypto-utils-1.1.5.tgz", - "integrity": "sha512-Bp3L9MvzYy1OhPcNJIPIPu7kAH1lQyI1ZMuGnIo53nLDcU+t7cWO8z8xpyGW1BAnQ9wn+xaqrycLRf76I0TBtA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/crypto-utils/-/crypto-utils-1.2.0.tgz", + "integrity": "sha512-9i1NrfW1IE6JO910ut7xrx4u5LxE++GETbpJhWLj4P5xpuGDDSDLEn/MXaYisls2DpE897aOrGPaa1qyt8V6tw==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "peerDependencies": { "tslib": "^2.6.2" } }, + "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==", + "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/schema-utils": "1.4.0", + "@trezor/utils": "9.5.0" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@noble/curves": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", + "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "2.0.1" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@trezor/schema-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-1.4.0.tgz", + "integrity": "sha512-K7upSeh7VDrORaIC4KAxYVW93XNlohmUnH5if/5GKYmTdQSRp1nBkO6Jm+Z4hzIthdnz/1aLgnbeN3bDxWLRxA==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.33.7", + "ts-mixer": "^6.0.3" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, "node_modules/@trezor/device-utils": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-1.1.4.tgz", - "integrity": "sha512-hFC0nVnWVFaWx0IfCsoHGvBrh5SKsnTHwrX5pvBotwOw51lzTDMd43CkA7nHRybkhcc2JgX1Qq2UbYdwgEWhPg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-1.2.0.tgz", + "integrity": "sha512-Aqp7pIooFTx21zRUtTI6i1AS4d9Lrx7cclvksh2nJQF9WJvbzuCXshEGkLoOsHwhQrCl3IXfbGuMdA12yDenPA==", "license": "See LICENSE.md in repo root", "peer": true }, "node_modules/@trezor/env-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@trezor/env-utils/-/env-utils-1.4.3.tgz", - "integrity": "sha512-sWC828NRNQi5vc9W4M9rHOJDeI9XlsgnzZaML/lHju7WhlZCmSq5BOntZQvD8d1W0fSwLMLdlcBKBr/gQkvFZQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/env-utils/-/env-utils-1.5.0.tgz", + "integrity": "sha512-u1TN7dMQ5Qhpbae08Z4JJmI9fQrbbJ4yj8eIAsuzMQn6vb+Sg9vbntl+IDsZ1G9WeI73uHTLu1wWMmAgiujH8w==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { @@ -6367,13 +6468,13 @@ } }, "node_modules/@trezor/protobuf": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.4.4.tgz", - "integrity": "sha512-+DwcXkio4qlMkPu6KxnEfhXv5PHTkKh2n6Fo88i5zishUHpYD3NhCS0pouzti8PIPyJE73HQ9MoisG44KQjbtg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.1.tgz", + "integrity": "sha512-nAkaCCAqLpErBd+IuKeG5MpbyLR/2RMgCw18TWc80m1Ws/XgQirhHY9Jbk6gLImTXb9GTrxP0+MDSahzd94rSA==", "license": "See LICENSE.md in repo root", "peer": true, "dependencies": { - "@trezor/schema-utils": "1.3.4", + "@trezor/schema-utils": "1.4.0", "long": "5.2.5", "protobufjs": "7.4.0" }, @@ -6381,10 +6482,24 @@ "tslib": "^2.6.2" } }, + "node_modules/@trezor/protobuf/node_modules/@trezor/schema-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-1.4.0.tgz", + "integrity": "sha512-K7upSeh7VDrORaIC4KAxYVW93XNlohmUnH5if/5GKYmTdQSRp1nBkO6Jm+Z4hzIthdnz/1aLgnbeN3bDxWLRxA==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.33.7", + "ts-mixer": "^6.0.3" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, "node_modules/@trezor/protocol": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-1.2.10.tgz", - "integrity": "sha512-Ek5bHu2s4OAWOaJU5ksd1kcpe/STyLWOtUVTq6Vn4oMT3++qtrjWRQx/aTN/UaTfNoZlKvFXCC/STGlgBv9CKQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-1.3.0.tgz", + "integrity": "sha512-rmrxbDrdgxTouBPbZcSeqU7ba/e5WVT1dxvxxEntHqRdTiDl7d3VK+BErCrlyol8EH5YCqEF3/rXt0crSOfoFw==", "license": "See LICENSE.md in repo root", "peer": true, "peerDependencies": { @@ -6405,16 +6520,16 @@ } }, "node_modules/@trezor/transport": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.5.4.tgz", - "integrity": "sha512-3vGn2IEofbzhKMyLjzmTCwVTE5Wj0gkncLCNc66DU95IEW5WlwNGt/nXSJCg9TMBHK6qtlbY1HOBFuUzEW2Q7w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.6.1.tgz", + "integrity": "sha512-RQNQingZ1TOVKSJu3Av9bmQovsu9n1NkcAYJ64+ZfapORfl/AzmZizRflhxU3FlIujQJK1gbIaW79+L54g7a8w==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@trezor/protobuf": "1.4.4", - "@trezor/protocol": "1.2.10", - "@trezor/type-utils": "1.1.9", - "@trezor/utils": "9.4.4", + "@trezor/protobuf": "1.5.1", + "@trezor/protocol": "1.3.0", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0", "cross-fetch": "^4.0.0", "usb": "^2.15.0" }, @@ -6423,16 +6538,16 @@ } }, "node_modules/@trezor/type-utils": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-1.1.9.tgz", - "integrity": "sha512-/Ug5pmVEpT5OVrf007kEvDj+zOdedHV0QcToUHG/WpVAKH9IsOssOAYIfRr8lDDgT+mDHuArZk/bYa1qvVz8Hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-1.2.0.tgz", + "integrity": "sha512-+E2QntxkyQuYfQQyl8RvT01tq2i5Dp/LFUOXuizF+KVOqsZBjBY43j5hewcCO3+MokD7deDiPyekbUEN5/iVlw==", "license": "See LICENSE.md in repo root", "peer": true }, "node_modules/@trezor/utils": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.4.4.tgz", - "integrity": "sha512-08ciafbBqhApn58q3KkewdLQ3dCA71MsK/BOUfD43EB2GpB420zzky7REilXhOONc3giD0fBbTG3Zdt3HNL0/Q==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.5.0.tgz", + "integrity": "sha512-kdyMyDbxzvOZmwBNvTjAK+C/kzyOz8T4oUbFvq+KaXn5mBFf1uf8rq5X2HkxgdYRPArtHS3PxLKsfkNCdhCYtQ==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { @@ -6443,13 +6558,13 @@ } }, "node_modules/@trezor/utxo-lib": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@trezor/utxo-lib/-/utxo-lib-2.4.4.tgz", - "integrity": "sha512-rccdH3+iqvBL/Nkso/wGCdIXAQY+M/ubLIf/i/hBbcpRH6JoOg8oyaoaHzegsYNE6yHKnykTOZWz5Q4MTG02Bw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@trezor/utxo-lib/-/utxo-lib-2.5.0.tgz", + "integrity": "sha512-Fa2cZh0037oX6AHNLfpFIj65UR/OoX0ZJTocFuQASe77/1PjZHysf6BvvGfmzuFToKfrAQ+DM/1Sx+P/vnyNmA==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@trezor/utils": "9.4.4", + "@trezor/utils": "9.5.0", "bech32": "^2.0.0", "bip66": "^2.0.0", "bitcoin-ops": "^1.4.1", @@ -6489,13 +6604,13 @@ } }, "node_modules/@trezor/websocket-client": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-1.2.4.tgz", - "integrity": "sha512-UgU31gFX8gY0abeI5DjRVnH4RfbXqHcOb019ogkR51KlfjkiWXTvUWKRLLqwslWiUIMEAI3ZFeXQds84b7Uw/Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-1.3.0.tgz", + "integrity": "sha512-9KQSaVc3NtmM6rFFj1e+9bM0C5mVKVidbnxlfzuBJu7G2YMRdIdLPcAXhvmRZjs40uzDuBeApK+p547kODz2ug==", "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "@trezor/utils": "9.4.4", + "@trezor/utils": "9.5.0", "ws": "^8.18.0" }, "peerDependencies": { @@ -11299,9 +11414,9 @@ "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, "node_modules/h3": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", - "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", "license": "MIT", "dependencies": { "cookie-es": "^1.2.2", @@ -11309,9 +11424,9 @@ "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.2", + "node-mock-http": "^1.0.4", "radix3": "^1.1.2", - "ufo": "^1.6.1", + "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, @@ -14751,9 +14866,9 @@ } }, "node_modules/node-mock-http": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", - "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", "license": "MIT" }, "node_modules/node-releases": { @@ -18273,9 +18388,9 @@ "license": "MIT" }, "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT" }, "node_modules/uint8array-tools": { From bcdba5f8e333e253aabd972e5cd6606fcf1e86df Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sun, 18 Jan 2026 15:59:46 +0100 Subject: [PATCH 03/10] feat: remove redundant getMeWithStats function from auth API --- lib/api/auth.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/api/auth.ts b/lib/api/auth.ts index b7709cc3b..799186c0d 100644 --- a/lib/api/auth.ts +++ b/lib/api/auth.ts @@ -15,10 +15,6 @@ export const getMe = async (): Promise => { const res = await api.get>('/users/me'); return res.data.data as User; }; -export const getMeWithStats = async (): Promise => { - const res = await api.get>('/users/me'); - return res.data.data as User; -}; /** * Get user profile by username from backend API From 51cbde650d6c1c381e7b91e55df214a4fdbac204 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Mon, 19 Jan 2026 03:54:27 +0100 Subject: [PATCH 04/10] feat: implement MeDashboard component and integrate user data fetching refactor: update user data structure in auth store and API response style: adjust sidebar inset background color and site header text color --- app/me/layout.tsx | 3 +- app/me/page.tsx | 3 +- components/chart-area-interactive.tsx | 332 ++++++++------------------ components/me-dashboard.tsx | 59 +++++ components/section-cards.tsx | 195 ++++++++------- components/site-header.tsx | 2 +- lib/api/auth.ts | 7 +- lib/api/types.ts | 110 +++++++-- lib/stores/auth-store.ts | 48 ++-- 9 files changed, 387 insertions(+), 372 deletions(-) create mode 100644 components/me-dashboard.tsx diff --git a/app/me/layout.tsx b/app/me/layout.tsx index 6142748cb..865b62341 100644 --- a/app/me/layout.tsx +++ b/app/me/layout.tsx @@ -19,6 +19,7 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { } const { name = '', email = '', profile, image: userImage = '' } = user || {}; + const userData = { name: name || '', email, @@ -35,7 +36,7 @@ export default function MeLayout({ children }: { children: React.ReactNode }) { } > - +
{children}
diff --git a/app/me/page.tsx b/app/me/page.tsx index 5266ed9ae..5a29f8b5c 100644 --- a/app/me/page.tsx +++ b/app/me/page.tsx @@ -1,5 +1,6 @@ import { Metadata } from 'next'; import { AuthGuard } from '@/components/auth'; +import { MeDashboard } from '@/components/me-dashboard'; export const metadata: Metadata = { title: 'My Profile | Boundless', @@ -12,7 +13,7 @@ export default async function MePage() { redirectTo='/auth?mode=signin' fallback={
Authenticating...
} > -
+ ); } diff --git a/components/chart-area-interactive.tsx b/components/chart-area-interactive.tsx index 15e330b71..7a5110b3c 100644 --- a/components/chart-area-interactive.tsx +++ b/components/chart-area-interactive.tsx @@ -1,7 +1,8 @@ 'use client'; import * as React from 'react'; -import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'; +import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'; +import { motion } from 'framer-motion'; import { useIsMobile } from '@/hooks/use-mobile'; import { @@ -27,117 +28,24 @@ import { } from '@/components/ui/select'; import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; -export const description = 'An interactive area chart'; - -const chartData = [ - { date: '2024-04-01', desktop: 222, mobile: 150 }, - { date: '2024-04-02', desktop: 97, mobile: 180 }, - { date: '2024-04-03', desktop: 167, mobile: 120 }, - { date: '2024-04-04', desktop: 242, mobile: 260 }, - { date: '2024-04-05', desktop: 373, mobile: 290 }, - { date: '2024-04-06', desktop: 301, mobile: 340 }, - { date: '2024-04-07', desktop: 245, mobile: 180 }, - { date: '2024-04-08', desktop: 409, mobile: 320 }, - { date: '2024-04-09', desktop: 59, mobile: 110 }, - { date: '2024-04-10', desktop: 261, mobile: 190 }, - { date: '2024-04-11', desktop: 327, mobile: 350 }, - { date: '2024-04-12', desktop: 292, mobile: 210 }, - { date: '2024-04-13', desktop: 342, mobile: 380 }, - { date: '2024-04-14', desktop: 137, mobile: 220 }, - { date: '2024-04-15', desktop: 120, mobile: 170 }, - { date: '2024-04-16', desktop: 138, mobile: 190 }, - { date: '2024-04-17', desktop: 446, mobile: 360 }, - { date: '2024-04-18', desktop: 364, mobile: 410 }, - { date: '2024-04-19', desktop: 243, mobile: 180 }, - { date: '2024-04-20', desktop: 89, mobile: 150 }, - { date: '2024-04-21', desktop: 137, mobile: 200 }, - { date: '2024-04-22', desktop: 224, mobile: 170 }, - { date: '2024-04-23', desktop: 138, mobile: 230 }, - { date: '2024-04-24', desktop: 387, mobile: 290 }, - { date: '2024-04-25', desktop: 215, mobile: 250 }, - { date: '2024-04-26', desktop: 75, mobile: 130 }, - { date: '2024-04-27', desktop: 383, mobile: 420 }, - { date: '2024-04-28', desktop: 122, mobile: 180 }, - { date: '2024-04-29', desktop: 315, mobile: 240 }, - { date: '2024-04-30', desktop: 454, mobile: 380 }, - { date: '2024-05-01', desktop: 165, mobile: 220 }, - { date: '2024-05-02', desktop: 293, mobile: 310 }, - { date: '2024-05-03', desktop: 247, mobile: 190 }, - { date: '2024-05-04', desktop: 385, mobile: 420 }, - { date: '2024-05-05', desktop: 481, mobile: 390 }, - { date: '2024-05-06', desktop: 498, mobile: 520 }, - { date: '2024-05-07', desktop: 388, mobile: 300 }, - { date: '2024-05-08', desktop: 149, mobile: 210 }, - { date: '2024-05-09', desktop: 227, mobile: 180 }, - { date: '2024-05-10', desktop: 293, mobile: 330 }, - { date: '2024-05-11', desktop: 335, mobile: 270 }, - { date: '2024-05-12', desktop: 197, mobile: 240 }, - { date: '2024-05-13', desktop: 197, mobile: 160 }, - { date: '2024-05-14', desktop: 448, mobile: 490 }, - { date: '2024-05-15', desktop: 473, mobile: 380 }, - { date: '2024-05-16', desktop: 338, mobile: 400 }, - { date: '2024-05-17', desktop: 499, mobile: 420 }, - { date: '2024-05-18', desktop: 315, mobile: 350 }, - { date: '2024-05-19', desktop: 235, mobile: 180 }, - { date: '2024-05-20', desktop: 177, mobile: 230 }, - { date: '2024-05-21', desktop: 82, mobile: 140 }, - { date: '2024-05-22', desktop: 81, mobile: 120 }, - { date: '2024-05-23', desktop: 252, mobile: 290 }, - { date: '2024-05-24', desktop: 294, mobile: 220 }, - { date: '2024-05-25', desktop: 201, mobile: 250 }, - { date: '2024-05-26', desktop: 213, mobile: 170 }, - { date: '2024-05-27', desktop: 420, mobile: 460 }, - { date: '2024-05-28', desktop: 233, mobile: 190 }, - { date: '2024-05-29', desktop: 78, mobile: 130 }, - { date: '2024-05-30', desktop: 340, mobile: 280 }, - { date: '2024-05-31', desktop: 178, mobile: 230 }, - { date: '2024-06-01', desktop: 178, mobile: 200 }, - { date: '2024-06-02', desktop: 470, mobile: 410 }, - { date: '2024-06-03', desktop: 103, mobile: 160 }, - { date: '2024-06-04', desktop: 439, mobile: 380 }, - { date: '2024-06-05', desktop: 88, mobile: 140 }, - { date: '2024-06-06', desktop: 294, mobile: 250 }, - { date: '2024-06-07', desktop: 323, mobile: 370 }, - { date: '2024-06-08', desktop: 385, mobile: 320 }, - { date: '2024-06-09', desktop: 438, mobile: 480 }, - { date: '2024-06-10', desktop: 155, mobile: 200 }, - { date: '2024-06-11', desktop: 92, mobile: 150 }, - { date: '2024-06-12', desktop: 492, mobile: 420 }, - { date: '2024-06-13', desktop: 81, mobile: 130 }, - { date: '2024-06-14', desktop: 426, mobile: 380 }, - { date: '2024-06-15', desktop: 307, mobile: 350 }, - { date: '2024-06-16', desktop: 371, mobile: 310 }, - { date: '2024-06-17', desktop: 475, mobile: 520 }, - { date: '2024-06-18', desktop: 107, mobile: 170 }, - { date: '2024-06-19', desktop: 341, mobile: 290 }, - { date: '2024-06-20', desktop: 408, mobile: 450 }, - { date: '2024-06-21', desktop: 169, mobile: 210 }, - { date: '2024-06-22', desktop: 317, mobile: 270 }, - { date: '2024-06-23', desktop: 480, mobile: 530 }, - { date: '2024-06-24', desktop: 132, mobile: 180 }, - { date: '2024-06-25', desktop: 141, mobile: 190 }, - { date: '2024-06-26', desktop: 434, mobile: 380 }, - { date: '2024-06-27', desktop: 448, mobile: 490 }, - { date: '2024-06-28', desktop: 149, mobile: 200 }, - { date: '2024-06-29', desktop: 103, mobile: 160 }, - { date: '2024-06-30', desktop: 446, mobile: 400 }, -]; - const chartConfig = { - visitors: { - label: 'Visitors', - }, - desktop: { - label: 'Desktop', - color: 'var(--primary)', - }, - mobile: { - label: 'Mobile', - color: 'var(--primary)', + projects: { + label: 'Projects Created', + color: '#a7f950', }, } satisfies ChartConfig; -export function ChartAreaInteractive() { +interface ChartAreaInteractiveProps { + chartData?: Array<{ date: string; projects: number }>; + title?: string; + description?: string; +} + +export function ChartAreaInteractive({ + chartData = [], + title = 'Total Projects Created', + description: desc = 'Total for the last 3 months', +}: ChartAreaInteractiveProps = {}) { const isMobile = useIsMobile(); const [timeRange, setTimeRange] = React.useState('90d'); @@ -149,7 +57,7 @@ export function ChartAreaInteractive() { const filteredData = chartData.filter(item => { const date = new Date(item.date); - const referenceDate = new Date('2024-06-30'); + const referenceDate = new Date(); let daysToSubtract = 90; if (timeRange === '30d') { daysToSubtract = 30; @@ -162,127 +70,91 @@ export function ChartAreaInteractive() { }); return ( - - - Total Visitors - - - Total for the last 3 months - - Last 3 months - - - - Last 3 months - Last 30 days - Last 7 days - - - - - - - - - - - - - - - - - - - { - const date = new Date(value); - return date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - }); - }} - /> - { - return new Date(value).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - }); - }} - indicator='dot' - /> - } - /> - - - - - - + Last 3 months + Last 30 days + Last 7 days + + + + + + + + + { + const date = new Date(value); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + }); + }} + /> + { + return new Date(value).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + }} + /> + } + /> + + + + + + ); } diff --git a/components/me-dashboard.tsx b/components/me-dashboard.tsx new file mode 100644 index 000000000..e38b44876 --- /dev/null +++ b/components/me-dashboard.tsx @@ -0,0 +1,59 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { getMe } from '@/lib/api/auth'; +import { GetMeResponse } from '@/lib/api/types'; +import { SectionCards } from '@/components/section-cards'; +import { ChartAreaInteractive } from '@/components/chart-area-interactive'; +import { useAuthStatus } from '@/hooks/use-auth'; + +export function MeDashboard() { + const { user, isLoading } = useAuthStatus(); + const [meData, setMeData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const data = await getMe(); + setMeData(data); + } catch (error) { + console.error('Failed to fetch user data:', error); + } finally { + setLoading(false); + } + }; + + if (user) { + fetchData(); + } + }, [user]); + + if (isLoading || loading) { + return
Loading...
; + } + + if (!meData) { + return
Failed to load data
; + } + + const chartData = meData.chart.map(item => ({ + date: item.date, + projects: item.count, + })); + + return ( +
+
+ +
+ +
+
+
+ ); +} diff --git a/components/section-cards.tsx b/components/section-cards.tsx index cd2b59681..6c2602759 100644 --- a/components/section-cards.tsx +++ b/components/section-cards.tsx @@ -1,102 +1,115 @@ -import { IconTrendingDown, IconTrendingUp } from '@tabler/icons-react'; - -import { Badge } from '@/components/ui/badge'; import { Card, - CardAction, CardDescription, - CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; +import { Package2, Users, Star, Trophy, LucideIcon } from 'lucide-react'; + +interface StatsData { + projectsCreated?: number; + followers?: number; + reputation?: number; + communityScore?: number; +} + +interface CardConfig { + title: string; + value?: number; + icon?: LucideIcon; + description?: string; +} + +interface SectionCardsProps { + stats?: StatsData; + cards?: CardConfig[]; +} + +function formatNumber(num?: number): string { + if (num === undefined || num === null) return '0'; + if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; + if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; + return num.toLocaleString(); +} + +export function SectionCards({ stats, cards }: SectionCardsProps = {}) { + if (cards && cards.length > 0) { + return ( +
+ {cards.map((card, index) => ( + + ))} +
+ ); + } + + const defaultCards: CardConfig[] = [ + { + title: 'Projects Created', + value: stats?.projectsCreated, + icon: Package2, + }, + { + title: 'Followers', + value: stats?.followers, + icon: Users, + }, + { + title: 'Reputation', + value: stats?.reputation, + icon: Star, + }, + { + title: 'Community Score', + value: stats?.communityScore, + icon: Trophy, + }, + ]; -export function SectionCards() { return ( -
- - - Total Revenue - - $1,250.00 - - - - - +12.5% - - - - -
- Trending up this month -
-
- Visitors for the last 6 months -
-
-
- - - New Customers - - 1,234 - - - - - -20% - - - - -
- Down 20% this period -
-
- Acquisition needs attention -
-
-
- - - Active Accounts - - 45,678 - - - - - +12.5% - - - - -
- Strong user retention -
-
Engagement exceed targets
-
-
- - - Growth Rate - - 4.5% - - - - - +4.5% - - - - -
- Steady performance increase -
-
Meets growth projections
-
-
+
+ {defaultCards.map((card, index) => ( + + ))}
); } + +interface DashCardProps { + value?: number; + title: string; + icon?: LucideIcon; + description?: string; +} + +function DashCard({ + value, + title, + icon: Icon = Package2, + description, +}: DashCardProps) { + return ( + + + + {description || title} + {Icon && } + + + {formatNumber(value)} + + + + ); +} diff --git a/components/site-header.tsx b/components/site-header.tsx index 9fe9e1c7e..8c45232ab 100644 --- a/components/site-header.tsx +++ b/components/site-header.tsx @@ -6,7 +6,7 @@ export function SiteHeader() { return (
- + => { - const res = await api.get>('/users/me'); - return res.data.data as User; +export const getMe = async (): Promise => { + const res = await api.get>('/users/me'); + return res.data.data as GetMeResponse; }; /** diff --git a/lib/api/types.ts b/lib/api/types.ts index 616792c8f..38e6bc0e9 100644 --- a/lib/api/types.ts +++ b/lib/api/types.ts @@ -49,13 +49,74 @@ export interface UserProfile { } export interface User { - _id: string; + id: string; + name: string; email: string; - profile: UserProfile; - isVerified: boolean; - roles: string[]; - lastLogin?: string; - [key: string]: unknown; + emailVerified: boolean; + image?: string; + createdAt: string; + updatedAt: string; + lastLoginMethod?: string; + role: string; + banned: boolean; + banReason?: string | null; + banExpires?: string | null; + username: string; + displayUsername: string; + metadata?: any; + twoFactorEnabled: boolean; + members?: Array<{ + id: string; + organizationId: string; + userId: string; + role: string; + createdAt: string; + organization: { + id: string; + name: string; + slug: string; + logo: string; + createdAt: string; + _count: { + hackathons: number; + members: number; + }; + }; + }>; + projects?: Array<{ + id: string; + title: string; + description: string; + category: string; + status: string; + banner?: string | null; + logo?: string | null; + createdAt: string; + }>; + activities?: Array<{ + id: string; + type: string; + userId: string; + projectId?: string | null; + organizationId?: string | null; + metadata?: any; + createdAt: string; + updatedAt: string; + project?: any; + }>; + userBadges?: any[]; + grantApplicationsAsApplicant?: any[]; + hackathonSubmissionsAsParticipant?: Array<{ + id: string; + status: string; + rank?: number | null; + submittedAt: string; + }>; + profile?: any; + stats?: { + followers: number; + following: number; + }; } export interface OrganizationLinks { website: string; @@ -162,29 +223,36 @@ export interface GoogleAuthRequest { export type GoogleAuthResponse = AuthTokens; // GetMe -export type GetMeResponse = User & { - organizations: Organization[]; - projects: CrowdfundingProject[]; - following: User[]; - followers: User[]; +export interface GetMeResponse { + user: User; stats: { - votes: number; - grants: number; - hackathons: number; - donations: number; projectsCreated: number; projectsFunded: number; totalContributed: number; - reputation: number; - communityScore: number; commentsPosted: number; - organizations: number; + votes: number; + grants: number; + hackathons: number; followers: number; following: number; + reputation: number; + communityScore: number; }; - activities: unknown[]; - contributedProjects: unknown[]; -}; + chart: Array<{ date: string; count: number }>; + activitiesGraph: Array<{ date: string; count: number }>; + recentActivities: Array<{ + id: string; + type: string; + userId: string; + projectId?: string | null; + organizationId?: string | null; + metadata?: any; + createdAt: string; + updatedAt: string; + project?: any; + organization?: any; + }>; +} // Logout export interface LogoutResponse { diff --git a/lib/stores/auth-store.ts b/lib/stores/auth-store.ts index e3c9f1ba4..08c092bd8 100644 --- a/lib/stores/auth-store.ts +++ b/lib/stores/auth-store.ts @@ -165,14 +165,14 @@ export const useAuthStore = create()( const user = await getMe(); const transformedUser: User = { - id: user.id as string, - email: user.email as string, - name: user.name as string | null, - image: user.image as string | null, - username: user.username as string | null, - role: user.role as 'USER' | 'ADMIN', - isVerified: user.emailVerified as boolean | undefined, - profile: user.profile as User['profile'], + id: user.user.id as string, + email: user.user.email as string, + name: user.user.name as string | null, + image: user.user.image as string | null, + username: user.user.username as string | null, + role: user.user.role as 'USER' | 'ADMIN', + isVerified: user.user.emailVerified as boolean | undefined, + profile: user.user.profile as User['profile'], } as User; set({ @@ -234,14 +234,14 @@ export const useAuthStore = create()( const user = await getMe(); const transformedUser: User = { - id: user.id as string, - email: user.email as string, - name: user.name as string | null, - image: user.image as string | null, - username: user.username as string | null, - role: user.role as 'USER' | 'ADMIN', - isVerified: user.emailVerified as boolean | undefined, - profile: user.profile as User['profile'], + id: user.user.id as string, + email: user.user.email as string, + name: user.user.name as string | null, + image: user.user.image as string | null, + username: user.user.username as string | null, + role: user.user.role as 'USER' | 'ADMIN', + isVerified: user.user.emailVerified as boolean | undefined, + profile: user.user.profile as User['profile'], } as User; set({ @@ -314,14 +314,14 @@ export const useAuthStore = create()( const user = await getMe(); const transformedUser: User = { - id: user.id as string, - email: user.email as string, - name: user.name as string | null, - image: user.image as string | null, - username: user.username as string | null, - role: user.role as 'USER' | 'ADMIN', - isVerified: user.emailVerified as boolean | undefined, - profile: user.profile as User['profile'], + id: user.user.id as string, + email: user.user.email as string, + name: user.user.name as string | null, + image: user.user.image as string | null, + username: user.user.username as string | null, + role: user.user.role as 'USER' | 'ADMIN', + isVerified: user.user.emailVerified as boolean | undefined, + profile: user.user.profile as User['profile'], } as User; set({ From 3c2c7d6278087c0511b7afaa5d3ce6825171088f Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Tue, 20 Jan 2026 22:47:53 +0100 Subject: [PATCH 05/10] feat: add delete campaign alert component and integrate into dashboard - Implemented DeleteCampaignAlert component for deleting crowdfunding campaigns with confirmation dialog and success/error handling. - Integrated RecentProjects component into MeDashboard to display user's recent projects. - Updated ActivityFeed and ProfileDataClient components to accommodate new user data structure. - Created RecentProjects component to list user's recent crowdfunding projects with sorting and pagination. - Enhanced ProjectsTab to reflect updated user data structure and display projects correctly. - Added smart breadcrumb navigation to SiteHeader for better user experience. - Introduced DataTablePagination for improved table navigation and row selection feedback. - Updated API methods for fetching crowdfunding projects and user data to align with new backend structure. - Refactored user and project types to accommodate changes in API response structure. --- app/globals.css | 61 +-- .../[slug]/components/CampaignBanner.tsx | 27 ++ .../[slug]/components/CampaignTabs.tsx | 74 ++++ .../[slug]/components/FundingProgress.tsx | 76 ++++ .../[slug]/components/ProjectDetails.tsx | 89 +++++ .../[slug]/components/ProjectLinks.tsx | 69 ++++ .../[slug]/components/TagsSection.tsx | 33 ++ .../crowdfunding/[slug]/components/index.ts | 6 + .../edit/components/BasicInfoSection.tsx | 233 +++++++++++ .../components/ContactSocialSection.old.tsx | 165 ++++++++ .../edit/components/ContactSocialSection.tsx | 249 ++++++++++++ .../edit/components/DetailsFundingSection.tsx | 82 ++++ .../edit/components/MilestonesSection.tsx | 362 ++++++++++++++++++ .../edit/components/ProjectLinksSection.tsx | 57 +++ .../edit/components/RepoLinksSection.tsx | 75 ++++ .../[slug]/edit/components/TeamSection.tsx | 243 ++++++++++++ .../[slug]/edit/components/index.ts | 7 + .../edit/components/md-editor-custom.css | 98 +++++ app/me/crowdfunding/[slug]/edit/page.tsx | 314 +++++++++++++++ app/me/crowdfunding/[slug]/page.tsx | 107 ++++++ app/me/crowdfunding/page.tsx | 98 +++++ app/me/profile-data.tsx | 4 +- app/me/projects/page.tsx | 138 +++++++ app/me/settings/SettingsContent.tsx | 5 +- components/app-sidebar.tsx | 10 +- components/crowdfunding-data-table.tsx | 181 +++++++++ components/crowdfunding-table-columns.tsx | 305 +++++++++++++++ components/crowdfunding-table-toolbar.tsx | 77 ++++ .../crowdfunding/campaign-comments-tab.tsx | 209 ++++++++++ .../crowdfunding/campaign-funding-tab.tsx | 186 +++++++++ .../crowdfunding/campaign-milestones-tab.tsx | 148 +++++++ components/crowdfunding/campaign-team-tab.tsx | 65 ++++ components/delete-campaign-alert.tsx | 109 ++++++ components/me-dashboard.tsx | 10 +- components/profile/ActivityFeed.tsx | 9 +- components/profile/ProfileDataClient.tsx | 17 +- components/profile/ProjectsTab.tsx | 12 +- components/profile/update/Profile.tsx | 2 +- components/recent-projects.tsx | 328 ++++++++++++++++ components/section-cards.tsx | 2 +- components/site-header.tsx | 7 +- components/smart-breadcrumb.tsx | 110 ++++++ components/ui/data-table-pagination.tsx | 103 +++++ components/ui/select.tsx | 2 +- hooks/use-markdown.ts | 1 + lib/activities-description.ts | 4 +- lib/api/project.ts | 88 ++++- lib/api/types.ts | 3 + lib/auth/server-auth.ts | 14 +- lib/auth/utils.ts | 15 +- lib/providers/OrganizationProvider.tsx | 4 +- types/project.ts | 1 + types/user.ts | 97 +++-- 53 files changed, 4684 insertions(+), 107 deletions(-) create mode 100644 app/me/crowdfunding/[slug]/components/CampaignBanner.tsx create mode 100644 app/me/crowdfunding/[slug]/components/CampaignTabs.tsx create mode 100644 app/me/crowdfunding/[slug]/components/FundingProgress.tsx create mode 100644 app/me/crowdfunding/[slug]/components/ProjectDetails.tsx create mode 100644 app/me/crowdfunding/[slug]/components/ProjectLinks.tsx create mode 100644 app/me/crowdfunding/[slug]/components/TagsSection.tsx create mode 100644 app/me/crowdfunding/[slug]/components/index.ts create mode 100644 app/me/crowdfunding/[slug]/edit/components/BasicInfoSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/ContactSocialSection.old.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/ContactSocialSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/DetailsFundingSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/MilestonesSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/ProjectLinksSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/RepoLinksSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/TeamSection.tsx create mode 100644 app/me/crowdfunding/[slug]/edit/components/index.ts create mode 100644 app/me/crowdfunding/[slug]/edit/components/md-editor-custom.css create mode 100644 app/me/crowdfunding/[slug]/edit/page.tsx create mode 100644 app/me/crowdfunding/[slug]/page.tsx create mode 100644 app/me/crowdfunding/page.tsx create mode 100644 app/me/projects/page.tsx create mode 100644 components/crowdfunding-data-table.tsx create mode 100644 components/crowdfunding-table-columns.tsx create mode 100644 components/crowdfunding-table-toolbar.tsx create mode 100644 components/crowdfunding/campaign-comments-tab.tsx create mode 100644 components/crowdfunding/campaign-funding-tab.tsx create mode 100644 components/crowdfunding/campaign-milestones-tab.tsx create mode 100644 components/crowdfunding/campaign-team-tab.tsx create mode 100644 components/delete-campaign-alert.tsx create mode 100644 components/recent-projects.tsx create mode 100644 components/smart-breadcrumb.tsx create mode 100644 components/ui/data-table-pagination.tsx diff --git a/app/globals.css b/app/globals.css index adeda57c5..7bddde3b2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -206,42 +206,47 @@ body { :root { --radius: 0.625rem; - --background: #030303; - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.145 0 0); --primary: #a7f950; - --primary-foreground: #020502; - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: #030303; - --stepper-foreground: oklch(0.2046 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); --stepper-border: oklch(0.2264 0 0); --stepper-border-active: oklch(0.3501 0.0381 190.35); --stepper-text-inactive: oklch(0.7731 0 0); --placeholder: oklch(0.6567 0 0); --sidebar-foreground: #fff; - --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); --office-brown: #e4dbdb; --office-brown-darker: #645d5d; --warning-orange: #fbe2b7; diff --git a/app/me/crowdfunding/[slug]/components/CampaignBanner.tsx b/app/me/crowdfunding/[slug]/components/CampaignBanner.tsx new file mode 100644 index 000000000..09e9215b5 --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/CampaignBanner.tsx @@ -0,0 +1,27 @@ +import { Card, CardContent } from '@/components/ui/card'; +import Image from 'next/image'; + +interface CampaignBannerProps { + project: { + banner: string | null; + title: string; + }; +} + +export function CampaignBanner({ project }: CampaignBannerProps) { + if (!project.banner) return null; + + return ( + + + {project.title} + + + ); +} diff --git a/app/me/crowdfunding/[slug]/components/CampaignTabs.tsx b/app/me/crowdfunding/[slug]/components/CampaignTabs.tsx new file mode 100644 index 000000000..b88b38065 --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/CampaignTabs.tsx @@ -0,0 +1,74 @@ +import { Target, DollarSign, Users } from 'lucide-react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { CampaignTeamTab } from '@/components/crowdfunding/campaign-team-tab'; +import { CampaignMilestonesTab } from '@/components/crowdfunding/campaign-milestones-tab'; +import { CampaignCommentsTab } from '@/components/crowdfunding/campaign-comments-tab'; +import { CampaignFundingTab } from '@/components/crowdfunding/campaign-funding-tab'; +import { Crowdfunding } from '@/types/project'; + +interface CampaignTabsProps { + campaign: Crowdfunding; +} + +export function CampaignTabs({ campaign }: CampaignTabsProps) { + return ( + + + + + Team + + + + Milestones + + + + + + Comments + + + + Funding + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/app/me/crowdfunding/[slug]/components/FundingProgress.tsx b/app/me/crowdfunding/[slug]/components/FundingProgress.tsx new file mode 100644 index 000000000..c4e768e83 --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/FundingProgress.tsx @@ -0,0 +1,76 @@ +import { format } from 'date-fns'; +import { Target } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { Separator } from '@/components/ui/separator'; +import { Crowdfunding } from '@/types/project'; + +interface FundingProgressProps { + campaign: Crowdfunding; +} + +export function FundingProgress({ campaign }: FundingProgressProps) { + const fundingProgress = + campaign.fundingGoal > 0 + ? (campaign.fundingRaised / campaign.fundingGoal) * 100 + : 0; + const daysLeft = Math.ceil( + (new Date(campaign.fundingEndDate).getTime() - new Date().getTime()) / + (1000 * 60 * 60 * 24) + ); + + return ( + + + + + Funding Progress + + + +
+
+ ${campaign.fundingRaised.toLocaleString()} +
+
+ of ${campaign.fundingGoal.toLocaleString()} goal +
+
+ + + +
+
+
+ {fundingProgress.toFixed(1)}% +
+
Funded
+
+
+
+ {daysLeft > 0 ? daysLeft : 0} +
+
+ {daysLeft > 0 ? 'Days Left' : 'Ended'} +
+
+
+ + + +
+
+ Currency + {campaign.fundingCurrency} +
+
+ End Date + + {format(new Date(campaign.fundingEndDate), 'MMM dd, yyyy')} + +
+
+
+
+ ); +} diff --git a/app/me/crowdfunding/[slug]/components/ProjectDetails.tsx b/app/me/crowdfunding/[slug]/components/ProjectDetails.tsx new file mode 100644 index 000000000..d7fb55c45 --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/ProjectDetails.tsx @@ -0,0 +1,89 @@ +import { format } from 'date-fns'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { useMarkdown } from '@/hooks/use-markdown'; +import { Crowdfunding } from '@/types/project'; +import Image from 'next/image'; + +interface ProjectDetailsProps { + campaign: Crowdfunding; + project: Crowdfunding['project']; +} + +export function ProjectDetails({ campaign, project }: ProjectDetailsProps) { + const { styledContent } = useMarkdown(project.description || '', { + breaks: true, + gfm: true, + pedantic: true, + loadingDelay: 0, + }); + + const getStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case 'active': + case 'funding': + return 'bg-green-500/20 text-green-400 border-green-500/30'; + case 'completed': + return 'bg-blue-500/20 text-blue-400 border-blue-500/30'; + case 'draft': + return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'; + default: + return 'bg-gray-500/20 text-gray-400 border-gray-500/30'; + } + }; + + return ( + + +
+
+ {project.logo && ( + {project.title} + )} +
+ {project.title} + + Created {format(new Date(campaign.createdAt), 'MMM dd, yyyy')} + +
+
+ + {project.status} + +
+
+ +
+

Description

+
{styledContent}
+
+ + {project.details && ( +
+

Details

+

{project.details}

+
+ )} + + {project.vision && ( +
+

Vision

+

{project.vision}

+
+ )} +
+
+ ); +} diff --git a/app/me/crowdfunding/[slug]/components/ProjectLinks.tsx b/app/me/crowdfunding/[slug]/components/ProjectLinks.tsx new file mode 100644 index 000000000..775279d6b --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/ProjectLinks.tsx @@ -0,0 +1,69 @@ +import { ExternalLink, Github, Globe } from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; + +interface ProjectLinksProps { + project: { + projectWebsite?: string; + githubUrl?: string; + demoVideo?: string; + }; +} + +export function ProjectLinks({ project }: ProjectLinksProps) { + const hasLinks = + project.projectWebsite || project.githubUrl || project.demoVideo; + + if (!hasLinks) return null; + + return ( + + + Project Links + + + {project.projectWebsite && ( + + )} + + {project.githubUrl && ( + + )} + + {project.demoVideo && ( + + )} + + + ); +} diff --git a/app/me/crowdfunding/[slug]/components/TagsSection.tsx b/app/me/crowdfunding/[slug]/components/TagsSection.tsx new file mode 100644 index 000000000..1acd661ac --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/TagsSection.tsx @@ -0,0 +1,33 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; + +interface TagsSectionProps { + project: { + tags?: string[]; + }; +} + +export function TagsSection({ project }: TagsSectionProps) { + if (!project.tags || project.tags.length === 0) return null; + + return ( + + + Tags + + +
+ {project.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+
+ ); +} diff --git a/app/me/crowdfunding/[slug]/components/index.ts b/app/me/crowdfunding/[slug]/components/index.ts new file mode 100644 index 000000000..da09ad809 --- /dev/null +++ b/app/me/crowdfunding/[slug]/components/index.ts @@ -0,0 +1,6 @@ +export { CampaignBanner } from './CampaignBanner'; +export { ProjectDetails } from './ProjectDetails'; +export { CampaignTabs } from './CampaignTabs'; +export { FundingProgress } from './FundingProgress'; +export { ProjectLinks } from './ProjectLinks'; +export { TagsSection } from './TagsSection'; diff --git a/app/me/crowdfunding/[slug]/edit/components/BasicInfoSection.tsx b/app/me/crowdfunding/[slug]/edit/components/BasicInfoSection.tsx new file mode 100644 index 000000000..0221136f9 --- /dev/null +++ b/app/me/crowdfunding/[slug]/edit/components/BasicInfoSection.tsx @@ -0,0 +1,233 @@ +'use client'; + +import React, { useState } from 'react'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Textarea } from '@/components/ui/textarea'; +import { Button } from '@/components/ui/button'; +import { Upload, X } from 'lucide-react'; +import Image from 'next/image'; +import { uploadService } from '@/lib/api/upload'; +import { cn } from '@/lib/utils'; + +interface BasicInfoSectionProps { + data: { + title: string; + logo: string; + vision: string; + category: string; + }; + onChange: (field: string, value: any) => void; +} + +const categories = [ + 'DeFi & Finance', + 'Gaming & Metaverse', + 'Social & Community', + 'Infrastructure & Tooling', + 'AI & Machine Learning', + 'Sustainability & Impact', + 'Other', +]; + +export function BasicInfoSection({ data, onChange }: BasicInfoSectionProps) { + const [uploading, setUploading] = useState(false); + const [isDragOver, setIsDragOver] = useState(false); + + const handleLogoUpload = async ( + event: React.ChangeEvent + ) => { + const file = event.target.files?.[0]; + if (!file) return; + + setUploading(true); + try { + const result = await uploadService.uploadSingle(file); + onChange('logo', result.data.url); + } catch (error) { + console.error('Upload failed:', error); + } finally { + setUploading(false); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(true); + }; + + const handleDragLeave = () => { + setIsDragOver(false); + }; + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + const file = e.dataTransfer.files?.[0]; + if (!file) return; + + setUploading(true); + try { + const result = await uploadService.uploadSingle(file); + onChange('logo', result.data.url); + } catch (error) { + console.error('Upload failed:', error); + } finally { + setUploading(false); + } + }; + + const removeLogo = () => { + onChange('logo', ''); + }; + + return ( +
+ {/* Two Column Grid Layout */} +
+ {/* Left Column - Title and Logo */} +
+ {/* Title */} +
+ + onChange('title', e.target.value)} + placeholder='Enter project name/title' + className='bg-card text-foreground placeholder:text-muted-foreground' + /> +
+ + {/* Logo */} +
+ +
+ {data.logo ? ( +
+
+ Project logo +
+ +
+ ) : null} + +
+
+
+ + {/* Right Column - Vision and Category */} +
+ {/* Vision */} +
+ +