refactor: migrate authentication from Supabase to Better Auth and update project configuration

This commit is contained in:
Jose Selesan
2026-04-17 08:35:54 -03:00
parent aabd9a8266
commit b3f1b72da4
14 changed files with 87 additions and 332 deletions

View File

@@ -18,11 +18,7 @@ WORKDIR /app
COPY apps/frontend ./apps/frontend COPY apps/frontend ./apps/frontend
COPY packages ./packages COPY packages ./packages
ARG VITE_API_BASE_URL ARG VITE_API_BASE_URL
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_ANON_KEY
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
ENV VITE_SUPABASE_URL=${VITE_SUPABASE_URL}
ENV VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY}
RUN bun run build:frontend RUN bun run build:frontend
# ============================================================================= # =============================================================================

183
README.md
View File

@@ -1,184 +1,105 @@
# Monorepo (Frontend + Backend + Contrato API) # Playzer Monorepo
Este repositorio contiene: Este repositorio contiene:
- `apps/frontend`: app web en React + Vite + TanStack Router/Query. - `apps/frontend`: React + Vite + TanStack Router/Query.
- `apps/backend`: API en Bun + Hono + Prisma + Supabase Auth. - `apps/backend`: Bun + Hono + Prisma + Better Auth.
- `packages/api-contract`: contratos compartidos (schemas/types/rutas) entre frontend y backend. - `packages/api-contract`: contratos Zod/tipos compartidos.
## Objetivo de este README ## Requisitos
Dejarte productivo/a en pocos minutos para que puedas: - Bun reciente
- PostgreSQL disponible
1. Levantar frontend y backend en local.
2. Configurar variables de entorno.
3. Ejecutar migraciones de base de datos.
4. Entender la estructura y el flujo de trabajo del proyecto.
## Stack técnico
- Runtime/package manager: `bun`
- Frontend: `React 19`, `Vite`, `TanStack Router`, `TanStack Query`
- Backend: `Bun`, `Hono`, `Prisma`, `PostgreSQL`
- Auth: `Supabase Auth` (token Bearer validado en backend)
- Validación de contratos: `zod` en `@repo/api-contract`
## Prerrequisitos
- `bun` instalado (versión reciente).
- `PostgreSQL` corriendo localmente (o accesible remotamente).
- Proyecto de `Supabase` para autenticación (URL + anon key).
## Setup rápido ## Setup rápido
1. Instalar dependencias desde la raíz: 1. Instalar dependencias:
```sh ```sh
bun install bun install
``` ```
2. Configurar variables de entorno del backend: 2. Configurar backend:
```sh ```sh
cp apps/backend/.env.example apps/backend/.env cp apps/backend/.env.example apps/backend/.env
``` ```
3. Configurar variables de entorno del frontend: 3. Configurar frontend:
```sh ```sh
cp apps/frontend/.env.example apps/frontend/.env cp apps/frontend/.env.example apps/frontend/.env
``` ```
4. Revisar/editar valores en ambos `.env` según tu entorno. 4. Ejecutar migraciones:
## Variables de entorno
### Backend (`apps/backend/.env`)
Variables esperadas:
- `SUPABASE_URL`
- `SUPABASE_ANON_KEY`
- `CORS_ORIGIN` (por defecto local frontend)
- `LOG_LEVEL`
- `DATABASE_URL` (PostgreSQL)
Notas:
- El backend también acepta `VITE_SUPABASE_URL` y `VITE_SUPABASE_ANON_KEY` como fallback.
- Si falta `DATABASE_URL`, Prisma no puede inicializarse.
### Frontend (`apps/frontend/.env`)
Variables esperadas:
- `VITE_SUPABASE_URL`
- `VITE_SUPABASE_ANON_KEY`
- `VITE_API_BASE_URL` (por defecto: `http://localhost:3000`)
## Base de datos (Prisma)
Antes de arrancar backend por primera vez, corré migraciones:
```sh ```sh
bun --filter backend prisma:migrate bun --filter backend prisma:migrate
``` ```
Si cambiás el schema, regenerá cliente: ## Variables de entorno
```sh ### Backend (`apps/backend/.env`)
bun --filter backend prisma:generate
```
## Ejecutar en desarrollo - `DATABASE_URL`
- `BETTER_AUTH_SECRET`
- `BETTER_AUTH_URL`
- `APP_BASE_URL`
- `CORS_ORIGIN`
Terminal 1 (backend): ### Frontend (`apps/frontend/.env`)
- `VITE_API_BASE_URL`
## Desarrollo
Backend:
```sh ```sh
bun run dev:backend bun run dev:backend
``` ```
Terminal 2 (frontend): Frontend:
```sh ```sh
bun run dev:frontend bun run dev:frontend
``` ```
Puertos por defecto: Todo junto:
- Frontend: `http://localhost:5173` ```sh
- Backend: `http://localhost:3000` bun run dev
## Scripts útiles
Desde la raíz:
- `bun run dev:frontend`
- `bun run dev:backend`
- `bun run build:frontend`
- `bun run lint:frontend`
Desde `apps/backend`:
- `bun run dev`
- `bun run start`
- `bun run prisma:migrate`
- `bun run prisma:generate`
## Estructura del repo
```txt
.
├─ apps/
│ ├─ frontend/ # UI y navegación
│ └─ backend/ # API, auth middleware, acceso a DB
└─ packages/
└─ api-contract/ # Schemas/tipos/rutas compartidos
``` ```
## Flujo de autenticación (resumen) ## Prisma
1. Frontend autentica con Supabase. Regenerar cliente después de cambios de schema:
2. Frontend guarda el access token y lo envía como `Authorization: Bearer ...` al backend.
3. Backend valida token con Supabase (`requireAuth` middleware).
4. Si el token es válido, habilita rutas protegidas (`/api/user/*`, `/api/tenants/*`).
## Endpoints principales ```sh
bun --filter backend prisma:generate
```
- `GET /api/user/profile` ## Autenticación
- `POST /api/tenants`
- `GET /api/tenants/:id`
- `PATCH /api/tenants/:id`
## Convenciones recomendadas para el equipo El proyecto usa **Better Auth** con sesión por cookie.
- Mantener contratos de API en `packages/api-contract` (evitar duplicar tipos). - Backend: `c.get('user')` y `c.get('session')` en contexto Hono.
- Si agregás/cambiás endpoint: - Frontend: `authClient` con `fetchOptions.credentials = 'include'`.
1. Actualizá schema/tipos en `api-contract`. - Frontend API: Axios con `withCredentials = true`.
2. Implementá handler/ruta en backend. - Backend CORS: `credentials: true`.
3. Consumí el contrato desde frontend.
- Validar payloads con Zod tanto en contrato como en rutas.
- Mantener PRs chicos y enfocados.
## Troubleshooting rápido ## Deploy (Docker)
- Error de CORS: - Build context: raíz del monorepo (`./`)
- Revisar `CORS_ORIGIN` en backend y que incluya `http://localhost:5173`. - Dockerfile: `./Dockerfile`
- `403 Missing bearer token`: - Build args requeridos:
- Verificar login en frontend y envío del header `Authorization`. - `DATABASE_URL`
- Error de Supabase env vars: - `VITE_API_BASE_URL`
- Confirmar `SUPABASE_URL`/`SUPABASE_ANON_KEY` (backend) y `VITE_*` (frontend). - `BETTER_AUTH_SECRET`
- Error de Prisma/DB: - `BETTER_AUTH_URL`
- Confirmar `DATABASE_URL`, conectividad a PostgreSQL y migraciones aplicadas.
## Primer día sugerido para onboarding ## Flujo recomendado de cambios de API
1. Levantar backend y frontend en local. 1. Actualizar contrato en `packages/api-contract`.
2. Crear usuario o iniciar sesión desde la pantalla de onboard. 2. Implementar backend en `apps/backend`.
3. Crear un tenant de prueba. 3. Consumir contrato desde frontend (`@repo/api-contract`).
4. Probar lectura/edición de tenant.
5. Revisar `packages/api-contract` para entender cómo se comparten tipos y rutas.
---
Si te trabás en el setup, empezá verificando `.env`, estado de PostgreSQL y que ambos procesos (`dev:backend` y `dev:frontend`) estén corriendo.

View File

@@ -1,9 +1,5 @@
SUPABASE_URL=https://your-project-ref.supabase.co BETTER_AUTH_SECRET=your-random-secret
SUPABASE_ANON_KEY=your-anon-key BETTER_AUTH_URL=http://localhost:3000
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Fallbacks accepted too:
# VITE_SUPABASE_URL=https://your-project-ref.supabase.co
# VITE_SUPABASE_ANON_KEY=your-anon-key
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173 CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
LOG_LEVEL=debug LOG_LEVEL=debug
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/monorepo DATABASE_URL=postgresql://postgres:postgres@localhost:5432/monorepo

View File

@@ -1,11 +1 @@
// model User { // User model is defined in auth.prisma (Better Auth schema).
// id String @id @db.Uuid
// supabaseUserId String @unique @map("supabase_user_id") @db.Uuid
// email String @unique
// fullName String @map("full_name")
// createdAt DateTime @default(now()) @map("created_at")
// updatedAt DateTime @updatedAt @map("updated_at")
// complexes ComplexUser[]
// @@map("users")
// }

View File

@@ -1,26 +0,0 @@
import { createClient } from '@supabase/supabase-js';
let cachedClient: ReturnType<typeof createClient> | null = null;
export function getSupabaseAdminClient() {
if (cachedClient) return cachedClient;
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL;
const supabaseServiceRoleKey = Bun.env.SUPABASE_SERVICE_ROLE_KEY;
if (!supabaseUrl || !supabaseServiceRoleKey) {
throw new Error(
'Missing Supabase admin env vars. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in backend environment.'
);
}
cachedClient = createClient(supabaseUrl, supabaseServiceRoleKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false,
},
});
return cachedClient;
}

View File

@@ -1,18 +0,0 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL;
const supabaseAnonKey = Bun.env.SUPABASE_ANON_KEY ?? Bun.env.VITE_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
throw new Error(
'Missing Supabase env vars. Set SUPABASE_URL/SUPABASE_ANON_KEY (or VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEY) in backend environment.'
);
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false,
},
});

View File

@@ -2,14 +2,12 @@ import { createHash, randomInt } from 'node:crypto';
import { auth } from '@/lib/auth'; import { auth } from '@/lib/auth';
import { sendMail } from '@/lib/mailer'; import { sendMail } from '@/lib/mailer';
import { db } from '@/lib/prisma'; import { db } from '@/lib/prisma';
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
import type { import type {
OnboardingCompleteInput, OnboardingCompleteInput,
OnboardingResendOtpInput, OnboardingResendOtpInput,
OnboardingStartInput, OnboardingStartInput,
OnboardingVerifyOtpInput, OnboardingVerifyOtpInput,
} from '@repo/api-contract'; } from '@repo/api-contract';
import type { User as SupabaseAuthUser } from '@supabase/supabase-js';
import { v7 as uuidv7 } from 'uuid'; import { v7 as uuidv7 } from 'uuid';
const OTP_LENGTH = 6; const OTP_LENGTH = 6;
@@ -126,77 +124,6 @@ async function sendOtpEmail(email: string, otpCode: string) {
}); });
} }
async function findSupabaseUserByEmail(email: string): Promise<SupabaseAuthUser | null> {
let page = 1;
const perPage = 100;
while (page <= 10) {
const { data, error } = await getSupabaseAdminClient().auth.admin.listUsers({
page,
perPage,
});
if (error) {
throw new Error(`No se pudo consultar usuarios en Supabase: ${error.message}`);
}
const found = data.users.find((user) => user.email?.toLowerCase() === email.toLowerCase());
if (found) {
return found;
}
if (data.users.length < perPage) {
return null;
}
page += 1;
}
return null;
}
async function ensureSupabaseUser(params: {
email: string;
fullName: string;
password: string;
}): Promise<string> {
const existingUser = await findSupabaseUserByEmail(params.email);
if (existingUser) {
const { error } = await getSupabaseAdminClient().auth.admin.updateUserById(existingUser.id, {
password: params.password,
email_confirm: true,
user_metadata: {
full_name: params.fullName,
name: params.fullName,
},
});
if (error) {
throw new Error(`No se pudo actualizar usuario existente en Supabase: ${error.message}`);
}
return existingUser.id;
}
const { data, error } = await getSupabaseAdminClient().auth.admin.createUser({
email: params.email,
password: params.password,
email_confirm: true,
user_metadata: {
full_name: params.fullName,
name: params.fullName,
},
});
if (error || !data.user) {
throw new Error(`No se pudo crear usuario en Supabase: ${error?.message ?? 'unknown error'}`);
}
return data.user.id;
}
export async function startOnboarding(input: OnboardingStartInput): Promise<StartOnboardingResult> { export async function startOnboarding(input: OnboardingStartInput): Promise<StartOnboardingResult> {
const email = normalizeEmail(input.email); const email = normalizeEmail(input.email);
const otpCode = createOtpCode(); const otpCode = createOtpCode();

View File

@@ -1,7 +1,7 @@
import { createHash, randomInt } from 'node:crypto'; import { createHash, randomInt } from 'node:crypto';
import { auth } from '@/lib/auth';
import { sendMail } from '@/lib/mailer'; import { sendMail } from '@/lib/mailer';
import { db } from '@/lib/prisma'; import { db } from '@/lib/prisma';
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
import type { import type {
PasswordResetResendInput, PasswordResetResendInput,
PasswordResetStartInput, PasswordResetStartInput,
@@ -189,34 +189,17 @@ async function sendPasswordChangedEmail(
}); });
} }
async function findSupabaseUserByEmail(email: string): Promise<{ id: string } | null> { async function findBetterAuthUserByEmail(email: string): Promise<{ id: string } | null> {
let page = 1; const user = await db.user.findUnique({
const perPage = 100; where: { email },
select: { id: true },
while (page <= 10) {
const { data, error } = await getSupabaseAdminClient().auth.admin.listUsers({
page,
perPage,
}); });
if (error) { if (!user) {
throw new Error(`No se pudo consultar usuarios en Supabase: ${error.message}`);
}
const found = data.users.find((user) => user.email?.toLowerCase() === email.toLowerCase());
if (found) {
return { id: found.id };
}
if (data.users.length < perPage) {
return null; return null;
} }
page += 1; return { id: user.id };
}
return null;
} }
export async function startPasswordReset( export async function startPasswordReset(
@@ -224,7 +207,7 @@ export async function startPasswordReset(
): Promise<PasswordResetStartResult> { ): Promise<PasswordResetStartResult> {
const email = normalizeEmail(input.email); const email = normalizeEmail(input.email);
const existingUser = await findSupabaseUserByEmail(email); const existingUser = await findBetterAuthUserByEmail(email);
if (!existingUser) { if (!existingUser) {
return { return {
message: 'Si el email existe, recibirás un código de verificación.', message: 'Si el email existe, recibirás un código de verificación.',
@@ -369,13 +352,30 @@ export async function verifyOtpAndResetPassword(
throw new PasswordResetError('Código incorrecto.', 400); throw new PasswordResetError('Código incorrecto.', 400);
} }
const existingUser = await findSupabaseUserByEmail(request.email); const existingUser = await findBetterAuthUserByEmail(request.email);
if (!existingUser) { if (!existingUser) {
throw new PasswordResetError('Usuario no encontrado.', 404); throw new PasswordResetError('Usuario no encontrado.', 404);
} }
await getSupabaseAdminClient().auth.admin.updateUserById(existingUser.id, { const resetToken = uuidv7();
password: newPassword, const identifier = `reset-password:${resetToken}`;
const expiresAt = nowPlusMinutes(OTP_TTL_MINUTES);
await db.verification.create({
data: {
id: uuidv7(),
identifier,
value: existingUser.id,
expiresAt,
},
});
await auth.api.resetPassword({
asResponse: false,
body: {
token: resetToken,
newPassword,
},
}); });
await db.passwordResetRequest.update({ await db.passwordResetRequest.update({

View File

@@ -1,3 +0,0 @@
import type { User } from '@supabase/supabase-js';
export type AuthUser = User;

View File

@@ -1,3 +1 @@
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
VITE_API_BASE_URL=http://localhost:3000 VITE_API_BASE_URL=http://localhost:3000

View File

@@ -18,7 +18,6 @@
"@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@repo/api-contract": "workspace:*", "@repo/api-contract": "workspace:*",
"@supabase/supabase-js": "^2.101.1",
"@tanstack/react-query": "^5.96.1", "@tanstack/react-query": "^5.96.1",
"@tanstack/react-query-devtools": "^5.96.1", "@tanstack/react-query-devtools": "^5.96.1",
"@tanstack/react-router": "^1.168.10", "@tanstack/react-router": "^1.168.10",

View File

@@ -22,9 +22,6 @@ export default defineConfig({
if (id.includes('node_modules/@tanstack/react-router')) { if (id.includes('node_modules/@tanstack/react-router')) {
return 'router-vendor'; return 'router-vendor';
} }
if (id.includes('node_modules/@supabase/supabase-js')) {
return 'supabase-vendor';
}
if (id.includes('node_modules/radix-ui') || id.includes('node_modules/lucide-react')) { if (id.includes('node_modules/radix-ui') || id.includes('node_modules/lucide-react')) {
return 'ui-vendor'; return 'ui-vendor';
} }

View File

@@ -47,7 +47,6 @@
"@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@repo/api-contract": "workspace:*", "@repo/api-contract": "workspace:*",
"@supabase/supabase-js": "^2.101.1",
"@tanstack/react-query": "^5.96.1", "@tanstack/react-query": "^5.96.1",
"@tanstack/react-query-devtools": "^5.96.1", "@tanstack/react-query-devtools": "^5.96.1",
"@tanstack/react-router": "^1.168.10", "@tanstack/react-router": "^1.168.10",
@@ -515,20 +514,6 @@
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
"@supabase/auth-js": ["@supabase/auth-js@2.101.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-Kd0Wey+RkFHgyVep7adS6UOE2pN6MJ3mZ32PAXSvfw6IjUkFRC7IQpdZZjUOcUe5pXr1ejufCRgF6lsGINe4Tw=="],
"@supabase/functions-js": ["@supabase/functions-js@2.101.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-OZWU7YtaG+NNNFZK8p/FuJ6gpq7pFyrG2fLOopP73HAIDHDGpOttPJapvO8ADu3RkqfQfkwrB354vPkSBbZ20A=="],
"@supabase/phoenix": ["@supabase/phoenix@0.4.0", "", {}, "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw=="],
"@supabase/postgrest-js": ["@supabase/postgrest-js@2.101.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-UW1RajH5jbZoK+ldAJ1I6VZ+HWwZ2oaKjEQ6Gn+AQ67CHQVxGl8wNQoLYyumbyaExm41I+wn7arulcY1eHeZJw=="],
"@supabase/realtime-js": ["@supabase/realtime-js@2.101.1", "", { "dependencies": { "@supabase/phoenix": "^0.4.0", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-Oa6dno0OB9I+hv5do5zsZHbFu41ViZnE9IWjmkeeF/8fPmB5fWoHGqeTYEC3/0DAgtpUoFJa4FpvzFH0SBHo1Q=="],
"@supabase/storage-js": ["@supabase/storage-js@2.101.1", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-WhTaUOBgeEvnKLy95Cdlp6+D5igSF/65yC727w1olxbet5nzUvMlajKUWyzNtQu2efrz2cQ7FcdVBdQqgT9YKQ=="],
"@supabase/supabase-js": ["@supabase/supabase-js@2.101.1", "", { "dependencies": { "@supabase/auth-js": "2.101.1", "@supabase/functions-js": "2.101.1", "@supabase/postgrest-js": "2.101.1", "@supabase/realtime-js": "2.101.1", "@supabase/storage-js": "2.101.1" } }, "sha512-Jnhm3LfuACwjIzvk2pfUbGQn7pa7hi6MFzfSyPrRYWVCCu69RPLCFyHSBl7HSBwadbQ3UZOznnD3gPca3ePrRA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
@@ -611,8 +596,6 @@
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@@ -945,8 +928,6 @@
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
"iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -1461,8 +1442,6 @@
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],

View File

@@ -93,7 +93,6 @@ export const onboardingCompleteResponseSchema = z.object({
userId: z.uuid(), userId: z.uuid(),
complexId: z.uuid(), complexId: z.uuid(),
complexSlug: z.string(), complexSlug: z.string(),
supabaseUserId: z.uuid(),
}) })
export type OnboardingStartInput = z.infer<typeof onboardingStartSchema> export type OnboardingStartInput = z.infer<typeof onboardingStartSchema>