diff --git a/mvp/b2c/src/app/signup/actions.ts b/mvp/b2c/src/app/signup/actions.ts new file mode 100644 index 0000000..def78d2 --- /dev/null +++ b/mvp/b2c/src/app/signup/actions.ts @@ -0,0 +1,42 @@ +'use server'; + +import { redirect } from 'next/navigation'; +import { eq } from 'drizzle-orm'; +import { db } from '@/db'; +import { tenants } from '@/db/schema'; +import { signupSchema, slugify } from '@/lib/validation/signup'; +import { getUserByEmail, createTenantWithOwner, slugDisponible } from '@/db/auth-queries'; +import { hashPassword } from '@/lib/auth/password'; +import { createSession } from '@/lib/auth/session'; + +const TRIAL_MS = 14 * 24 * 60 * 60 * 1000; + +export async function signup(_prev: string | null, formData: FormData): Promise { + const parsed = signupSchema.safeParse(Object.fromEntries(formData)); + if (!parsed.success) return parsed.error.issues[0]?.message ?? 'Datos no válidos.'; + const data = parsed.data; + + if (await getUserByEmail(data.email)) return 'Ya existe una cuenta con ese email.'; + + let slug = slugify(data.empresa); + let n = 1; + while (!(await slugDisponible(slug))) slug = `${slugify(data.empresa)}-${++n}`; + + const passwordHash = await hashPassword(data.password); + const { tenant, user } = await createTenantWithOwner({ + nombreEmpresa: data.empresa, + slug, + provincia: data.provincia, + email: data.email, + passwordHash, + nombre: data.nombre, + }); + + await db + .update(tenants) + .set({ trialEndsAt: new Date(Date.now() + TRIAL_MS) }) + .where(eq(tenants.id, tenant.id)); + + await createSession(user.id); + redirect('/panel'); +} diff --git a/mvp/b2c/src/app/signup/page.tsx b/mvp/b2c/src/app/signup/page.tsx new file mode 100644 index 0000000..7c5b86e --- /dev/null +++ b/mvp/b2c/src/app/signup/page.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { useActionState } from 'react'; +import { signup } from './actions'; + +export default function SignupPage() { + const [error, formAction, pending] = useActionState(signup, null); + return ( +
+
+

Empieza gratis 14 días

+

Sin tarjeta. Configura tu catálogo y recibe leads.

+ + + + + + + {error &&

{error}

} + + Ya tengo cuenta +
+
+ ); +} diff --git a/mvp/b2c/src/db/auth-queries.ts b/mvp/b2c/src/db/auth-queries.ts index 57536a6..fb60eee 100644 --- a/mvp/b2c/src/db/auth-queries.ts +++ b/mvp/b2c/src/db/auth-queries.ts @@ -45,3 +45,8 @@ export async function createTenantWithOwner(input: { return { tenant, user }; } + +export async function slugDisponible(slug: string): Promise { + const [row] = await db.select({ id: tenants.id }).from(tenants).where(eq(tenants.slug, slug)).limit(1); + return !row; +}