feat: Refactor app initialization to sequentially authenticate users and load shops from the database, removing mock data and local storage dependency for shops.

This commit is contained in:
2026-02-27 15:27:26 +00:00
parent f41791adb2
commit 24cb02bf0c

View File

@@ -8,7 +8,7 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { nanoid } from 'nanoid';
import { Appointment, Barber, BarberShop, CartItem, Order, Product, Service, User } from '../types';
import { mockShops, mockUsers } from '../data/mock';
import { mockUsers } from '../data/mock';
import { storage } from '../lib/storage';
import { supabase } from '../lib/supabase';
@@ -66,7 +66,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const stored = storage.get<Partial<State> | null>('smart-agenda', null);
const safeStored = stored && typeof stored === 'object' ? stored : {};
const safeUsers = Array.isArray(safeStored.users) ? safeStored.users : initialState.users;
// shops are no longer bound by storage safely
const safeAppointments = Array.isArray(safeStored.appointments) ? safeStored.appointments : initialState.appointments;
const safeOrders = Array.isArray(safeStored.orders) ? safeStored.orders : initialState.orders;
const safeCart = Array.isArray(safeStored.cart) ? safeStored.cart : initialState.cart;
@@ -107,6 +106,10 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
storage.set('smart-agenda', state);
}, [state]);
/**
* Hook de Inicialização Master — sequencial para evitar race condition.
* Ordem garantida: autenticar utilizador → carregar shops da BD → libertar UI.
*/
useEffect(() => {
let mounted = true;
@@ -116,13 +119,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
/**
* Sincroniza o Perfil do Utilizador a partir do Supabase Database ('profiles').
* Cria automaticamente a shop ("Barbearia") se a Role determinar, integrando-o
* no estado (simulado em LocalStorage) das Shops.
* @param userId - UID emitido pela Supabase Auth.
* @param email - Endereço de Email (Payload do token).
* Cria automaticamente a shop se a Role for 'barbearia'.
*/
const applyProfile = async (userId: string, email?: string | null) => {
// Pedido restrito à tabela profiles de mapeamento Auth -> App Logic
const { data, error } = await supabase
.from('profiles')
.select('id,name,role,shop_name')
@@ -154,11 +153,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
: [...s.users, nextUser];
let shops = s.shops;
// Removida condicional restrita (/*role === 'barbearia' && shopId*/true)
// para garantir que *todas* as shops criadas por donos fiquem listadas no array maestro global
// para qualquer utilizador que faça fetch deste `id`
if (data.shop_name) {
const shopIdFromOwner = userId; // O dono usa o user ID como shop ID
const shopIdFromOwner = userId;
const exists = shops.some((shop) => shop.id === shopIdFromOwner);
if (!exists) {
@@ -174,24 +170,18 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
};
shops = [...shops, shop];
// 🔹 INSERT REAL NA BD SUPABASE
// Garante que a mobile app e outros clients consigam obter esta nova loja
// 🔹 INSERT NA BD SUPABASE
void supabase.from('shops').insert([
{
id: shopIdFromOwner,
name: shopName,
address: 'Endereço a definir',
rating: 0
rating: 0,
}
]).then(({ error }) => {
if (error) {
// Ignora o erro de duplicado (shop já existe na BD)
if (error.code !== '23505') {
console.error("Erro ao sincronizar shop nova na BD:", error);
}
]).then(({ error: insertError }) => {
if (insertError && insertError.code !== '23505') {
console.error('Erro ao sincronizar shop nova na BD:', insertError);
}
// Recarrega shops da BD para garantir que a nova shop aparece corretamente
void refreshShops();
});
}
}
@@ -206,28 +196,42 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
};
/**
* "Boot" Inicial: Verifica a sessão global com `getSession` de forma assíncrona.
* Boot sequencial: autenticar primeiro, depois carregar shops.
* Isso garante que quando o Dashboard renderiza, a shop já está no estado.
*/
const boot = async () => {
const { data } = await supabase.auth.getSession();
if (!mounted) return;
const session = data.session;
if (session?.user) {
await applyProfile(session.user.id, session.user.email);
} else {
clearUser();
try {
const { data } = await supabase.auth.getSession();
if (!mounted) return;
const session = data.session;
if (session?.user) {
// 1. Aplicar perfil (adiciona shop ao estado local se necessário)
await applyProfile(session.user.id, session.user.email);
// 2. Recarregar todas as shops da BD (garante sync completo)
if (mounted) await refreshShops();
} else {
clearUser();
// Mesmo sem user, carrega shops para o Explore page
if (mounted) await refreshShops();
}
} catch (err) {
console.error('Erro durante o boot:', err);
} finally {
if (mounted) setLoading(false);
}
};
void boot();
/**
* Listener Global: Subscrição de eventos Auth que atualiza a context se
* utilizador fizer Log Out ou novo Login fora deste hook imediato.
* Listener de eventos Auth (login/logout em tempo real).
*/
const { data: sub } = supabase.auth.onAuthStateChange((_event, session) => {
const { data: sub } = supabase.auth.onAuthStateChange(async (_event, session) => {
if (!mounted) return;
if (session?.user) {
void applyProfile(session.user.id, session.user.email);
await applyProfile(session.user.id, session.user.email);
if (mounted) await refreshShops();
} else {
clearUser();
}
@@ -267,7 +271,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
if (exists) return false;
if (payload.role === 'barbearia') {
const userId = nanoid(); // Geramos o userId agora e usamos como shopId
const userId = nanoid();
const shopId = userId;
const shop: BarberShop = {
id: shopId,
@@ -464,7 +468,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
};
const updateShopDetails: AppContextValue['updateShopDetails'] = async (shopId, payload) => {
// DB Update
const { error } = await supabase
.from('shops')
.update(payload)
@@ -475,7 +478,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
return;
}
// Local State Update
setState((s) => ({
...s,
shops: s.shops.map((shop) => (shop.id === shopId ? { ...shop, ...payload } : shop)),
@@ -509,19 +511,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
refreshShops,
};
/**
* Hook de Inicialização Master.
* Aciona a função de preenchimento do Contexto assincronamente e liberta
* a interface UI da view de Loading.
*/
useEffect(() => {
const init = async () => {
await refreshShops();
setLoading(false);
};
init();
}, []);
// Loading Shield evita quebra generalizada se o app renderizar sem BD disponível
if (loading) return null;