refactor: migrate authentication from Supabase to Better Auth and update project configuration
This commit is contained in:
@@ -18,11 +18,7 @@ WORKDIR /app
|
||||
COPY apps/frontend ./apps/frontend
|
||||
COPY packages ./packages
|
||||
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_SUPABASE_URL=${VITE_SUPABASE_URL}
|
||||
ENV VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY}
|
||||
RUN bun run build:frontend
|
||||
|
||||
# =============================================================================
|
||||
@@ -70,4 +66,4 @@ COPY entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["/app/entrypoint.sh"]
|
||||
CMD ["/app/entrypoint.sh"]
|
||||
|
||||
183
README.md
183
README.md
@@ -1,184 +1,105 @@
|
||||
# Monorepo (Frontend + Backend + Contrato API)
|
||||
# Playzer Monorepo
|
||||
|
||||
Este repositorio contiene:
|
||||
|
||||
- `apps/frontend`: app web en React + Vite + TanStack Router/Query.
|
||||
- `apps/backend`: API en Bun + Hono + Prisma + Supabase Auth.
|
||||
- `packages/api-contract`: contratos compartidos (schemas/types/rutas) entre frontend y backend.
|
||||
- `apps/frontend`: React + Vite + TanStack Router/Query.
|
||||
- `apps/backend`: Bun + Hono + Prisma + Better Auth.
|
||||
- `packages/api-contract`: contratos Zod/tipos compartidos.
|
||||
|
||||
## Objetivo de este README
|
||||
## Requisitos
|
||||
|
||||
Dejarte productivo/a en pocos minutos para que puedas:
|
||||
|
||||
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).
|
||||
- Bun reciente
|
||||
- PostgreSQL disponible
|
||||
|
||||
## Setup rápido
|
||||
|
||||
1. Instalar dependencias desde la raíz:
|
||||
1. Instalar dependencias:
|
||||
|
||||
```sh
|
||||
bun install
|
||||
```
|
||||
|
||||
2. Configurar variables de entorno del backend:
|
||||
2. Configurar backend:
|
||||
|
||||
```sh
|
||||
cp apps/backend/.env.example apps/backend/.env
|
||||
```
|
||||
|
||||
3. Configurar variables de entorno del frontend:
|
||||
3. Configurar frontend:
|
||||
|
||||
```sh
|
||||
cp apps/frontend/.env.example apps/frontend/.env
|
||||
```
|
||||
|
||||
4. Revisar/editar valores en ambos `.env` según tu entorno.
|
||||
|
||||
## 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:
|
||||
4. Ejecutar migraciones:
|
||||
|
||||
```sh
|
||||
bun --filter backend prisma:migrate
|
||||
```
|
||||
|
||||
Si cambiás el schema, regenerá cliente:
|
||||
## Variables de entorno
|
||||
|
||||
```sh
|
||||
bun --filter backend prisma:generate
|
||||
```
|
||||
### Backend (`apps/backend/.env`)
|
||||
|
||||
## 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
|
||||
bun run dev:backend
|
||||
```
|
||||
|
||||
Terminal 2 (frontend):
|
||||
Frontend:
|
||||
|
||||
```sh
|
||||
bun run dev:frontend
|
||||
```
|
||||
|
||||
Puertos por defecto:
|
||||
Todo junto:
|
||||
|
||||
- Frontend: `http://localhost:5173`
|
||||
- Backend: `http://localhost:3000`
|
||||
|
||||
## 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
|
||||
```sh
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Flujo de autenticación (resumen)
|
||||
## Prisma
|
||||
|
||||
1. Frontend autentica con Supabase.
|
||||
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/*`).
|
||||
Regenerar cliente después de cambios de schema:
|
||||
|
||||
## Endpoints principales
|
||||
```sh
|
||||
bun --filter backend prisma:generate
|
||||
```
|
||||
|
||||
- `GET /api/user/profile`
|
||||
- `POST /api/tenants`
|
||||
- `GET /api/tenants/:id`
|
||||
- `PATCH /api/tenants/:id`
|
||||
## Autenticación
|
||||
|
||||
## 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).
|
||||
- Si agregás/cambiás endpoint:
|
||||
1. Actualizá schema/tipos en `api-contract`.
|
||||
2. Implementá handler/ruta en backend.
|
||||
3. Consumí el contrato desde frontend.
|
||||
- Validar payloads con Zod tanto en contrato como en rutas.
|
||||
- Mantener PRs chicos y enfocados.
|
||||
- Backend: `c.get('user')` y `c.get('session')` en contexto Hono.
|
||||
- Frontend: `authClient` con `fetchOptions.credentials = 'include'`.
|
||||
- Frontend API: Axios con `withCredentials = true`.
|
||||
- Backend CORS: `credentials: true`.
|
||||
|
||||
## Troubleshooting rápido
|
||||
## Deploy (Docker)
|
||||
|
||||
- Error de CORS:
|
||||
- Revisar `CORS_ORIGIN` en backend y que incluya `http://localhost:5173`.
|
||||
- `403 Missing bearer token`:
|
||||
- Verificar login en frontend y envío del header `Authorization`.
|
||||
- Error de Supabase env vars:
|
||||
- Confirmar `SUPABASE_URL`/`SUPABASE_ANON_KEY` (backend) y `VITE_*` (frontend).
|
||||
- Error de Prisma/DB:
|
||||
- Confirmar `DATABASE_URL`, conectividad a PostgreSQL y migraciones aplicadas.
|
||||
- Build context: raíz del monorepo (`./`)
|
||||
- Dockerfile: `./Dockerfile`
|
||||
- Build args requeridos:
|
||||
- `DATABASE_URL`
|
||||
- `VITE_API_BASE_URL`
|
||||
- `BETTER_AUTH_SECRET`
|
||||
- `BETTER_AUTH_URL`
|
||||
|
||||
## Primer día sugerido para onboarding
|
||||
## Flujo recomendado de cambios de API
|
||||
|
||||
1. Levantar backend y frontend en local.
|
||||
2. Crear usuario o iniciar sesión desde la pantalla de onboard.
|
||||
3. Crear un tenant de prueba.
|
||||
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.
|
||||
1. Actualizar contrato en `packages/api-contract`.
|
||||
2. Implementar backend en `apps/backend`.
|
||||
3. Consumir contrato desde frontend (`@repo/api-contract`).
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
SUPABASE_URL=https://your-project-ref.supabase.co
|
||||
SUPABASE_ANON_KEY=your-anon-key
|
||||
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
|
||||
BETTER_AUTH_SECRET=your-random-secret
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
|
||||
LOG_LEVEL=debug
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/monorepo
|
||||
|
||||
@@ -1,11 +1 @@
|
||||
// model User {
|
||||
// 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")
|
||||
// }
|
||||
// User model is defined in auth.prisma (Better Auth schema).
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -2,14 +2,12 @@ import { createHash, randomInt } from 'node:crypto';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { sendMail } from '@/lib/mailer';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
|
||||
import type {
|
||||
OnboardingCompleteInput,
|
||||
OnboardingResendOtpInput,
|
||||
OnboardingStartInput,
|
||||
OnboardingVerifyOtpInput,
|
||||
} from '@repo/api-contract';
|
||||
import type { User as SupabaseAuthUser } from '@supabase/supabase-js';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
|
||||
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> {
|
||||
const email = normalizeEmail(input.email);
|
||||
const otpCode = createOtpCode();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createHash, randomInt } from 'node:crypto';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { sendMail } from '@/lib/mailer';
|
||||
import { db } from '@/lib/prisma';
|
||||
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
|
||||
import type {
|
||||
PasswordResetResendInput,
|
||||
PasswordResetStartInput,
|
||||
@@ -189,34 +189,17 @@ async function sendPasswordChangedEmail(
|
||||
});
|
||||
}
|
||||
|
||||
async function findSupabaseUserByEmail(email: string): Promise<{ id: string } | null> {
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
async function findBetterAuthUserByEmail(email: string): Promise<{ id: string } | null> {
|
||||
const user = await db.user.findUnique({
|
||||
where: { email },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
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 { id: found.id };
|
||||
}
|
||||
|
||||
if (data.users.length < perPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
page += 1;
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return { id: user.id };
|
||||
}
|
||||
|
||||
export async function startPasswordReset(
|
||||
@@ -224,7 +207,7 @@ export async function startPasswordReset(
|
||||
): Promise<PasswordResetStartResult> {
|
||||
const email = normalizeEmail(input.email);
|
||||
|
||||
const existingUser = await findSupabaseUserByEmail(email);
|
||||
const existingUser = await findBetterAuthUserByEmail(email);
|
||||
if (!existingUser) {
|
||||
return {
|
||||
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);
|
||||
}
|
||||
|
||||
const existingUser = await findSupabaseUserByEmail(request.email);
|
||||
const existingUser = await findBetterAuthUserByEmail(request.email);
|
||||
if (!existingUser) {
|
||||
throw new PasswordResetError('Usuario no encontrado.', 404);
|
||||
}
|
||||
|
||||
await getSupabaseAdminClient().auth.admin.updateUserById(existingUser.id, {
|
||||
password: newPassword,
|
||||
const resetToken = uuidv7();
|
||||
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({
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { User } from '@supabase/supabase-js';
|
||||
|
||||
export type AuthUser = User;
|
||||
@@ -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
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"@radix-ui/react-popover": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@repo/api-contract": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.101.1",
|
||||
"@tanstack/react-query": "^5.96.1",
|
||||
"@tanstack/react-query-devtools": "^5.96.1",
|
||||
"@tanstack/react-router": "^1.168.10",
|
||||
@@ -52,4 +51,4 @@
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^8.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ export default defineConfig({
|
||||
if (id.includes('node_modules/@tanstack/react-router')) {
|
||||
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')) {
|
||||
return 'ui-vendor';
|
||||
}
|
||||
|
||||
21
bun.lock
21
bun.lock
@@ -47,7 +47,6 @@
|
||||
"@radix-ui/react-popover": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@repo/api-contract": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.101.1",
|
||||
"@tanstack/react-query": "^5.96.1",
|
||||
"@tanstack/react-query-devtools": "^5.96.1",
|
||||
"@tanstack/react-router": "^1.168.10",
|
||||
@@ -515,20 +514,6 @@
|
||||
|
||||
"@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/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/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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||
|
||||
@@ -93,7 +93,6 @@ export const onboardingCompleteResponseSchema = z.object({
|
||||
userId: z.uuid(),
|
||||
complexId: z.uuid(),
|
||||
complexSlug: z.string(),
|
||||
supabaseUserId: z.uuid(),
|
||||
})
|
||||
|
||||
export type OnboardingStartInput = z.infer<typeof onboardingStartSchema>
|
||||
|
||||
Reference in New Issue
Block a user