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,119 +1,117 @@
import { setApiAccessToken } from '@/lib/api-client';
import { supabase } from '@/lib/supabase';
import type { Session, User } from '@supabase/supabase-js';
import {
type PropsWithChildren,
createContext,
useContext,
useEffect,
useMemo,
useState,
type PropsWithChildren,
} from 'react'
import type { Session, User } from '@supabase/supabase-js'
import { setApiAccessToken } from '@/lib/api-client'
import { supabase } from '@/lib/supabase'
} from 'react';
type SignInParams = {
email: string
password: string
}
email: string;
password: string;
};
type SignUpParams = {
email: string
password: string
fullName: string
}
email: string;
password: string;
fullName: string;
};
type UpdateProfileParams = {
fullName: string
}
fullName: string;
};
type UpdatePasswordParams = {
password: string
}
password: string;
};
export type AuthContextValue = {
user: User | null
session: Session | null
loading: boolean
isAuthenticated: boolean
displayName: string
initials: string
avatarUrl: string | null
signInWithPassword: (params: SignInParams) => Promise<void>
signUp: (params: SignUpParams) => Promise<{ requiresEmailConfirmation: boolean }>
updateProfile: (params: UpdateProfileParams) => Promise<void>
updatePassword: (params: UpdatePasswordParams) => Promise<void>
signOut: () => Promise<void>
}
user: User | null;
session: Session | null;
loading: boolean;
isAuthenticated: boolean;
displayName: string;
initials: string;
avatarUrl: string | null;
signInWithPassword: (params: SignInParams) => Promise<void>;
signUp: (params: SignUpParams) => Promise<{ requiresEmailConfirmation: boolean }>;
updateProfile: (params: UpdateProfileParams) => Promise<void>;
updatePassword: (params: UpdatePasswordParams) => Promise<void>;
signOut: () => Promise<void>;
};
const AuthContext = createContext<AuthContextValue | null>(null)
const AuthContext = createContext<AuthContextValue | null>(null);
function getDisplayName(user: User | null): string {
if (!user) return 'Usuario'
if (!user) return 'Usuario';
const fromMetadata =
user.user_metadata?.name ??
user.user_metadata?.full_name ??
user.user_metadata?.user_name
user.user_metadata?.name ?? user.user_metadata?.full_name ?? user.user_metadata?.user_name;
if (typeof fromMetadata === 'string' && fromMetadata.trim().length > 0) {
return fromMetadata.trim()
return fromMetadata.trim();
}
return user.email ?? 'Usuario'
return user.email ?? 'Usuario';
}
function getInitials(name: string): string {
const words = name.trim().split(/\s+/).slice(0, 2)
const initials = words.map((word) => word[0]?.toUpperCase() ?? '').join('')
return initials || 'U'
const words = name.trim().split(/\s+/).slice(0, 2);
const initials = words.map((word) => word[0]?.toUpperCase() ?? '').join('');
return initials || 'U';
}
function getAvatarUrl(user: User | null): string | null {
if (!user) return null
if (!user) return null;
const value = user.user_metadata?.avatar_url ?? user.user_metadata?.picture
return typeof value === 'string' && value.length > 0 ? value : null
const value = user.user_metadata?.avatar_url ?? user.user_metadata?.picture;
return typeof value === 'string' && value.length > 0 ? value : null;
}
export function AuthProvider({ children }: PropsWithChildren) {
const [session, setSession] = useState<Session | null>(null)
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [session, setSession] = useState<Session | null>(null);
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let mounted = true
let mounted = true;
const init = async () => {
const { data } = await supabase.auth.getSession()
const { data } = await supabase.auth.getSession();
if (!mounted) return
if (!mounted) return;
setSession(data.session)
setUser(data.session?.user ?? null)
setApiAccessToken(data.session?.access_token ?? null)
setLoading(false)
}
setSession(data.session);
setUser(data.session?.user ?? null);
setApiAccessToken(data.session?.access_token ?? null);
setLoading(false);
};
void init()
void init();
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, nextSession) => {
setSession(nextSession)
setUser(nextSession?.user ?? null)
setApiAccessToken(nextSession?.access_token ?? null)
setLoading(false)
})
setSession(nextSession);
setUser(nextSession?.user ?? null);
setApiAccessToken(nextSession?.access_token ?? null);
setLoading(false);
});
return () => {
mounted = false
subscription.unsubscribe()
}
}, [])
mounted = false;
subscription.unsubscribe();
};
}, []);
const value = useMemo<AuthContextValue>(() => {
const displayName = getDisplayName(user)
const initials = getInitials(displayName)
const avatarUrl = getAvatarUrl(user)
const displayName = getDisplayName(user);
const initials = getInitials(displayName);
const avatarUrl = getAvatarUrl(user);
return {
user,
@@ -127,10 +125,10 @@ export function AuthProvider({ children }: PropsWithChildren) {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
});
if (error) {
throw error
throw error;
}
},
signUp: async ({ email, password, fullName }) => {
@@ -143,15 +141,15 @@ export function AuthProvider({ children }: PropsWithChildren) {
name: fullName,
},
},
})
});
if (error) {
throw error
throw error;
}
return {
requiresEmailConfirmation: !data.session,
}
};
},
updateProfile: async ({ fullName }) => {
const { error } = await supabase.auth.updateUser({
@@ -159,39 +157,39 @@ export function AuthProvider({ children }: PropsWithChildren) {
full_name: fullName,
name: fullName,
},
})
});
if (error) {
throw error
throw error;
}
},
updatePassword: async ({ password }) => {
const { error } = await supabase.auth.updateUser({
password,
})
});
if (error) {
throw error
throw error;
}
},
signOut: async () => {
const { error } = await supabase.auth.signOut()
const { error } = await supabase.auth.signOut();
if (error) {
throw error
throw error;
}
},
}
}, [loading, session, user])
};
}, [loading, session, user]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext)
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
throw new Error('useAuth must be used within AuthProvider');
}
return context
return context;
}