tout marche

This commit is contained in:
Arthur Belleville 2025-03-25 09:17:53 +01:00
parent 2151d5aa5d
commit 45fc8513a1
No known key found for this signature in database
15 changed files with 199 additions and 30 deletions

View file

@ -18,6 +18,7 @@ FIRST_SUPERUSER_PASSWORD=admin12345_gxydlksjwqnlk
# run `supabase status`
# API URL
SUPABASE_URL=https://mhcafqvzbrrwvahpvvzd.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oY2FmcXZ6YnJyd3ZhaHB2dnpkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDEyNDEzMjEsImV4cCI6MjA1NjgxNzMyMX0.Otxn5BWCPD2ABlMM59hCgeur9Tf_Q7PndAbTkqXDPtM
# service_role key
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oY2FmcXZ6YnJyd3ZhaHB2dnpkIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTI0MTMyMSwiZXhwIjoyMDU2ODE3MzIxfQ.9r33CUsu6ZR4vyv4ed-UY6cLE1FZzSSxTNE8pFUKjN4

View file

@ -90,12 +90,9 @@ async def logout(user=Depends(get_current_user_required), supabase: Client = Dep
@router.get("/users/me")
async def get_me(
user = Depends(get_current_user_required),
supabase: Client = Depends(get_supabase)
):
try:
return {
"user": jsonable_encoder(user)
}
return jsonable_encoder(user)
except IndexError:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,

View file

@ -4,11 +4,12 @@ from typing import Optional
from supabase import Client
from app.config import settings
from supabase import create_client
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login", auto_error=False)
supabase_client = create_client(settings.supabase_url, settings.supabase_key)
def get_supabase() -> Client:
supabase_client = create_client(settings.supabase_url, settings.supabase_key)
return supabase_client
# Updated current user dependency

View file

@ -44,6 +44,7 @@
},
"dependencies": {
"@react-stately/calendar": "^3.7.1",
"@supabase/supabase-js": "^2.49.3",
"@tailwindcss/vite": "^4.0.14",
"@tanstack/react-query": "^5.69.0",
"@types/react-router-dom": "^5.3.3",

View file

@ -11,6 +11,9 @@ importers:
'@react-stately/calendar':
specifier: ^3.7.1
version: 3.7.1(react@19.0.0)
'@supabase/supabase-js':
specifier: ^2.49.3
version: 2.49.3
'@tailwindcss/vite':
specifier: ^4.0.14
version: 4.0.14(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2))
@ -1179,6 +1182,28 @@ packages:
cpu: [x64]
os: [win32]
'@supabase/auth-js@2.69.1':
resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==}
'@supabase/functions-js@2.4.4':
resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==}
'@supabase/node-fetch@2.6.15':
resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==}
engines: {node: 4.x || >=6.0.0}
'@supabase/postgrest-js@1.19.2':
resolution: {integrity: sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==}
'@supabase/realtime-js@2.11.2':
resolution: {integrity: sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==}
'@supabase/storage-js@2.7.1':
resolution: {integrity: sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==}
'@supabase/supabase-js@2.49.3':
resolution: {integrity: sha512-42imTuAm9VEQGlXT0O6zrSwNnsIblU1eieqrAWj8HSmFaYkxepk/IuUVw1M5hKelk0ZYlqDKNwRErI1rF1EL4w==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@ -1303,6 +1328,9 @@ packages:
'@types/node@22.13.10':
resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==}
'@types/phoenix@1.6.6':
resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==}
'@types/react-dom@19.0.4':
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
peerDependencies:
@ -1317,6 +1345,9 @@ packages:
'@types/react@19.0.10':
resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==}
'@types/ws@8.18.0':
resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==}
'@typescript-eslint/eslint-plugin@7.18.0':
resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==}
engines: {node: ^18.18.0 || >=20.0.0}
@ -2605,6 +2636,9 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'}
@ -2719,6 +2753,12 @@ packages:
yaml:
optional: true
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@ -2748,6 +2788,18 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
ws@8.18.1:
resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
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
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -4213,6 +4265,48 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.34.6':
optional: true
'@supabase/auth-js@2.69.1':
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/functions-js@2.4.4':
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/node-fetch@2.6.15':
dependencies:
whatwg-url: 5.0.0
'@supabase/postgrest-js@1.19.2':
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/realtime-js@2.11.2':
dependencies:
'@supabase/node-fetch': 2.6.15
'@types/phoenix': 1.6.6
'@types/ws': 8.18.0
ws: 8.18.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@supabase/storage-js@2.7.1':
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/supabase-js@2.49.3':
dependencies:
'@supabase/auth-js': 2.69.1
'@supabase/functions-js': 2.4.4
'@supabase/node-fetch': 2.6.15
'@supabase/postgrest-js': 1.19.2
'@supabase/realtime-js': 2.11.2
'@supabase/storage-js': 2.7.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@ -4324,6 +4418,8 @@ snapshots:
dependencies:
undici-types: 6.20.0
'@types/phoenix@1.6.6': {}
'@types/react-dom@19.0.4(@types/react@19.0.10)':
dependencies:
'@types/react': 19.0.10
@ -4343,6 +4439,10 @@ snapshots:
dependencies:
csstype: 3.1.3
'@types/ws@8.18.0':
dependencies:
'@types/node': 22.13.10
'@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.22.0(jiti@2.4.2))(typescript@5.7.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@ -5890,6 +5990,8 @@ snapshots:
dependencies:
is-number: 7.0.0
tr46@0.0.3: {}
ts-api-utils@1.4.3(typescript@5.7.3):
dependencies:
typescript: 5.7.3
@ -5987,6 +6089,13 @@ snapshots:
jiti: 2.4.2
lightningcss: 1.29.2
webidl-conversions@3.0.1: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
@ -6040,6 +6149,8 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
ws@8.18.1: {}
y18n@5.0.8: {}
yallist@3.1.1: {}

View file

@ -9,10 +9,18 @@ import { LandingPage } from "./pages/landing";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { TabloPage } from "./pages/tablo";
import { createClient } from "@supabase/supabase-js";
// Create a single supabase client for interacting with your database
const supabase = createClient(
"https://mhcafqvzbrrwvahpvvzd.supabase.co",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oY2FmcXZ6YnJyd3ZhaHB2dnpkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDEyNDEzMjEsImV4cCI6MjA1NjgxNzMyMX0.Otxn5BWCPD2ABlMM59hCgeur9Tf_Q7PndAbTkqXDPtM"
);
export const App = () => {
return (
<ThemeProvider>
<AuthProvider>
<AuthProvider supabase={supabase}>
<Router>
<div
className={twMerge(
@ -29,6 +37,14 @@ export const App = () => {
</ProtectedRoute>
}
/>
<Route
path="/tablo/:id"
element={
<ProtectedRoute>
<TabloPage />
</ProtectedRoute>
}
/>
<Route path="/landing" element={<LandingPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignUpPage />} />

View file

@ -1,8 +1,8 @@
import "./login-with-google.css";
import { useLoginWithGoogle } from "../../hooks/auth";
import { useAuth } from "../../contexts/AuthContext";
export function LoginWithGoogle() {
const { mutate: loginWithGoogle } = useLoginWithGoogle();
const { loginWithGoogle } = useAuth();
return (
<button className="gsi-material-button" onClick={() => loginWithGoogle()}>

View file

@ -4,6 +4,7 @@ interface ProtectedRouteProps {
}
export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
// const { isAuthenticated } = useAuth();
// if (!isAuthenticated) {
// // Redirect to login page if user is not authenticated
// return <Navigate to="/landing" replace />;

View file

@ -1,7 +1,7 @@
import { jwtDecode } from "jwt-decode";
import { createContext, useContext, useState, ReactNode } from "react";
import { api } from "../lib/api";
import { SupabaseClient } from "@supabase/supabase-js";
interface UserMetadata {
email: string;
first_name: string;
@ -14,6 +14,7 @@ interface AuthContextType {
login: (token: string) => void;
logout: () => void;
user: UserMetadata | null;
loginWithGoogle: () => void;
}
type SupabaseToken = {
@ -34,9 +35,10 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
interface AuthProviderProps {
children: ReactNode;
supabase: SupabaseClient;
}
export const AuthProvider = ({ children }: AuthProviderProps) => {
export const AuthProvider = ({ children, supabase }: AuthProviderProps) => {
const [isAuthenticated, setIsAuthenticated] = useState(() => {
// Check if there's a token in localStorage on initial load
return !!localStorage.getItem("auth_token");
@ -73,6 +75,16 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
}
};
const loginWithGoogle = async () => {
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: "http://localhost:5173/",
scopes: "profile email",
},
});
};
const logout = () => {
localStorage.removeItem("auth_token");
setIsAuthenticated(false);
@ -80,7 +92,9 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
};
return (
<AuthContext.Provider value={{ isAuthenticated, login, logout, user }}>
<AuthContext.Provider
value={{ isAuthenticated, login, logout, user, loginWithGoogle }}
>
{children}
</AuthContext.Provider>
);

View file

@ -22,6 +22,7 @@ interface LoginData {
type SignUpErrorCodes = "user_already_exists";
type LoginErrorCodes = "invalid_credentials" | "user_not_found";
export function useSignUp() {
const [errors, setErrors] = useState<Record<string, string>>({});
const { mutate, isPending } = useMutation<
@ -121,22 +122,9 @@ export function useLoginEmail() {
return { mutate, isPending, errors };
}
export function useLoginWithGoogle() {
return useMutation({
mutationFn: async () => {
const response = await api.get("/auth/login/google");
const { auth_url } = response.data;
return auth_url;
},
onSuccess: (url) => {
window.location.href = url;
console.log("url", url);
},
});
}
export function useLogout() {
const { logout } = useAuth();
const navigate = useNavigate();
return useMutation({
mutationFn: async () => {
const response = await api.post("/auth/logout");
@ -144,6 +132,7 @@ export function useLogout() {
},
onSuccess: () => {
logout();
navigate("/landing");
},
});
}

27
ui/src/hooks/user.ts Normal file
View file

@ -0,0 +1,27 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "../lib/api";
interface UserMetadata {
email: string;
first_name: string;
last_name: string;
business_name: string;
}
interface GetMeResponse {
user: { user_metadata: UserMetadata };
}
export function useGetUser() {
const { data } = useQuery<GetMeResponse>({
queryKey: ["me"],
queryFn: async () => {
const response = await api.get("/auth/users/me");
return response.data;
},
});
console.log("data", data);
return { me: data?.user.user_metadata };
}

View file

@ -1,8 +1,14 @@
import { useAuth } from "../contexts/AuthContext";
import { SignOutButton } from "../components/SignOutButton";
import { useParams } from "react-router-dom";
export const TabloPage = () => {
const { isAuthenticated, user } = useAuth();
const { id, access_token } = useParams();
const me = {
first_name: "John",
last_name: "Doe",
};
console.log({ access_token });
return (
<div className="min-h-screen">
@ -21,15 +27,20 @@ export const TabloPage = () => {
Tableau de bord
</h1>
<div className="text-sm text-gray-600 dark:text-gray-300">
{isAuthenticated ? "Connected" : "Not connected"}
{me ? "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?.first_name}{" "}
{user?.last_name}
Bienvenue sur votre tableau de bord {me?.first_name}{" "}
{me?.last_name}
</p>
{id && (
<p className="text-gray-600 dark:text-gray-300 mt-2">
ID du tableau: {id}
</p>
)}
</div>
</div>
</main>