Finish login handling
This commit is contained in:
parent
86f6004f5c
commit
91fd958fba
9 changed files with 144 additions and 45 deletions
7
.cursor/mcp.json
Normal file
7
.cursor/mcp.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"Datadog Extension": {
|
||||
"url": "http://localhost:5594/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
ui/.gitignore
vendored
2
ui/.gitignore
vendored
|
|
@ -22,3 +22,5 @@ dist-ssr
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.cursor
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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}</>;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
40
ui/src/contexts/SessionContext.tsx
Normal file
40
ui/src/contexts/SessionContext.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
39
ui/src/pages/NotFoundPage.tsx
Normal file
39
ui/src/pages/NotFoundPage.tsx
Normal 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'existe pas ou a été déplacée.
|
||||
</p>
|
||||
<Button
|
||||
onPress={() => navigate("/login")}
|
||||
className={twMerge(
|
||||
"bg-emerald-700 text-white",
|
||||
"hover:bg-emerald-600"
|
||||
)}
|
||||
>
|
||||
Retour à l'accueil
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue