diff --git a/design-system.md b/design-system.md new file mode 100644 index 0000000..da5d65b --- /dev/null +++ b/design-system.md @@ -0,0 +1,76 @@ +# Design System - Plateforme d'Emploi + +## Principes Fondamentaux +- Minimalisme et simplicité +- Espace blanc généreux +- Typographie claire et lisible +- Hiérarchie visuelle forte +- Animations subtiles et fluides + +## Palette de Couleurs +- **Primaire** : + - Bleu Apple (#007AFF) + - Blanc (#FFFFFF) + - Noir (#000000) +- **Secondaire** : + - Gris clair (#F5F5F7) + - Gris moyen (#86868B) + - Gris foncé (#1D1D1F) +- **Accents** : + - Vert (#34C759) - pour les actions positives + - Rouge (#FF3B30) - pour les alertes/erreurs + +## Typographie +- **Titres** : SF Pro Display + - H1: 48px, 600 + - H2: 36px, 600 + - H3: 24px, 600 +- **Corps** : SF Pro Text + - Paragraphe: 16px, 400 + - Petit texte: 14px, 400 + - Légendes: 12px, 400 + +## Espacement +- **Base** : 8px +- **Petit** : 4px +- **Moyen** : 16px +- **Grand** : 24px +- **Très grand** : 32px + +## Composants Clés + +### Boutons +- **Primaire** : Fond bleu, texte blanc, coins arrondis (8px) +- **Secondaire** : Fond transparent, bordure bleue, texte bleu +- **Tertiaire** : Texte bleu uniquement + +### Cartes +- Ombre légère +- Coins arrondis (12px) +- Espacement intérieur généreux +- Transitions fluides au survol + +### Formulaires +- Champs avec bordures subtiles +- Labels flottants +- États de focus et d'erreur clairs +- Boutons d'action bien visibles + +### Navigation +- Barre de navigation fixe en haut +- Menu latéral pour les sections principales +- Indicateurs d'état clairs + +## Animations +- Transitions douces (0.3s) +- Effets de survol subtils +- Animations de chargement minimalistes +- Transitions de page fluides + +## Responsive Design +- Breakpoints : + - Mobile : < 768px + - Tablet : 768px - 1024px + - Desktop : > 1024px +- Adaptation fluide des composants +- Hiérarchie visuelle maintenue sur tous les écrans \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6639609 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,63 @@ +version: '3.8' +services: + db: + image: supabase/postgres:latest + container_name: supabase-db + ports: + - "5432:5432" + environment: + POSTGRES_PASSWORD: postgres + volumes: + - db_data:/var/lib/postgresql/data + + studio: + image: supabase/studio:latest + container_name: supabase-studio + ports: + - "3000:3000" + environment: + STUDIO_PG_META_URL: http://meta:8080 + POSTGRES_PASSWORD: postgres + + meta: + image: supabase/postgres-meta:latest + container_name: supabase-meta + ports: + - "8080:8080" + environment: + PG_META_PORT: 8080 + PG_META_DB_HOST: db + PG_META_DB_PASSWORD: postgres + + auth: + image: supabase/gotrue:latest + container_name: supabase-auth + ports: + - "9999:9999" + environment: + GOTRUE_API_HOST: 0.0.0.0 + GOTRUE_API_PORT: 9999 + API_EXTERNAL_URL: http://localhost:9999 + GOTRUE_DB_DRIVER: postgres + GOTRUE_DB_HOST: db + GOTRUE_DB_PORT: 5432 + GOTRUE_DB_NAME: postgres + GOTRUE_DB_USER: postgres + GOTRUE_DB_PASSWORD: postgres + GOTRUE_JWT_SECRET: your-super-secret-jwt-token-with-at-least-32-characters-long + GOTRUE_JWT_EXP: 3600 + GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + + rest: + image: postgrest/postgrest:latest + container_name: supabase-rest + ports: + - "3001:3000" + environment: + PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres + PGRST_DB_SCHEMA: public + PGRST_DB_ANON_ROLE: anon + PGRST_JWT_SECRET: your-super-secret-jwt-token-with-at-least-32-characters-long + +volumes: + db_data: \ No newline at end of file diff --git a/fdr.md b/fdr.md new file mode 100644 index 0000000..869ee3c --- /dev/null +++ b/fdr.md @@ -0,0 +1,68 @@ +Phase 1 : Infrastructure de Base et Parcours Candidat Initial + + Objectif Principal : Mettre en place l'architecture de base de l'application et permettre aux candidats de s'inscrire, de créer un profil simple et de rechercher des offres d'emploi via l'API France Travail. + Étapes : + Initialisation du Projet : Créer un nouveau projet Next.js. + Configuration de la Base de Données et de l'Authentification : Configurer Supabase pour la base de données et l'authentification des utilisateurs. + Installation des Librairies : Installer les librairies nécessaires : Zustand, Tailwind CSS, Material UI. + Structure du Projet : Créer la structure de base des dossiers et des composants. + Formulaires d'Inscription et de Connexion (Candidats) : Implémenter les interfaces pour l'inscription et la connexion des candidats. + Gestion des Sessions Utilisateur : Mettre en place la gestion des sessions pour les candidats connectés. + Formulaire de Création de Profil Candidat : Développer le formulaire permettant aux candidats de renseigner leurs informations essentielles (nom, prénom, e-mail, compétences techniques, niveau d'expérience, type de poste recherché, localisation souhaitée). + Sauvegarde du Profil Candidat : Implémenter la sauvegarde des informations du profil dans la base de données Supabase. + Interface de Recherche d'Offres : Créer une interface de recherche simple avec un champ de mots-clés et un filtre de localisation. + Intégration de l'API France Travail : Implémenter les appels à l'API France Travail pour récupérer les offres d'emploi tech. + Affichage des Résultats de Recherche : Afficher les offres d'emploi récupérées (titre, entreprise si disponible, localisation, brève description). + +Phase 2 : Système de Matching Basique et Score de Compatibilité Primaire + + Objectif Principal : Implémenter un algorithme de matching simple basé sur les compétences techniques et afficher un score de compatibilité basique. + Étapes : + Logique de Matching Initial : Implémenter l'algorithme comparant les compétences techniques du candidat avec les mots-clés des offres, en tenant compte du type de poste et de la localisation. + Calcul du Score de Compatibilité Primaire : Attribuer un score basé sur le nombre de compétences techniques communes. + Affichage du Score dans les Résultats : Afficher le score de compatibilité (par exemple, en pourcentage) à côté de chaque offre dans les résultats de recherche. + Page de Détail de l'Offre : Créer une page pour afficher toutes les informations détaillées d'une offre. + Récupération des Détails de l'Offre via l'API : Implémenter l'appel à l'API France Travail pour obtenir les détails d'une offre spécifique. + Affichage du Score de Compatibilité sur la Page de Détail : Afficher le score de compatibilité du candidat pour l'offre consultée. + +Phase 3 : Interface Recruteur Basique + + Objectif Principal : Permettre aux recruteurs (vous dans un premier temps) de visualiser les profils des candidats inscrits. + Étapes : + Système d'Authentification Recruteur : Mettre en place un système d'authentification distinct ou basé sur des rôles pour les recruteurs. + Tableau de Bord Recruteur : Développer l'interface affichant la liste des candidats inscrits avec leurs informations de base (nom, compétences principales, niveau d'expérience). + Filtrage des Candidats : Implémenter la possibilité pour les recruteurs de filtrer les candidats par compétences techniques. + Consultation du Profil Détail : Permettre aux recruteurs de cliquer sur un candidat pour afficher son profil complet. + +Phase 4 : Intégration du Test de Personnalité et Définition des Valeurs + + Objectif Principal : Ajouter la possibilité pour les candidats de réaliser un test de personnalité simple et de définir leurs valeurs. + Étapes : + Choix du Test de Personnalité : Sélectionner un questionnaire de personnalité court et pertinent. + Implémentation du Questionnaire : Intégrer l'affichage du questionnaire dans l'application. + Stockage des Résultats du Test : Sauvegarder les résultats du test de personnalité dans la base de données du candidat. + Affichage des Résultats du Test dans le Profil : Rendre les résultats du test visibles sur le profil du candidat. + Création de la Liste de Valeurs : Définir une liste de valeurs prédéfinies. + Sélection des Valeurs par le Candidat : Permettre aux candidats de sélectionner leurs valeurs importantes dans leur profil. + Stockage des Valeurs Sélectionnées : Sauvegarder les valeurs choisies par le candidat dans la base de données. + +Phase 5 : Historique des Offres Consultées + + Objectif Principal : Permettre aux candidats de voir un historique des offres qu'ils ont consultées. + Étapes : + Création de la Table "OffresConsultées" : Définir la structure de la table dans la base de données pour enregistrer les offres consultées. + Enregistrement des Consultations : Implémenter la logique pour enregistrer chaque fois qu'un candidat consulte une offre détaillée. + Page d'Historique des Offres : Créer une page dans l'espace candidat pour afficher la liste des offres consultées, avec la date de consultation et un lien vers l'offre. + +Itérations et Améliorations Futures : + + Matching basé sur les valeurs. + Suggestions d'amélioration (formations). + Fonctionnalités avancées pour les recruteurs (gestion des candidatures, publication d'offres). + Intégration de l'IA. + +Points Importants : + + Logique Séquentielle : Les étapes sont numérotées pour refléter un ordre logique de développement, mais certaines étapes au sein d'une même phase peuvent être réalisées en parallèle. + Flexibilité : Cette liste d'étapes peut être ajustée en fonction de vos besoins et des défis rencontrés. + Tests : N'oubliez pas de prévoir des tests après chaque étape ou groupe d'étapes pour garantir la qualité. \ No newline at end of file diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..de1fc22 --- /dev/null +++ b/init.sql @@ -0,0 +1,47 @@ +-- Enable the pgcrypto extension for UUID generation +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +-- Create profiles table +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, + first_name TEXT, + last_name TEXT, + email TEXT UNIQUE, + skills TEXT[], + experience_level TEXT, + job_type TEXT[], + location TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL +); + +-- Create RLS policies +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view their own profile" + ON profiles FOR SELECT + USING (auth.uid() = user_id); + +CREATE POLICY "Users can update their own profile" + ON profiles FOR UPDATE + USING (auth.uid() = user_id); + +CREATE POLICY "Users can insert their own profile" + ON profiles FOR INSERT + WITH CHECK (auth.uid() = user_id); + +-- Create function to handle new user signup +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (user_id, email) + VALUES (new.id, new.email); + RETURN new; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Create trigger for new user signup +CREATE OR REPLACE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e7ba378..046ceac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,18 @@ "name": "job", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.2", + "@mui/material": "^7.0.2", + "@supabase/auth-helpers-nextjs": "^0.10.0", + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.49.4", + "framer-motion": "^12.7.3", "next": "15.3.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -37,6 +46,148 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.1.tgz", @@ -70,6 +221,152 @@ "tslib": "^2.4.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.0.tgz", @@ -664,6 +961,299 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.0.2.tgz", + "integrity": "sha512-TfeFU9TgN1N06hyb/pV/63FfO34nijZRMqgHk0TJ3gkl4Fbd+wZ73+ZtOd7jag6hMmzO9HSrBc6Vdn591nhkAg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.0.2.tgz", + "integrity": "sha512-Bo57PFLOqXOqPNrXjd8AhzH5s6TCsNUQbvnQ0VKZ8D+lIlteqKnrk/O1luMJUc/BXONK7BfIdTdc7qOnXYbMdw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.0.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.0.2.tgz", + "integrity": "sha512-rjJlJ13+3LdLfobRplkXbjIFEIkn6LgpetgU/Cs3Xd8qINCCQK9qXQIjjQ6P0FXFTPFzEVMj0VgBR1mN+FhOcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/core-downloads-tracker": "^7.0.2", + "@mui/system": "^7.0.2", + "@mui/types": "^7.4.1", + "@mui/utils": "^7.0.2", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.0.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.0.2.tgz", + "integrity": "sha512-6lt8heDC9wN8YaRqEdhqnm0cFCv08AMf4IlttFvOVn7ZdKd81PNpD/rEtPGLLwQAFyyKSxBG4/2XCgpbcdNKiA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/utils": "^7.0.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.0.2.tgz", + "integrity": "sha512-11Bt4YdHGlh7sB8P75S9mRCUxTlgv7HGbr0UKz6m6Z9KLeiw1Bm9y/t3iqLLVMvSHYB6zL8X8X+LmfTE++gyBw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.0.2.tgz", + "integrity": "sha512-yFUraAWYWuKIISPPEVPSQ1NLeqmTT4qiQ+ktmyS8LO/KwHxB+NNVOacEZaIofh5x1NxY8rzphvU5X2heRZ/RDA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/private-theming": "^7.0.2", + "@mui/styled-engine": "^7.0.2", + "@mui/types": "^7.4.1", + "@mui/utils": "^7.0.2", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.1.tgz", + "integrity": "sha512-gUL8IIAI52CRXP/MixT1tJKt3SI6tVv4U/9soFsTtAsHzaJQptZ42ffdHZV3niX1ei0aUgMvOxBBN0KYqdG39g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.2.tgz", + "integrity": "sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/types": "^7.4.1", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz", @@ -869,6 +1459,16 @@ "node": ">=12.4.0" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -883,6 +1483,119 @@ "dev": true, "license": "MIT" }, + "node_modules/@supabase/auth-helpers-nextjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.10.0.tgz", + "integrity": "sha512-2dfOGsM4yZt0oS4TPiE7bD4vf7EVz7NRz/IJrV6vLg0GP7sMUx8wndv2euLGq4BjN9lUCpu6DG/uCC8j+ylwPg==", + "deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.", + "license": "MIT", + "dependencies": { + "@supabase/auth-helpers-shared": "0.7.0", + "set-cookie-parser": "^2.6.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.39.8" + } + }, + "node_modules/@supabase/auth-helpers-shared": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.7.0.tgz", + "integrity": "sha512-FBFf2ei2R7QC+B/5wWkthMha8Ca2bWHAndN+syfuEUUfufv4mLcAgBCcgNg5nJR8L0gZfyuaxgubtOc9aW3Cpg==", + "deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.", + "license": "MIT", + "dependencies": { + "jose": "^4.14.4" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.39.8" + } + }, + "node_modules/@supabase/auth-js": { + "version": "2.69.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", + "integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.6.1.tgz", + "integrity": "sha512-QtQgEMvaDzr77Mk3vZ3jWg2/y+D8tExYF7vcJT+wQ8ysuvOeGGjYbZlvj5bHYsj/SpC0bihcisnwPrM4Gp5G4g==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.43.4" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.4", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.4.tgz", + "integrity": "sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.69.1", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -1202,17 +1915,33 @@ "version": "20.17.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1228,6 +1957,24 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.30.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", @@ -1975,6 +2722,21 @@ "node": ">= 0.4" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2071,7 +2833,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2120,6 +2881,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2172,6 +2942,37 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "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": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2191,7 +2992,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2259,7 +3059,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2339,6 +3138,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2375,6 +3184,21 @@ "node": ">=10.13.0" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -2553,7 +3377,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -3075,6 +3898,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3129,11 +3958,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/framer-motion": { + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.7.3.tgz", + "integrity": "sha512-dNT4l5gEnUo2ytXLUBUf6AI21dZ77TMclDKE3ElaIHZ8m90nJ/NCcExW51zdSIaS0RhAS5iXcF7bEIxZe8XG2g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.7.3", + "motion-utils": "^12.7.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3395,7 +4250,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3404,6 +4258,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3418,7 +4281,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3561,7 +4423,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -3893,11 +4754,19 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -3913,6 +4782,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3920,6 +4801,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4246,6 +5133,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4273,7 +5166,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -4339,11 +5231,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.3.tgz", + "integrity": "sha512-IjMt1YJHrvyvruFvmpmd6bGXXGCvmygrnvSb3aZ8KhOzF4H3PulU+cMBzH+U8TBJHjC/mnmJFRIA1Cu4vBfcBA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.7.2" + } + }, + "node_modules/motion-utils": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.7.2.tgz", + "integrity": "sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4457,7 +5363,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4648,7 +5553,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -4657,6 +5561,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4681,9 +5603,17 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4756,7 +5686,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -4820,9 +5749,24 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4846,6 +5790,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -4871,7 +5821,6 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -4892,7 +5841,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -5017,6 +5965,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "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", @@ -5216,6 +6170,15 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5399,6 +6362,12 @@ } } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5416,7 +6385,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5500,6 +6468,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5660,7 +6634,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -5701,6 +6674,22 @@ "punycode": "^2.1.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5816,6 +6805,36 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5828,6 +6847,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 66db16c..e9c79c4 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,28 @@ "lint": "next lint" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.2", + "@mui/material": "^7.0.2", + "@supabase/auth-helpers-nextjs": "^0.10.0", + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.49.4", + "framer-motion": "^12.7.3", + "next": "15.3.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "next": "15.3.0" + "zustand": "^5.0.3" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", "eslint-config-next": "15.3.0", - "@eslint/eslintrc": "^3" + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/pitch.md b/pitch.md new file mode 100644 index 0000000..344d168 --- /dev/null +++ b/pitch.md @@ -0,0 +1 @@ +L'application que je souhaite créer est une plateforme d'emploi innovante conçue pour aller au-delà de la simple mise en relation par mots-clés. En s'inspirant de plateformes comme HelloWork, notre solution intègre un système de matching basique et un score de compatibilité primaire dès la première phase. L'objectif est de rapidement identifier les offres qui correspondent le mieux aux compétences techniques et aux préférences des candidats. Dans les phases ultérieures, nous enrichirons l'expérience avec un test de personnalité simple et la possibilité pour les candidats de définir leurs valeurs, afin d'améliorer la pertinence du matching avec la culture et les attentes des entreprises. Une interface dédiée permettra aux recruteurs de visualiser les profils des candidats et d'accéder à ces informations pour des recrutements plus éclairés. \ No newline at end of file diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx new file mode 100644 index 0000000..e1e2fd1 --- /dev/null +++ b/src/app/auth/login/page.tsx @@ -0,0 +1,5 @@ +import LoginForm from '@/components/auth/LoginForm'; + +export default function LoginPage() { + return ; +} \ No newline at end of file diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx new file mode 100644 index 0000000..f447818 --- /dev/null +++ b/src/app/auth/register/page.tsx @@ -0,0 +1,5 @@ +import RegisterForm from '@/components/auth/RegisterForm'; + +export default function RegisterPage() { + return ; +} \ No newline at end of file diff --git a/src/app/components/ElegantButton.tsx b/src/app/components/ElegantButton.tsx new file mode 100644 index 0000000..577fead --- /dev/null +++ b/src/app/components/ElegantButton.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { Button, ButtonProps } from '@mui/material'; +import { motion } from 'framer-motion'; + +interface ElegantButtonProps extends Omit { + variant?: 'primary' | 'secondary' | 'tertiary'; +} + +export default function ElegantButton({ + children, + variant = 'primary', + sx, + ...props +}: ElegantButtonProps) { + const getVariantStyles = () => { + switch (variant) { + case 'primary': + return { + backgroundColor: '#007AFF', + color: 'white', + '&:hover': { + backgroundColor: '#0062cc', + transform: 'translateY(-2px)', + boxShadow: '0 4px 12px rgba(0, 122, 255, 0.3)', + }, + }; + case 'secondary': + return { + backgroundColor: 'transparent', + color: '#007AFF', + border: '1px solid #007AFF', + '&:hover': { + backgroundColor: 'rgba(0, 122, 255, 0.05)', + transform: 'translateY(-2px)', + }, + }; + case 'tertiary': + return { + backgroundColor: 'transparent', + color: '#007AFF', + '&:hover': { + backgroundColor: 'rgba(0, 122, 255, 0.05)', + }, + }; + default: + return {}; + } + }; + + return ( + + + + ); +} \ No newline at end of file diff --git a/src/app/components/Header.tsx b/src/app/components/Header.tsx new file mode 100644 index 0000000..6827c93 --- /dev/null +++ b/src/app/components/Header.tsx @@ -0,0 +1,204 @@ +'use client'; + +import { AppBar, Toolbar, Button, Typography, Box, IconButton, Menu, MenuItem, useMediaQuery, useTheme } from '@mui/material'; +import Link from 'next/link'; +import MenuIcon from '@mui/icons-material/Menu'; +import WorkOutlineIcon from '@mui/icons-material/WorkOutline'; +import BusinessCenterIcon from '@mui/icons-material/BusinessCenter'; +import SchoolIcon from '@mui/icons-material/School'; +import { useState } from 'react'; + +export default function Header() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const [mobileMenuAnchor, setMobileMenuAnchor] = useState(null); + + const handleMobileMenuOpen = (event: React.MouseEvent) => { + setMobileMenuAnchor(event.currentTarget); + }; + + const handleMobileMenuClose = () => { + setMobileMenuAnchor(null); + }; + + const navItems = [ + { label: 'Offres', icon: , href: '/offres' }, + { label: 'Entreprises', icon: , href: '/entreprises' }, + { label: 'Formations', icon: , href: '/formations' }, + ]; + + return ( + + + + + JobMatch + + + {!isMobile && ( + + {navItems.map((item) => ( + + ))} + + )} + + + + {isMobile ? ( + <> + + + + + {navItems.map((item) => ( + + + {item.icon} + {item.label} + + + ))} + + Inscription + + + Connexion + + + + ) : ( + <> + + + + )} + + + + ); +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..88b709e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { Providers } from './providers'; const geistSans = Geist({ variable: "--font-geist-sans", @@ -13,21 +14,19 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "JobMatch - Plateforme d'emploi intelligente", + description: "Trouvez votre prochain emploi tech avec une approche intelligente basée sur vos compétences et aspirations", }; export default function RootLayout({ children, -}: Readonly<{ +}: { children: React.ReactNode; -}>) { +}) { return ( - - - {children} + + + {children} ); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..12ccbf5 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,137 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Box, Container, Typography, TextField, Button, Link as MuiLink } from '@mui/material'; +import Link from 'next/link'; +import { supabase } from '@/lib/supabase'; +import { useAuthStore } from '@/store/auth'; + +export default function Login() { + const router = useRouter(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const { setUser } = useAuthStore(); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setIsLoading(true); + + try { + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error) throw error; + + setUser(data.user); + router.push('/profile'); + } catch (error) { + setError('Email ou mot de passe incorrect'); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + Connexion + + + + setEmail(e.target.value)} + required + fullWidth + error={!!error} + helperText={error} + /> + + setPassword(e.target.value)} + required + fullWidth + /> + + + + + Pas encore de compte ?{' '} + + Créer un compte + + + + + + ); +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index e68abe6..55a9a79 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,103 +1,200 @@ -import Image from "next/image"; +'use client'; + +import { Typography, Box, Container, TextField, InputAdornment, Chip } from '@mui/material'; +import Header from './components/Header'; +import ElegantButton from './components/ElegantButton'; +import { motion } from 'framer-motion'; +import SearchIcon from '@mui/icons-material/Search'; +import LocationOnIcon from '@mui/icons-material/LocationOn'; + +const contractTypes = [ + 'CDI', + 'CDD', + 'Stage', + 'Alternance', + 'Freelance', +]; export default function Home() { return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
- - -
- -
+ + + Trouvez votre prochain emploi tech avec une approche intelligente + + + + + + Découvrez des opportunités qui correspondent vraiment à vos compétences et aspirations + + + + + + + + + ), + sx: { + borderRadius: '12px', + backgroundColor: 'white', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + }, + boxShadow: '0 2px 8px rgba(0,0,0,0.05)', + } + }} + /> + + + + ), + sx: { + borderRadius: '12px', + backgroundColor: 'white', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + }, + boxShadow: '0 2px 8px rgba(0,0,0,0.05)', + } + }} + /> + + + {contractTypes.map((type, index) => ( + + + + ))} + + + Rechercher des offres + + + + + + ); } diff --git a/src/app/providers.tsx b/src/app/providers.tsx new file mode 100644 index 0000000..65ca409 --- /dev/null +++ b/src/app/providers.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { ThemeProvider } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import theme from './theme'; +import { AuthProvider } from '@/contexts/AuthContext'; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + ); +} \ No newline at end of file diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx new file mode 100644 index 0000000..f37d623 --- /dev/null +++ b/src/app/signup/page.tsx @@ -0,0 +1,153 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Box, Container, Typography, TextField, Button, Link as MuiLink } from '@mui/material'; +import Link from 'next/link'; +import { supabase } from '@/lib/supabase'; +import { useAuthStore } from '@/store/auth'; + +export default function Signup() { + const router = useRouter(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const { setUser } = useAuthStore(); + + const handleSignup = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + if (password !== confirmPassword) { + setError('Les mots de passe ne correspondent pas'); + return; + } + + setIsLoading(true); + + try { + const { data, error } = await supabase.auth.signUp({ + email, + password, + }); + + if (error) throw error; + + setUser(data.user); + router.push('/profile'); + } catch (error) { + setError('Une erreur est survenue lors de l\'inscription'); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + Créer un compte + + + + setEmail(e.target.value)} + required + fullWidth + error={!!error} + helperText={error} + /> + + setPassword(e.target.value)} + required + fullWidth + /> + + setConfirmPassword(e.target.value)} + required + fullWidth + /> + + + + + Déjà un compte ?{' '} + + Se connecter + + + + + + ); +} \ No newline at end of file diff --git a/src/app/theme.ts b/src/app/theme.ts new file mode 100644 index 0000000..4849ad0 --- /dev/null +++ b/src/app/theme.ts @@ -0,0 +1,64 @@ +import { createTheme } from '@mui/material/styles'; + +const theme = createTheme({ + palette: { + primary: { + main: '#007AFF', + light: '#4da3ff', + dark: '#0062cc', + }, + secondary: { + main: '#86868B', + light: '#a3a3a7', + dark: '#6a6a6e', + }, + background: { + default: '#FFFFFF', + paper: '#F5F5F7', + }, + text: { + primary: '#000000', + secondary: '#86868B', + }, + }, + typography: { + fontFamily: '"SF Pro Display", "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + h1: { + fontSize: '3rem', + fontWeight: 600, + }, + h2: { + fontSize: '2.25rem', + fontWeight: 600, + }, + h3: { + fontSize: '1.5rem', + fontWeight: 600, + }, + body1: { + fontSize: '1rem', + lineHeight: 1.5, + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: 'none', + borderRadius: '8px', + fontWeight: 500, + }, + }, + }, + MuiAppBar: { + styleOverrides: { + root: { + backgroundColor: '#FFFFFF', + color: '#000000', + }, + }, + }, + }, +}); + +export default theme; \ No newline at end of file diff --git a/src/components/AuthGuard.tsx b/src/components/AuthGuard.tsx new file mode 100644 index 0000000..8aa560b --- /dev/null +++ b/src/components/AuthGuard.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuthStore } from '@/store/auth'; +import { supabase } from '@/lib/supabase'; +import { Box, CircularProgress } from '@mui/material'; + +export default function AuthGuard({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const { user, setUser, isLoading, setIsLoading } = useAuthStore(); + + useEffect(() => { + const checkUser = async () => { + try { + const { data: { session } } = await supabase.auth.getSession(); + setUser(session?.user ?? null); + } catch (error) { + console.error('Error checking session:', error); + setUser(null); + } finally { + setIsLoading(false); + } + }; + + checkUser(); + + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setUser(session?.user ?? null); + }); + + return () => { + subscription.unsubscribe(); + }; + }, [setUser, setIsLoading]); + + if (isLoading) { + return ( + + + + ); + } + + if (!user) { + router.push('/login'); + return null; + } + + return <>{children}; +} \ No newline at end of file diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx new file mode 100644 index 0000000..aaa2777 --- /dev/null +++ b/src/components/auth/LoginForm.tsx @@ -0,0 +1,98 @@ +'use client'; + +import { useState } from 'react'; +import { useAuth } from '@/contexts/AuthContext'; +import { + Box, + Button, + TextField, + Typography, + Alert, + CircularProgress, +} from '@mui/material'; +import Link from 'next/link'; + +export default function LoginForm() { + const { signIn } = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setLoading(true); + + try { + await signIn(email, password); + } catch (err) { + setError(err instanceof Error ? err.message : 'Une erreur est survenue'); + } finally { + setLoading(false); + } + }; + + return ( + + + Connexion + + + {error && ( + setError(null)}> + {error} + + )} + + setEmail(e.target.value)} + required + fullWidth + /> + + setPassword(e.target.value)} + required + fullWidth + /> + + + + + Pas encore de compte ?{' '} + + + + + + ); +} \ No newline at end of file diff --git a/src/components/auth/LogoutButton.tsx b/src/components/auth/LogoutButton.tsx new file mode 100644 index 0000000..cb78846 --- /dev/null +++ b/src/components/auth/LogoutButton.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { useAuth } from '@/contexts/AuthContext'; +import { Button } from '@mui/material'; +import { useRouter } from 'next/navigation'; + +export default function LogoutButton() { + const { signOut } = useAuth(); + const router = useRouter(); + + const handleLogout = async () => { + try { + await signOut(); + router.push('/auth/login'); + } catch (error) { + console.error('Erreur lors de la déconnexion:', error); + } + }; + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/auth/RegisterForm.tsx b/src/components/auth/RegisterForm.tsx new file mode 100644 index 0000000..8e4e084 --- /dev/null +++ b/src/components/auth/RegisterForm.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { useState } from 'react'; +import { useAuth } from '@/contexts/AuthContext'; +import { + Box, + Button, + TextField, + Typography, + Alert, + CircularProgress, +} from '@mui/material'; +import Link from 'next/link'; + +export default function RegisterForm() { + const { signUp } = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + if (password !== confirmPassword) { + setError('Les mots de passe ne correspondent pas'); + return; + } + + setLoading(true); + + try { + await signUp(email, password); + setSuccess(true); + } catch (err) { + setError(err instanceof Error ? err.message : 'Une erreur est survenue'); + } finally { + setLoading(false); + } + }; + + if (success) { + return ( + + + Un email de confirmation a été envoyé à {email} + + + + + + ); + } + + return ( + + + Inscription + + + {error && ( + setError(null)}> + {error} + + )} + + setEmail(e.target.value)} + required + fullWidth + /> + + setPassword(e.target.value)} + required + fullWidth + /> + + setConfirmPassword(e.target.value)} + required + fullWidth + /> + + + + + Déjà un compte ?{' '} + + + + + + ); +} \ No newline at end of file diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..64614fd --- /dev/null +++ b/src/contexts/AuthContext.tsx @@ -0,0 +1,74 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import { User } from '@supabase/supabase-js'; +import { supabase } from '@/lib/supabase'; + +type AuthContextType = { + user: User | null; + loading: boolean; + signIn: (email: string, password: string) => Promise; + signUp: (email: string, password: string) => Promise; + signOut: () => Promise; +}; + +const AuthContext = createContext({ + user: null, + loading: true, + signIn: async () => {}, + signUp: async () => {}, + signOut: async () => {}, +}); + +export const AuthProvider = ({ children }: { children: React.ReactNode }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Vérifier la session active + const checkSession = async () => { + const { data: { session } } = await supabase.auth.getSession(); + setUser(session?.user ?? null); + setLoading(false); + }; + + checkSession(); + + // Écouter les changements d'authentification + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setUser(session?.user ?? null); + setLoading(false); + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); + + const signIn = async (email: string, password: string) => { + const { error } = await supabase.auth.signInWithPassword({ email, password }); + if (error) throw error; + }; + + const signUp = async (email: string, password: string) => { + const { error } = await supabase.auth.signUp({ email, password }); + if (error) throw error; + }; + + const signOut = async () => { + const { error } = await supabase.auth.signOut(); + if (error) throw error; + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..5876b4b --- /dev/null +++ b/src/lib/supabase.ts @@ -0,0 +1,6 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'http://localhost:8000'; +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'your-anon-key'; + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); \ No newline at end of file diff --git a/src/store/auth.ts b/src/store/auth.ts new file mode 100644 index 0000000..d805f32 --- /dev/null +++ b/src/store/auth.ts @@ -0,0 +1,16 @@ +import { create } from 'zustand'; +import { User } from '@supabase/supabase-js'; + +interface AuthState { + user: User | null; + setUser: (user: User | null) => void; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; +} + +export const useAuthStore = create((set) => ({ + user: null, + setUser: (user) => set({ user }), + isLoading: true, + setIsLoading: (isLoading) => set({ isLoading }), +})); \ No newline at end of file diff --git a/start-supabase.sh b/start-supabase.sh new file mode 100755 index 0000000..373996d --- /dev/null +++ b/start-supabase.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Start Supabase services +docker-compose up -d + +# Wait for services to be ready +echo "Waiting for services to be ready..." +sleep 10 + +# Initialize the database +echo "Initializing database..." +docker exec -i supabase-db psql -U postgres -d postgres < init.sql + +echo "Supabase is ready!" +echo "Studio: http://localhost:3000" +echo "API: http://localhost:3001" \ No newline at end of file diff --git a/supabase b/supabase new file mode 160000 index 0000000..8491409 --- /dev/null +++ b/supabase @@ -0,0 +1 @@ +Subproject commit 84914090f9c06973b788a63edf8a3e14ae2a77fc diff --git a/wireframes.md b/wireframes.md new file mode 100644 index 0000000..a99ea11 --- /dev/null +++ b/wireframes.md @@ -0,0 +1,147 @@ +# Maquettes - Phase 1 + +## Page d'Accueil +``` ++------------------------------------------+ +| Logo Inscription Connexion | ++------------------------------------------+ +| | +| Trouvez votre prochain emploi tech | +| avec une approche intelligente | +| | +| [Rechercher des offres] | +| | +| +----------------------------------+ | +| | Découvrez comment nous | | +| | révolutionnons le recrutement | | +| +----------------------------------+ | +| | ++------------------------------------------+ +``` + +## Page d'Inscription +``` ++------------------------------------------+ +| Logo Retour | ++------------------------------------------+ +| | +| Créez votre compte | +| | +| [Email] | +| [Mot de passe] | +| [Confirmer le mot de passe] | +| | +| [Créer mon compte] | +| | +| Déjà un compte ? [Se connecter] | +| | ++------------------------------------------+ +``` + +## Page de Connexion +``` ++------------------------------------------+ +| Logo Retour | ++------------------------------------------+ +| | +| Connectez-vous | +| | +| [Email] | +| [Mot de passe] | +| | +| [Se connecter] | +| | +| Mot de passe oublié ? | +| | +| Pas encore de compte ? [S'inscrire] | +| | ++------------------------------------------+ +``` + +## Formulaire de Profil +``` ++------------------------------------------+ +| Logo Menu | ++------------------------------------------+ +| | +| Complétez votre profil | +| | +| Informations personnelles | +| [Nom] | +| [Prénom] | +| [Email] | +| | +| Compétences | +| [Ajouter une compétence] | +| - Compétence 1 | +| - Compétence 2 | +| | +| Expérience | +| [Niveau d'expérience] | +| [Type de poste recherché] | +| [Localisation souhaitée] | +| | +| [Enregistrer mon profil] | +| | ++------------------------------------------+ +``` + +## Page de Recherche +``` ++------------------------------------------+ +| Logo Profil Déconnexion | ++------------------------------------------+ +| | +| [Rechercher...] | +| | +| Filtres | +| [Localisation] | +| [Type de contrat] | +| [Niveau d'expérience] | +| | +| Résultats | +| +----------------------------------+ | +| | Développeur Full Stack | | +| | Paris - CDI | | +| | Score de compatibilité: 85% | | +| +----------------------------------+ | +| | +| +----------------------------------+ | +| | UX Designer | | +| | Lyon - CDI | | +| | Score de compatibilité: 72% | | +| +----------------------------------+ | +| | ++------------------------------------------+ +``` + +## Page de Détail d'Offre +``` ++------------------------------------------+ +| Logo Retour | ++------------------------------------------+ +| | +| Développeur Full Stack | +| Paris - CDI | +| | +| Score de compatibilité: 85% | +| | +| Description | +| Lorem ipsum dolor sit amet... | +| | +| Compétences requises | +| - JavaScript | +| - React | +| - Node.js | +| | +| [Postuler] | +| | ++------------------------------------------+ +``` + +## Notes de Design +- Utilisation de la palette de couleurs définie dans le design system +- Espacement cohérent selon les guidelines +- Typographie SF Pro pour tous les textes +- Animations fluides sur les interactions +- Design responsive adapté à tous les écrans \ No newline at end of file