first commit

This commit is contained in:
Michaël
2026-01-29 14:13:11 -03:00
commit 2302748c87
105 changed files with 93301 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
'use client';
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
interface User {
id: string;
email: string;
name: string;
role: string;
phone?: string;
languagePreference?: string;
isClaimed?: boolean;
rucNumber?: string;
accountStatus?: string;
}
interface AuthContextType {
user: User | null;
token: string | null;
isLoading: boolean;
isAdmin: boolean;
login: (email: string, password: string) => Promise<void>;
loginWithGoogle: (credential: string) => Promise<void>;
loginWithMagicLink: (token: string) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => void;
updateUser: (user: User) => void;
setAuthData: (data: { user: User; token: string }) => void;
}
interface RegisterData {
email: string;
password: string;
name: string;
phone?: string;
languagePreference?: string;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
const TOKEN_KEY = 'spanglish-token';
const USER_KEY = 'spanglish-user';
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Load auth state from localStorage
const savedToken = localStorage.getItem(TOKEN_KEY);
const savedUser = localStorage.getItem(USER_KEY);
if (savedToken && savedUser) {
setToken(savedToken);
setUser(JSON.parse(savedUser));
}
setIsLoading(false);
}, []);
const setAuthData = useCallback((data: { user: User; token: string }) => {
setToken(data.token);
setUser(data.user);
localStorage.setItem(TOKEN_KEY, data.token);
localStorage.setItem(USER_KEY, JSON.stringify(data.user));
}, []);
const login = async (email: string, password: string) => {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.error || 'Login failed');
}
const data = await res.json();
setAuthData(data);
};
const loginWithGoogle = async (credential: string) => {
const res = await fetch('/api/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential }),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.error || 'Google login failed');
}
const data = await res.json();
setAuthData(data);
};
const loginWithMagicLink = async (magicToken: string) => {
const res = await fetch('/api/auth/magic-link/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: magicToken }),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.error || 'Magic link login failed');
}
const data = await res.json();
setAuthData(data);
};
const register = async (registerData: RegisterData) => {
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(registerData),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.error || 'Registration failed');
}
const data = await res.json();
setAuthData(data);
};
const logout = useCallback(() => {
setToken(null);
setUser(null);
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}, []);
const updateUser = useCallback((updatedUser: User) => {
setUser(updatedUser);
localStorage.setItem(USER_KEY, JSON.stringify(updatedUser));
}, []);
const isAdmin = user?.role === 'admin' || user?.role === 'organizer';
return (
<AuthContext.Provider
value={{
user,
token,
isLoading,
isAdmin,
login,
loginWithGoogle,
loginWithMagicLink,
register,
logout,
updateUser,
setAuthData,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}

View File

@@ -0,0 +1,72 @@
'use client';
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { Locale, defaultLocale, t, locales } from '@/i18n';
interface LanguageContextType {
locale: Locale;
setLocale: (locale: Locale) => void;
t: (key: string, params?: Record<string, string | number>) => string;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
const STORAGE_KEY = 'spanglish-locale';
export function LanguageProvider({ children }: { children: ReactNode }) {
const [locale, setLocaleState] = useState<Locale>(defaultLocale);
const [mounted, setMounted] = useState(false);
useEffect(() => {
// Load saved locale from localStorage
const saved = localStorage.getItem(STORAGE_KEY) as Locale | null;
if (saved && locales[saved]) {
setLocaleState(saved);
} else {
// Try to detect browser language
const browserLang = navigator.language.split('-')[0] as Locale;
if (locales[browserLang]) {
setLocaleState(browserLang);
}
}
setMounted(true);
}, []);
const setLocale = (newLocale: Locale) => {
setLocaleState(newLocale);
localStorage.setItem(STORAGE_KEY, newLocale);
};
const translate = (key: string, params?: Record<string, string | number>) => {
return t(locale, key, params);
};
// Prevent hydration mismatch
if (!mounted) {
return (
<LanguageContext.Provider
value={{
locale: defaultLocale,
setLocale,
t: (key, params) => t(defaultLocale, key, params),
}}
>
{children}
</LanguageContext.Provider>
);
}
return (
<LanguageContext.Provider value={{ locale, setLocale, t: translate }}>
{children}
</LanguageContext.Provider>
);
}
export function useLanguage() {
const context = useContext(LanguageContext);
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
}