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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user