antes de alterar login
This commit is contained in:
@@ -23,11 +23,51 @@ export default function LoginPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await signInWithEmailAndPassword(auth, email, password);
|
const userCredential = await signInWithEmailAndPassword(auth, email, password);
|
||||||
|
|
||||||
|
// Verify the user has a restaurant record before redirecting
|
||||||
|
if (!userCredential.user?.email) {
|
||||||
|
setError("Erro: Utilizador sem email válido.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success — redirect to dashboard
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error("[Login Error]", err.code, err.message);
|
||||||
setError("Credenciais inválidas. Verifique o seu email e palavra-passe.");
|
|
||||||
|
// Map Firebase Auth error codes to user-friendly messages in Portuguese
|
||||||
|
switch (err.code) {
|
||||||
|
case "auth/user-not-found":
|
||||||
|
setError("Esta conta não existe. Verifique o email ou registe-se.");
|
||||||
|
break;
|
||||||
|
case "auth/wrong-password":
|
||||||
|
setError("Palavra-passe incorrecta. Tente novamente.");
|
||||||
|
break;
|
||||||
|
case "auth/invalid-email":
|
||||||
|
setError("Email inválido. Verifique o formato do email.");
|
||||||
|
break;
|
||||||
|
case "auth/invalid-credential":
|
||||||
|
setError("Credenciais inválidas. Verifique o email e a palavra-passe.");
|
||||||
|
break;
|
||||||
|
case "auth/too-many-requests":
|
||||||
|
setError("Demasiadas tentativas falhadas. Aguarde alguns minutos e tente novamente.");
|
||||||
|
break;
|
||||||
|
case "auth/invalid-verification-id":
|
||||||
|
setError("Sessão expirada. Por favor, recarregue a página e tente novamente.");
|
||||||
|
break;
|
||||||
|
case "auth/network-request-failed":
|
||||||
|
setError("Erro de conexão. Verifique a sua ligação à internet.");
|
||||||
|
break;
|
||||||
|
case "auth/weak-password":
|
||||||
|
setError("A palavra-passe é demasiado curta. Deve ter pelo menos 6 caracteres.");
|
||||||
|
break;
|
||||||
|
case "auth/popup-closed-by-user":
|
||||||
|
// User closed the popup — no error needed
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setError("Erro ao entrar. Verifique as suas credenciais e tente novamente.");
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,23 @@ export default function RegisterPage() {
|
|||||||
setError("");
|
setError("");
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
// Validate inputs before attempting registration
|
||||||
|
if (!formData.email || !formData.email.includes("@")) {
|
||||||
|
setError("Por favor, insira um email válido.");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formData.password.length < 6) {
|
||||||
|
setError("A palavra-passe deve ter pelo menos 6 caracteres.");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!formData.establishmentName.trim()) {
|
||||||
|
setError("Por favor, insira o nome do restaurante.");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Criar utilizador na Firebase Auth
|
// 1. Criar utilizador na Firebase Auth
|
||||||
const userCredential = await createUserWithEmailAndPassword(auth, formData.email, formData.password);
|
const userCredential = await createUserWithEmailAndPassword(auth, formData.email, formData.password);
|
||||||
@@ -62,10 +79,37 @@ export default function RegisterPage() {
|
|||||||
// 3. Gravar na Realtime Database em /Restaurantes
|
// 3. Gravar na Realtime Database em /Restaurantes
|
||||||
await set(ref(db, `Restaurantes/${documentId}`), payload);
|
await set(ref(db, `Restaurantes/${documentId}`), payload);
|
||||||
|
|
||||||
|
// 4. Success — redirect to dashboard
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error("[Register Error]", err.code, err.message);
|
||||||
setError(err.message || "Ocorreu um erro ao registar o restaurante.");
|
|
||||||
|
// Map Firebase Auth error codes to user-friendly messages in Portuguese
|
||||||
|
switch (err.code) {
|
||||||
|
case "auth/email-already-in-use":
|
||||||
|
setError("Este email já está registado. Tente fazer login.");
|
||||||
|
break;
|
||||||
|
case "auth/weak-password":
|
||||||
|
setError("A palavra-passe deve ter pelo menos 6 caracteres.");
|
||||||
|
break;
|
||||||
|
case "auth/invalid-email":
|
||||||
|
setError("Email inválido. Verifique o formato do email.");
|
||||||
|
break;
|
||||||
|
case "auth/operation-not-allowed":
|
||||||
|
setError("Registo de contas está desactivado. Contacte o suporte.");
|
||||||
|
break;
|
||||||
|
case "auth/network-request-failed":
|
||||||
|
setError("Erro de conexão. Verifique a sua ligação à internet.");
|
||||||
|
break;
|
||||||
|
case "auth/too-many-requests":
|
||||||
|
setError("Demasiadas tentativas. Aguarde alguns minutos e tente novamente.");
|
||||||
|
break;
|
||||||
|
case "auth/invalid-credential":
|
||||||
|
setError("Credenciais inválidas.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setError(`Erro ao criar conta: ${err.message || "Tente novamente."}`);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ export default function DashboardHomePage() {
|
|||||||
const { mesas, loading: loadingMesas } = useMesas();
|
const { mesas, loading: loadingMesas } = useMesas();
|
||||||
const { staff } = useStaff();
|
const { staff } = useStaff();
|
||||||
|
|
||||||
|
// Guard against missing user data
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-[60vh]">
|
||||||
|
<div className="text-primary font-display text-2xl animate-pulse">A carregar...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Calculate top stats
|
// 1. Calculate top stats
|
||||||
const todayStr = new Date().toISOString().split('T')[0];
|
const todayStr = new Date().toISOString().split('T')[0];
|
||||||
const todayReservas = reservas.filter(r => r.data === todayStr || r.estado.startsWith("Confirmada"));
|
const todayReservas = reservas.filter(r => r.data === todayStr || r.estado.startsWith("Confirmada"));
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default function AuthGuard({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
|
// If user is not authenticated and not on a public page, redirect to login
|
||||||
if (!user && !pathname.startsWith("/login") && !pathname.startsWith("/register")) {
|
if (!user && !pathname.startsWith("/login") && !pathname.startsWith("/register")) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
}
|
}
|
||||||
@@ -25,8 +26,8 @@ export default function AuthGuard({ children }: { children: React.ReactNode }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se não estiver logado e não estiver numa rota pública, não renderiza nada
|
// If not authenticated and not on a public page, don't render children
|
||||||
// (o useEffect vai redirecionar)
|
// (the useEffect will redirect)
|
||||||
if (!user && !pathname.startsWith("/login") && !pathname.startsWith("/register")) {
|
if (!user && !pathname.startsWith("/login") && !pathname.startsWith("/register")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,17 @@ export function NotificationMonitor() {
|
|||||||
// Primeiro, marcamos todas as reservas existentes como "vistas"
|
// Primeiro, marcamos todas as reservas existentes como "vistas"
|
||||||
// para não disparar notificações para o passado
|
// para não disparar notificações para o passado
|
||||||
const loadExisting = async () => {
|
const loadExisting = async () => {
|
||||||
const snapshot = await get(reservasRef);
|
try {
|
||||||
if (snapshot.exists()) {
|
const snapshot = await get(reservasRef);
|
||||||
const data = snapshot.val();
|
if (snapshot.exists()) {
|
||||||
Object.keys(data).forEach(id => seenReservas.current.add(id));
|
const data = snapshot.val();
|
||||||
|
Object.keys(data).forEach(id => seenReservas.current.add(id));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NotificationMonitor] Error loading existing reservas:", error);
|
||||||
|
} finally {
|
||||||
|
isInitialLoad.current = false;
|
||||||
}
|
}
|
||||||
isInitialLoad.current = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loadExisting();
|
loadExisting();
|
||||||
@@ -40,9 +45,13 @@ export function NotificationMonitor() {
|
|||||||
// Se ainda estivermos no load inicial (do get), ignoramos o toast
|
// Se ainda estivermos no load inicial (do get), ignoramos o toast
|
||||||
if (isInitialLoad.current) return;
|
if (isInitialLoad.current) return;
|
||||||
|
|
||||||
const data = snapshot.val();
|
try {
|
||||||
if (data.restauranteEmail === user.email && data.estado === "Pendente") {
|
const data = snapshot.val();
|
||||||
toast(`Nova reserva recebida de ${data.clienteEmail}!`, "info");
|
if (data?.restauranteEmail === user.email && data?.estado === "Pendente") {
|
||||||
|
toast(`Nova reserva recebida de ${data?.clienteEmail || "cliente"}!`, "info");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NotificationMonitor] Error processing new reserva:", error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,34 +33,106 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return email.replace(/\./g, "_").replace(/@/g, "_at_");
|
return email.replace(/\./g, "_").replace(/@/g, "_at_");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Search for user by UID across all Restaurantes documents
|
||||||
|
const findUserByUid = async (uid: string, currentUser: FirebaseUser): Promise<RestaurantUser | null> => {
|
||||||
|
try {
|
||||||
|
const restaurantsRef = ref(db, "Restaurantes");
|
||||||
|
const snapshot = await get(restaurantsRef);
|
||||||
|
if (!snapshot.exists()) return null;
|
||||||
|
|
||||||
|
const data = snapshot.val();
|
||||||
|
for (const key of Object.keys(data)) {
|
||||||
|
const item = data[key];
|
||||||
|
if (item?.uid === uid) {
|
||||||
|
return { ...item, email: item.email || currentUser.email } as RestaurantUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Auth] Error searching by UID:", error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search for user by email across all Restaurantes documents
|
||||||
|
const findUserByEmail = async (email: string): Promise<RestaurantUser | null> => {
|
||||||
|
try {
|
||||||
|
const restaurantsRef = ref(db, "Restaurantes");
|
||||||
|
const snapshot = await get(restaurantsRef);
|
||||||
|
if (!snapshot.exists()) return null;
|
||||||
|
|
||||||
|
const data = snapshot.val();
|
||||||
|
for (const key of Object.keys(data)) {
|
||||||
|
const item = data[key];
|
||||||
|
if (item?.email === email || item?.establishmentEmail === email || item?.ownerEmail === email) {
|
||||||
|
return { ...item, email: item.email || email } as RestaurantUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Auth] Error searching by email:", error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
|
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
|
||||||
try {
|
try {
|
||||||
if (currentUser && currentUser.email) {
|
if (currentUser && currentUser.email) {
|
||||||
setFirebaseUser(currentUser);
|
setFirebaseUser(currentUser);
|
||||||
|
|
||||||
|
let data: RestaurantUser | null = null;
|
||||||
|
|
||||||
|
// Strategy 1: Try building document ID from email (website format)
|
||||||
const docId = buildDocumentId(currentUser.email);
|
const docId = buildDocumentId(currentUser.email);
|
||||||
const userRef = ref(db, `Restaurantes/${docId}`);
|
const userRef = ref(db, `Restaurantes/${docId}`);
|
||||||
const snapshot = await get(userRef);
|
let snapshot = await get(userRef);
|
||||||
|
|
||||||
if (snapshot.exists()) {
|
if (snapshot.exists()) {
|
||||||
const data = snapshot.val() as RestaurantUser;
|
data = snapshot.val() as RestaurantUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 2: Try with original email as document ID (Android format)
|
||||||
|
if (!data) {
|
||||||
|
const originalRef = ref(db, `Restaurantes/${currentUser.email}`);
|
||||||
|
snapshot = await get(originalRef);
|
||||||
|
if (snapshot.exists()) {
|
||||||
|
data = snapshot.val() as RestaurantUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 3: Search all Restaurantes documents by UID
|
||||||
|
if (!data && currentUser.uid) {
|
||||||
|
data = await findUserByUid(currentUser.uid, currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 4: Search all Restaurantes documents by email
|
||||||
|
if (!data) {
|
||||||
|
data = await findUserByEmail(currentUser.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
if (data.accountType === "ESTABELECIMENTO") {
|
if (data.accountType === "ESTABELECIMENTO") {
|
||||||
setUser(data);
|
setUser(data);
|
||||||
} else {
|
} else {
|
||||||
console.warn("User is not a restaurant");
|
console.warn("[Auth] User is not a restaurant account type:", data.accountType);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
await signOut(auth);
|
await signOut(auth);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("User record not found in Restaurantes");
|
// User exists in Auth but not in database — possible orphaned account
|
||||||
|
console.warn("[Auth] User record not found in Restaurantes for:", currentUser.email);
|
||||||
|
console.warn("[Auth] UID:", currentUser.uid);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// No Firebase user — clear state
|
||||||
setFirebaseUser(null);
|
setFirebaseUser(null);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Auth initialization error:", error);
|
console.error("[Auth] Error in onAuthStateChanged:", error);
|
||||||
|
// On error, clear user state to prevent broken state
|
||||||
|
setUser(null);
|
||||||
|
setFirebaseUser(null);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,26 +17,32 @@ export function useMesas() {
|
|||||||
const mesasRef = ref(db, "Mesas");
|
const mesasRef = ref(db, "Mesas");
|
||||||
|
|
||||||
const unsubscribe = onValue(mesasRef, (snapshot) => {
|
const unsubscribe = onValue(mesasRef, (snapshot) => {
|
||||||
const data = snapshot.val();
|
try {
|
||||||
const list: Mesa[] = [];
|
const data = snapshot.val();
|
||||||
|
const list: Mesa[] = [];
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
const item = data[key];
|
const item = data[key];
|
||||||
if (item.restauranteEmail === user.email) {
|
if (item?.restauranteEmail === user.email) {
|
||||||
list.push({
|
list.push({
|
||||||
id: key,
|
id: key,
|
||||||
...item
|
...item
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by table number
|
||||||
|
list.sort((a, b) => a.numero - b.numero);
|
||||||
|
|
||||||
|
setMesas(list);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[useMesas] Error processing data:", error);
|
||||||
|
setMesas([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by table number
|
|
||||||
list.sort((a, b) => a.numero - b.numero);
|
|
||||||
|
|
||||||
setMesas(list);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => off(mesasRef, "value", unsubscribe);
|
return () => off(mesasRef, "value", unsubscribe);
|
||||||
|
|||||||
@@ -17,30 +17,36 @@ export function useReservas() {
|
|||||||
const reservasRef = ref(db, "reservas");
|
const reservasRef = ref(db, "reservas");
|
||||||
|
|
||||||
const unsubscribe = onValue(reservasRef, (snapshot) => {
|
const unsubscribe = onValue(reservasRef, (snapshot) => {
|
||||||
const data = snapshot.val();
|
try {
|
||||||
const list: Reserva[] = [];
|
const data = snapshot.val();
|
||||||
|
const list: Reserva[] = [];
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
const item = data[key];
|
const item = data[key];
|
||||||
if (item.restauranteEmail === user.email) {
|
if (item?.restauranteEmail === user.email) {
|
||||||
list.push({
|
list.push({
|
||||||
id: key,
|
id: key,
|
||||||
...item
|
...item
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by date and time (newest first for management)
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const dateA = new Date(`${a.data.replace(/-/g, "/")} ${a.hora}`);
|
||||||
|
const dateB = new Date(`${b.data.replace(/-/g, "/")} ${b.hora}`);
|
||||||
|
return dateB.getTime() - dateA.getTime();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setReservas(list);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[useReservas] Error processing data:", error);
|
||||||
|
setReservas([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by date and time (newest first for management)
|
|
||||||
list.sort((a, b) => {
|
|
||||||
const dateA = new Date(`${a.data.replace(/-/g, "/")} ${a.hora}`);
|
|
||||||
const dateB = new Date(`${b.data.replace(/-/g, "/")} ${b.hora}`);
|
|
||||||
return dateB.getTime() - dateA.getTime();
|
|
||||||
});
|
|
||||||
|
|
||||||
setReservas(list);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => off(reservasRef, "value", unsubscribe);
|
return () => off(reservasRef, "value", unsubscribe);
|
||||||
|
|||||||
@@ -17,23 +17,29 @@ export function useStaff() {
|
|||||||
const staffRef = ref(db, "Staff");
|
const staffRef = ref(db, "Staff");
|
||||||
|
|
||||||
const unsubscribe = onValue(staffRef, (snapshot) => {
|
const unsubscribe = onValue(staffRef, (snapshot) => {
|
||||||
const data = snapshot.val();
|
try {
|
||||||
const list: Staff[] = [];
|
const data = snapshot.val();
|
||||||
|
const list: Staff[] = [];
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
const item = data[key];
|
const item = data[key];
|
||||||
if (item.restauranteEmail === user.email) {
|
if (item?.restauranteEmail === user.email) {
|
||||||
list.push({
|
list.push({
|
||||||
id: key,
|
id: key,
|
||||||
...item
|
...item
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setStaff(list);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[useStaff] Error processing data:", error);
|
||||||
|
setStaff([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStaff(list);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => off(staffRef, "value", unsubscribe);
|
return () => off(staffRef, "value", unsubscribe);
|
||||||
|
|||||||
@@ -1,22 +1,53 @@
|
|||||||
import { initializeApp, getApps } from "firebase/app";
|
import { initializeApp, getApps, getApp } from "firebase/app";
|
||||||
import { getAuth } from "firebase/auth";
|
import { getAuth } from "firebase/auth";
|
||||||
import { getDatabase } from "firebase/database";
|
import { getDatabase } from "firebase/database";
|
||||||
|
|
||||||
// As variáveis de ambiente devem ser configuradas no Vercel e no ficheiro .env.local
|
// As variáveis de ambiente devem ser configuradas no Vercel e no ficheiro .env.local
|
||||||
const firebaseConfig = {
|
const firebaseConfig = {
|
||||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || "AIzaSyCPz7Pd3tJj3QkF7fV_vudCJythNsyR57k",
|
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || "namesa-429c1.firebaseapp.com",
|
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || "namesa-429c1",
|
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
||||||
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || "namesa-429c1.firebasestorage.app",
|
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
||||||
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || "476421715902",
|
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
||||||
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "1:476421715902:web:placeholder", // placeholder needed for web client SDK
|
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
|
||||||
// Nota importante: Como verificado na codebase Android,
|
|
||||||
// O ReservaMesa usa Realtime Database e não Firestore.
|
|
||||||
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL || "https://namesa-429c1-default-rtdb.firebaseio.com"
|
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL || "https://namesa-429c1-default-rtdb.firebaseio.com"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize Firebase only if there are no apps initialized yet
|
// Validate that all required environment variables are present
|
||||||
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
|
const requiredEnvVars = [
|
||||||
|
"NEXT_PUBLIC_FIREBASE_API_KEY",
|
||||||
|
"NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN",
|
||||||
|
"NEXT_PUBLIC_FIREBASE_PROJECT_ID",
|
||||||
|
"NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET",
|
||||||
|
"NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID",
|
||||||
|
"NEXT_PUBLIC_FIREBASE_APP_ID",
|
||||||
|
];
|
||||||
|
|
||||||
|
const missingVars = requiredEnvVars.filter(
|
||||||
|
(key) => !process.env[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (missingVars.length > 0) {
|
||||||
|
console.error(
|
||||||
|
"[Firebase] Missing environment variables:",
|
||||||
|
missingVars.join(", "),
|
||||||
|
"\nPlease ensure all Firebase environment variables are set in .env.local or your hosting platform."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Firebase only once — prevents duplicate app errors in development (HMR) and SSR
|
||||||
|
let app;
|
||||||
|
try {
|
||||||
|
app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.code === "app/duplicate-app") {
|
||||||
|
app = getApp();
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`[Firebase] Failed to initialize: ${error?.message || "Unknown error"}. Check your environment variables.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const auth = getAuth(app);
|
export const auth = getAuth(app);
|
||||||
export const db = getDatabase(app);
|
export const db = getDatabase(app);
|
||||||
|
|||||||
2
reserva-mesa-dashboard/package-lock.json
generated
2
reserva-mesa-dashboard/package-lock.json
generated
@@ -14,7 +14,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"firebase": "^10.12.0",
|
"firebase": "^10.14.1",
|
||||||
"framer-motion": "^11.1.7",
|
"framer-motion": "^11.1.7",
|
||||||
"lucide-react": "^0.378.0",
|
"lucide-react": "^0.378.0",
|
||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
|
|||||||
@@ -10,10 +10,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"firebase": "^10.12.0",
|
"firebase": "^10.14.1",
|
||||||
"framer-motion": "^11.1.7",
|
"framer-motion": "^11.1.7",
|
||||||
"lucide-react": "^0.378.0",
|
"lucide-react": "^0.378.0",
|
||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
@@ -23,8 +25,6 @@
|
|||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
|
||||||
"zod": "^3.23.6"
|
"zod": "^3.23.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Reference in New Issue
Block a user