diff --git a/Dockerfile b/Dockerfile index a2756d3..ac29e79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Dockerfile optimizado para Next.js production -FROM node:18-alpine AS base +FROM node:20-alpine AS base # Instalar dependencias para build FROM base AS deps diff --git a/app/api/locations/route.ts b/app/api/locations/route.ts index 9a37caa..03982a2 100644 --- a/app/api/locations/route.ts +++ b/app/api/locations/route.ts @@ -6,11 +6,24 @@ import { supabase } from '@/lib/supabase/client' */ export async function GET(request: NextRequest) { try { + console.log('=== LOCATIONS API START ===') console.log('Locations API called with URL:', request.url) - // Check Supabase connection - console.log('Supabase URL:', process.env.NEXT_PUBLIC_SUPABASE_URL) - console.log('Supabase key exists:', !!process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) + // Test basic fetch to Supabase URL + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL + console.log('Testing basic connectivity to Supabase...') + try { + const testResponse = await fetch(`${supabaseUrl}/rest/v1/`, { + method: 'GET', + headers: { + 'apikey': process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '', + 'Content-Type': 'application/json' + } + }) + console.log('Basic Supabase connectivity test:', testResponse.status, testResponse.statusText) + } catch (fetchError) { + console.error('Basic fetch test failed:', fetchError) + } console.log('Executing locations query...') const { data: locationsData, error: queryError } = await supabase @@ -19,7 +32,7 @@ export async function GET(request: NextRequest) { .eq('is_active', true) .order('name', { ascending: true }) - console.log('Query result - data:', !!locationsData, 'error:', !!queryError) + console.log('Query result - data exists:', !!locationsData, 'error exists:', !!queryError) if (queryError) { console.error('Locations GET error details:', { @@ -32,25 +45,31 @@ export async function GET(request: NextRequest) { { error: queryError.message, code: queryError.code, - details: queryError.details + details: queryError.details, + timestamp: new Date().toISOString() }, { status: 500 } ) } console.log('Locations found:', locationsData?.length || 0) + console.log('=== LOCATIONS API END ===') return NextResponse.json({ success: true, locations: locationsData || [], - count: locationsData?.length || 0 + count: locationsData?.length || 0, + timestamp: new Date().toISOString() }) } catch (error) { + console.error('=== LOCATIONS API ERROR ===') console.error('Locations GET unexpected error:', error) console.error('Error stack:', error instanceof Error ? error.stack : 'Unknown error') + console.error('Error type:', error instanceof Error ? error.constructor.name : typeof error) return NextResponse.json( { error: 'Internal server error', - details: error instanceof Error ? error.message : 'Unknown error' + details: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() }, { status: 500 } ) diff --git a/app/api/services/route.ts b/app/api/services/route.ts index 78414b2..de64bca 100644 --- a/app/api/services/route.ts +++ b/app/api/services/route.ts @@ -6,15 +6,28 @@ import { supabase } from '@/lib/supabase/client' */ export async function GET(request: NextRequest) { try { + console.log('=== SERVICES API START ===') console.log('Services API called with URL:', request.url) const { searchParams } = new URL(request.url) const locationId = searchParams.get('location_id') console.log('Location ID filter:', locationId) - // Check Supabase connection - console.log('Supabase URL:', process.env.NEXT_PUBLIC_SUPABASE_URL) - console.log('Supabase key exists:', !!process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) + // Test basic fetch to Supabase URL + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL + console.log('Testing basic connectivity to Supabase...') + try { + const testResponse = await fetch(`${supabaseUrl}/rest/v1/`, { + method: 'GET', + headers: { + 'apikey': process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '', + 'Content-Type': 'application/json' + } + }) + console.log('Basic Supabase connectivity test:', testResponse.status, testResponse.statusText) + } catch (fetchError) { + console.error('Basic fetch test failed:', fetchError) + } let query = supabase .from('services') @@ -26,10 +39,10 @@ export async function GET(request: NextRequest) { query = query.eq('location_id', locationId) } - console.log('Executing query...') + console.log('Executing Supabase query...') const { data: servicesData, error: queryError } = await query - console.log('Query result - data:', !!servicesData, 'error:', !!queryError) + console.log('Query result - data exists:', !!servicesData, 'error exists:', !!queryError) if (queryError) { console.error('Services GET error details:', { @@ -42,25 +55,31 @@ export async function GET(request: NextRequest) { { error: queryError.message, code: queryError.code, - details: queryError.details + details: queryError.details, + timestamp: new Date().toISOString() }, { status: 500 } ) } console.log('Services found:', servicesData?.length || 0) + console.log('=== SERVICES API END ===') return NextResponse.json({ success: true, services: servicesData || [], - count: servicesData?.length || 0 + count: servicesData?.length || 0, + timestamp: new Date().toISOString() }) } catch (error) { + console.error('=== SERVICES API ERROR ===') console.error('Services GET unexpected error:', error) console.error('Error stack:', error instanceof Error ? error.stack : 'Unknown error') + console.error('Error type:', error instanceof Error ? error.constructor.name : typeof error) return NextResponse.json( { error: 'Internal server error', - details: error instanceof Error ? error.message : 'Unknown error' + details: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() }, { status: 500 } ) diff --git a/app/servicios/page.tsx b/app/servicios/page.tsx index 0638e0a..08e9e91 100644 --- a/app/servicios/page.tsx +++ b/app/servicios/page.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react' import { AnimatedLogo } from '@/components/animated-logo' import { RollingPhrases } from '@/components/rolling-phrases' -/** @description Services page with home page style structure */ +/** @description Premium services page with elegant layout and sophisticated design */ interface Service { id: string @@ -57,22 +57,35 @@ export default function ServiciosPage() { const getCategoryTitle = (category: string) => { const titles: Record = { - core: 'CORE EXPERIENCES - El corazón de Anchor 23', - nails: 'NAIL COUTURE - Técnica invisible. Resultado impecable.', + core: 'CORE EXPERIENCES', + nails: 'NAIL COUTURE', hair: 'HAIR FINISHING RITUALS', - lashes: 'LASH & BROW RITUALS - Mirada definida con sutileza.', - brows: 'LASH & BROW RITUALS - Mirada definida con sutileza.', - events: 'EVENT EXPERIENCES - Agenda especial', - permanent: 'PERMANENT RITUALS - Agenda limitada · Especialista certificada' + lashes: 'LASH & BROW RITUALS', + brows: 'LASH & BROW RITUALS', + events: 'EVENT EXPERIENCES', + permanent: 'PERMANENT RITUALS' } return titles[category] || category } + const getCategorySubtitle = (category: string) => { + const subtitles: Record = { + core: 'El corazón de Anchor 23', + nails: 'Técnica invisible. Resultado impecable.', + hair: 'Disponibles únicamente para clientas con experiencia Anchor el mismo día', + lashes: 'Mirada definida con sutileza', + brows: 'Mirada definida con sutileza', + events: 'Agenda especial', + permanent: 'Agenda limitada · Especialista certificada' + } + return subtitles[category] || '' + } + const getCategoryDescription = (category: string) => { const descriptions: Record = { core: 'Rituales conscientes donde el tiempo se desacelera. Cada experiencia está diseñada para mujeres que valoran el silencio, la atención absoluta y los resultados impecables.', nails: 'En Anchor 23 no eliges técnicas. Cada decisión se toma internamente para lograr un resultado elegante, duradero y natural. No ofrecemos servicios de mantenimiento ni correcciones.', - hair: 'Disponibles únicamente para clientas con experiencia Anchor el mismo día.', + hair: '', lashes: '', brows: '', events: 'Agenda especial para ocasiones selectas.', @@ -93,10 +106,10 @@ export default function ServiciosPage() { if (loading) { return ( -
-
-

Nuestros Servicios

-

Cargando servicios...

+
+
+
+

Cargando servicios...

) @@ -104,94 +117,152 @@ export default function ServiciosPage() { return ( <> -
-
- -

Servicios

-

Anchor:23

- -
- - Reservar Cita + {/* Hero Section - Simplified and Elegant */} +
+ {/* Background Pattern */} +
+
+
+ +
-
-
- Imagen Servicios +
+ + {/* Philosophy Section */} +
+
+
+
+

+ Nuestra Filosofía +

+

+ Criterio antes que cantidad +

+

+ Anchor 23 es un espacio privado donde el tiempo se desacelera. Aquí, cada experiencia está diseñada para mujeres que valoran el silencio, la atención absoluta y los resultados impecables. +

+

+ No trabajamos con volumen. Trabajamos con intención. +

+
+
+
+ Imagen Experiencias +
+
-
-
-

Experiencias

-

Criterio antes que cantidad

-

- Anchor 23 es un espacio privado donde el tiempo se desacelera. Aquí, cada experiencia está diseñada para mujeres que valoran el silencio, la atención absoluta y los resultados impecables. -

-

- No trabajamos con volumen. Trabajamos con intención. -

-
- -
- -
-

Nuestros Servicios

-
+ {/* Services Catalog */} +
+
{categoryOrder.map(category => { const categoryServices = groupedServices[category] if (!categoryServices || categoryServices.length === 0) return null return ( -
-
-

+
+ {/* Category Header */} +
+

+ {getCategorySubtitle(category)} +

+

{getCategoryTitle(category)} -

+

{getCategoryDescription(category) && ( -

+

{getCategoryDescription(category)}

)}
-
+ {/* Service Cards Grid */} +
{categoryServices.map((service) => (
{ + e.currentTarget.style.borderColor = 'var(--mocha-taupe)' + e.currentTarget.style.background = 'var(--bone-white)' + }} + onMouseLeave={(e) => { + e.currentTarget.style.borderColor = 'transparent' + e.currentTarget.style.background = 'var(--soft-cream)' + }} > -
-
+ {/* Service Header */} +
+

{service.name} -

+
{service.description && ( -

+

{service.description}

)}
-
- - ⏳ {formatDuration(service.duration_minutes)} - + {/* Service Meta */} +
+
+ + + + + {formatDuration(service.duration_minutes)} + +
{service.requires_dual_artist && ( - Dual Artist + + Dual Artist + )}
+ {/* Price and CTA */} @@ -201,38 +272,55 @@ export default function ServiciosPage() {
) })} +
+
-
-

Lo que Define Anchor 23

-
-
-
-
- - No ofrecemos retoques ni servicios aislados -
-
- - No trabajamos con prisas -
-
- - No explicamos de más -
+ {/* Values Section */} +
+
+

+ Lo que Define Anchor 23 +

+
+
+ {[ + 'No ofrecemos retoques ni servicios aislados', + 'No trabajamos con prisas', + 'No explicamos de más' + ].map((text, idx) => ( +
+
+

{text}

-
-
- - No negociamos estándares -
-
- - Cada experiencia está pensada para durar, sentirse y recordarse -
-
-
+ ))}
-
+
+ {[ + 'No negociamos estándares', + 'Cada experiencia está pensada para durar, sentirse y recordarse' + ].map((text, idx) => ( +
+
+

{text}

+
+ ))} +
+
+
+
+ + {/* Final CTA */} +
+
+

+ ¿Lista para tu experiencia? +

+

+ Reserva tu cita y descubre lo que significa una atención verdaderamente personalizada. +

+ + Reservar Ahora +
diff --git a/lib/supabase/client.ts b/lib/supabase/client.ts index 8142355..9e096c9 100644 --- a/lib/supabase/client.ts +++ b/lib/supabase/client.ts @@ -1,14 +1,42 @@ import { createClient } from '@supabase/supabase-js' -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! -const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! +// Lazy initialization to ensure env vars are available at runtime +let supabaseInstance: ReturnType | null = null + +function getSupabaseClient() { + if (!supabaseInstance) { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL + const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + + console.log('=== SUPABASE CLIENT INIT ===') + console.log('SUPABASE_URL available:', !!supabaseUrl) + console.log('SUPABASE_ANON_KEY available:', !!supabaseAnonKey) + console.log('SUPABASE_URL value:', supabaseUrl) + console.log('SUPABASE_ANON_KEY preview:', supabaseAnonKey ? supabaseAnonKey.substring(0, 20) + '...' : 'null') + + if (!supabaseUrl || !supabaseAnonKey) { + throw new Error(`Missing Supabase environment variables: URL=${!!supabaseUrl}, KEY=${!!supabaseAnonKey}`) + } + + supabaseInstance = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true + } + }) + + console.log('Supabase client initialized successfully') + } + + return supabaseInstance +} // Public Supabase client for client-side operations -export const supabase = createClient(supabaseUrl, supabaseAnonKey, { - auth: { - autoRefreshToken: true, - persistSession: true, - detectSessionInUrl: true +export const supabase = new Proxy({} as ReturnType, { + get(target, prop) { + const client = getSupabaseClient() + return client[prop as keyof typeof client] } })