Finish login handling

This commit is contained in:
Arthur Belleville 2025-03-26 21:21:18 +01:00
parent 86f6004f5c
commit 91fd958fba
No known key found for this signature in database
9 changed files with 144 additions and 45 deletions

7
.cursor/mcp.json Normal file
View file

@ -0,0 +1,7 @@
{
"mcpServers": {
"Datadog Extension": {
"url": "http://localhost:5594/sse"
}
}
}

2
ui/.gitignore vendored
View file

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.cursor

View file

@ -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 (
<ThemeProvider>
<AuthProvider>
<SessionProvider>
<Router>
<div
className={twMerge(
@ -55,7 +55,7 @@ export const App = () => {
</style>
</div>
</Router>
</AuthProvider>
</SessionProvider>
</ThemeProvider>
);
};

View file

@ -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 <Navigate to="/landing" replace />;
// }
const { session } = useSession();
if (!session) {
return <Navigate to="/login" replace />;
}
// If authenticated, render the protected component
return <>{children}</>;

View file

@ -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<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const { user } = useGetCurrentUser();
const value = {
user,
isAuthenticated: !!user,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}

View file

@ -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<Session | null>(null);
useEffect(() => {
const authStateListener = supabase.auth.onAuthStateChange(
async (_, session) => {
setSession(session);
}
);
return () => {
authStateListener.data.subscription.unsubscribe();
};
}, [supabase]);
return (
<SessionContext.Provider value={{ session }}>
{children}
</SessionContext.Provider>
);
};

View file

@ -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 (
<div className="min-h-screen">
<header className="bg-white dark:bg-gray-800 shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
404 - Page Not Found
</h1>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="container mx-auto px-4 py-8">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 text-center">
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
Oups ! Page introuvable
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
La page que vous recherchez n&apos;existe pas ou a é déplacée.
</p>
<Button
onPress={() => navigate("/login")}
className={twMerge(
"bg-emerald-700 text-white",
"hover:bg-emerald-600"
)}
>
Retour à l&apos;accueil
</Button>
</div>
</div>
</main>
</div>
);
};

View file

@ -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<Record<string, string>>({});
const [formData, setFormData] = useState({
email: "",
@ -22,9 +23,43 @@ export function SignUpPage() {
business_name: "",
});
const validateForm = () => {
const errors: Record<string, string> = {};
// 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
/>
<FieldError />
<Text slot="description">
Le mot de passe doit contenir au moins 8 caractères.
</Text>
{!errors.password && (
<Text slot="description" className="text-red-500">
{errors.password}
</Text>
)}
</TextField>
<TextField isRequired name="confirm_password">
<TextField isRequired name="confirmPassword">
<Label>Confirmer le mot de passe</Label>
<Input
type="password"

View file

@ -1,8 +1,8 @@
import { SignOutButton } from "../components/SignOutButton";
import { useAuth } from "../contexts/AuthContext";
import { useSession } from "../contexts/SessionContext";
export const TabloPage = () => {
const { user, isAuthenticated } = useAuth();
const { session } = useSession();
return (
<div className="min-h-screen">
<header className="bg-white dark:bg-gray-800 shadow">
@ -20,14 +20,15 @@ export const TabloPage = () => {
Tableau de bord
</h1>
<div className="text-sm text-gray-600 dark:text-gray-300">
{isAuthenticated ? "Connected" : "Not connected"}
{session ? "Connected" : "Not connected"}
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<p className="text-gray-600 dark:text-gray-300">
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}
</p>
</div>
</div>