From 7c3d65dadf53feb4b71da85c0e5bc81cc62d2135 Mon Sep 17 00:00:00 2001 From: Magnus Smari Smarason Date: Sat, 17 May 2025 20:58:29 +0000 Subject: [PATCH] Fixed CRUD operations for CITES listings, common names, and IUCN assessments. Added admin routes and authentication context. Updated UI components and added new pages for admin functionalities. --- components.json | 21 + data_architecture_may2025.md | 213 ++++++ package-lock.json | 670 ++++++++++++++++- package.json | 8 +- src/App.tsx | 74 +- src/components/admin/CitesListingForm.tsx | 207 +++++ src/components/admin/CommonNameForm.tsx | 197 +++++ src/components/admin/IucnAssessmentForm.tsx | 354 +++++++++ src/components/admin/SpeciesForm.tsx | 628 ++++++++++++++++ src/components/auth/AdminRoute.tsx | 16 + src/components/auth/LoginForm.tsx | 70 ++ src/components/compare-trade-tab.tsx | 281 +++++++ src/components/debug-panel.tsx | 10 +- src/components/layout/AdminLayout.tsx | 104 +++ src/components/results-container.tsx | 181 +++-- src/components/results-container.tsx.bak | 706 ++++++++++++++++++ src/components/search-form.tsx | 203 +++-- src/components/species-report.tsx | 33 +- src/components/species-tabs.tsx | 344 ++++++++- src/components/species/SpeciesImage.tsx | 93 +-- .../species/tabs/CitesListingsTab.tsx | 71 ++ src/components/species/tabs/CitesTab.tsx | 256 ------- src/components/species/tabs/IucnTab.tsx | 190 ++--- src/components/species/tabs/OverviewTab.tsx | 99 +-- src/components/species/tabs/TimelineTab.tsx | 240 ------ src/components/species/tabs/TradeDataTab.tsx | 204 ++--- .../species/visualizations/CatchChart.tsx | 93 +++ .../species/visualizations/TradeCharts.tsx | 268 +++++-- src/components/ui/alert.tsx | 59 ++ src/components/ui/badge.tsx | 6 +- src/components/ui/button.tsx | 12 +- src/components/ui/card.tsx | 10 +- src/components/ui/checkbox.tsx | 28 + src/components/ui/dialog.tsx | 8 +- src/components/ui/form.tsx | 176 +++++ src/components/ui/input.tsx | 4 +- src/components/ui/label.tsx | 4 +- src/components/ui/pagination.tsx | 117 +++ src/components/ui/progress.tsx | 26 + src/components/ui/scroll-area.tsx | 46 ++ src/components/ui/select.tsx | 14 +- src/components/ui/table.tsx | 120 +++ src/components/ui/tabs.tsx | 8 +- src/components/ui/textarea.tsx | 22 + src/components/ui/toast.tsx | 127 ++++ src/components/ui/toaster.tsx | 33 + src/contexts/auth/AuthContext.tsx | 179 +++++ src/hooks/use-toast.ts | 194 +++++ src/hooks/useTradeRecordFilters.ts | 72 +- src/index.css | 47 +- src/lib/api.ts | 518 +++++++++++++ src/lib/countries.ts | 260 +++++++ src/lib/supabase.ts | 17 +- src/lib/utils.ts | 65 +- src/pages/admin/cites-listings/create.tsx | 161 ++++ src/pages/admin/cites-listings/edit.tsx | 141 ++++ src/pages/admin/cites-listings/list.tsx | 272 +++++++ src/pages/admin/common-names/create.tsx | 161 ++++ src/pages/admin/common-names/edit.tsx | 118 +++ src/pages/admin/common-names/list.tsx | 274 +++++++ src/pages/admin/dashboard.tsx | 126 ++++ src/pages/admin/iucn-assessments/create.tsx | 161 ++++ src/pages/admin/iucn-assessments/edit.tsx | 141 ++++ src/pages/admin/iucn-assessments/list.tsx | 298 ++++++++ src/pages/admin/login.tsx | 22 + src/pages/admin/species/create.tsx | 57 ++ src/pages/admin/species/edit.tsx | 102 +++ src/pages/admin/species/list.tsx | 278 +++++++ src/pages/home.tsx | 296 +++++--- src/services/adminApi.ts | 357 +++++++++ tailwind.config.js | 141 ++-- 71 files changed, 9727 insertions(+), 1385 deletions(-) create mode 100644 components.json create mode 100644 data_architecture_may2025.md create mode 100644 src/components/admin/CitesListingForm.tsx create mode 100644 src/components/admin/CommonNameForm.tsx create mode 100644 src/components/admin/IucnAssessmentForm.tsx create mode 100644 src/components/admin/SpeciesForm.tsx create mode 100644 src/components/auth/AdminRoute.tsx create mode 100644 src/components/auth/LoginForm.tsx create mode 100644 src/components/compare-trade-tab.tsx create mode 100644 src/components/layout/AdminLayout.tsx create mode 100644 src/components/results-container.tsx.bak create mode 100644 src/components/species/tabs/CitesListingsTab.tsx delete mode 100644 src/components/species/tabs/CitesTab.tsx delete mode 100644 src/components/species/tabs/TimelineTab.tsx create mode 100644 src/components/species/visualizations/CatchChart.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toast.tsx create mode 100644 src/components/ui/toaster.tsx create mode 100644 src/contexts/auth/AuthContext.tsx create mode 100644 src/hooks/use-toast.ts create mode 100644 src/lib/countries.ts create mode 100644 src/pages/admin/cites-listings/create.tsx create mode 100644 src/pages/admin/cites-listings/edit.tsx create mode 100644 src/pages/admin/cites-listings/list.tsx create mode 100644 src/pages/admin/common-names/create.tsx create mode 100644 src/pages/admin/common-names/edit.tsx create mode 100644 src/pages/admin/common-names/list.tsx create mode 100644 src/pages/admin/dashboard.tsx create mode 100644 src/pages/admin/iucn-assessments/create.tsx create mode 100644 src/pages/admin/iucn-assessments/edit.tsx create mode 100644 src/pages/admin/iucn-assessments/list.tsx create mode 100644 src/pages/admin/login.tsx create mode 100644 src/pages/admin/species/create.tsx create mode 100644 src/pages/admin/species/edit.tsx create mode 100644 src/pages/admin/species/list.tsx create mode 100644 src/services/adminApi.ts diff --git a/components.json b/components.json new file mode 100644 index 0000000..299ea38 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/data_architecture_may2025.md b/data_architecture_may2025.md new file mode 100644 index 0000000..d0b4379 --- /dev/null +++ b/data_architecture_may2025.md @@ -0,0 +1,213 @@ +# Supabase Database Schema (Arctic Species) + +This document outlines the data architecture of the Arctic Species Supabase database. + +## Tables + +The database contains the following tables: + +* `catch_records` +* `cites_listings` +* `cites_trade_records` +* `common_names` +* `conservation_measures` +* `distribution_ranges` +* `iucn_assessments` +* `profiles` +* `species` +* `species_threats` +* `subpopulations` +* `timeline_events` + +## Table Structures + +Below is a preliminary structure for each table, inferred by querying a single record. + +--- + +### `species` + +| Column Name | Data Type (Inferred) | Notes | +| ----------------------- | -------------------- | ----------------------------------------- | +| `id` | UUID | Primary Key | +| `scientific_name` | TEXT | Scientific name of the species | +| `common_name` | TEXT | Common name of the species | +| `kingdom` | TEXT | Taxonomic kingdom | +| `phylum` | TEXT | Taxonomic phylum | +| `class` | TEXT | Taxonomic class | +| `order_name` | TEXT | Taxonomic order | +| `family` | TEXT | Taxonomic family | +| `genus` | TEXT | Taxonomic genus | +| `species_name` | TEXT | Specific epithet | +| `authority` | TEXT | Authority who named the species | +| `sis_id` | INTEGER | Species Information Service ID (IUCN) | +| `created_at` | TIMESTAMP WITH TIME ZONE | Timestamp of record creation | +| `inaturalist_id` | INTEGER | iNaturalist taxon ID (nullable) | +| `default_image_url` | TEXT | URL for a default image (nullable) | +| `description` | TEXT | General description (nullable) | +| `habitat_description` | TEXT | Description of habitat (nullable) | +| `population_trend` | TEXT | Population trend (e.g., decreasing, stable) (nullable) | +| `population_size` | TEXT | Estimated population size (nullable) | +| `generation_length` | TEXT | Generation length in years (nullable) | +| `movement_patterns` | TEXT | Description of movement patterns (nullable) | +| `use_and_trade` | TEXT | Information on use and trade (nullable) | +| `threats_overview` | TEXT | Overview of threats (nullable) | +| `conservation_overview` | TEXT | Overview of conservation efforts (nullable) | + +--- + +### `catch_records` + +| Column Name | Data Type (Inferred) | Notes | +| ----------- | -------------------- | ----------------------------------------- | +| `id` | INTEGER | Primary Key | +| `species_id`| UUID | Foreign Key referencing `species.id` | +| `country` | TEXT | Country where the catch was recorded | +| `year` | INTEGER | Year of the catch record | +| `area` | TEXT | Specific area of catch (nullable) | +| `catch_total` | INTEGER | Total catch amount | +| `quota` | INTEGER | Catch quota, if applicable (nullable) | +| `source` | TEXT | Source of the catch data (e.g., NAMMCO) | +| `created_at`| TIMESTAMP WITH TIME ZONE | Timestamp of record creation | + +--- + +### `cites_listings` + +| Column Name | Data Type (Inferred) | Notes | +| -------------- | -------------------- | ------------------------------------------- | +| `id` | UUID | Primary Key | +| `species_id` | UUID | Foreign Key referencing `species.id` | +| `appendix` | TEXT | CITES Appendix (e.g., I, II, III) | +| `listing_date` | DATE | Date the species was listed on the appendix | +| `notes` | TEXT | Notes regarding the CITES listing (nullable)| +| `is_current` | BOOLEAN | Indicates if the listing is current | + +--- + +### `cites_trade_records` + +| Column Name | Data Type (Inferred) | Notes | +| --------------- | -------------------- | ------------------------------------------- | +| `id` | UUID | Primary Key | +| `species_id` | UUID | Foreign Key referencing `species.id` | +| `record_id` | TEXT | Unique ID for the trade record | +| `year` | INTEGER | Year the trade occurred | +| `appendix` | TEXT | CITES Appendix at the time of trade | +| `taxon` | TEXT | Scientific name of the taxon in trade | +| `class` | TEXT | Taxonomic class | +| `order_name` | TEXT | Taxonomic order | +| `family` | TEXT | Taxonomic family | +| `genus` | TEXT | Taxonomic genus | +| `term` | TEXT | Description of the traded item (e.g., skins)| +| `quantity` | FLOAT | Quantity of the item traded | +| `unit` | TEXT | Unit of measurement for quantity (nullable) | +| `importer` | TEXT | Importing country code (ISO 2-letter) | +| `exporter` | TEXT | Exporting country code (ISO 2-letter) | +| `origin` | TEXT | Country of origin code (nullable) | +| `purpose` | TEXT | Purpose of trade code (e.g., P for Personal)| +| `source` | TEXT | Source of specimen code (e.g., W for Wild) | +| `reporter_type` | TEXT | E for Exporter, I for Importer | +| `import_permit` | TEXT | Import permit number (nullable) | +| `export_permit` | TEXT | Export permit number (nullable) | +| `origin_permit` | TEXT | Origin permit number (nullable) | + +--- + +### `common_names` + +| Column Name | Data Type (Inferred) | Notes | +| ----------- | -------------------- | ------------------------------------------- | +| `id` | UUID | Primary Key | +| `species_id`| UUID | Foreign Key referencing `species.id` | +| `name` | TEXT | Common name of the species | +| `language` | TEXT | Language code for the common name (e.g., eng)| +| `is_main` | BOOLEAN | Indicates if this is the primary common name| + +--- + +### `conservation_measures` + +Structure for this table could not be determined by querying a single record (the table might be empty or access is restricted in this manner). Further investigation or direct schema inspection is required. + +--- + +### `profiles` + +Structure for this table could not be determined by querying a single record (the table might be empty or access is restricted in this manner). Further investigation or direct schema inspection is required. + +--- + +### `distribution_ranges` + +Structure for this table could not be determined by querying a single record (the table might be empty or access is restricted in this manner). Further investigation or direct schema inspection is required. + +--- + +### `iucn_assessments` + +| Column Name | Data Type (Inferred) | Notes | +| ------------------------ | -------------------- | ------------------------------------------------------------ | +| `id` | UUID | Primary Key | +| `species_id` | UUID | Foreign Key referencing `species.id` | +| `year_published` | INTEGER | Year the IUCN assessment was published | +| `is_latest` | BOOLEAN | Indicates if this is the latest assessment for the species | +| `possibly_extinct` | BOOLEAN | Indicates if the species is possibly extinct | +| `possibly_extinct_in_wild` | BOOLEAN | Indicates if the species is possibly extinct in the wild | +| `status` | TEXT | IUCN Red List status code (e.g., LC, VU, EN) | +| `url` | TEXT | URL to the assessment on the IUCN Red List website | +| `assessment_id` | INTEGER | Unique ID for the assessment from IUCN | +| `scope_code` | TEXT | Code indicating the geographic scope of the assessment | +| `scope_description` | TEXT | Description of the geographic scope (e.g., Europe, Global) | + +--- + +### `species_threats` + +Structure for this table could not be determined by querying a single record (the table might be empty or access is restricted in this manner). Further investigation or direct schema inspection is required. + +--- + +### `subpopulations` + +| Column Name | Data Type (Inferred) | Notes | +| -------------------- | -------------------- | --------------------------------------------- | +| `id` | UUID | Primary Key | +| `species_id` | UUID | Foreign Key referencing `species.id` | +| `scientific_name` | TEXT | Full scientific name including subpopulation | +| `subpopulation_name` | TEXT | Name of the subpopulation | +| `sis_id` | INTEGER | Species Information Service ID (IUCN) | +| `authority` | TEXT | Authority who named the subpopulation (nullable)| + +--- + +### `timeline_events` + +| Column Name | Data Type (Inferred) | Notes | +| ------------- | -------------------- | ------------------------------------------------------------ | +| `id` | UUID | Primary Key | +| `species_id` | UUID | Foreign Key referencing `species.id` | +| `event_date` | DATE | Date of the event | +| `year` | INTEGER | Year of the event (can be derived from event_date) | +| `event_type` | TEXT | Type of event (e.g., iucn_assessment, cites_listing) | +| `title` | TEXT | Title of the event | +| `description` | TEXT | Description of the event (nullable) | +| `status` | TEXT | Status associated with the event (e.g., LC, Appendix II) (nullable) | +| `source_type` | TEXT | Type of the source table (e.g., iucn_assessments) (nullable) | +| `source_id` | UUID | Foreign Key to the source record (e.g., `iucn_assessments.id`) (nullable) | + +--- + +## Authentication (`auth` schema) + +Supabase provides a built-in authentication system that operates within its own `auth` schema. This schema is separate from the `public` schema detailed above but is integral to managing user identities, sessions, and access control. + +Key tables typically found in the `auth` schema include: + +* `users`: Stores user identity information (e.g., email, phone, password hash, user metadata). Each user is assigned a unique UUID. +* `sessions`: Manages active user sessions. +* `instances`: Information about authentication instances. +* `refresh_tokens`: Stores refresh tokens for maintaining sessions. +* `audit_log_entries`: Logs significant authentication events. + +While these tables are managed by Supabase, the `id` from `auth.users` is commonly used as a foreign key in `public` schema tables (like `profiles`) to link user-specific data to their authentication record. For example, a `profiles` table would typically have a `user_id` column that references `auth.users.id`. diff --git a/package-lock.json b/package-lock.json index 548c237..d699671 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,15 @@ "name": "arctic-species-tracker", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toast": "^1.2.13", "@react-pdf/renderer": "^4.3.0", "@supabase/supabase-js": "^2.39.7", "@tanstack/react-query": "^5.24.1", @@ -27,11 +31,13 @@ "lucide-react": "^0.338.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.52.1", "react-router-dom": "^6.22.1", "recharts": "^2.12.2", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20.11.20", @@ -913,6 +919,15 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1147,6 +1162,204 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.1.tgz", + "integrity": "sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.2", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "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-checkbox/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-checkbox/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-checkbox/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-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "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-checkbox/node_modules/@radix-ui/react-primitive": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.2.tgz", + "integrity": "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "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-checkbox/node_modules/@radix-ui/react-slot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", + "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "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-checkbox/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-checkbox/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/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "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-checkbox/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "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-collection": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", @@ -1465,6 +1678,101 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.6.tgz", + "integrity": "sha512-QzN9a36nKk2eZKMf9EBCia35x3TT+SOgZuzQBVIHyRrmYYi73VYBRK3zKwdJ6az/F5IZ6QlacGJBg7zfB85liA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.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-progress/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-progress/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-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.2.tgz", + "integrity": "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "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-progress/node_modules/@radix-ui/react-slot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", + "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "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-roving-focus": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", @@ -1587,6 +1895,308 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.13.tgz", + "integrity": "sha512-e/e43mQAwgYs8BY4y9l99xTK6ig1bK2uXsFLOMn9IZ16lAgulSTsotcPHVT2ZlSb/ye6Sllq7IgyDB8dGhpeXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.6", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.9", + "@radix-ui/react-portal": "1.1.8", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.2", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "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-toast/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-collection": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.6.tgz", + "integrity": "sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.2", + "@radix-ui/react-slot": "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-toast/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-toast/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-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.9.tgz", + "integrity": "sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.2", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "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-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.8.tgz", + "integrity": "sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.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-toast/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "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-toast/node_modules/@radix-ui/react-primitive": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.2.tgz", + "integrity": "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "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-toast/node_modules/@radix-ui/react-slot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", + "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "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-toast/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-toast/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-toast/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "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-toast/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/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.2.tgz", + "integrity": "sha512-ORCmRUbNiZIv6uV5mhFrhsIKw4UX/N3syZtyqvry61tbGm4JlgQuSn0hk5TwCARsCjkcnuRkSdCE3xfb+ADHew==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.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-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -1620,6 +2230,39 @@ } } }, + "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-effect-event/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/@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", @@ -5495,6 +6138,22 @@ "react": "^18.3.1" } }, + "node_modules/react-hook-form": { + "version": "7.56.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.4.tgz", + "integrity": "sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -6839,6 +7498,15 @@ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", "license": "MIT" + }, + "node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 2aeabb1..bc72451 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,15 @@ "deploy": "gh-pages -d dist" }, "dependencies": { + "@hookform/resolvers": "^3.9.0", + "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toast": "^1.2.13", "@react-pdf/renderer": "^4.3.0", "@supabase/supabase-js": "^2.39.7", "@tanstack/react-query": "^5.24.1", @@ -32,11 +36,13 @@ "lucide-react": "^0.338.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.52.1", "react-router-dom": "^6.22.1", "recharts": "^2.12.2", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20.11.20", diff --git a/src/App.tsx b/src/App.tsx index d201add..a3da978 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,76 @@ import { Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { AuthProvider } from './contexts/auth/AuthContext'; +import { AdminRoute } from './components/auth/AdminRoute'; + +// Public pages import { HomePage } from '@/pages/home'; +// import SpeciesDetail from './pages/species-detail'; // This will be created later + +// Admin pages +import AdminLogin from '@/pages/admin/login'; +import AdminDashboard from '@/pages/admin/dashboard'; +import SpeciesList from '@/pages/admin/species/list'; +import SpeciesEdit from '@/pages/admin/species/edit'; +import SpeciesCreate from '@/pages/admin/species/create'; +import CitesListingsList from '@/pages/admin/cites-listings/list'; +import CitesListingsEdit from '@/pages/admin/cites-listings/edit'; +import CitesListingsCreate from '@/pages/admin/cites-listings/create'; +import IucnAssessmentsList from '@/pages/admin/iucn-assessments/list'; +import IucnAssessmentsEdit from '@/pages/admin/iucn-assessments/edit'; +import IucnAssessmentsCreate from '@/pages/admin/iucn-assessments/create'; +import CommonNamesList from '@/pages/admin/common-names/list'; +import CommonNamesEdit from '@/pages/admin/common-names/edit'; +import CommonNamesCreate from '@/pages/admin/common-names/create'; +// ...other admin page imports + +const queryClient = new QueryClient(); function App() { return ( -
- - } /> - Page not found
} /> - - + + +
+ + {/* Public routes */} + } /> + {/* } /> */} + + {/* Admin authentication */} + } /> + + {/* Protected admin routes */} + }> + } /> + + {/* Species management */} + } /> + } /> + } /> + + {/* CITES listings management */} + } /> + } /> + } /> + + {/* IUCN Assessments management */} + } /> + } /> + } /> + + {/* Common Names management */} + } /> + } /> + } /> + + {/* Other admin routes... */} + + + Page not found
} /> + + +
+
); } diff --git a/src/components/admin/CitesListingForm.tsx b/src/components/admin/CitesListingForm.tsx new file mode 100644 index 0000000..f35bf86 --- /dev/null +++ b/src/components/admin/CitesListingForm.tsx @@ -0,0 +1,207 @@ +import { useState, useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Checkbox } from '@/components/ui/checkbox'; +import { CitesListing } from '@/services/adminApi'; + +// Validation schema +const citesListingSchema = z.object({ + species_id: z.string().min(1, 'Species ID is required'), + appendix: z.string().min(1, 'Appendix is required'), + listing_date: z.string().min(1, 'Listing date is required'), + notes: z.string().optional(), + is_current: z.boolean().default(false), +}); + +interface CitesListingFormProps { + initialData?: CitesListing; + onSubmit: (data: CitesListing) => void; + isSubmitting: boolean; + speciesId?: string; // Optional species ID for creating new listings from a species context +} + +export function CitesListingForm({ + initialData, + onSubmit, + isSubmitting, + speciesId +}: CitesListingFormProps) { + + // Prepare default values, using speciesId if provided and no initialData + const defaultValues = { + species_id: initialData?.species_id || speciesId || '', + appendix: initialData?.appendix || '', + listing_date: initialData?.listing_date + ? new Date(initialData.listing_date).toISOString().split('T')[0] + : '', + notes: initialData?.notes || '', + is_current: initialData?.is_current || false, + }; + + // Initialize form with react-hook-form + const form = useForm>({ + resolver: zodResolver(citesListingSchema), + defaultValues, + }); + + // Update form values if speciesId changes (useful for adding new listings from species page) + useEffect(() => { + if (speciesId && !initialData) { + form.setValue('species_id', speciesId); + } + }, [speciesId, form, initialData]); + + const handleSubmit = (values: z.infer) => { + // Convert form values to the expected data format + const formattedData: CitesListing = { + ...values, + // Ensure listing_date is in the correct format + listing_date: values.listing_date, + }; + + if (initialData?.id) { + formattedData.id = initialData.id; + } + + onSubmit(formattedData); + }; + + return ( +
+ + {/* Hidden species_id field for when provided via context */} + ( + + )} + /> + +
+ ( + + CITES Appendix* + + + The CITES appendix for this listing + + + + )} + /> + + ( + + Listing Date* + + + + + The date when this listing came into effect + + + + )} + /> +
+ + ( + + Notes + +