diff --git a/backend/package.json b/backend/package.json index f73b48f..7804498 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,7 @@ "dotenv": "^16.3.1", "fastify": "^4.19.0", "fastify-plugin": "^4.5.0", + "google-auth-library": "^9.15.1", "openai": "^4.0.0", "stripe": "^12.12.0" }, diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index dd7c930..f8bfa73 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: fastify-plugin: specifier: ^4.5.0 version: 4.5.1 + google-auth-library: + specifier: ^9.15.1 + version: 9.15.1 openai: specifier: ^4.0.0 version: 4.86.2 @@ -133,6 +136,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + agentkeepalive@4.6.0: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} @@ -188,10 +195,16 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bcrypt@5.1.1: resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} engines: {node: '>= 10.0.0'} + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -206,6 +219,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -294,6 +310,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} @@ -385,6 +404,14 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + gaxios@6.7.1: + resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} + engines: {node: '>=14'} + + gcp-metadata@6.1.1: + resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} + engines: {node: '>=14'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -401,10 +428,22 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + google-auth-library@9.15.1: + resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} + engines: {node: '>=14'} + + google-logging-utils@0.0.2: + resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} + engines: {node: '>=14'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -428,6 +467,10 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -465,12 +508,25 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-schema-ref-resolver@1.0.1: resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jwa@2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + + jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + light-my-request@5.14.0: resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==} @@ -782,6 +838,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -920,6 +980,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.3: {} + agentkeepalive@4.6.0: dependencies: humanize-ms: 1.2.1 @@ -971,6 +1033,8 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + bcrypt@5.1.1: dependencies: '@mapbox/node-pre-gyp': 1.0.11 @@ -979,6 +1043,8 @@ snapshots: - encoding - supports-color + bignumber.js@9.1.2: {} + binary-extensions@2.3.0: {} bn.js@4.12.1: {} @@ -992,6 +1058,8 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-equal-constant-time@1.0.1: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -1071,6 +1139,8 @@ snapshots: event-target-shim@5.0.1: {} + extend@3.0.2: {} + fast-content-type-parse@1.1.0: {} fast-decode-uri-component@1.0.1: {} @@ -1192,6 +1262,26 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + gaxios@6.7.1: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + is-stream: 2.0.1 + node-fetch: 2.7.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + gcp-metadata@6.1.1: + dependencies: + gaxios: 6.7.1 + google-logging-utils: 0.0.2 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -1223,8 +1313,30 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + google-auth-library@9.15.1: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.7.1 + gcp-metadata: 6.1.1 + gtoken: 7.1.0 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + google-logging-utils@0.0.2: {} + gopd@1.2.0: {} + gtoken@7.1.0: + dependencies: + gaxios: 6.7.1 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + has-flag@3.0.0: {} has-symbols@1.1.0: {} @@ -1246,6 +1358,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + humanize-ms@1.2.1: dependencies: ms: 2.1.3 @@ -1275,12 +1394,29 @@ snapshots: is-number@7.0.0: {} + is-stream@2.0.1: {} + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.1.2 + json-schema-ref-resolver@1.0.1: dependencies: fast-deep-equal: 3.1.3 json-schema-traverse@1.0.0: {} + jwa@2.0.0: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.0: + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + light-my-request@5.14.0: dependencies: cookie: 0.7.2 @@ -1581,6 +1717,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@9.0.1: {} + web-streams-polyfill@4.0.0-beta.3: {} webidl-conversions@3.0.1: {} diff --git a/backend/prisma/migrations/20250311114801_add_google_id/migration.sql b/backend/prisma/migrations/20250311114801_add_google_id/migration.sql new file mode 100644 index 0000000..e8a55ab --- /dev/null +++ b/backend/prisma/migrations/20250311114801_add_google_id/migration.sql @@ -0,0 +1,29 @@ +/* + Warnings: + + - Made the column `name` on table `User` required. This step will fail if there are existing NULL values in that column. + - Made the column `stripeId` on table `User` required. This step will fail if there are existing NULL values in that column. + +*/ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_User" ( + "id" TEXT NOT NULL PRIMARY KEY, + "email" TEXT NOT NULL, + "password" TEXT, + "name" TEXT NOT NULL, + "googleId" TEXT, + "stripeId" TEXT NOT NULL, + "subscription" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_User" ("createdAt", "email", "id", "name", "password", "stripeId", "subscription", "updatedAt") SELECT "createdAt", "email", "id", "name", "password", "stripeId", "subscription", "updatedAt" FROM "User"; +DROP TABLE "User"; +ALTER TABLE "new_User" RENAME TO "User"; +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +CREATE UNIQUE INDEX "User_googleId_key" ON "User"("googleId"); +CREATE UNIQUE INDEX "User_stripeId_key" ON "User"("stripeId"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 44bbb39..1eac1fc 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -10,10 +10,11 @@ datasource db { model User { id String @id @default(uuid()) email String @unique - password String - name String? - subscription String @default("free") // "free" ou "premium" - stripeId String? + password String? // Optionnel pour les utilisateurs Google + name String + googleId String? @unique // ID Google pour SSO + stripeId String @unique + subscription String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt recipes Recipe[] diff --git a/backend/src/plugins/google-auth.js b/backend/src/plugins/google-auth.js new file mode 100644 index 0000000..33fba2e --- /dev/null +++ b/backend/src/plugins/google-auth.js @@ -0,0 +1,8 @@ +const fp = require('fastify-plugin'); +const { OAuth2Client } = require('google-auth-library'); + +module.exports = fp(async function (fastify, opts) { + const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID); + + fastify.decorate('googleClient', googleClient); +}); \ No newline at end of file diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 9f34c71..ea170ff 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -108,4 +108,75 @@ module.exports = async function (fastify, opts) { return reply.code(500).send({ error: 'Erreur lors de la connexion' }); } }); + + fastify.post('/google-auth', { + schema: { + body: { + type: 'object', + required: ['token'], + properties: { + token: { type: 'string' } + } + } + } + }, async (request, reply) => { + const { token } = request.body; + + try { + // Utiliser le token d'accès pour obtenir les informations utilisateur + // au lieu d'essayer de le vérifier comme un ID token + const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', { + headers: { Authorization: `Bearer ${token}` } + }); + + if (!response.ok) { + throw new Error('Impossible de récupérer les informations utilisateur'); + } + + const userData = await response.json(); + const { email, name, sub: googleId } = userData; + + // Vérifier si l'utilisateur existe déjà + let user = await fastify.prisma.user.findUnique({ + where: { email } + }); + + if (!user) { + // Créer un client Stripe + const customer = await fastify.createCustomer(email, name); + + // Créer l'utilisateur + user = await fastify.prisma.user.create({ + data: { + email, + name, + googleId, + stripeId: customer.id + } + }); + } else if (!user.googleId) { + // Mettre à jour l'utilisateur existant avec l'ID Google + user = await fastify.prisma.user.update({ + where: { id: user.id }, + data: { googleId } + }); + } + + // Générer un token JWT + const jwtToken = fastify.jwt.sign({ id: user.id }, { expiresIn: '7d' }); + + return { + user: { + id: user.id, + email: user.email, + name: user.name, + subscription: user.subscription + }, + token: jwtToken + }; + } catch (error) { + fastify.log.error(error); + return reply.code(500).send({ error: 'Erreur lors de l\'authentification Google' }); + } + }); }; \ No newline at end of file diff --git a/backend/src/server.js b/backend/src/server.js index 8f473ce..2dcdae5 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -14,10 +14,10 @@ fastify.register(require('@fastify/cors'), { allowedHeaders: ['Content-Type', 'Authorization'], credentials: true }); - fastify.register(require('./plugins/auth')); fastify.register(require('./plugins/stripe')); fastify.register(require('./plugins/ai')); +fastify.register(require('./plugins/google-auth')); // Routes fastify.register(require('./routes/auth'), { prefix: '/auth' }); diff --git a/frontend/package.json b/frontend/package.json index b99e2c4..b0d70d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "build-no-error": "vite build", "lint": "eslint .", "preview": "vite preview" }, @@ -19,6 +20,7 @@ "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-toast": "^1.2.6", + "@react-oauth/google": "^0.12.1", "@tailwindcss/vite": "^4.0.12", "axios": "^1.8.2", "class-variance-authority": "^0.7.1", @@ -47,4 +49,4 @@ "typescript-eslint": "^8.24.1", "vite": "^6.2.0" } -} +} \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 9ce8e57..d671cea 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@radix-ui/react-toast': specifier: ^1.2.6 version: 1.2.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@react-oauth/google': + specifier: ^0.12.1 + version: 0.12.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tailwindcss/vite': specifier: ^4.0.12 version: 4.0.12(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(lightningcss@1.29.2)) @@ -806,6 +809,12 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@react-oauth/google@0.12.1': + resolution: {integrity: sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@rollup/rollup-android-arm-eabi@4.34.9': resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} cpu: [arm] @@ -2533,6 +2542,11 @@ snapshots: '@radix-ui/rect@1.1.0': {} + '@react-oauth/google@0.12.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + '@rollup/rollup-android-arm-eabi@4.34.9': optional: true diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 23672ef..8527019 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -77,4 +77,14 @@ export const isAuthenticated = (): boolean => { // Récupérer le token export const getToken = (): string | null => { return localStorage.getItem('token'); -}; \ No newline at end of file +}; + +export const googleAuth = async (token: string) => { + const response = await base.post('/auth/google-auth', { token }); + + if (!response.data.token) { + throw new Error('Erreur lors de l\'authentification Google'); + } + + return response.data; +}; diff --git a/frontend/src/components/login-form.tsx b/frontend/src/components/login-form.tsx index e719e5d..7f456e0 100644 --- a/frontend/src/components/login-form.tsx +++ b/frontend/src/components/login-form.tsx @@ -1,10 +1,12 @@ import { useState } from "react" import { useNavigate } from "react-router-dom" +import { useGoogleLogin } from "@react-oauth/google" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { login } from "@/api/auth" +import { login, googleAuth } from "@/api/auth" + export function LoginForm({ className, ...props @@ -36,6 +38,33 @@ export function LoginForm({ } } + const handleGoogleLogin = useGoogleLogin({ + onSuccess: async (tokenResponse) => { + setLoading(true) + setError("") + + try { + const response = await googleAuth(tokenResponse.access_token) + + if (!response.user) { + throw new Error("Échec de la connexion") + } + + localStorage.setItem("token", response.token) + + navigate("/") + } catch (err) { + console.error("Erreur de connexion Google:", err) + setError(err instanceof Error ? err.message : "Une erreur est survenue") + } finally { + setLoading(false) + } + }, + onError: () => { + setError("Échec de l'authentification Google") + } + }) + return (
@@ -87,11 +116,29 @@ export function LoginForm({ Ou continuer avec
-