diff --git a/ntc-web/app/analysis/page.tsx b/ntc-web/app/analysis/page.tsx index 2b9b39c..d481180 100644 --- a/ntc-web/app/analysis/page.tsx +++ b/ntc-web/app/analysis/page.tsx @@ -1,10 +1,300 @@ +'use client' + +import { useState } from 'react' +import { Card } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react" + +interface AnalysisItem { + name: string + description: string + state: string + listedOnMarketplace: string +} + export default function Analysis() { - return ( -
-

Analysis

-
- {/* Add analysis content here */} -
-
+ const [search, setSearch] = useState('') + const [stateFilters, setStateFilters] = useState([]) + const [marketplaceFilter, setMarketplaceFilter] = useState([]) + const [sortConfig, setSortConfig] = useState<{ + key: keyof AnalysisItem | null; + direction: 'asc' | 'desc' | null; + }>({ key: null, direction: null }) + + // Sample data + const items: AnalysisItem[] = [ + { + name: "Market Analysis 2024", + description: "Consumer behavior trends Q1", + state: "active", + listedOnMarketplace: "yes" + }, + { + name: "Financial Metrics", + description: "Revenue analysis by region", + state: "completed", + listedOnMarketplace: "no" + }, + { + name: "Customer Demographics", + description: "Age distribution study", + state: "active", + listedOnMarketplace: "yes" + }, + { + name: "Sales Performance", + description: "Monthly sales metrics", + state: "pending", + listedOnMarketplace: "no" + }, + { + name: "Product Analytics", + description: "Usage patterns Q4 2023", + state: "active", + listedOnMarketplace: "yes" + } + ] + + // Handle sort + const handleSort = (key: keyof AnalysisItem) => { + let direction: 'asc' | 'desc' | null = 'asc' + + if (sortConfig.key === key) { + if (sortConfig.direction === 'asc') direction = 'desc' + else if (sortConfig.direction === 'desc') direction = null + } + + setSortConfig({ key, direction }) + } + + // Get sort icon + const getSortIcon = (key: keyof AnalysisItem) => { + if (sortConfig.key !== key) return + if (sortConfig.direction === 'asc') return + if (sortConfig.direction === 'desc') return + return + } + + // Filter and sort items + const filteredAndSortedItems = items + .filter(item => { + const searchTerm = search.toLowerCase() + const matchesSearch = + item.name.toLowerCase().includes(searchTerm) || + item.description.toLowerCase().includes(searchTerm) || + item.state.toLowerCase().includes(searchTerm) || + item.listedOnMarketplace.toLowerCase().includes(searchTerm) + + const matchesState = stateFilters.length === 0 || stateFilters.includes(item.state) + const matchesMarketplace = marketplaceFilter.length === 0 || marketplaceFilter.includes(item.listedOnMarketplace) + + return matchesSearch && matchesState && matchesMarketplace + }) + .sort((a, b) => { + if (!sortConfig.key || !sortConfig.direction) return 0 + + const aValue = a[sortConfig.key] + const bValue = b[sortConfig.key] + + if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1 + if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1 + return 0 + }) + + // Toggle filter functions + const toggleStateFilter = (state: string) => { + setStateFilters(prev => + prev.includes(state) + ? prev.filter(s => s !== state) + : [...prev, state] + ) + } + + const toggleMarketplaceFilter = (value: string) => { + setMarketplaceFilter(prev => + prev.includes(value) + ? prev.filter(v => v !== value) + : [...prev, value] ) + } + + return ( +
+

Analysis

+ + + {/* Search and Filters */} +
+
+ setSearch(e.target.value)} + className="max-w-xs bg-white" + /> + +
+
+ + + +
+ +
+ + +
+
+
+
+ + {/* Table */} +
+ + + + handleSort('name')} + > +
+ Name + {getSortIcon('name')} +
+
+ handleSort('description')} + > +
+ Description + {getSortIcon('description')} +
+
+ handleSort('state')} + > +
+ State + {getSortIcon('state')} +
+
+ handleSort('listedOnMarketplace')} + > +
+ Listed on marketplace + {getSortIcon('listedOnMarketplace')} +
+
+ +
+
+ + {filteredAndSortedItems.map((item, index) => ( + + {item.name} + {item.description} + + + {item.state} + + + + + {item.listedOnMarketplace} + + + +
+ + +
+
+
+ ))} + {filteredAndSortedItems.length === 0 && ( + + + No results found. + + + )} +
+
+
+
+
+ ) } \ No newline at end of file diff --git a/ntc-web/app/globals.css b/ntc-web/app/globals.css index f102207..d9a6c24 100644 --- a/ntc-web/app/globals.css +++ b/ntc-web/app/globals.css @@ -2,11 +2,25 @@ @tailwind components; @tailwind utilities; -body { - font-family: Arial, Helvetica, sans-serif; -} - @layer base { + @font-face { + font-family: "Visby"; + src: url("/fonts/VisbyRoundCF-Regular.woff2"); + font-weight: normal; + } + + @font-face { + font-family: "Visby"; + src: url("/fonts/VisbyRoundCF-Medium.woff2"); + font-weight: 500; + } + + @font-face { + font-family: "Visby"; + src: url("/fonts/VisbyRoundCF-Bold.woff2"); + font-weight: bold; + } + :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; @@ -34,6 +48,7 @@ body { --chart-5: 27 87% 67%; --radius: 0.5rem; } + .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; @@ -60,13 +75,15 @@ body { --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; } -} -@layer base { * { @apply border-border; } + body { + font-family: Visby, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; @apply bg-background text-foreground; } -} +} \ No newline at end of file diff --git a/ntc-web/app/layout.tsx b/ntc-web/app/layout.tsx index cfb4730..0ebced7 100644 --- a/ntc-web/app/layout.tsx +++ b/ntc-web/app/layout.tsx @@ -1,6 +1,13 @@ import { Inter } from 'next/font/google' import './globals.css' import LayoutClient from './LayoutClient' +import { + ClerkProvider, + SignInButton, + SignedIn, + SignedOut, + UserButton +} from '@clerk/nextjs' const inter = Inter({ subsets: ['latin'] }) @@ -10,14 +17,16 @@ export default function RootLayout({ children: React.ReactNode }) { return ( - - -
- - {children} - -
- - + + + +
+ + {children} + +
+ + +
) } \ No newline at end of file diff --git a/ntc-web/app/market/page.tsx b/ntc-web/app/market/page.tsx index 593f1c3..9d88b2b 100644 --- a/ntc-web/app/market/page.tsx +++ b/ntc-web/app/market/page.tsx @@ -1,10 +1,232 @@ +'use client' + +import { useState } from 'react' +import { Card } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react" + +interface DigitalRight { + id: string + name: string + description: string +} + +interface MarketItem { + name: string + description: string + price: string + digitalRights: DigitalRight + seller: string + state: string +} + export default function Market() { - return ( -
-

Market

-
- {/* Add market content here */} + const [search, setSearch] = useState('') + const [sortConfig, setSortConfig] = useState<{ + key: keyof MarketItem | null; + direction: 'asc' | 'desc' | null; + }>({ key: null, direction: null }) + const [loading, setLoading] = useState(false) + + // Available digital rights + const availableRights: DigitalRight[] = [ + { id: '2', name: 'Append data pool', description: 'Permits adding new data entries to existing pools while maintaining schema requirements' }, + { id: '3', name: 'Execute Median WASM', description: 'Enables running Rust WebAssembly-based median calculations on data pools' }, + { id: '4', name: 'Execute Median Python', description: 'Allows execution of Python-based median computations on data pools' } + ] + + // Sample market data (replace with API call) + const items: MarketItem[] = [ + { + name: "Market Analysis 2024", + description: "Consumer behavior trends Q1", + price: "$299", + digitalRights: availableRights[0], + seller: "AnalyticsCorp", + state: "available" + }, + { + name: "Customer Demographics", + description: "Age distribution study", + price: "$199", + digitalRights: availableRights[1], + seller: "DataMetrics", + state: "available" + }, + { + name: "Product Analytics", + description: "Usage patterns Q4 2023", + price: "$399", + digitalRights: availableRights[2], + seller: "InsightPro", + state: "available" + } + ] + + // Handle sort + const handleSort = (key: keyof MarketItem) => { + let direction: 'asc' | 'desc' | null = 'asc' + + if (sortConfig.key === key) { + if (sortConfig.direction === 'asc') direction = 'desc' + else if (sortConfig.direction === 'desc') direction = null + } + + setSortConfig({ key, direction }) + } + + // Get sort icon + const getSortIcon = (key: keyof MarketItem) => { + if (sortConfig.key !== key) return + if (sortConfig.direction === 'asc') return + if (sortConfig.direction === 'desc') return + return + } + + // Filter and sort items + const filteredAndSortedItems = items + .filter(item => { + const searchTerm = search.toLowerCase() + return ( + item.name.toLowerCase().includes(searchTerm) || + item.description.toLowerCase().includes(searchTerm) || + item.seller.toLowerCase().includes(searchTerm) || + item.digitalRights.name.toLowerCase().includes(searchTerm) + ) + }) + .sort((a, b) => { + if (!sortConfig.key || !sortConfig.direction) return 0 + + const aValue = a[sortConfig.key] + const bValue = b[sortConfig.key] + + if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1 + if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1 + return 0 + }) + + return ( +
+

Market

+ + + {/* Search */} +
+
+ setSearch(e.target.value)} + className="max-w-xs bg-white" + /> +
-
- ) + + {/* Table */} +
+ + + + handleSort('name')} + > +
+ Name + {getSortIcon('name')} +
+
+ handleSort('description')} + > +
+ Description + {getSortIcon('description')} +
+
+ handleSort('price')} + > +
+ Price + {getSortIcon('price')} +
+
+ handleSort('digitalRights')} + > +
+ Digital Rights + {getSortIcon('digitalRights')} +
+
+ handleSort('seller')} + > +
+ Seller + {getSortIcon('seller')} +
+
+ +
+
+ + {loading ? ( + + + Loading... + + + ) : ( + filteredAndSortedItems.map((item, index) => ( + + {item.name} + {item.description} + {item.price} + + + {item.digitalRights.name} + + + {item.seller} + + + + + )) + )} + {!loading && filteredAndSortedItems.length === 0 && ( + + + No results found. + + + )} + +
+
+ +
+ ) } \ No newline at end of file diff --git a/ntc-web/app/page.tsx b/ntc-web/app/page.tsx index c929406..9df4c3a 100644 --- a/ntc-web/app/page.tsx +++ b/ntc-web/app/page.tsx @@ -1,8 +1,48 @@ -// app/page.tsx "use client" +import { useState } from 'react' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { Card } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { Input } from "@/components/ui/input" +import { ExternalLink, ChevronDown, ChevronUp, ChevronsUpDown } from 'lucide-react' + +interface StatsItem { + label: string + value: string +} + +interface DigitalRight { + id: string + name: string + description: string +} + +interface Pool { + id: number + name: string + description: string + rights: string[] + contractAddress: string +} + +type SortField = 'name' | 'description' | 'rights' | 'contractAddress'; +type SortDirection = 'asc' | 'desc' | null; + export default function Home() { - const statsItems = [ + const [search, setSearch] = useState('') + const [sortField, setSortField] = useState(null) + const [sortDirection, setSortDirection] = useState(null) + + const statsItems: StatsItem[] = [ { label: "Data Pool", value: "1" @@ -21,52 +61,187 @@ export default function Home() { } ] - const fields = ["Name", "Description", "Digital Rights", ""] + const availableRights: DigitalRight[] = [ + { + id: '2', + name: 'Append data pool', + description: 'Permits adding new data entries to existing pools while maintaining schema requirements' + }, + { + id: '3', + name: 'Execute Median WASM', + description: 'Enables running Rust WebAssembly-based median calculations on data pools' + } + ] + + const ownedPool: Pool = { + id: 3, + name: "Financial Metrics", + description: "Quarterly financial performance data", + rights: ['2', '3'], + contractAddress: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + } + + const handleSort = (field: SortField) => { + if (sortField === field) { + // Cycle through: asc -> desc -> null + if (sortDirection === 'asc') { + setSortDirection('desc') + } else if (sortDirection === 'desc') { + setSortField(null) + setSortDirection(null) + } + } else { + setSortField(field) + setSortDirection('asc') + } + } + + const getSortIcon = (field: SortField) => { + if (sortField !== field) return + if (sortDirection === 'asc') return + if (sortDirection === 'desc') return + return + } + + const getRightById = (rightId: string): DigitalRight | undefined => { + return availableRights.find((right) => right.id === rightId) + } + + const getSolanaExplorerUrl = (address: string): string => { + return `https://explorer.solana.com/address/${address}` + } + + const isPoolVisible = + ownedPool.name.toLowerCase().includes(search.toLowerCase()) || + ownedPool.description.toLowerCase().includes(search.toLowerCase()) || + !search return (
-
-
-
-
    - {statsItems.map((item, index) => ( -
  • - {item.label} - - {item.value} - -
  • - ))} -
-
-
+
+ {statsItems.map((item, index) => ( + +
+ {item.label} + + {item.value} + +
+
+ ))}
- - - - {fields.map((field, index) => ( -
My Pools + +
+ setSearch(e.target.value)} + className="max-w-md bg-white" + /> +
+ + + + handleSort('name')} + > +
+ Pool Name + {getSortIcon('name')} +
+
+ handleSort('description')} + > +
+ Description + {getSortIcon('description')} +
+
+ handleSort('rights')} + > +
+ Digital Rights Tokens (DRT) + {getSortIcon('rights')} +
+
+ handleSort('contractAddress')} > - {field} - - ))} - - -
- - - - -
- No data available -
+
+ Smart Contract + {getSortIcon('contractAddress')} +
+ + + + + {isPoolVisible ? ( + + {ownedPool.name} + {ownedPool.description} + +
+ {ownedPool.rights.map((rightId) => { + const right = getRightById(rightId) + if (!right) return null + return ( + + + + + {right.name} + + + +

{right.description}

+
+
+
+ ) + })} +
+
+ + + + + + {ownedPool.contractAddress} + + + + +

View on Solana Explorer

+
+
+
+
+
+ ) : ( + + + No results found. + + + )} +
+
+
) diff --git a/ntc-web/app/pools/FilePicker.tsx b/ntc-web/app/pools/FilePicker.tsx index 05253b9..597e24e 100644 --- a/ntc-web/app/pools/FilePicker.tsx +++ b/ntc-web/app/pools/FilePicker.tsx @@ -3,7 +3,7 @@ import { useState, useRef } from 'react' import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" -import { Upload } from 'lucide-react' +import { Upload, Trash2 } from 'lucide-react' interface FilePickerProps { label: string; @@ -32,15 +32,32 @@ export function FilePicker({ label, accept, onChange }: FilePickerProps) { e.preventDefault() } + const handleClear = (e: React.MouseEvent) => { + e.stopPropagation() // Prevent card click event + setSelectedFile(null) + onChange(null) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + } + return (
fileInputRef.current?.click()} onDrop={handleDrop} onDragOver={handleDragOver} > + {selectedFile && ( +
+ +
+ )}
{selectedFile ? ( -
+
Selected: {selectedFile.name}
) : ( diff --git a/ntc-web/app/pools/PoolsTable.tsx b/ntc-web/app/pools/PoolsTable.tsx new file mode 100644 index 0000000..f7f746e --- /dev/null +++ b/ntc-web/app/pools/PoolsTable.tsx @@ -0,0 +1,280 @@ +import React, { useState } from 'react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; +import { ExternalLink, ChevronDown, ChevronUp, ChevronsUpDown } from 'lucide-react'; + +interface Right { + id: string; + name: string; + description: string; +} + +interface Pool { + id: number; + name: string; + description: string; + rights: string[]; + contractAddress: string; + isOwned: boolean; +} + +interface PoolsTableProps { + // Add any props if needed +} + +type SortField = 'name' | 'description' | 'rights' | 'contractAddress'; +type SortDirection = 'asc' | 'desc' | null; + +const PoolsTable: React.FC = () => { + const [search, setSearch] = useState(''); + const [showMyPools, setShowMyPools] = useState(false); + const [sortField, setSortField] = useState(null); + const [sortDirection, setSortDirection] = useState(null); + + const availableRights: Right[] = [ + { + id: '1', + name: 'Create data pool', + description: 'Allows creation of new data pools and setting initial parameters for data structure and digital right tokens' + }, + { + id: '2', + name: 'Append data pool', + description: 'Permits adding new data entries to existing pools while maintaining schema requirements' + }, + { + id: '3', + name: 'Execute Median WASM', + description: 'Enables running Rust WebAssembly-based median calculations on data pools' + }, + { + id: '4', + name: 'Execute Median Python', + description: 'Allows execution of Python-based median computations on data pools' + }, + ]; + + const pools: Pool[] = [ + { + id: 1, + name: "Market Analysis Pool", + description: "Contains market trend data from 2023-2024", + rights: ['2'], + contractAddress: "7nYuwdHqwrxbr5CKqRqZY6ZduuB3ZSLJsBz8RPKkqvCp", + isOwned: false, + }, + { + id: 2, + name: "Customer Insights", + description: "Aggregated customer behavior metrics", + rights: ['2', '3', '4'], + contractAddress: "BPFLoader2111111111111111111111111111111111", + isOwned: false, + }, + { + id: 3, + name: "Financial Metrics", + description: "Quarterly financial performance data", + rights: ['2', '3'], + contractAddress: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + isOwned: true, + }, + ]; + + const handleSort = (field: SortField) => { + if (sortField === field) { + // Cycle through: asc -> desc -> null + if (sortDirection === 'asc') { + setSortDirection('desc'); + } else if (sortDirection === 'desc') { + setSortField(null); + setSortDirection(null); + } + } else { + setSortField(field); + setSortDirection('asc'); + } + }; + + const getSortIcon = (field: SortField) => { + if (sortField !== field) return ; + if (sortDirection === 'asc') return ; + if (sortDirection === 'desc') return ; + return ; + }; + + const getRightById = (rightId: string): Right | undefined => { + return availableRights.find((right) => right.id === rightId); + }; + + const getSolanaExplorerUrl = (address: string): string => { + return `https://explorer.solana.com/address/${address}`; + }; + + const sortPools = (pools: Pool[]): Pool[] => { + if (!sortField || !sortDirection) return pools; + + return [...pools].sort((a, b) => { + if (sortField === 'rights') { + // Compare by number of rights + const aValue = a.rights.length; + const bValue = b.rights.length; + return sortDirection === 'asc' ? aValue - bValue : bValue - aValue; + } + + // For all other fields (which are strings) + const aValue = a[sortField]; + const bValue = b[sortField]; + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return sortDirection === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + } + + return 0; + }); + }; + + const filteredPools = sortPools( + pools + .filter((pool) => !showMyPools || pool.isOwned) + .filter((pool) => + pool.name.toLowerCase().includes(search.toLowerCase()) || + pool.description.toLowerCase().includes(search.toLowerCase()) + ) + ); + + return ( + +
+ setSearch(e.target.value)} + className="max-w-md bg-white" + /> +
+ + +
+
+ +
+ + + + handleSort('name')} + > +
+ Pool Name + {getSortIcon('name')} +
+
+ handleSort('description')} + > +
+ Description + {getSortIcon('description')} +
+
+ handleSort('rights')} + > +
+ Digital Rights Tokens (DRT) + {getSortIcon('rights')} +
+
+ handleSort('contractAddress')} + > +
+ Smart Contract + {getSortIcon('contractAddress')} +
+
+
+
+ + {filteredPools.map((pool) => ( + + {pool.name} + {pool.description} + +
+ {pool.rights.map((rightId) => { + const right = getRightById(rightId); + if (!right) return null; + return ( + + + + + {right.name} + + + +

{right.description}

+
+
+
+ ); + })} +
+
+ + + + + + {pool.contractAddress} + + + + +

View on Solana Explorer

+
+
+
+
+
+ ))} + {filteredPools.length === 0 && ( + + + No results found. + + + )} +
+
+
+
+ ); +}; + +export default PoolsTable; \ No newline at end of file diff --git a/ntc-web/app/pools/SchemaPreview.tsx b/ntc-web/app/pools/SchemaPreview.tsx new file mode 100644 index 0000000..e617258 --- /dev/null +++ b/ntc-web/app/pools/SchemaPreview.tsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Eye } from 'lucide-react' + +interface SchemaPreviewProps { + schemaFile: File | null; +} + +interface SchemaColumn { + name: string; + type: string; + required: boolean; + items?: { + type: string; + }; +} + +export function SchemaPreview({ schemaFile }: SchemaPreviewProps) { + const [open, setOpen] = useState(false); + const [schema, setSchema] = useState(null); + const [columns, setColumns] = useState([]); + const [error, setError] = useState(null); + + const loadSchema = async () => { + if (!schemaFile) return; + + try { + const text = await schemaFile.text(); + const parsed = JSON.parse(text); + setSchema(parsed); + + // Extract columns from schema + const extractedColumns: SchemaColumn[] = Object.entries(parsed.properties || {}).map( + ([name, prop]: [string, any]) => ({ + name, + type: prop.type, + required: (parsed.required || []).includes(name), + items: prop.items + }) + ); + setColumns(extractedColumns); + setError(null); + } catch (err) { + setError('Failed to parse schema file'); + setSchema(null); + setColumns([]); + } + }; + + const formatSchemaTree = (schema: any) => { + return JSON.stringify(schema, null, 2); + }; + + return ( + <> + + + + + + Schema Preview + + + {error ? ( +
{error}
+ ) : ( + + + + Table + + + Tree + + + + + + + + + Column Name + Type + Required + Item Type + + + + {columns.map((column) => ( + + {column.name} + {column.type} + {column.required ? 'Yes' : 'No'} + {column.items?.type || '-'} + + ))} + +
+
+
+ + + +
+                    {schema ? formatSchemaTree(schema) : 'No schema loaded'}
+                  
+
+
+
+ )} +
+
+ + ); +} \ No newline at end of file diff --git a/ntc-web/app/pools/page.tsx b/ntc-web/app/pools/page.tsx index 3072532..3b1adad 100644 --- a/ntc-web/app/pools/page.tsx +++ b/ntc-web/app/pools/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useState } from 'react' -import { FilePicker } from './FilePicker' +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import { @@ -16,6 +16,14 @@ import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Checkbox } from "@/components/ui/checkbox" import { Alert, AlertDescription } from "@/components/ui/alert" +import { FilePicker } from './FilePicker' +import { validateJsonSchema } from './schemaValidation' +import { SchemaPreview } from './SchemaPreview' +import PoolsTable from './PoolsTable' + +// Common styles +const buttonBaseClass = "bg-gray-900 text-white hover:bg-gray-800" +const buttonOutlineClass = "border-2 border-gray-900 text-gray-900 hover:bg-gray-100" interface StepProps { isActive: boolean; @@ -31,6 +39,7 @@ function FileSelectionStep({ isActive, onNext }: StepProps) { success: false, error: null }) + const [isValidating, setIsValidating] = useState(false) const validateFiles = async () => { if (!schemaFile || !dataFile) { @@ -38,20 +47,21 @@ function FileSelectionStep({ isActive, onNext }: StepProps) { return } + setIsValidating(true) try { - // Read and validate schema - const schemaText = await schemaFile.text() - const schema = JSON.parse(schemaText) - - // Read and validate data against schema - const dataText = await dataFile.text() - const data = JSON.parse(dataText) - - // TODO: Add actual schema validation - setValidation({ success: true, error: null }) - onNext() + const result = await validateJsonSchema(schemaFile, dataFile) + setValidation(result) + + if (result.success) { + onNext() + } } catch (error) { - setValidation({ success: false, error: 'Invalid JSON format or schema mismatch' }) + setValidation({ + success: false, + error: 'An error occurred during validation' + }) + } finally { + setIsValidating(false) } } @@ -63,16 +73,29 @@ function FileSelectionStep({ isActive, onNext }: StepProps) {

Select Files

- setSchemaFile(file)} - /> +
+ { + setSchemaFile(file) + setValidation({ success: false, error: null }) + }} + /> + {schemaFile && ( +
+ +
+ )} +
setDataFile(file)} + onChange={(file) => { + setDataFile(file) + setValidation({ success: false, error: null }) + }} />
@@ -84,10 +107,10 @@ function FileSelectionStep({ isActive, onNext }: StepProps) {
@@ -101,7 +124,10 @@ function DigitalRightsStep({ isActive, onNext, onPrev }: StepProps) { if (!isActive) return null const dummyRights = [ - { id: '1', name: 'DRT Name', description: 'Lorem Ipsum' } + { id: '1', name: 'Create data pool', description: 'Allows creation of new data pools and setting initial parameters for data structure and digital right tokens' }, + { id: '2', name: 'Append data pool', description: 'Permits adding new data entries to existing pools while maintaining schema requirements' }, + { id: '3', name: 'Execute Median WASM', description: 'Enables running Rust WebAssembly-based median calculations on data pools' }, + { id: '4', name: 'Execute Median Python', description: 'Allows execution of Python-based median computations on data pools' } ] return ( @@ -141,8 +167,12 @@ function DigitalRightsStep({ isActive, onNext, onPrev }: StepProps) {
- - + +
) @@ -152,6 +182,19 @@ function DigitalRightsStep({ isActive, onNext, onPrev }: StepProps) { function DescriptionStep({ isActive, onNext, onPrev }: StepProps) { const [name, setName] = useState('') const [description, setDescription] = useState('') + const [validation, setValidation] = useState<{ error: string | null }>({ error: null }) + + const validateAndProceed = () => { + if (!name.trim()) { + setValidation({ error: 'Pool name is required' }) + return + } + if (!description.trim()) { + setValidation({ error: 'Pool description is required' }) + return + } + onNext() + } if (!isActive) return null @@ -166,7 +209,10 @@ function DescriptionStep({ isActive, onNext, onPrev }: StepProps) { placeholder="Enter name (Max 50 Characters)" maxLength={50} value={name} - onChange={(e) => setName(e.target.value)} + onChange={(e) => { + setName(e.target.value) + setValidation({ error: null }) + }} />
@@ -176,78 +222,230 @@ function DescriptionStep({ isActive, onNext, onPrev }: StepProps) { placeholder="Enter description (Max 200 Characters)" maxLength={200} value={description} - onChange={(e) => setDescription(e.target.value)} + onChange={(e) => { + setDescription(e.target.value) + setValidation({ error: null }) + }} />
+ + {validation.error && ( + + {validation.error} + + )}
- - + +
) } -export default function Pools() { +function CreatePool() { const [currentStep, setCurrentStep] = useState(1) return ( -
-

Create Pool

- - -
-
- {[1, 2, 3].map((step) => ( + + {/* Progress Steps */} +
+
+ {[1, 2, 3].map((step) => ( +
= step + ? 'bg-gray-900 text-white border-gray-900' + : 'border-gray-300 text-gray-400' + }`} > -
= step - ? 'bg-gray-800 text-white border-gray-800' - : 'border-gray-300 text-gray-400' - }`} - > - {step} -
- {step < 3 && ( -
step ? 'bg-gray-800' : 'bg-gray-200' - }`} - /> - )} + {step}
+ {step < 3 && ( +
step ? 'bg-gray-900' : 'bg-gray-200' + }`} + /> + )} +
+ ))} +
+
+ + {/* Step Components */} + setCurrentStep(2)} + /> + + setCurrentStep(3)} + onPrev={() => setCurrentStep(1)} + /> + + { + alert('Pool created successfully!') + setCurrentStep(1) + }} + onPrev={() => setCurrentStep(2)} + /> + + ) +} + +function JoinPool() { + const [dataFile, setDataFile] = useState(null) + const [validation, setValidation] = useState<{ + success: boolean | null; + error: string | null; + }>({ + success: null, + error: null, + }) + const [isValidating, setIsValidating] = useState(false) + + // Dummy digital rights + const digitalRights = [ + 'Create data pool', + 'Append data pool', + 'Execute Median WASM', + 'Execute Python Median', + ] + + const handleJoinPool = async () => { + if (!dataFile) { + setValidation({ success: false, error: 'Please select a data file' }) + return + } + + setIsValidating(true) + try { + // Here you would validate against the pool's schema + // For now, we'll simulate validation + await new Promise(resolve => setTimeout(resolve, 1000)) + + setValidation({ + success: true, + error: null, + }) + alert('Successfully joined pool!') + } catch (error) { + setValidation({ + success: false, + error: 'Failed to validate data file against pool schema', + }) + } finally { + setIsValidating(false) + } + } + + return ( + +
+
+

Pool Name

+

Pool Description

+
+ +
+
Digital Rights
+
+ {digitalRights.map((right, index) => ( + +

{right}

+
))}
- setCurrentStep(2)} - /> - - setCurrentStep(3)} - onPrev={() => setCurrentStep(1)} - /> - - { - // Handle pool creation - alert('Pool created successfully!') - setCurrentStep(1) - }} - onPrev={() => setCurrentStep(2)} - /> - + + +
+ { + setDataFile(file) + setValidation({ + success: null, + error: null, + }) + }} + /> + + {validation.error && ( + + {validation.error} + + )} + + +
+
+
+ ) +} + +export default function Pools() { + return ( +
+

Pools

+ +
+ + + + Create Pool + + + Join Pool + + + + + + + + + + + +
+ + {/* Pools Table Section - Full width */} +
+

Existing Pools

+ +
) } \ No newline at end of file diff --git a/ntc-web/app/pools/schemaValidation.tsx b/ntc-web/app/pools/schemaValidation.tsx new file mode 100644 index 0000000..7648769 --- /dev/null +++ b/ntc-web/app/pools/schemaValidation.tsx @@ -0,0 +1,226 @@ +import React from 'react'; + +interface ValidationResult { + success: boolean; + error: string | null; +} + +export async function validateJsonSchema(schemaFile: File, dataFile: File): Promise { + try { + // Read schema file + const schemaText = await schemaFile.text(); + let schema; + try { + schema = JSON.parse(schemaText); + } catch (error) { + return { + success: false, + error: 'The schema file contains invalid JSON. Please check for syntax errors like missing commas or quotes.' + }; + } + + // Validate schema structure + if (!schema || typeof schema !== 'object') { + return { + success: false, + error: 'The schema must be a JSON object. Please make sure it starts with { } and contains proper field definitions.' + }; + } + + if (!schema.type || !schema.properties) { + return { + success: false, + error: 'The schema is missing required fields. It must include both "type" and "properties" fields to define the data structure.' + }; + } + + // Read data file + const dataText = await dataFile.text(); + let data; + try { + data = JSON.parse(dataText); + } catch (error) { + return { + success: false, + error: 'The data file contains invalid JSON. Please check for proper formatting and syntax.' + }; + } + + // Validate data against schema + const validationResult = validateDataAgainstSchema(data, schema); + if (!validationResult.success) { + return validationResult; + } + + return { + success: true, + error: null + }; + } catch (error) { + return { + success: false, + error: 'An unexpected error occurred during validation. Please try again or contact support if the issue persists.' + }; + } +} + +function validateDataAgainstSchema(data: any, schema: any): ValidationResult { + // Validate type + if (schema.type === 'object') { + if (typeof data !== 'object' || Array.isArray(data)) { + return { + success: false, + error: 'The data should be a JSON object, but received a different type. Please ensure your data is enclosed in curly braces { }.' + }; + } + + // Validate required properties + if (schema.required) { + const missingFields: string[] = schema.required.filter((prop: string) => !(prop in data)); + if (missingFields.length > 0) { + return { + success: false, + error: missingFields.length === 1 + ? `The required field "${missingFields[0]}" is missing from your data. Please add this field to proceed.` + : `The following required fields are missing: ${missingFields.map((f: string) => `"${f}"`).join(', ')}. Please add these fields to proceed.` + }; + } + } + + // Validate properties + for (const [propName, propSchema] of Object.entries(schema.properties)) { + if (propName in data) { + const propValidation = validateProperty(data[propName], propSchema as any); + if (!propValidation.success) { + return { + success: false, + error: `Issue with field "${propName}": ${propValidation.error}` + }; + } + } + } + } else if (schema.type === 'array') { + if (!Array.isArray(data)) { + return { + success: false, + error: 'The data should be an array. Please ensure your data is enclosed in square brackets [ ].' + }; + } + + // Validate array items + if (schema.items) { + for (let i = 0; i < data.length; i++) { + const itemValidation = validateProperty(data[i], schema.items); + if (!itemValidation.success) { + return { + success: false, + error: `Issue with item #${i + 1}: ${itemValidation.error}` + }; + } + } + } + } + + return { + success: true, + error: null + }; +} + +function validateProperty(value: any, schema: any): ValidationResult { + // Type validation + switch (schema.type) { + case 'string': + if (typeof value !== 'string') { + return { + success: false, + error: 'This field should be a text value (enclosed in quotes).' + }; + } + // Validate string constraints + if (schema.minLength && value.length < schema.minLength) { + return { + success: false, + error: `This text is too short. It needs at least ${schema.minLength} characters.` + }; + } + if (schema.maxLength && value.length > schema.maxLength) { + return { + success: false, + error: `This text is too long. It should not exceed ${schema.maxLength} characters.` + }; + } + if (schema.pattern && !new RegExp(schema.pattern).test(value)) { + return { + success: false, + error: 'This text doesn\'t match the required format pattern.' + }; + } + break; + + case 'number': + if (typeof value !== 'number') { + return { + success: false, + error: 'This field should be a number (without quotes).' + }; + } + // Validate number constraints + if (schema.minimum !== undefined && value < schema.minimum) { + return { + success: false, + error: `This number is too small. It must be ${schema.minimum} or greater.` + }; + } + if (schema.maximum !== undefined && value > schema.maximum) { + return { + success: false, + error: `This number is too large. It must be ${schema.maximum} or less.` + }; + } + break; + + case 'boolean': + if (typeof value !== 'boolean') { + return { + success: false, + error: 'This field should be either true or false (without quotes).' + }; + } + break; + + case 'object': + return validateDataAgainstSchema(value, schema); + + case 'array': + if (!Array.isArray(value)) { + return { + success: false, + error: 'This field should be an array (enclosed in square brackets [ ]).' + }; + } + if (schema.items) { + for (let i = 0; i < value.length; i++) { + const itemValidation = validateProperty(value[i], schema.items); + if (!itemValidation.success) { + return { + success: false, + error: `Issue with item #${i + 1}: ${itemValidation.error}` + }; + } + } + } + break; + + default: + return { + success: false, + error: `Unsupported data type: "${schema.type}". Please check the schema definition.` + }; + } + + return { + success: true, + error: null + }; +} \ No newline at end of file diff --git a/ntc-web/components/TopBar.tsx b/ntc-web/components/TopBar.tsx index 7b2a21e..bf5dc34 100644 --- a/ntc-web/components/TopBar.tsx +++ b/ntc-web/components/TopBar.tsx @@ -2,6 +2,14 @@ import Image from 'next/image' import { PanelLeftClose, PanelLeftOpen } from 'lucide-react' +import { + SignInButton, + SignedIn, + SignedOut, + UserButton, + ClerkLoading, + ClerkLoaded +} from '@clerk/nextjs' interface TopBarProps { isNavOpen: boolean; @@ -47,8 +55,23 @@ export default function TopBar({ isNavOpen, toggleNav }: TopBarProps) { )} -
+
+ +
+
+ + + + + + + + + +
) -} +} \ No newline at end of file diff --git a/ntc-web/components/ui/badge.tsx b/ntc-web/components/ui/badge.tsx new file mode 100644 index 0000000..f000e3e --- /dev/null +++ b/ntc-web/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/ntc-web/components/ui/dialog.tsx b/ntc-web/components/ui/dialog.tsx new file mode 100644 index 0000000..01ff19c --- /dev/null +++ b/ntc-web/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/ntc-web/components/ui/label.tsx b/ntc-web/components/ui/label.tsx new file mode 100644 index 0000000..5341821 --- /dev/null +++ b/ntc-web/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/ntc-web/components/ui/scroll-area.tsx b/ntc-web/components/ui/scroll-area.tsx new file mode 100644 index 0000000..0b4a48d --- /dev/null +++ b/ntc-web/components/ui/scroll-area.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/ntc-web/components/ui/switch.tsx b/ntc-web/components/ui/switch.tsx new file mode 100644 index 0000000..bc69cf2 --- /dev/null +++ b/ntc-web/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/ntc-web/components/ui/tabs.tsx b/ntc-web/components/ui/tabs.tsx new file mode 100644 index 0000000..26eb109 --- /dev/null +++ b/ntc-web/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/ntc-web/components/ui/tooltip.tsx b/ntc-web/components/ui/tooltip.tsx new file mode 100644 index 0000000..30fc44d --- /dev/null +++ b/ntc-web/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/ntc-web/middleware.ts b/ntc-web/middleware.ts new file mode 100644 index 0000000..3c977e9 --- /dev/null +++ b/ntc-web/middleware.ts @@ -0,0 +1,12 @@ +import { clerkMiddleware } from "@clerk/nextjs/server"; + +export default clerkMiddleware(); + +export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', + // Always run for API routes + '/(api|trpc)(.*)', + ], +}; \ No newline at end of file diff --git a/ntc-web/package-lock.json b/ntc-web/package-lock.json index de70c3b..bcde945 100644 --- a/ntc-web/package-lock.json +++ b/ntc-web/package-lock.json @@ -8,8 +8,15 @@ "name": "ntc-web", "version": "0.1.0", "dependencies": { + "@clerk/nextjs": "^6.9.11", "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.6", "@shadcn/ui": "^0.0.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -476,6 +483,129 @@ "node": ">=6.9.0" } }, + "node_modules/@clerk/backend": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.23.0.tgz", + "integrity": "sha512-AcXO0Xs6e5HpU3coSPmnvsMysfuTPtfxv7Zgg+uaLYLD2/3E8OOMyLzPyiRmayacOxP6To1i8ETUJzpA08nRcA==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^2.20.7", + "@clerk/types": "^4.40.3", + "cookie": "1.0.2", + "snakecase-keys": "8.0.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/backend/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/clerk-react": { + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.21.3.tgz", + "integrity": "sha512-v94kVhvLIIAnWSBw3+klod7xbaGigGPd1JWKwCcU10a89XgftRhZyY5HO89E4rtID/lo8DyfJe0+1w3BDAUcBA==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^2.20.7", + "@clerk/types": "^4.40.3", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/clerk-react/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/nextjs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.9.11.tgz", + "integrity": "sha512-FThhcOn1nfZDCHRMDS2Lr9mI1QVWR6G1A1ea3Kl7yBPqUcU1ukEatNQJNudCmmZYHIIYOPh/uDofRth62ucTnQ==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "^1.23.0", + "@clerk/clerk-react": "^5.21.3", + "@clerk/shared": "^2.20.7", + "@clerk/types": "^4.40.3", + "crypto-js": "4.2.0", + "server-only": "0.0.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "next": "^13.5.4 || ^14.0.3 || ^15.0.0", + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/nextjs/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/shared": { + "version": "2.20.7", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-2.20.7.tgz", + "integrity": "sha512-59EbMhqAi1331ovs5botm+OVVw3NHVLllO4X9/zBUb64PVtx1W7RSBYf+NxTFjWIalHp0kfVzpcm+jK3JkfXCw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@clerk/types": "^4.40.3", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.7.0", + "swr": "^2.2.0" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/types": { + "version": "4.40.3", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.40.3.tgz", + "integrity": "sha512-gPCL9thu3haTWFGTSX0Dn4rqHPAc+v2J30JIQvHciSgCtrWbhDlYGh0yND3UqAZC0B0FsA2BpGAu7ClfvqDH/A==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/types/node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "license": "MIT" + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -614,6 +744,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1305,12 +1473,41 @@ "node": ">=14" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", "license": "MIT" }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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-checkbox": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz", @@ -1341,6 +1538,32 @@ } } }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "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-compose-refs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", @@ -1371,6 +1594,221 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz", + "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "^2.6.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-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "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-dismissable-layer": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", + "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "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-focus-scope": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", + "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.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-label": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz", + "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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-popper": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "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-portal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-presence": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", @@ -1395,13 +1833,152 @@ } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", - "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "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-roving-focus": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", + "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "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-scroll-area": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz", + "integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "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-switch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.2.tgz", + "integrity": "sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "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": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz", + "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.1" + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -1418,21 +1995,37 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", - "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz", + "integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, @@ -1469,6 +2062,24 @@ } } }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.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.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", @@ -1499,6 +2110,24 @@ } } }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.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-size": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", @@ -1517,6 +2146,35 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz", + "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2016,6 +2674,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -2652,6 +3322,15 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -2692,6 +3371,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2853,6 +3538,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2863,6 +3557,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2897,6 +3597,16 @@ "node": ">=0.10.0" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3934,6 +4644,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -4023,6 +4742,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -4797,6 +5522,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5036,6 +5770,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -5051,6 +5794,18 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5271,6 +6026,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -5972,6 +6737,75 @@ "dev": true, "license": "MIT" }, + "node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6279,6 +7113,12 @@ "node": ">=10" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -6599,6 +7439,30 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snakecase-keys": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-8.0.1.tgz", + "integrity": "sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==", + "license": "MIT", + "dependencies": { + "map-obj": "^4.1.0", + "snake-case": "^3.0.4", + "type-fest": "^4.15.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6624,6 +7488,12 @@ "dev": true, "license": "MIT" }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", @@ -6969,6 +7839,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.0.tgz", + "integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwind-merge": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", @@ -7163,6 +8046,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.32.0.tgz", + "integrity": "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -7330,6 +8225,58 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/ntc-web/package.json b/ntc-web/package.json index 45a7626..b374bde 100644 --- a/ntc-web/package.json +++ b/ntc-web/package.json @@ -9,8 +9,15 @@ "lint": "next lint" }, "dependencies": { + "@clerk/nextjs": "^6.9.11", "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.6", "@shadcn/ui": "^0.0.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/ntc-web/public/fonts/VisbyRoundCF-Bold.woff2 b/ntc-web/public/fonts/VisbyRoundCF-Bold.woff2 new file mode 100644 index 0000000..6024942 Binary files /dev/null and b/ntc-web/public/fonts/VisbyRoundCF-Bold.woff2 differ diff --git a/ntc-web/public/fonts/VisbyRoundCF-Medium.woff2 b/ntc-web/public/fonts/VisbyRoundCF-Medium.woff2 new file mode 100644 index 0000000..3be8b56 Binary files /dev/null and b/ntc-web/public/fonts/VisbyRoundCF-Medium.woff2 differ diff --git a/ntc-web/public/fonts/VisbyRoundCF-Regular.woff2 b/ntc-web/public/fonts/VisbyRoundCF-Regular.woff2 new file mode 100644 index 0000000..7ff3d55 Binary files /dev/null and b/ntc-web/public/fonts/VisbyRoundCF-Regular.woff2 differ diff --git a/ntc-web/sign-in/[[...sign-in]]/page.tsx b/ntc-web/sign-in/[[...sign-in]]/page.tsx new file mode 100644 index 0000000..4dba57a --- /dev/null +++ b/ntc-web/sign-in/[[...sign-in]]/page.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { SignIn } from '@clerk/nextjs'; +import { useRouter, useSearchParams } from 'next/navigation'; +import Image from 'next/image'; + +export default function SignInPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const redirectUrl = searchParams.get('redirect_url') || '/dashboard'; // Default to '/dashboard' + + return ( +
+
+
+
+
+
+ Logo +
+
+

Sign In

+
+ +
+ Don't have an account? + + Sign Up + +
+
+
+
+ ); +} diff --git a/ntc-web/sign-up/[[...sign-up]]/page.tsx b/ntc-web/sign-up/[[...sign-up]]/page.tsx new file mode 100644 index 0000000..8fb650b --- /dev/null +++ b/ntc-web/sign-up/[[...sign-up]]/page.tsx @@ -0,0 +1,53 @@ +import { SignUp } from "@clerk/nextjs"; +import Image from "next/image"; + +export default function SignUpPage() { + return ( +
+
+
+
+
+
+ Logo +
+
+

Create Account

+
+ + + +
+ Already have an account? + + Sign In + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/ntc-web/tailwind.config.ts b/ntc-web/tailwind.config.ts index 773b1e6..ab9743a 100644 --- a/ntc-web/tailwind.config.ts +++ b/ntc-web/tailwind.config.ts @@ -8,55 +8,58 @@ export default { "./app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { - extend: { - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - } - } + extend: { + fontFamily: { + sans: ['Visby', 'sans-serif'], + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + } + } }, plugins: [require("tailwindcss-animate")], -} satisfies Config; +} satisfies Config; \ No newline at end of file