From 91fd958fbaf2151d2f4f3f667246d50b91afb7ba Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Wed, 26 Mar 2025 21:21:18 +0100 Subject: [PATCH] Finish login handling --- .cursor/mcp.json | 7 +++++ ui/.gitignore | 2 ++ ui/src/App.tsx | 6 ++-- ui/src/components/ProtectedRoute.tsx | 11 ++++--- ui/src/contexts/AuthContext.tsx | 28 ----------------- ui/src/contexts/SessionContext.tsx | 40 +++++++++++++++++++++++ ui/src/pages/NotFoundPage.tsx | 39 +++++++++++++++++++++++ ui/src/pages/signup.tsx | 47 +++++++++++++++++++++++++--- ui/src/pages/tablo.tsx | 9 +++--- 9 files changed, 144 insertions(+), 45 deletions(-) create mode 100644 .cursor/mcp.json delete mode 100644 ui/src/contexts/AuthContext.tsx create mode 100644 ui/src/contexts/SessionContext.tsx create mode 100644 ui/src/pages/NotFoundPage.tsx diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..18cd9b7 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "Datadog Extension": { + "url": "http://localhost:5594/sse" + } + } +} \ No newline at end of file diff --git a/ui/.gitignore b/ui/.gitignore index a547bf3..1e2d60c 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -22,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? + +.cursor diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 40df9cf..3bc3436 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -7,12 +7,12 @@ import { ResetPasswordPage } from "./pages/reset-password"; import { LandingPage } from "./pages/landing"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { TabloPage } from "./pages/tablo"; -import { AuthProvider } from "./contexts/AuthContext"; +import { SessionProvider } from "./contexts/SessionContext"; export const App = () => { return ( - +
{
-
+
); }; diff --git a/ui/src/components/ProtectedRoute.tsx b/ui/src/components/ProtectedRoute.tsx index 5e0a166..2547719 100644 --- a/ui/src/components/ProtectedRoute.tsx +++ b/ui/src/components/ProtectedRoute.tsx @@ -1,14 +1,15 @@ import { ReactNode } from "react"; +import { useSession } from "../contexts/SessionContext"; +import { Navigate } from "react-router-dom"; interface ProtectedRouteProps { children: ReactNode; } export const ProtectedRoute = ({ children }: ProtectedRouteProps) => { - // const { isAuthenticated } = useAuth(); - // if (!isAuthenticated) { - // // Redirect to login page if user is not authenticated - // return ; - // } + const { session } = useSession(); + if (!session) { + return ; + } // If authenticated, render the protected component return <>{children}; diff --git a/ui/src/contexts/AuthContext.tsx b/ui/src/contexts/AuthContext.tsx deleted file mode 100644 index 728bbac..0000000 --- a/ui/src/contexts/AuthContext.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { createContext, useContext, ReactNode } from "react"; -import { useGetCurrentUser, User } from "../hooks/auth"; - -interface AuthContextType { - user: User | null; - isAuthenticated: boolean; -} - -const AuthContext = createContext(undefined); - -export function AuthProvider({ children }: { children: ReactNode }) { - const { user } = useGetCurrentUser(); - - const value = { - user, - isAuthenticated: !!user, - }; - - return {children}; -} - -export function useAuth() { - const context = useContext(AuthContext); - if (context === undefined) { - throw new Error("useAuth must be used within an AuthProvider"); - } - return context; -} diff --git a/ui/src/contexts/SessionContext.tsx b/ui/src/contexts/SessionContext.tsx new file mode 100644 index 0000000..b924ce4 --- /dev/null +++ b/ui/src/contexts/SessionContext.tsx @@ -0,0 +1,40 @@ +import { createContext, useContext, useEffect, useState } from "react"; +import { Session } from "@supabase/supabase-js"; +import { supabase } from "../hooks/auth"; + +const SessionContext = createContext<{ + session: Session | null; +}>({ + session: null, +}); + +export const useSession = () => { + const context = useContext(SessionContext); + if (!context) { + throw new Error("useSession must be used within a SessionProvider"); + } + return context; +}; + +type Props = { children: React.ReactNode }; +export const SessionProvider = ({ children }: Props) => { + const [session, setSession] = useState(null); + + useEffect(() => { + const authStateListener = supabase.auth.onAuthStateChange( + async (_, session) => { + setSession(session); + } + ); + + return () => { + authStateListener.data.subscription.unsubscribe(); + }; + }, [supabase]); + + return ( + + {children} + + ); +}; diff --git a/ui/src/pages/NotFoundPage.tsx b/ui/src/pages/NotFoundPage.tsx new file mode 100644 index 0000000..3bd5ba4 --- /dev/null +++ b/ui/src/pages/NotFoundPage.tsx @@ -0,0 +1,39 @@ +import { twMerge } from "tailwind-merge"; +import { Button } from "../ui-library/button"; +import { useNavigate } from "react-router-dom"; + +export const NotFoundPage = () => { + const navigate = useNavigate(); + return ( +
+
+
+

+ 404 - Page Not Found +

+
+
+
+
+
+

+ Oups ! Page introuvable +

+

+ La page que vous recherchez n'existe pas ou a été déplacée. +

+ +
+
+
+
+ ); +}; diff --git a/ui/src/pages/signup.tsx b/ui/src/pages/signup.tsx index 638a349..93feb3a 100644 --- a/ui/src/pages/signup.tsx +++ b/ui/src/pages/signup.tsx @@ -10,7 +10,8 @@ import { Text } from "../ui-library/text"; export function SignUpPage() { const navigate = useNavigate(); - const { mutate: signUp, isPending, errors } = useSignUp(); + const { mutate: signUp, isPending } = useSignUp(); + const [errors, setErrors] = useState>({}); const [formData, setFormData] = useState({ email: "", @@ -22,9 +23,43 @@ export function SignUpPage() { business_name: "", }); + const validateForm = () => { + const errors: Record = {}; + + // Business name validation + if (formData.business_name.length < 3) { + errors.business_name = + "Le nom de l'entreprise doit contenir au moins 3 caractères"; + } + + // Password length validation + if (formData.password.length < 8) { + errors.password = "Le mot de passe doit contenir au moins 8 caractères"; + } + + // Password match validation + if (formData.password !== formData.confirmPassword) { + errors.confirmPassword = "Les mots de passe ne correspondent pas"; + } + + // Email validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(formData.email)) { + errors.email = "Veuillez entrer une adresse email valide"; + } + + return Object.keys(errors).length === 0 ? null : errors; + }; + const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); + const validationErrors = validateForm(); + if (validationErrors) { + setErrors(validationErrors); + return; + } + signUp({ email: formData.email, password: formData.password, @@ -144,12 +179,14 @@ export function SignUpPage() { required /> - - Le mot de passe doit contenir au moins 8 caractères. - + {!errors.password && ( + + {errors.password} + + )} - + { - const { user, isAuthenticated } = useAuth(); + const { session } = useSession(); return (
@@ -20,14 +20,15 @@ export const TabloPage = () => { Tableau de bord
- {isAuthenticated ? "Connected" : "Not connected"} + {session ? "Connected" : "Not connected"}

Bienvenue sur votre tableau de bord{" "} - {user?.user_metadata.first_name} {user?.user_metadata.last_name} + {session?.user?.user_metadata.first_name}{" "} + {session?.user?.user_metadata.last_name}