feat: add city/state/country to complex, new settings page with sidebar, and Biome linting

- Added city, state, and country optional fields to Complex model
- Updated onboarding to include optional location fields
- Created new settings page with sidebar navigation (Datos del Complejo, Canchas)
- Replaced ESLint with Biome for frontend and backend linting
- Added parallel dev script with concurrently
- Migrated register-routes to use direct app.route() pattern
This commit is contained in:
Jose Selesan
2026-04-10 15:36:15 -03:00
parent 77004b14b0
commit 9ee98a4cb4
129 changed files with 2929 additions and 3186 deletions

View File

@@ -1,6 +1,6 @@
import pino from 'pino'
import pino from 'pino';
const isProd = Bun.env.NODE_ENV === 'production'
const isProd = Bun.env.NODE_ENV === 'production';
export const logger = pino({
level: Bun.env.LOG_LEVEL ?? (isProd ? 'info' : 'debug'),
@@ -15,4 +15,4 @@ export const logger = pino({
ignore: 'pid,hostname',
},
},
})
});

View File

@@ -1,19 +1,17 @@
import nodemailer from 'nodemailer'
import nodemailer from 'nodemailer';
let cachedTransporter: ReturnType<typeof nodemailer.createTransport> | null = null
let cachedTransporter: ReturnType<typeof nodemailer.createTransport> | null = null;
function getTransporter() {
if (cachedTransporter) return cachedTransporter
if (cachedTransporter) return cachedTransporter;
const smtpHost = Bun.env.SMTP_HOST
const smtpPort = Number(Bun.env.SMTP_PORT ?? 587)
const smtpUser = Bun.env.SMTP_USER
const smtpPass = Bun.env.SMTP_PASS
const smtpHost = Bun.env.SMTP_HOST;
const smtpPort = Number(Bun.env.SMTP_PORT ?? 587);
const smtpUser = Bun.env.SMTP_USER;
const smtpPass = Bun.env.SMTP_PASS;
if (!smtpHost || !smtpUser || !smtpPass) {
throw new Error(
'Missing SMTP env vars. Set SMTP_HOST, SMTP_PORT, SMTP_USER and SMTP_PASS.',
)
throw new Error('Missing SMTP env vars. Set SMTP_HOST, SMTP_PORT, SMTP_USER and SMTP_PASS.');
}
cachedTransporter = nodemailer.createTransport({
@@ -24,21 +22,21 @@ function getTransporter() {
user: smtpUser,
pass: smtpPass,
},
})
});
return cachedTransporter
return cachedTransporter;
}
export async function sendMail(input: {
to: string
subject: string
html: string
text: string
to: string;
subject: string;
html: string;
text: string;
}) {
const smtpFrom = Bun.env.SMTP_FROM
const smtpFrom = Bun.env.SMTP_FROM;
if (!smtpFrom) {
throw new Error('Missing SMTP_FROM env var.')
throw new Error('Missing SMTP_FROM env var.');
}
await getTransporter().sendMail({
@@ -47,5 +45,5 @@ export async function sendMail(input: {
subject: input.subject,
html: input.html,
text: input.text,
})
});
}

View File

@@ -1,50 +1,50 @@
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from '@/generated/prisma/client'
import { PrismaClient } from '@/generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
function hasExpectedDelegates(client: PrismaClient) {
const prismaClient = client as unknown as {
court?: { findMany?: unknown }
sport?: { findMany?: unknown }
}
court?: { findMany?: unknown };
sport?: { findMany?: unknown };
};
return (
typeof prismaClient.court?.findMany === 'function' &&
typeof prismaClient.sport?.findMany === 'function'
)
);
}
function buildPrismaClient() {
const databaseUrl = Bun.env.DATABASE_URL
const databaseUrl = Bun.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('Missing DATABASE_URL in backend environment.')
throw new Error('Missing DATABASE_URL in backend environment.');
}
const adapter = new PrismaPg({
connectionString: databaseUrl,
})
});
return new PrismaClient({ adapter })
return new PrismaClient({ adapter });
}
export function getPrismaClient() {
if (globalForPrisma.prisma && hasExpectedDelegates(globalForPrisma.prisma)) {
return globalForPrisma.prisma
return globalForPrisma.prisma;
}
if (globalForPrisma.prisma && !hasExpectedDelegates(globalForPrisma.prisma)) {
void globalForPrisma.prisma.$disconnect()
void globalForPrisma.prisma.$disconnect();
}
const prisma = buildPrismaClient()
const prisma = buildPrismaClient();
if (Bun.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
globalForPrisma.prisma = prisma;
}
return prisma
return prisma;
}
export const db = getPrismaClient()
export const db = getPrismaClient();

View File

@@ -1,17 +1,17 @@
import { createClient } from '@supabase/supabase-js'
import { createClient } from '@supabase/supabase-js';
let cachedClient: ReturnType<typeof createClient> | null = null
let cachedClient: ReturnType<typeof createClient> | null = null;
export function getSupabaseAdminClient() {
if (cachedClient) return cachedClient
if (cachedClient) return cachedClient;
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL
const supabaseServiceRoleKey = Bun.env.SUPABASE_SERVICE_ROLE_KEY
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.',
)
'Missing Supabase admin env vars. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in backend environment.'
);
}
cachedClient = createClient(supabaseUrl, supabaseServiceRoleKey, {
@@ -20,7 +20,7 @@ export function getSupabaseAdminClient() {
persistSession: false,
detectSessionInUrl: false,
},
})
});
return cachedClient
return cachedClient;
}

View File

@@ -1,13 +1,12 @@
import { createClient } from '@supabase/supabase-js'
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
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.',
)
'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, {
@@ -16,4 +15,4 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
persistSession: false,
detectSessionInUrl: false,
},
})
});