diff --git a/frontend/app/globals.css b/frontend/app/globals.css index dbeecce..1100be2 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -1,30 +1,18 @@ @import url("https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"); -/* Font utility classes */ -.font-nunito { - font-family: "Nunito", sans-serif; -} - - - -.font-poppins { - font-family: "Poppins", sans-serif; -} - -.font-roboto { - font-family: "Roboto", sans-serif; -} @import "tailwindcss"; +/* Theme variables */ :root { --background: #ffffff; --foreground: #171717; } +/* Tailwind theme mapping (keep if you need it) */ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); + --font-sans: "Poppins", sans-serif; --font-mono: var(--font-geist-mono); } @@ -35,112 +23,35 @@ } } +/* ✅ Global styles (ONE body rule) */ body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} - -/* Toast animations */ -@keyframes slide-in-from-right { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -@keyframes slide-out-to-right { - from { - transform: translateX(0); - opacity: 1; - } - to { - transform: translateX(100%); - opacity: 0; - } -} - -.animate-in { - animation: slide-in-from-right 0.3s ease-out; -} - -.slide-in-from-right-full { - animation: slide-in-from-right 0.3s ease-out; -} - -/* Level Complete Animations */ -@keyframes bounce-slow { - 0%, 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-10px); - } + font-family: "Poppins", sans-serif; } -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } +/* Optional helper classes (if you still want them) */ +.font-nunito { + font-family: "Nunito", sans-serif; } -@keyframes slide-in-from-top { - from { - transform: translateY(-20px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } +.font-poppins { + font-family: "Poppins", sans-serif; } -@keyframes slide-in-from-bottom { - from { - transform: translateY(20px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } +.font-roboto { + font-family: "Roboto", sans-serif; } -@keyframes zoom-in { +/* Toast animations… (leave your keyframes below here) */ +@keyframes slide-in-from-right { from { - transform: scale(0.95); + transform: translateX(100%); opacity: 0; } to { - transform: scale(1); + transform: translateX(0); opacity: 1; } } - -.animate-bounce-slow { - animation: bounce-slow 2s ease-in-out infinite; -} - -.animate-fade-in { - animation: fade-in 0.5s ease-out forwards; -} - -.animate-slide-in-from-top { - animation: slide-in-from-top 0.7s ease-out forwards; -} - -.animate-slide-in-from-bottom { - animation: slide-in-from-bottom 0.5s ease-out forwards; -} - -.animate-zoom-in { - animation: zoom-in 0.5s ease-out forwards; -} - +/* ... rest of animations unchanged ... */ diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 7b691b3..e1414d2 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,9 +1,15 @@ -import { Geist, Geist_Mono } from "next/font/google"; +import { Poppins, Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ToastProvider } from "@/components/ui/ToastProvider"; import StoreProvider from "@/providers/storeProvider"; import ClientLayout from "@/components/ClientLayout"; +const poppins = Poppins({ + subsets: ["latin"], + weight: ["300", "400", "500", "600", "700"], + variable: "--font-poppins", +}); + const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], @@ -26,9 +32,7 @@ export default function RootLayout({ > - - - {children} + {children} diff --git a/frontend/app/profile/page.tsx b/frontend/app/profile/page.tsx index 369cb0d..c615c8e 100644 --- a/frontend/app/profile/page.tsx +++ b/frontend/app/profile/page.tsx @@ -1,19 +1,114 @@ -import React from "react"; +"use client"; + +import { LogOut } from "lucide-react"; +import Button from "@/components/Button"; +import { ProfileHeader } from "@/components/profile/ProfileHeader"; +import { ProfileOverview } from "@/components/profile/ProfileOverview"; +import { AchievementsSection } from "@/components/profile/AchievementSection"; + +// Mock data - in a real app, this would come from props or API +const mockUserData = { + avatarUrl: "", + name: "Adeyemi Shola", + handle: "Shola", + joinDate: "Joined August 2025", + following: 5, + followers: 5, + walletId: "Fcwy...Ol5K", +}; + +const mockOverviewData = { + dayStreak: 3, + totalPoints: 830, + rank: 8, + challengeLevel: "Advanced", +}; + +const mockAchievements = [ + { + id: "1", + icon: "brain" as const, + title: "Perfect Lessons", + date: "Aug 23, 2025", + }, + { + id: "2", + icon: "droplet" as const, + title: "Longest Streak", + date: "Aug 23, 2025", + badge: "#3", + }, + { + id: "3", + icon: "star" as const, + title: "Perfect Week", + date: "Aug 23, 2025", + badge: 1, + }, +]; + +export default function ProfilePage() { + const handleLogout = () => { + // UI only - no actual logout logic + console.log("Logout clicked"); + }; + + const handleViewAllAchievements = () => { + // UI only - no actual navigation + console.log("View all achievements clicked"); + }; -const ProfilePage = () => { return ( -
-
-

- Placeholder Route -

-

Profile

-

- This page will contain your achievements, stats, and account settings. -

+
+
+ {/* Page Header */} + + {/* Main Content */} +
+ {/* Left Column - Profile Info & Overview */} +
+ {/* Profile Header Card */} +
+ +
+ + {/* Overview Section */} + +
+ + {/* Right Column - Achievements & Logout */} +
+ {/* Achievements Section */} + + + {/* Logout Button */} + +
+
); -}; - -export default ProfilePage; +} diff --git a/frontend/components/Button.tsx b/frontend/components/Button.tsx index 271e489..8e07bfa 100644 --- a/frontend/components/Button.tsx +++ b/frontend/components/Button.tsx @@ -3,7 +3,7 @@ import React from 'react'; interface ButtonProps extends React.ButtonHTMLAttributes { children: React.ReactNode; - variant?: 'primary' | 'secondary'; + variant?: 'primary' | 'secondary'| 'tertiary'| 'logOut'; } const Button: React.FC = ({ @@ -16,8 +16,8 @@ const Button: React.FC = ({ }) => { const containerBaseClasses = 'rounded-2xl'; const wrapperBaseClasses = - 'flex flex-row items-center justify-center py-3 px-6 rounded-2xl -translate-y-1 transition-transform'; - const textBaseClasses = 'font-bold text-lg text-center'; + 'flex flex-row items-center justify-center cursor-pointer py-3 px-6 rounded-md -translate-y-1 transition-transform'; + const textBaseClasses = ' text-center'; const variantStyles = { primary: { @@ -30,6 +30,16 @@ const Button: React.FC = ({ wrapper: 'bg-transparent border-2 border-blue-600', text: 'text-[#3B82F6]', }, + tertiary: { + container: 'bg-none w-10 h-5', + wrapper: 'border-2 border-[#E6E6E64D] sm:py-2 sm:px-10', + text: 'text-[#3B82F6]', + }, + logOut: { + container: 'bg-none w-10 h-5', + wrapper: 'border-2 border-[#F43F5E4D] sm:py-2 sm:px-10', + text: 'text-[#F43F5E] text-xs', + }, }; const pressEffect = !disabled ? 'active:translate-y-0' : ''; diff --git a/frontend/components/profile/AchievementCard.tsx b/frontend/components/profile/AchievementCard.tsx new file mode 100644 index 0000000..03b1beb --- /dev/null +++ b/frontend/components/profile/AchievementCard.tsx @@ -0,0 +1,36 @@ +import { cn } from "@/lib/utils"; +import type { ReactNode } from "react"; + +interface AchievementCardProps { + icon: ReactNode; + title: string; + date: string; + badge?: string | number; + className?: string; +} + +export function AchievementCard({ + icon, + title, + date, + badge, + className, +}: AchievementCardProps) { + return ( +
+
{icon}
+ {badge && ( + + {badge} + + )} + {title} + {date} +
+ ); +} diff --git a/frontend/components/profile/AchievementSection.tsx b/frontend/components/profile/AchievementSection.tsx new file mode 100644 index 0000000..4011617 --- /dev/null +++ b/frontend/components/profile/AchievementSection.tsx @@ -0,0 +1,71 @@ +"use client"; + +import React from "react"; +import { AchievementCard } from "./AchievementCard"; + +interface Achievement { + id: string; + icon: "brain" | "droplet" | "star"; + title: string; + date: string; + badge?: string | number; +} + +interface AchievementsSectionProps { + achievements: Achievement[]; + onViewAll?: () => void; +} + +type AchievementIcon = Achievement["icon"]; + +const iconSrcMap: Record = { + brain: "/brain.svg", + droplet: "/fire.svg", + star: "/awardTag.svg", +}; + +function AchievementIconImg({ name }: { name: AchievementIcon }) { + return ( + {name} + ); +} + +export function AchievementsSection({ + achievements, + onViewAll, +}: AchievementsSectionProps) { + return ( +
+
+

Achievements

+ + {onViewAll && ( + + )} +
+ +
+ {achievements.map((achievement) => ( + } + title={achievement.title} + date={achievement.date} + badge={achievement.badge} + /> + ))} +
+
+ ); +} diff --git a/frontend/components/profile/ProfileHeader.tsx b/frontend/components/profile/ProfileHeader.tsx new file mode 100644 index 0000000..e554263 --- /dev/null +++ b/frontend/components/profile/ProfileHeader.tsx @@ -0,0 +1,105 @@ +"use client"; + +import Link from "next/link"; +import { Pencil, Share2, Copy, Settings } from "lucide-react"; +import Button from "../Button"; +import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar"; + +interface ProfileHeaderProps { + avatarUrl?: string; + name: string; + handle: string; + joinDate: string; + following: number; + followers: number; + walletId: string; +} + +export function ProfileHeader({ + avatarUrl, + name, + handle, + joinDate, + following, + followers, + walletId, +}: ProfileHeaderProps) { + return ( +
+
+

Profile

+ +
+ +
+ + {/* Avatar with edit badge */} +
+ + + + {name + .split(" ") + .map((n) => n[0]) + .join("")} + + + +
+ + {/* User info */} +
+

{name}

+

@{handle}

+

{joinDate}

+
+ + {/* Following / Followers */} +
+ + {following} Following + + + {followers} Followers + +
+ + {/* Wallet ID */} +
+ + + {walletId} + +
+ + {/* Action buttons */} +
+ + +
+
+
+ ); +} diff --git a/frontend/components/profile/ProfileOverview.tsx b/frontend/components/profile/ProfileOverview.tsx new file mode 100644 index 0000000..6735714 --- /dev/null +++ b/frontend/components/profile/ProfileOverview.tsx @@ -0,0 +1,63 @@ +import { StatCard } from "./StatCard"; + +interface ProfileOverviewProps { + dayStreak: number; + totalPoints: number; + rank: number; + challengeLevel: string; +} + +export function ProfileOverview({ + dayStreak, + totalPoints, + rank, + challengeLevel, +}: ProfileOverviewProps) { + const stats = [ + { + iconSrc: "/fire.svg", + iconAlt: "fire", + value: dayStreak, + label: "Day streak", + }, + { + iconSrc: "/diamond.svg", + iconAlt: "diamond", + value: totalPoints, + label: "Total Points", + }, + { + iconSrc: "/trophy.svg", + iconAlt: "trophy", + value: `#${rank}`, + label: "Rank", + }, + { + iconSrc: "/puzzlePiece.svg", + iconAlt: "puzzle piece", + value: challengeLevel, + label: "Challenge Level", + }, + ] as const; + + return ( +
+

Overview

+ +
+ {stats.map((stat) => ( + + {stat.iconAlt} +
+ } + value={stat.value} + label={stat.label} + /> + ))} +
+ + ); +} diff --git a/frontend/components/profile/StatCard.tsx b/frontend/components/profile/StatCard.tsx new file mode 100644 index 0000000..91c80ee --- /dev/null +++ b/frontend/components/profile/StatCard.tsx @@ -0,0 +1,26 @@ +import { cn } from "@/lib/utils"; +import type { ReactNode } from "react"; + +interface StatCardProps { + icon: ReactNode; + value: string | number; + label: string; + className?: string; +} + +export function StatCard({ icon, value, label, className }: StatCardProps) { + return ( +
+
{icon}
+
+ {value} + {label} +
+
+ ); +} diff --git a/frontend/lib/utils.ts b/frontend/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/frontend/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/package.json b/frontend/package.json index 98369f1..903da67 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,12 +9,18 @@ "lint": "eslint" }, "dependencies": { + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@reduxjs/toolkit": "^2.11.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "lucide-react": "^0.542.0", "next": "^16.1.3", "react": "19.1.0", "react-dom": "19.1.0", - "react-redux": "^9.2.0" + "react-redux": "^9.2.0", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/frontend/public/awardTag.svg b/frontend/public/awardTag.svg new file mode 100644 index 0000000..334615c --- /dev/null +++ b/frontend/public/awardTag.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/brain.svg b/frontend/public/brain.svg new file mode 100644 index 0000000..279ef6a --- /dev/null +++ b/frontend/public/brain.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/diamond.svg b/frontend/public/diamond.svg new file mode 100644 index 0000000..435eaeb --- /dev/null +++ b/frontend/public/diamond.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/fire.svg b/frontend/public/fire.svg new file mode 100644 index 0000000..355f170 --- /dev/null +++ b/frontend/public/fire.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/frontend/public/profileAvatar.svg b/frontend/public/profileAvatar.svg new file mode 100644 index 0000000..23e501d --- /dev/null +++ b/frontend/public/profileAvatar.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/public/puzzlePiece.svg b/frontend/public/puzzlePiece.svg new file mode 100644 index 0000000..4278c43 --- /dev/null +++ b/frontend/public/puzzlePiece.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/trophy.svg b/frontend/public/trophy.svg new file mode 100644 index 0000000..b2b2aef --- /dev/null +++ b/frontend/public/trophy.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..3bab687 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,10 @@ +export default { + theme: { + extend: { + fontFamily: { + poppins: ["var(--font-poppins)", "sans-serif"], + }, + }, + }, + plugins: [], +}; diff --git a/package-lock.json b/package-lock.json index 6ac6197..6973767 100644 --- a/package-lock.json +++ b/package-lock.json @@ -236,6 +236,9 @@ }, "backend/node_modules/@swc/core": { "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", + "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "devOptional": true, "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -684,12 +687,18 @@ "frontend": { "version": "0.1.0", "dependencies": { + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", "@reduxjs/toolkit": "^2.11.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "lucide-react": "^0.542.0", "next": "^16.1.3", "react": "19.1.0", "react-dom": "19.1.0", - "react-redux": "^9.2.0" + "react-redux": "^9.2.0", + "tailwind-merge": "^3.4.0" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -3948,6 +3957,507 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", @@ -4073,7 +4583,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4090,7 +4599,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4107,7 +4615,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4124,7 +4631,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4141,7 +4647,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4158,7 +4663,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4175,7 +4679,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4192,7 +4695,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4209,7 +4711,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4226,7 +4727,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4240,7 +4740,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@swc/helpers": { @@ -7007,6 +7507,18 @@ "validator": "^13.15.20" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -7136,6 +7648,15 @@ "node": ">=0.8" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -14756,7 +15277,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -16025,6 +16545,16 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",