Conecta el dialogo 'Tu cuenta ARI' (antes solo ofrecia WhatsApp) al Store API real de Medusa. Flujo verificado en vivo con un cliente de prueba real antes de escribir el codigo: registro -> crear customer -> login -> /store/customers/me, todo confirmado funcionando. Detalle no documentado que se confirmo en vivo: el token que devuelve /auth/customer/emailpass/register sirve para crear el customer via POST /store/customers, pero no queda como sesion real -- hay que loguear de nuevo (/auth/customer/emailpass) para obtener el token de sesion. Mensajes de error tambien verificados: correo repetido -> 401 'Identity with email already exists'; credenciales invalidas -> 401 'Invalid email or password'. Mismo patron arquitectonico que el carrito real (ADR 0009): rutas puente /api/customer/* (MEDUSA_BACKEND_URL es DNS interno, no resuelve desde el navegador), store de Zustand con sesion persistida y resincronizada contra el servidor al rehidratar. WhatsApp se mantiene como opcion secundaria, ya no es la unica salida.
112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { create } from "zustand";
|
|
import { createJSONStorage, persist } from "zustand/middleware";
|
|
|
|
// Cuenta de cliente real conectada a Medusa (Store API, via
|
|
// /api/customer/*), mismo patron que cart-store.ts: token + datos basicos
|
|
// persistidos en localStorage, resincronizados contra el servidor al
|
|
// rehidratar (el token puede llevar dias, o haber sido revocado).
|
|
export interface Customer {
|
|
id: string;
|
|
email: string;
|
|
firstName: string | null;
|
|
lastName: string | null;
|
|
}
|
|
|
|
interface CustomerState {
|
|
token: string | null;
|
|
customer: Customer | null;
|
|
hasHydrated: boolean;
|
|
loading: boolean;
|
|
error: string | null;
|
|
setHasHydrated: (value: boolean) => void;
|
|
register: (email: string, password: string, firstName: string, lastName: string) => Promise<boolean>;
|
|
login: (email: string, password: string) => Promise<boolean>;
|
|
logout: () => void;
|
|
syncCustomer: () => Promise<void>;
|
|
}
|
|
|
|
async function postJson(url: string, body: unknown) {
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
return response.json();
|
|
}
|
|
|
|
function toCustomer(data: Record<string, unknown>): Customer {
|
|
return {
|
|
id: data.id as string,
|
|
email: data.email as string,
|
|
firstName: (data.firstName as string | null) ?? null,
|
|
lastName: (data.lastName as string | null) ?? null,
|
|
};
|
|
}
|
|
|
|
export const useCustomerStore = create<CustomerState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
token: null,
|
|
customer: null,
|
|
hasHydrated: false,
|
|
loading: false,
|
|
error: null,
|
|
setHasHydrated: (value) => set({ hasHydrated: value }),
|
|
|
|
register: async (email, password, firstName, lastName) => {
|
|
set({ loading: true, error: null });
|
|
const result = await postJson("/api/customer/register", { email, password, firstName, lastName });
|
|
if (result.error) {
|
|
set({ loading: false, error: result.error });
|
|
return false;
|
|
}
|
|
set({ loading: false, token: result.token, customer: toCustomer(result) });
|
|
return true;
|
|
},
|
|
|
|
login: async (email, password) => {
|
|
set({ loading: true, error: null });
|
|
const result = await postJson("/api/customer/login", { email, password });
|
|
if (result.error) {
|
|
set({ loading: false, error: result.error });
|
|
return false;
|
|
}
|
|
set({ loading: false, token: result.token, customer: toCustomer(result) });
|
|
return true;
|
|
},
|
|
|
|
logout: () => set({ token: null, customer: null, error: null }),
|
|
|
|
// El token guardado en localStorage puede llevar dias o haber sido
|
|
// revocado -- se refresca contra el servidor en vez de confiar en el
|
|
// ultimo snapshot local.
|
|
syncCustomer: async () => {
|
|
const token = get().token;
|
|
if (!token) return;
|
|
try {
|
|
const response = await fetch(`/api/customer/me?token=${encodeURIComponent(token)}`);
|
|
if (!response.ok) {
|
|
set({ token: null, customer: null });
|
|
return;
|
|
}
|
|
const result = await response.json();
|
|
if (result.id) set({ customer: toCustomer(result) });
|
|
} catch {
|
|
// Sin red: se queda con el ultimo estado local conocido.
|
|
}
|
|
},
|
|
}),
|
|
{
|
|
name: "ari-shopping-customer-v1",
|
|
storage: createJSONStorage(() => localStorage),
|
|
partialize: (state) => ({ token: state.token, customer: state.customer }),
|
|
onRehydrateStorage: () => (state) => {
|
|
state?.setHasHydrated(true);
|
|
void state?.syncCustomer();
|
|
},
|
|
},
|
|
),
|
|
);
|