mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 13:24:27 +00:00
feat: implement customer registration flow and business hours system
Major changes: - Add customer registration with email/phone lookup (app/booking/registro) - Add customers API endpoint (app/api/customers/route) - Implement business hours for locations (mon-fri 10-7, sat 10-6, sun closed) - Fix availability function type casting issues - Add business hours utilities (lib/utils/business-hours.ts) - Update Location type to include business_hours JSONB - Add mock payment component for testing - Remove Supabase auth from booking flow - Fix /cita redirect path in booking flow Database migrations: - Add category column to services table - Add business_hours JSONB column to locations table - Fix availability functions with proper type casting - Update get_detailed_availability to use business_hours Features: - Customer lookup by email or phone - Auto-redirect to registration if customer not found - Pre-fill customer data if exists - Business hours per day of week - Location-specific opening/closing times
This commit is contained in:
@@ -8,6 +8,22 @@ export type InvitationStatus = 'pending' | 'used' | 'expired'
|
||||
export type ResourceType = 'station' | 'room' | 'equipment'
|
||||
export type AuditAction = 'create' | 'update' | 'delete' | 'reset_invitations' | 'payment' | 'status_change'
|
||||
|
||||
export interface DayHours {
|
||||
open: string
|
||||
close: string
|
||||
is_closed: boolean
|
||||
}
|
||||
|
||||
export interface BusinessHours {
|
||||
monday: DayHours
|
||||
tuesday: DayHours
|
||||
wednesday: DayHours
|
||||
thursday: DayHours
|
||||
friday: DayHours
|
||||
saturday: DayHours
|
||||
sunday: DayHours
|
||||
}
|
||||
|
||||
/** Represents a salon location with timezone and contact info */
|
||||
export interface Location {
|
||||
id: string
|
||||
@@ -16,6 +32,7 @@ export interface Location {
|
||||
address?: string
|
||||
phone?: string
|
||||
is_active: boolean
|
||||
business_hours?: BusinessHours
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@@ -151,8 +168,8 @@ export type Database = {
|
||||
Tables: {
|
||||
locations: {
|
||||
Row: Location
|
||||
Insert: Omit<Location, 'id' | 'created_at' | 'updated_at'>
|
||||
Update: Partial<Omit<Location, 'id' | 'created_at'>>
|
||||
Insert: Omit<Location, 'id' | 'created_at' | 'updated_at'> & { business_hours?: BusinessHours }
|
||||
Update: Partial<Omit<Location, 'id' | 'created_at'> & { business_hours?: BusinessHours }>
|
||||
}
|
||||
resources: {
|
||||
Row: Resource
|
||||
|
||||
16
lib/supabase/admin.ts
Normal file
16
lib/supabase/admin.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
||||
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!
|
||||
|
||||
// Admin Supabase client for server-side operations with service role
|
||||
export const supabaseAdmin = createClient(
|
||||
supabaseUrl,
|
||||
supabaseServiceRoleKey,
|
||||
{
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -6,16 +6,4 @@ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
// Public Supabase client for client-side operations
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
|
||||
// Admin Supabase client for server-side operations with service role
|
||||
export const supabaseAdmin = createClient(
|
||||
supabaseUrl,
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
||||
{
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default supabase
|
||||
|
||||
84
lib/utils/business-hours.ts
Normal file
84
lib/utils/business-hours.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { BusinessHours, DayHours } from '@/lib/db/types'
|
||||
|
||||
const DAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] as const
|
||||
type DayOfWeek = typeof DAYS[number]
|
||||
|
||||
export function getDayOfWeek(date: Date): DayOfWeek {
|
||||
return DAYS[date.getDay()]
|
||||
}
|
||||
|
||||
export function isOpenNow(businessHours: BusinessHours, date = new Date): boolean {
|
||||
const day = getDayOfWeek(date)
|
||||
const hours = businessHours[day]
|
||||
|
||||
if (!hours || hours.is_closed) {
|
||||
return false
|
||||
}
|
||||
|
||||
const now = date
|
||||
const [openHour, openMinute] = hours.open.split(':').map(Number)
|
||||
const [closeHour, closeMinute] = hours.close.split(':').map(Number)
|
||||
|
||||
const openTime = new Date(now)
|
||||
openTime.setHours(openHour, openMinute, 0, 0)
|
||||
|
||||
const closeTime = new Date(now)
|
||||
closeTime.setHours(closeHour, closeMinute, 0, 0)
|
||||
|
||||
return now >= openTime && now < closeTime
|
||||
}
|
||||
|
||||
export function getNextOpenTime(businessHours: BusinessHours, from = new Date): Date | null {
|
||||
const checkDate = new Date(from)
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const day = getDayOfWeek(checkDate)
|
||||
const hours = businessHours[day]
|
||||
|
||||
if (hours && !hours.is_closed) {
|
||||
const [openHour, openMinute] = hours.open.split(':').map(Number)
|
||||
|
||||
const openTime = new Date(checkDate)
|
||||
openTime.setHours(openHour, openMinute, 0, 0)
|
||||
|
||||
if (openTime > from) {
|
||||
return openTime
|
||||
}
|
||||
|
||||
openTime.setDate(openTime.getDate() + 1)
|
||||
return openTime
|
||||
}
|
||||
|
||||
checkDate.setDate(checkDate.getDate() + 1)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function isTimeWithinHours(time: string, dayHours: DayHours): boolean {
|
||||
if (dayHours.is_closed) {
|
||||
return false
|
||||
}
|
||||
|
||||
const [hour, minute] = time.split(':').map(Number)
|
||||
const checkMinutes = hour * 60 + minute
|
||||
|
||||
const [openHour, openMinute] = dayHours.open.split(':').map(Number)
|
||||
const [closeHour, closeMinute] = dayHours.close.split(':').map(Number)
|
||||
const openMinutes = openHour * 60 + openMinute
|
||||
const closeMinutes = closeHour * 60 + closeMinute
|
||||
|
||||
return checkMinutes >= openMinutes && checkMinutes < closeMinutes
|
||||
}
|
||||
|
||||
export function getBusinessHoursString(dayHours: DayHours): string {
|
||||
if (dayHours.is_closed) {
|
||||
return 'Cerrado'
|
||||
}
|
||||
return `${dayHours.open} - ${dayHours.close}`
|
||||
}
|
||||
|
||||
export function getTodayHours(businessHours: BusinessHours): string {
|
||||
const day = getDayOfWeek(new Date())
|
||||
return getBusinessHoursString(businessHours[day])
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { supabaseAdmin } from '@/lib/supabase/client'
|
||||
import { supabaseAdmin } from '@/lib/supabase/admin'
|
||||
|
||||
/**
|
||||
* generateShortId function that generates a unique short ID using Supabase RPC.
|
||||
|
||||
Reference in New Issue
Block a user