GrantForge Bot
Deploy sha-565ad85979610064f6d1c18ab3b6404357d61073 — source build (no GHCR)
ce8f04a
Raw
History Blame Contribute Delete
6.06 kB
import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import { useAuth, SignIn, SignUp } from '@clerk/clerk-react';
import React, { Suspense, lazy } from 'react';
import Layout from './components/dashboard/Layout';
import { ApiInterceptor } from './components/ApiInterceptor';
import { ErrorBoundary } from './components/common/ErrorBoundary';
import { CookieConsentBanner } from './components/common/CookieConsentBanner';
import { resolvePostAuthRedirect, safeSignInRedirectPath } from './utils/authGate';
const Pricing = lazy(() => import('./pages/Pricing'));
const Projects = lazy(() => import('./pages/Projects'));
const ProjectWorkspace = lazy(() => import('./pages/ProjectWorkspace'));
const LandingPage = lazy(() => import('./pages/LandingPage'));
const About = lazy(() => import('./pages/About'));
const Help = lazy(() => import('./pages/Help'));
const Settings = lazy(() => import('./pages/Settings'));
const Regulamin = lazy(() => import('./pages/Regulamin'));
const PolitykaPrywatnosci = lazy(() => import('./pages/PolitykaPrywatnosci'));
const Nabory = lazy(() => import('./pages/Nabory'));
const Pulse = lazy(() => import('./pages/Pulse'));
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
const BetaGuide = lazy(() => import('./pages/BetaGuide'));
const CompanyProfile = lazy(() => import('./pages/CompanyProfile'));
const ReverseAudit = lazy(() => import('./pages/ReverseAudit'));
const AuthBootSplash = () => (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
background: 'var(--bg-base)',
color: 'var(--text-muted)',
}}
>
Sprawdzanie sesji...
</div>
);
/** Protected shell: wait for Clerk load; never bounce while session is hydrating. */
const ProtectedLayout = () => {
const { isLoaded, isSignedIn } = useAuth();
const location = useLocation();
if (!isLoaded) {
return <AuthBootSplash />;
}
if (!isSignedIn) {
const target = safeSignInRedirectPath(location.pathname, location.search);
return <Navigate to={target} replace />;
}
return <Layout />;
};
/** Sign-in / sign-up: if already signed in, go to redirect_url or /projects (no /dashboard hop). */
const AuthRoute: React.FC<{ mode: 'sign-in' | 'sign-up' }> = ({ mode }) => {
const { isLoaded, isSignedIn } = useAuth();
const location = useLocation();
const afterAuth = resolvePostAuthRedirect(location.search);
if (!isLoaded) {
return <AuthBootSplash />;
}
if (isSignedIn) {
return <Navigate to={afterAuth} replace />;
}
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
background: 'var(--bg-base)',
}}
>
{mode === 'sign-in' ? (
<SignIn
routing="path"
path="/sign-in"
signUpUrl="/sign-up"
forceRedirectUrl={afterAuth}
fallbackRedirectUrl={afterAuth}
/>
) : (
<SignUp
routing="path"
path="/sign-up"
signInUrl="/sign-in"
forceRedirectUrl={afterAuth}
fallbackRedirectUrl={afterAuth}
/>
)}
</div>
);
};
function App() {
return (
<ErrorBoundary context="App">
<BrowserRouter>
<ApiInterceptor>
<Toaster position="top-right" toastOptions={{
style: { background: 'var(--bg-elevated)', color: 'var(--text-primary)', border: '1px solid var(--border-strong)' }
}} />
<CookieConsentBanner />
<Suspense fallback={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--bg-base)', color: 'var(--text-muted)' }}>
Ładowanie aplikacji...
</div>
}>
<Routes>
{/* Ścieżki publiczne */}
<Route path="/" element={<LandingPage />} />
<Route path="/sign-in/*" element={<AuthRoute mode="sign-in" />} />
<Route path="/sign-up/*" element={<AuthRoute mode="sign-up" />} />
{/* Strony prawne — publiczne (bez logowania) */}
<Route path="/regulamin" element={<Regulamin />} />
<Route path="/polityka-prywatnosci" element={<PolitykaPrywatnosci />} />
{/* Cennik — publiczny (widoczny przed logowaniem) */}
<Route path="/pricing" element={<Pricing />} />
<Route path="/cennik" element={<Pricing />} />
{/* Ścieżki chronione (wymagające zalogowania) */}
<Route element={<ProtectedLayout />}>
{/* Alias — no intermediate bounce that fights redirect_url */}
<Route path="/dashboard" element={<Navigate to="/projects" replace />} />
<Route path="/projects" element={<Projects />} />
<Route path="/projects/:id" element={<ProjectWorkspace />} />
<Route path="/nabory" element={<Nabory />} />
<Route path="/pulse" element={<Pulse />} />
<Route path="/puls" element={<Pulse />} />
<Route path="/about" element={<About />} />
<Route path="/help" element={<Help />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<CompanyProfile />} />
<Route path="/audit" element={<ReverseAudit />} />
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/beta" element={<BetaGuide />} />
</Route>
{/* Zabezpieczenie przed błędem nawigacji */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Suspense>
</ApiInterceptor>
</BrowserRouter>
</ErrorBoundary>
);
}
export default App;