Frontend: - Implementado fluxo de inicialização para novos perfis (modal "Complete seu Cadastro"). - Adicionada lógica para pré-preencher nome e email do usuário no cadastro. - Adicionada renderização condicional: abas "Dados Bancários" e "Profissional" são ocultadas para clientes (EVENT_OWNER). - Unificada a função de salvar (criação e edição) com tratativa correta de erros e feedback (Toast). - Adicionado fallback para exibir o email do usuário caso o do perfil esteja vazio. Backend: - SQL: Ajustada query `GetProfissionalByUsuarioID` para buscar email da tabela de usuários (LEFT JOIN). - Handler: Implementado fallback para usar `UsuarioEmail` na resposta se o `Email` do perfil for nulo. - Service: Correção no salvamento (Create/Update) para tratar `funcao_profissional_id` com UUID vazio (Nil) como NULL, evitando erro de chave estrangeira (FK). Fixes #profile-save-error, #role-visibility
797 lines
27 KiB
TypeScript
797 lines
27 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import {
|
|
BrowserRouter,
|
|
Routes,
|
|
Route,
|
|
Navigate,
|
|
useNavigate,
|
|
useLocation,
|
|
} from "react-router-dom";
|
|
import { Navbar } from "./components/Navbar";
|
|
import { Home } from "./pages/Home";
|
|
import { Dashboard } from "./pages/Dashboard";
|
|
import { Login } from "./pages/Login";
|
|
import { Register } from "./pages/Register";
|
|
import { ProfessionalRegister } from "./pages/ProfessionalRegister";
|
|
import { TeamPage } from "./pages/Team";
|
|
import EventDetails from "./pages/EventDetails";
|
|
import Finance from "./pages/Finance";
|
|
|
|
import { SettingsPage } from "./pages/Settings";
|
|
import { CourseManagement } from "./pages/CourseManagement";
|
|
import { InspirationPage } from "./pages/Inspiration";
|
|
import { UserApproval } from "./pages/UserApproval";
|
|
import { AccessCodes } from "./pages/AccessCodes";
|
|
import { PrivacyPolicy } from "./pages/PrivacyPolicy";
|
|
import { TermsOfUse } from "./pages/TermsOfUse";
|
|
import { LGPD } from "./pages/LGPD";
|
|
import { AuthProvider, useAuth } from "./contexts/AuthContext";
|
|
import { DataProvider, useData } from "./contexts/DataContext";
|
|
import { RegionProvider } from "./contexts/RegionContext";
|
|
import { UserRole } from "./types";
|
|
import { verifyAccessCode } from "./services/apiService";
|
|
import { Button } from "./components/Button";
|
|
import { X } from "lucide-react";
|
|
import { ShieldAlert } from "lucide-react";
|
|
import ProfessionalStatement from "./pages/ProfessionalStatement";
|
|
import { ProfilePage } from "./pages/Profile";
|
|
import { ImportData } from "./pages/ImportData";
|
|
import { LoadingScreen } from "./components/LoadingScreen";
|
|
|
|
// Componente de acesso negado
|
|
const AccessDenied: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-brand-purple/5 to-brand-gold/5">
|
|
<div className="text-center p-8 bg-white rounded-lg shadow-lg max-w-md">
|
|
<ShieldAlert className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
|
<h2 className="text-2xl font-bold text-brand-purple mb-2">
|
|
Acesso Negado
|
|
</h2>
|
|
<p className="text-gray-600 mb-6">
|
|
Você não tem permissão para acessar esta página.
|
|
</p>
|
|
<button
|
|
onClick={() => navigate("/painel")}
|
|
className="px-6 py-2 bg-brand-gold text-brand-purple font-semibold rounded-lg hover:bg-brand-gold/90 transition-colors"
|
|
>
|
|
Voltar ao Dashboard
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Componente de rota protegida por código de acesso
|
|
interface AccessCodeProtectedRouteProps {
|
|
children: React.ReactNode;
|
|
type: 'client' | 'professional';
|
|
}
|
|
|
|
const AccessCodeProtectedRoute: React.FC<AccessCodeProtectedRouteProps> = ({ children, type }) => {
|
|
const [showAccessCodeModal, setShowAccessCodeModal] = useState(false);
|
|
const [accessCode, setAccessCode] = useState("");
|
|
const [codeError, setCodeError] = useState("");
|
|
const [isValidated, setIsValidated] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const checkAccess = () => {
|
|
if (type === 'client') {
|
|
const isValidated = sessionStorage.getItem('accessCodeValidated');
|
|
const accessData = sessionStorage.getItem('accessCodeData');
|
|
|
|
if (!isValidated || !accessData) {
|
|
setShowAccessCodeModal(true);
|
|
return;
|
|
}
|
|
|
|
setIsValidated(true);
|
|
} else if (type === 'professional') {
|
|
const isValidated = sessionStorage.getItem('professionalAccessValidated');
|
|
const accessData = sessionStorage.getItem('professionalAccessData');
|
|
|
|
if (!isValidated || !accessData) {
|
|
setShowAccessCodeModal(true);
|
|
return;
|
|
}
|
|
|
|
setIsValidated(true);
|
|
}
|
|
};
|
|
|
|
checkAccess();
|
|
}, [type]);
|
|
|
|
const handleVerifyCode = async () => {
|
|
if (accessCode.trim() === "") {
|
|
setCodeError("Por favor, digite o código de acesso");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await verifyAccessCode(accessCode.toUpperCase());
|
|
if (res.data && res.data.valid) {
|
|
// Marcar na sessão que o código foi validado
|
|
if (type === 'client') {
|
|
sessionStorage.setItem('accessCodeValidated', 'true');
|
|
sessionStorage.setItem('accessCodeData', JSON.stringify({
|
|
code: accessCode.toUpperCase(),
|
|
empresa_id: res.data.empresa_id,
|
|
empresa_nome: res.data.empresa_nome
|
|
}));
|
|
} else if (type === 'professional') {
|
|
sessionStorage.setItem('professionalAccessValidated', 'true');
|
|
sessionStorage.setItem('professionalAccessData', JSON.stringify({
|
|
code: accessCode.toUpperCase()
|
|
}));
|
|
}
|
|
|
|
setShowAccessCodeModal(false);
|
|
setIsValidated(true);
|
|
|
|
// Se tem empresa e é cliente, atualizar URL com parâmetros
|
|
if (type === 'client' && res.data.empresa_id) {
|
|
window.history.replaceState({}, '', `${window.location.pathname}?empresa_id=${res.data.empresa_id}&empresa_nome=${encodeURIComponent(res.data.empresa_nome || '')}`);
|
|
}
|
|
} else {
|
|
setCodeError(res.data?.error || "Código de acesso inválido ou expirado");
|
|
}
|
|
} catch (e) {
|
|
setCodeError("Erro ao verificar código");
|
|
}
|
|
};
|
|
|
|
const resetCodeForm = () => {
|
|
setAccessCode("");
|
|
setCodeError("");
|
|
};
|
|
|
|
// Se ainda não foi validado, mostrar modal de código
|
|
if (!isValidated) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
{/* Modal de código de acesso */}
|
|
{showAccessCodeModal && (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4"
|
|
onClick={() => setShowAccessCodeModal(false)}
|
|
>
|
|
<div
|
|
className="bg-white rounded-2xl shadow-2xl max-w-md w-full p-6 sm:p-8 transform transition-all duration-300 ease-out scale-100"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h2 className="text-2xl font-bold text-gray-900">
|
|
Código de Acesso
|
|
</h2>
|
|
<button
|
|
onClick={() => window.history.back()}
|
|
className="text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
<X size={24} />
|
|
</button>
|
|
</div>
|
|
|
|
<p className="text-gray-600 mb-6">
|
|
Digite o código de acesso fornecido pela empresa para continuar
|
|
com o cadastro.
|
|
</p>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Código de Acesso *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={accessCode}
|
|
onChange={(e) => {
|
|
setAccessCode(e.target.value.toUpperCase());
|
|
resetCodeForm();
|
|
}}
|
|
onKeyPress={(e) => e.key === "Enter" && handleVerifyCode()}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#B9CF33] focus:border-transparent uppercase"
|
|
placeholder="Digite o código"
|
|
autoFocus
|
|
/>
|
|
{codeError && (
|
|
<p className="text-red-500 text-sm mt-2">{codeError}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
|
|
<p className="text-sm text-yellow-800">
|
|
<strong>Atenção:</strong> O código de acesso é fornecido pela
|
|
empresa e tem validade definida pela empresa.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => window.history.back()}
|
|
className="flex-1"
|
|
>
|
|
Cancelar
|
|
</Button>
|
|
<Button onClick={handleVerifyCode} className="flex-1">
|
|
Verificar
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Conteúdo de fundo quando modal não está visível */}
|
|
{!showAccessCodeModal && (
|
|
<div className="max-w-md text-center">
|
|
<div className="mb-8">
|
|
<div className="w-16 h-16 bg-[#492E61] rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-2xl font-bold text-gray-900 mb-2">Acesso Restrito</h1>
|
|
<p className="text-gray-600">
|
|
Esta página requer um código de acesso válido para continuar.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <>{children}</>;
|
|
};
|
|
|
|
// Componente de rota protegida
|
|
interface ProtectedRouteProps {
|
|
children: React.ReactNode;
|
|
allowedRoles?: UserRole[];
|
|
}
|
|
|
|
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
|
|
children,
|
|
allowedRoles,
|
|
}) => {
|
|
const { user } = useAuth();
|
|
|
|
if (!user) {
|
|
return <Navigate to="/entrar" replace />;
|
|
}
|
|
|
|
if (
|
|
allowedRoles &&
|
|
allowedRoles.length > 0 &&
|
|
!allowedRoles.includes(user.role)
|
|
) {
|
|
return <AccessDenied />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
};
|
|
|
|
// Wrapper para páginas que usam o sistema antigo de navegação
|
|
const PageWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
|
|
const handleNavigate = (page: string) => {
|
|
navigate(`/${page}`);
|
|
};
|
|
|
|
const getCurrentPage = () => {
|
|
const path = location.pathname.substring(1) || "home";
|
|
return path;
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-white">
|
|
<Navbar onNavigate={handleNavigate} currentPage={getCurrentPage()} />
|
|
<main>{children}</main>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Navbar simplificada apenas para Home
|
|
const SimpleNavbar: React.FC = () => {
|
|
return (
|
|
<nav className="fixed w-full z-50 bg-white/95 backdrop-blur-sm shadow-sm py-3">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex justify-between items-center h-16">
|
|
<div className="flex-shrink-0 flex items-center">
|
|
<img
|
|
src="/logo.png"
|
|
alt="Photum Formaturas"
|
|
className="h-18 sm:h-30 w-auto max-w-[200px] object-contain mb-4"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
// Componente Home com roteamento (com Navbar simplificada)
|
|
const HomeWithRouter: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const { user } = useAuth();
|
|
|
|
return (
|
|
<div className="min-h-screen bg-white">
|
|
<SimpleNavbar />
|
|
<main>
|
|
<Home onEnter={() => navigate(user ? "/painel" : "/entrar")} />
|
|
<Footer />
|
|
</main>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Componente de Login com redirecionamento
|
|
const LoginWithRouter: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const { user } = useAuth();
|
|
|
|
if (user) {
|
|
return <Navigate to="/painel" replace />;
|
|
}
|
|
|
|
return (
|
|
<PageWrapper>
|
|
<Login onNavigate={(page) => navigate(`/${page}`)} />
|
|
</PageWrapper>
|
|
);
|
|
};
|
|
|
|
// Componente de Registro com redirecionamento
|
|
const RegisterWithRouter: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const { user } = useAuth();
|
|
|
|
if (user) {
|
|
return <Navigate to="/painel" replace />;
|
|
}
|
|
|
|
return (
|
|
<PageWrapper>
|
|
<Register onNavigate={(page) => navigate(`/${page}`)} />
|
|
</PageWrapper>
|
|
);
|
|
};
|
|
|
|
// Componente de Cadastro de Profissional com redirecionamento
|
|
const ProfessionalRegisterWithRouter: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const { user } = useAuth();
|
|
|
|
if (user) {
|
|
return <Navigate to="/painel" replace />;
|
|
}
|
|
|
|
return (
|
|
<PageWrapper>
|
|
<ProfessionalRegister onNavigate={(page) => navigate(`/${page}`)} />
|
|
</PageWrapper>
|
|
);
|
|
};
|
|
|
|
// Footer component
|
|
const Footer: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<footer className="bg-gradient-to-br from-brand-purple to-brand-purple/90 text-brand-black py-8 sm:py-12 md:py-16">
|
|
<div className="w-full max-w-[1600px] mx-auto px-3 sm:px-6 md:px-8 lg:px-12 xl:px-16">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8 md:gap-12 lg:gap-16 mb-6 sm:mb-10 md:mb-16">
|
|
{/* Logo e Texto descritivo */}
|
|
<div className="text-center md:text-left">
|
|
<img
|
|
src="/logo_preta.png"
|
|
alt="Photum Formaturas"
|
|
className="h-16 sm:h-20 md:h-24 mb-3 md:mb-6 mx-auto md:mx-20 object-contain"
|
|
/>
|
|
<p className="text-brand-black/80 text-xs sm:text-sm md:text-base leading-relaxed px-2 sm:px-0">
|
|
Eternizando momentos únicos com excelência e profissionalismo
|
|
desde 2020.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Links Úteis */}
|
|
<div className="text-center">
|
|
<h4 className="font-bold text-brand-black mb-3 md:mb-4 uppercase tracking-wider text-xs sm:text-sm md:text-base">
|
|
Links Úteis
|
|
</h4>
|
|
<ul className="space-y-1.5 sm:space-y-2 md:space-y-3 text-brand-black/70 text-xs sm:text-sm md:text-base">
|
|
<li>
|
|
<a
|
|
href="#"
|
|
onClick={() => navigate("/entrar")}
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
Área do Cliente
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="#"
|
|
onClick={() => navigate("/cadastro")}
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
Cadastre sua Formatura
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{/* Contato */}
|
|
<div className="text-center">
|
|
<h4 className="font-bold text-brand-black mb-3 md:mb-4 uppercase tracking-wider text-xs sm:text-sm md:text-base">
|
|
Contato
|
|
</h4>
|
|
<ul className="space-y-2 sm:space-y-3 md:space-y-4 text-brand-black/70 text-xs sm:text-sm md:text-base">
|
|
<li className="flex items-center justify-center gap-1.5 sm:gap-2 md:gap-3">
|
|
<svg
|
|
className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 flex-shrink-0"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
|
</svg>
|
|
<a
|
|
href="mailto:contato@photum.com.br"
|
|
className="hover:text-brand-black transition-colors break-all"
|
|
>
|
|
contato@photum.com.br
|
|
</a>
|
|
</li>
|
|
<li className="flex items-start justify-center gap-1.5 sm:gap-2 md:gap-3">
|
|
<svg
|
|
className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 flex-shrink-0 mt-0.5"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M20 15.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.59l2.2-2.21c.28-.26.36-.65.25-1C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM19 12h2c0-4.97-4.03-9-9-9v2c3.87 0 7 3.13 7 7zm-4 0h2c0-2.76-2.24-5-5-5v2c1.66 0 3 1.34 3 3z" />
|
|
</svg>
|
|
<div>
|
|
<p>(19) 3405 5024</p>
|
|
<p>(19) 3621 4621</p>
|
|
</div>
|
|
</li>
|
|
<li className="flex items-center justify-center gap-1.5 sm:gap-2 md:gap-3">
|
|
<svg
|
|
className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 flex-shrink-0"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z" />
|
|
</svg>
|
|
<span>Americana, SP</span>
|
|
</li>
|
|
<li className="flex justify-center gap-3 sm:gap-4 md:gap-5 mt-3 sm:mt-4 md:mt-6">
|
|
<a
|
|
href="#"
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
<svg
|
|
className="w-6 h-6 sm:w-7 sm:h-7 md:w-8 md:h-8"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
|
|
</svg>
|
|
</a>
|
|
<a
|
|
href="#"
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
<svg
|
|
className="w-7 h-7 md:w-8 md:h-8"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M22.675 0h-21.35c-.732 0-1.325.593-1.325 1.325v21.351c0 .731.593 1.324 1.325 1.324h11.495v-9.294h-3.128v-3.622h3.128v-2.671c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12v9.293h6.116c.73 0 1.323-.593 1.323-1.325v-21.35c0-.732-.593-1.325-1.325-1.325z" />
|
|
</svg>
|
|
</a>
|
|
<a
|
|
href="#"
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
<svg
|
|
className="w-6 h-6 sm:w-7 sm:h-7 md:w-8 md:h-8"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom Bar */}
|
|
<div className="border-t border-black/20 pt-4 sm:pt-6 md:pt-8 flex flex-col md:flex-row justify-between items-center text-[10px] sm:text-xs md:text-sm text-brand-black/60 gap-3 sm:gap-4">
|
|
<p className="text-center px-2">
|
|
© Todos os direitos reservados PHOTUM - CNPJ 27.708.950/0001-21
|
|
</p>
|
|
<div className="flex flex-wrap justify-center gap-2 sm:gap-4 md:gap-6">
|
|
<a
|
|
href="#"
|
|
onClick={() => navigate("/privacidade")}
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
Política de Privacidade
|
|
</a>
|
|
<a
|
|
href="#"
|
|
onClick={() => navigate("/termos")}
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
Termos de Uso
|
|
</a>
|
|
<a
|
|
href="#"
|
|
onClick={() => navigate("/lgpd")}
|
|
className="hover:text-brand-black transition-colors"
|
|
>
|
|
LGPD
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
);
|
|
};
|
|
|
|
const AppContent: React.FC = () => {
|
|
const location = useLocation();
|
|
const showFooter = location.pathname === "/";
|
|
const { isLoading: isAuthLoading } = useAuth();
|
|
if (isAuthLoading) {
|
|
return <LoadingScreen />;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Routes>
|
|
{/* Rotas Públicas */}
|
|
<Route path="/" element={<HomeWithRouter />} />
|
|
<Route path="/entrar" element={<LoginWithRouter />} />
|
|
<Route
|
|
path="/cadastro"
|
|
element={
|
|
<AccessCodeProtectedRoute type="client">
|
|
<RegisterWithRouter />
|
|
</AccessCodeProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/cadastro-profissional"
|
|
element={
|
|
<ProfessionalRegisterWithRouter />
|
|
}
|
|
/>
|
|
<Route
|
|
path="/privacidade"
|
|
element={
|
|
<PageWrapper>
|
|
<PrivacyPolicy
|
|
onNavigate={(page) => (window.location.href = `/${page}`)}
|
|
/>
|
|
</PageWrapper>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/termos"
|
|
element={
|
|
<PageWrapper>
|
|
<TermsOfUse
|
|
onNavigate={(page) => (window.location.href = `/${page}`)}
|
|
/>
|
|
</PageWrapper>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/lgpd"
|
|
element={
|
|
<PageWrapper>
|
|
<LGPD
|
|
onNavigate={(page) => (window.location.href = `/${page}`)}
|
|
/>
|
|
</PageWrapper>
|
|
}
|
|
/>
|
|
|
|
{/* Rotas Protegidas - Todos os usuários autenticados */}
|
|
<Route
|
|
path="/painel"
|
|
element={
|
|
<ProtectedRoute>
|
|
<PageWrapper>
|
|
<Dashboard initialView="list" />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/eventos"
|
|
element={
|
|
<ProtectedRoute>
|
|
<PageWrapper>
|
|
<Dashboard initialView="list" />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/inspiracao"
|
|
element={
|
|
<ProtectedRoute>
|
|
<PageWrapper>
|
|
<InspirationPage />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/agenda/:id"
|
|
element={
|
|
<ProtectedRoute>
|
|
<PageWrapper>
|
|
<EventDetails />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
|
|
{/* Rota de solicitação de evento - Clientes e Administradores */}
|
|
<Route
|
|
path="/solicitar-evento"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[
|
|
UserRole.SUPERADMIN,
|
|
UserRole.BUSINESS_OWNER,
|
|
UserRole.EVENT_OWNER,
|
|
]}
|
|
>
|
|
<PageWrapper>
|
|
<Dashboard initialView="create" />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* Rotas Administrativas - Apenas gestão */}
|
|
<Route
|
|
path="/equipe"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<TeamPage />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/financeiro"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<Finance />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/meus-pagamentos"
|
|
element={
|
|
<ProtectedRoute allowedRoles={[UserRole.PHOTOGRAPHER, UserRole.SUPERADMIN]}>
|
|
<PageWrapper>
|
|
<ProfessionalStatement />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/configuracoes"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<SettingsPage />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/cursos"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<CourseManagement />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/aprovacao-cadastros"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<UserApproval />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/codigos-acesso"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<AccessCodes />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/importacao"
|
|
element={
|
|
<ProtectedRoute
|
|
allowedRoles={[UserRole.SUPERADMIN, UserRole.BUSINESS_OWNER]}
|
|
>
|
|
<PageWrapper>
|
|
<ImportData />
|
|
</PageWrapper>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="/profile"
|
|
element={
|
|
<ProtectedRoute>
|
|
<ProfilePage />
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
|
|
{/* Rota padrão - redireciona para home */}
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</>
|
|
);
|
|
};
|
|
|
|
import { Toaster } from "react-hot-toast";
|
|
|
|
function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<AuthProvider>
|
|
<RegionProvider>
|
|
<DataProvider>
|
|
<Toaster position="top-right" />
|
|
<AppContent />
|
|
</DataProvider>
|
|
</RegionProvider>
|
|
</AuthProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|
|
|
|
export default App;
|