From 77389aea190b4f20fd2866f051bc034919a1d5e8 Mon Sep 17 00:00:00 2001 From: Cristian Felipe Cruz Buitron Date: Sat, 29 Aug 2026 17:01:32 +0000 Subject: [PATCH] feat(ecommerce): login con Google para clientes Implementa el flujo completo de OAuth social de Medusa para el storefront: boton "Continuar con Google" en account-dialog, ruta proxy que arranca el flujo (GET /api/customer/google/start) y pagina de callback en /auth/google/callback (coincide con GOOGLE_CALLBACK_URL del secret) que resuelve el code/state contra el backend, decodifica el JWT para distinguir cliente nuevo vs existente, crea el customer si hace falta y refresca el token a sesion final -- mismo patron de store/customer-store.ts que login/register por email. Flujo verificado en vivo contra el backend ya desplegado (GET /auth/customer/google, /auth/customer/google/callback con state invalido, /auth/token/refresh con bearer invalido) antes de escribir el codigo, no solo contra la documentacion. --- .../app/api/customer/google/callback/route.ts | 14 ++++++ .../app/api/customer/google/start/route.ts | 14 ++++++ .../app/auth/google/callback/page.tsx | 10 ++++ .../components/account/account-dialog.tsx | 19 +++++++ .../account/google-callback-client.tsx | 44 ++++++++++++++++ .../lib/headless/providers/medusa-customer.ts | 50 +++++++++++++++++++ workloads/ecommerce/store/customer-store.ts | 12 +++++ 7 files changed, 163 insertions(+) create mode 100644 workloads/ecommerce/app/api/customer/google/callback/route.ts create mode 100644 workloads/ecommerce/app/api/customer/google/start/route.ts create mode 100644 workloads/ecommerce/app/auth/google/callback/page.tsx create mode 100644 workloads/ecommerce/components/account/google-callback-client.tsx diff --git a/workloads/ecommerce/app/api/customer/google/callback/route.ts b/workloads/ecommerce/app/api/customer/google/callback/route.ts new file mode 100644 index 0000000..d9d9e71 --- /dev/null +++ b/workloads/ecommerce/app/api/customer/google/callback/route.ts @@ -0,0 +1,14 @@ +import { handleGoogleCallback } from "@/lib/headless/providers/medusa-customer"; + +export async function POST(request: Request) { + const { code, state } = await request.json(); + if (!code || !state) { + return Response.json({ error: "Faltan parámetros de Google" }, { status: 400 }); + } + try { + const customer = await handleGoogleCallback(code, state); + return Response.json(customer); + } catch (error) { + return Response.json({ error: (error as Error).message }, { status: 400 }); + } +} diff --git a/workloads/ecommerce/app/api/customer/google/start/route.ts b/workloads/ecommerce/app/api/customer/google/start/route.ts new file mode 100644 index 0000000..8e0b68b --- /dev/null +++ b/workloads/ecommerce/app/api/customer/google/start/route.ts @@ -0,0 +1,14 @@ +import { getGoogleAuthUrl } from "@/lib/headless/providers/medusa-customer"; + +// Navegacion de pagina completa (no fetch/XHR) -- el boton "Continuar +// con Google" enlaza directo aca, el browser sigue el 302 hasta la +// pantalla real de consentimiento de Google. +export async function GET(request: Request) { + try { + const location = await getGoogleAuthUrl(); + return Response.redirect(location); + } catch (error) { + const message = encodeURIComponent((error as Error).message); + return Response.redirect(new URL(`/?googleAuthError=${message}`, request.url)); + } +} diff --git a/workloads/ecommerce/app/auth/google/callback/page.tsx b/workloads/ecommerce/app/auth/google/callback/page.tsx new file mode 100644 index 0000000..26b9d82 --- /dev/null +++ b/workloads/ecommerce/app/auth/google/callback/page.tsx @@ -0,0 +1,10 @@ +import { Suspense } from "react"; +import { GoogleCallbackClient } from "@/components/account/google-callback-client"; + +export default function GoogleCallbackPage() { + return ( + + + + ); +} diff --git a/workloads/ecommerce/components/account/account-dialog.tsx b/workloads/ecommerce/components/account/account-dialog.tsx index 8ae128d..efdfd0f 100644 --- a/workloads/ecommerce/components/account/account-dialog.tsx +++ b/workloads/ecommerce/components/account/account-dialog.tsx @@ -10,6 +10,17 @@ import { useCustomerStore } from "@/store/customer-store"; type Mode = "login" | "register"; +function GoogleIcon() { + return ( + + ); +} + export function AccountDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) { const { customer, hasHydrated, loading, error, login, register, logout } = useCustomerStore(); const [mode, setMode] = useState("login"); @@ -57,6 +68,14 @@ export function AccountDialog({ open, onOpenChange }: { open: boolean; onOpenCha {loading ? "Un momento…" : mode === "login" ? "Iniciar sesión" : "Crear cuenta"} +
+ o +
+