diff --git a/frontend/DESIGN_SYSTEM.md b/frontend/DESIGN_SYSTEM.md new file mode 100644 index 0000000..df4869c --- /dev/null +++ b/frontend/DESIGN_SYSTEM.md @@ -0,0 +1,28 @@ +# SaveInMed - Design System + +Diretrizes visuais para o frontend do SaveInMed. + +## 🎨 Paleta de Cores +- **Primária**: `blue-600` (#2563EB) - Botões principais, links ativos. +- **Sucesso**: `green-600` (#16A34A) - Confirmações, status "Entregue". +- **Aviso**: `yellow-500` (#EAB308) - Alertas, status "Pendente". +- **Erro**: `red-600` (#DC2626) - Botões de deletar, mensagens de erro. +- **Fundo**: `gray-50` (#F9FAFB) - Background das páginas. + +## 🔡 Tipografia +- **Títulos**: Inter ou Sans-serif, font-bold, text-gray-900. +- **Corpo**: Inter, text-gray-600. + +## 🧩 Componentes Padrão +- **Botões**: + - `bg-blue-600 hover:bg-blue-700 text-white rounded-lg px-4 py-2 transition-colors` +- **Inputs**: + - `border border-gray-300 focus:ring-2 focus:ring-blue-500 rounded-lg` +- **Cards**: + - `bg-white shadow-sm border border-gray-100 rounded-xl p-6` + +## 📦 Ícones +- **Biblioteca**: `Heroicons 24/outline` +- **Home**: `HomeIcon` +- **Carrinho**: `ShoppingCartIcon` +- **Usuário**: `UserIcon` diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index a6bc413..a3bb852 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -15,21 +15,21 @@ const LoginPageContent = () => { const { setEmpresaId } = useEmpresa(); const searchParams = useSearchParams(); - // Estados do formulÃÆ’ƒÂ¡rio - const [email, setEmail] = useState(""); // Email do usuÃÆ’ƒÂ¡rio - const [password, setPassword] = useState(""); // Senha do usuÃÆ’ƒÂ¡rio + // Estados do formulário + const [email, setEmail] = useState(""); // Email do usuário + const [password, setPassword] = useState(""); // Senha do usuário const [name, setName] = useState(""); // Nome completo (apenas para registro) const [loading, setLoading] = useState(false); // Estado de carregamento const [error, setError] = useState(""); // Mensagens de erro const [isLogin, setIsLogin] = useState(true); // Alterna entre login e registro - const [checkingAuth, setCheckingAuth] = useState(true); // VerificaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o inicial de autenticaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o + const [checkingAuth, setCheckingAuth] = useState(true); // Verificação inicial de autenticação const [showPassword, setShowPassword] = useState(false); // Controla visibilidade da senha const [accessToken, setAccessToken] = useState(""); // Token de acesso da API const [showSuccessModal, setShowSuccessModal] = useState(false); // Modal de sucesso do cadastro const [showPendingModal, setShowPendingModal] = useState(false); // Modal de cadastro pendente /** - * Verifica parÃÆ’ƒÂ¢metros da URL para definir aba inicial + * Verifica par¢metros da URL para definir aba inicial */ useEffect(() => { const tab = searchParams.get('tab'); @@ -39,13 +39,13 @@ const LoginPageContent = () => { }, [searchParams]); /** - * Verifica se o usuÃÆ’ƒÂ¡rio jÃÆ’ƒÂ¡ estÃÆ’ƒÂ¡ autenticado ao carregar a pÃÆ’ƒÂ¡gina + * Verifica se o usuário já está autenticado ao carregar a página * Se estiver autenticado, redireciona para o dashboard */ useEffect(() => { const checkAuth = async () => { try { - // Verificar se hÃÆ’ƒÂ¡ token armazenado + // Verificar se há token armazenado const storedToken = localStorage.getItem('access_token'); if (!storedToken) { @@ -53,14 +53,14 @@ const LoginPageContent = () => { return; } - // Verificar autenticaÃÆ’ÂÂ§ÃÆ’£o usando API com o token no header Authorization + // Verificar autenticação usando API com o token no header Authorization const response = await fetch( `${API_V1_BASE_URL}/auth/me`, { method: "GET", headers: { "accept": "application/json", - "Authorization": `Bearer ${storedToken}`, // Usar Authorization ao invÃÆ’ƒÂ©s de Cookie + "Authorization": `Bearer ${storedToken}`, // Usar Authorization ao invés de Cookie }, } ); @@ -71,7 +71,7 @@ const LoginPageContent = () => { router.push("/dashboard"); } else { const errorText = await response.text(); - // Limpar token invÃÆ’ƒÂ¡lido + // Limpar token inválido localStorage.removeItem('access_token'); } } catch (error) { @@ -83,9 +83,9 @@ const LoginPageContent = () => { }, [router]); /** - * FunÃÆ’ÂÂ§ÃÆ’£o para realizar login do usuÃÆ’¡rio usando API - * @param email - Email do usuÃÆ’ƒÂ¡rio (usado como identificador) - * @param password - Senha do usuÃÆ’ƒÂ¡rio + * Função para realizar login do usuário usando API + * @param email - Email do usuário (usado como identificador) + * @param password - Senha do usuário */ const login = async (email: string, password: string) => { setLoading(true); @@ -108,32 +108,32 @@ const LoginPageContent = () => { }) }); - // Ler e logar o corpo da resposta (usando clone para nÃÆ’ƒÂ£o consumir o body original) + // Ler e logar o corpo da resposta (usando clone para não consumir o body original) const respClone = response.clone(); const respText = await respClone.text(); let respBody: any = respText; try { respBody = JSON.parse(respText); } catch { - // corpo nÃÆ’ƒÂ£o ÃÆ’ƒÂ© JSON, manter como texto + // corpo não é JSON, manter como texto } if (!response.ok) { // Extrair mensagem de erro do backend let errorMessage = respBody?.message || respBody?.error || respBody?.detail || respText; - // Tratar cÃÆ’ƒÂ³digos de status especÃÆ’ƒÂ­ficos + // Tratar códigos de status específicos if (response.status === 401) { errorMessage = "Email ou senha incorretos. Verifique suas credenciais."; } else if (response.status === 403) { errorMessage = "Acesso negado. Sua conta pode estar inativa."; } else if (response.status === 404) { - errorMessage = "UsuÃÆ’ƒÂ¡rio nÃÆ’ƒÂ£o encontrado. Verifique o email informado."; + errorMessage = "Usuário não encontrado. Verifique o email informado."; } else if (response.status >= 500) { errorMessage = "Erro interno do servidor. Tente novamente em alguns minutos."; } - // Aplicar traduÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o se disponÃÆ’ƒÂ­vel + // Aplicar tradução se disponível const translatedError = translateError(errorMessage); throw new Error(translatedError); } @@ -158,24 +158,24 @@ const LoginPageContent = () => { if (meResponse.ok) { const userData = await meResponse.json(); } else { - console.log("ÃÆ’¢ÂÃɉ۪ Falha no /me:", await meResponse.text()); + console.log("¢Ãɉ۪ Falha no /me:", await meResponse.text()); } } else { - throw new Error("Token nÃÆ’ƒÂ£o recebido do servidor"); + throw new Error("Token não recebido do servidor"); } - // 4. Armazenar informaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂµes do usuÃÆ’ƒÂ¡rio no localStorage + // 4. Armazenar informaçµes do usuário no localStorage if (loginData.user) { localStorage.setItem('user', JSON.stringify(loginData.user)); - // Verificar se o registro estÃÆ’ƒÂ¡ completo + // Verificar se o registro está completo if (loginData.user["registro-completo"] === false) { setShowPendingModal(true); - return; // NÃÆ’ƒÂ£o continuar com o redirecionamento + return; // Não continuar com o redirecionamento } - // Armazenar ID do endereÃÆ’ƒÂ§o se disponÃÆ’ƒÂ­vel + // Armazenar ID do endereço se disponível // Backend pode retornar "endereco" (singular) ou "enderecos" (plural array) let enderecoId = loginData.user.endereco; @@ -189,7 +189,7 @@ const LoginPageContent = () => { } } - // 5. Verificar e armazenar empresa ID se disponÃÆ’ƒÂ­vel + // 5. Verificar e armazenar empresa ID se disponível if (loginData.user?.empresaId) { setEmpresaId(loginData.user.empresaId); } @@ -198,16 +198,16 @@ const LoginPageContent = () => { router.push("/dashboard"); } catch (error: any) { - console.error("ÃÆ’¢ÂÃɉ۪ Erro no login:", error); + console.error("¢Ãɉ۪ Erro no login:", error); - // Definir mensagem de erro amigÃÆ’ƒÂ¡vel + // Definir mensagem de erro amigável let friendlyMessage = error.message; // Se for um erro de rede if (error.name === 'TypeError' || error.message.includes('fetch')) { - friendlyMessage = "Erro de conexÃÆ’ƒÂ£o. Verifique sua internet e tente novamente."; + friendlyMessage = "Erro de conexão. Verifique sua internet e tente novamente."; } - // Se for um erro genÃÆ’ƒÂ©rico sem mensagem clara + // Se for um erro genérico sem mensagem clara else if (!error.message || error.message.length < 5) { friendlyMessage = "Erro inesperado. Verifique suas credenciais e tente novamente."; } @@ -219,7 +219,7 @@ const LoginPageContent = () => { }; /** - * FunÃÆ’ÂÂ§ÃÆ’£o para registrar novo usuÃÆ’¡rio usando API + * Função para registrar novo usuário usando API * Cria conta e mostra mensagem de sucesso */ const register = async () => { @@ -235,12 +235,12 @@ const LoginPageContent = () => { 'Content-Type': 'application/json', }, body: JSON.stringify({ - identificador: email, // identificador = email do usuÃÆ’ƒÂ¡rio (usado no login) + identificador: email, // identificador = email do usuário (usado no login) email: email, - nome: name, // nome = nome completo do usuÃÆ’ƒÂ¡rio + nome: name, // nome = nome completo do usuário senha: password, - nivel: "owner", // valor estÃÆ’ƒÂ¡tico - "registro-completo": false // valor estÃÆ’ƒÂ¡tico - registro incompleto por padrÃÆ’ƒÂ£o + nivel: "owner", // valor estático + "registro-completo": false // valor estático - registro incompleto por padrão }) }); @@ -248,25 +248,25 @@ const LoginPageContent = () => { if (!response.ok) { const errorData = await response.json().catch(() => ({})); - // Tratar cÃÆ’ƒÂ³digos de status especÃÆ’ƒÂ­ficos + // Tratar códigos de status específicos let errorMessage = errorData.message || 'Falha no registro'; - if (response.status === 409 || errorMessage.includes('already exists') || errorMessage.includes('jÃÆ’ƒÂ¡ existe')) { - errorMessage = 'Este email jÃÆ’ƒÂ¡ estÃÆ’ƒÂ¡ cadastrado. Tente fazer login ou use outro email.'; + if (response.status === 409 || errorMessage.includes('already exists') || errorMessage.includes('já existe')) { + errorMessage = 'Este email já está cadastrado. Tente fazer login ou use outro email.'; } else if (response.status === 400) { - errorMessage = 'Dados invÃÆ’ƒÂ¡lidos. Verifique se todos os campos estÃÆ’ƒÂ£o preenchidos corretamente.'; + errorMessage = 'Dados inválidos. Verifique se todos os campos estão preenchidos corretamente.'; } else if (response.status >= 500) { errorMessage = 'Erro interno do servidor. Tente novamente em alguns minutos.'; } - // Aplicar traduÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o se disponÃÆ’ƒÂ­vel + // Aplicar tradução se disponível const translatedError = translateError(errorMessage); throw new Error(translatedError); } const registerData = await response.json(); - // 2. Fazer login automÃÆ’ƒÂ¡tico apÃÆ’ƒÂ³s registro para obter token + // 2. Fazer login automático após registro para obter token const loginResponse = await fetch(`${baseUrl}/auth/login`, { method: 'POST', headers: { @@ -282,13 +282,13 @@ const LoginPageContent = () => { }); if (!loginResponse.ok) { - console.error("ÃÆ’¢ÂÃɉ۪ Erro no login automÃÆ’ƒÂ¡tico:", loginResponse.status); - throw new Error('Erro ao fazer login automÃÆ’ƒÂ¡tico apÃÆ’ƒÂ³s registro'); + console.error("¢Ãɉ۪ Erro no login automático:", loginResponse.status); + throw new Error('Erro ao fazer login automático após registro'); } const loginData = await loginResponse.json(); - // 3. Armazenar token e dados do usuÃÆ’ƒÂ¡rio + // 3. Armazenar token e dados do usuário if (loginData.access_token) { localStorage.setItem('access_token', loginData.access_token); } @@ -297,20 +297,20 @@ const LoginPageContent = () => { localStorage.setItem('user', JSON.stringify(loginData.user)); } - // 4. Redirecionar para completar registro com token vÃÆ’ƒÂ¡lido + // 4. Redirecionar para completar registro com token válido router.push(`/completar-registro?nome=${encodeURIComponent(name)}&email=${encodeURIComponent(email)}`); } catch (error: any) { - console.error("ÃÆ’¢ÂÃɉ۪ Erro no registro:", error); + console.error("¢Ãɉ۪ Erro no registro:", error); - // Definir mensagem de erro amigÃÆ’ƒÂ¡vel + // Definir mensagem de erro amigável let friendlyMessage = error.message; // Se for um erro de rede if (error.name === 'TypeError' || error.message.includes('fetch')) { - friendlyMessage = "Erro de conexÃÆ’ƒÂ£o. Verifique sua internet e tente novamente."; + friendlyMessage = "Erro de conexão. Verifique sua internet e tente novamente."; } - // Se for um erro genÃÆ’ƒÂ©rico sem mensagem clara + // Se for um erro genérico sem mensagem clara else if (!error.message || error.message.length < 5) { friendlyMessage = "Erro inesperado durante o cadastro. Tente novamente."; } @@ -322,11 +322,11 @@ const LoginPageContent = () => { }; /** - * FunÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o para notificar administrador sobre novo cadastro + * Função para notificar administrador sobre novo cadastro */ const notifyAdmin = async (userData: { nome: string, email: string, identificador: string }) => { try { - // Enviar notificaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂµes via API (usando Resend para emails reais) + // Enviar notificaçµes via API (usando Resend para emails reais) await fetch('/api/notify-admin-resend', { method: 'POST', headers: { @@ -335,13 +335,13 @@ const LoginPageContent = () => { body: JSON.stringify(userData) }); } catch (error) { - console.error("Erro ao enviar notificaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂµes:", error); - // NÃÆ’ƒÂ£o bloquear o cadastro por erro de notificaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o + console.error("Erro ao enviar notificaçµes:", error); + // Não bloquear o cadastro por erro de notificação } }; /** - * FunÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o para fechar modal de sucesso e redirecionar + * Função para fechar modal de sucesso e redirecionar */ const handleSuccessModalClose = () => { setShowSuccessModal(false); @@ -349,25 +349,25 @@ const LoginPageContent = () => { }; /** - * FunÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o para fechar modal de cadastro pendente + * Função para fechar modal de cadastro pendente */ const handlePendingModalClose = () => { setShowPendingModal(false); - // Fazer logout para limpar dados do usuÃÆ’ƒÂ¡rio + // Fazer logout para limpar dados do usuário localStorage.removeItem('access_token'); localStorage.removeItem('user'); setAccessToken(""); - // Redirecionar para pÃÆ’ƒÂ¡gina inicial + // Redirecionar para página inicial router.push('/'); }; - // Tela de carregamento durante verificaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o de autenticaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o + // Tela de carregamento durante verificação de autenticação if (checkingAuth) { return (
-

Verificando autenticaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o...

+

Verificando autenticação...

); @@ -376,7 +376,7 @@ const LoginPageContent = () => { return (
- {/* CabeÃÆ’ƒÂ§alho com logo e slogan */} + {/* Cabeçalho com logo e slogan */}
{

Plataforma B2B de Medicamentos

- {/* FormulÃÆ’ƒÂ¡rio de login/registro */} + {/* Formulário de login/registro */}
- {/* BotÃÆ’ƒÂµes de alternÃÆ’ƒÂ¢ncia entre Login e Cadastro */} + {/* Botµes de altern¢ncia entre Login e Cadastro */}
setName(e.target.value)} className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors text-gray-900 placeholder-gray-500" @@ -527,7 +527,7 @@ const LoginPageContent = () => {
setPassword(e.target.value)} className="w-full pl-10 pr-12 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors text-gray-900 placeholder-gray-500" @@ -576,7 +576,7 @@ const LoginPageContent = () => {
- {/* BotÃÆ’ƒÂµes de Submit */} + {/* Botµes de Submit */}
{isLogin ? (
- {/* ConteÃÆ’ƒÂºdo do Modal */} + {/* Conteúdo do Modal */}

Seu cadastro foi enviado com sucesso!

- Nossa equipe entrarÃÆ’ƒÂ¡ em contato em breve para finalizar seu acesso ÃÆ’ƒÂ  plataforma. + Nossa equipe entrará em contato em breve para finalizar seu acesso   plataforma.

- {/* ÃÆ’ƒÂcone decorativo */} + {/* cone decorativo */}
@@ -728,7 +728,7 @@ const LoginPageContent = () => {
- {/* BotÃÆ’ƒÂ£o de OK */} + {/* Botão de OK */}
- {/* ConteÃÆ’ƒÂºdo do Modal */} + {/* Conteúdo do Modal */}

- Seu cadastro estÃÆ’ƒÂ¡ pendente de validaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o! + Seu cadastro está pendente de validação!

- Nossa equipe ainda estÃÆ’ƒÂ¡ analisando suas informaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂµes. VocÃÆ’ƒÂª receberÃÆ’ƒÂ¡ uma confirmaÃÆ’ƒÂÂÂ§ÃÆ’ƒÂ£o em breve. + Nossa equipe ainda está analisando suas informaçµes. Você receberá uma confirmação em breve.

- Qualquer dÃÆ’ƒÂºvida, entre em contato conosco. + Qualquer dúvida, entre em contato conosco.

- {/* ÃÆ’ƒÂcone decorativo */} + {/* cone decorativo */}
@@ -789,7 +789,7 @@ const LoginPageContent = () => {
- {/* BotÃÆ’ƒÂ£o de OK */} + {/* Botão de OK */}