247 lines
8.4 KiB
TypeScript
247 lines
8.4 KiB
TypeScript
import './App.css';
|
|
import { useEffect } from 'react';
|
|
import { ForgotPasswordPage } from './presentation/auth/pages/ForgotPasswordPage';
|
|
import { LoginPage } from './presentation/auth/pages/LoginPage';
|
|
import { RegisterPage } from './presentation/auth/pages/RegisterPage';
|
|
import { DashboardPage } from './presentation/dashboard/pages/DashboardPage';
|
|
import { CvPage } from './presentation/cv/pages/CvPage';
|
|
import { JobsPage } from './presentation/jobs/pages/JobsPage';
|
|
import { JobDetailPage } from './presentation/jobs/pages/JobDetailPage';
|
|
import { BeskederPage } from './presentation/messages/pages/BeskederPage';
|
|
import { AiAgentPage } from './presentation/ai-agent/pages/AiAgentPage';
|
|
import { AiJobAgentPage } from './presentation/ai-jobagent/pages/AiJobAgentPage';
|
|
import { JobSimulatorPage } from './presentation/simulator/pages/JobSimulatorPage';
|
|
import { SubscriptionPage } from './presentation/subscription/pages/SubscriptionPage';
|
|
import { ThemeToggle } from './presentation/layout/components/ThemeToggle';
|
|
import { useAuthViewModel } from './presentation/auth/hooks/useAuthViewModel';
|
|
import { useBrowserRoute } from './presentation/router/useBrowserRoute';
|
|
import { localStorageService } from './mvvm/services/local-storage.service';
|
|
|
|
function App() {
|
|
const { path, navigate } = useBrowserRoute();
|
|
const { isLoading, result, login, register, forgotPassword } = useAuthViewModel();
|
|
const isJobDetail = path.startsWith('/jobs/');
|
|
const jobDetailMatch = isJobDetail ? path.match(/^\/jobs\/([^/]+)\/(jobnet|arbejd)$/) : null;
|
|
const jobIdFromPath = jobDetailMatch ? decodeURIComponent(jobDetailMatch[1]) : '';
|
|
const fromJobnetFromPath = jobDetailMatch ? jobDetailMatch[2] === 'jobnet' : false;
|
|
const mode =
|
|
path === '/register'
|
|
? 'register'
|
|
: path === '/forgot-password'
|
|
? 'forgot'
|
|
: path === '/dashboard'
|
|
? 'dashboard'
|
|
: path === '/cv'
|
|
? 'cv'
|
|
: path === '/jobs'
|
|
? 'jobs'
|
|
: path === '/beskeder'
|
|
? 'beskeder'
|
|
: path === '/ai-jobagent'
|
|
? 'ai-jobagent'
|
|
: path === '/ai-agent'
|
|
? 'ai-agent'
|
|
: path === '/simulator'
|
|
? 'simulator'
|
|
: path === '/abonnement'
|
|
? 'abonnement'
|
|
: isJobDetail
|
|
? 'job-detail'
|
|
: 'login';
|
|
|
|
useEffect(() => {
|
|
const token = window.localStorage.getItem('token');
|
|
const isAuthPage = path === '/login' || path === '/register' || path === '/forgot-password' || path === '/';
|
|
|
|
if ((path === '/dashboard' || path === '/cv' || path === '/jobs' || path === '/beskeder' || path === '/ai-jobagent' || path === '/ai-agent' || path === '/simulator' || path === '/abonnement' || isJobDetail) && !token) {
|
|
navigate('/login', true);
|
|
return;
|
|
}
|
|
|
|
if (isAuthPage && token) {
|
|
navigate('/dashboard', true);
|
|
}
|
|
}, [path, navigate, isJobDetail]);
|
|
|
|
async function logout() {
|
|
await localStorageService.clearCredentials();
|
|
navigate('/login', true);
|
|
}
|
|
|
|
function navigateFromSidebar(key: 'dashboard' | 'cv' | 'jobs' | 'beskeder' | 'ai-jobagent' | 'ai-agent' | 'simulator' | 'abonnement') {
|
|
if (key === 'dashboard') {
|
|
navigate('/dashboard');
|
|
return;
|
|
}
|
|
if (key === 'cv') {
|
|
navigate('/cv');
|
|
return;
|
|
}
|
|
if (key === 'jobs') {
|
|
navigate('/jobs');
|
|
return;
|
|
}
|
|
if (key === 'ai-jobagent') {
|
|
navigate('/ai-jobagent');
|
|
return;
|
|
}
|
|
if (key === 'ai-agent') {
|
|
navigate('/ai-agent');
|
|
return;
|
|
}
|
|
if (key === 'simulator') {
|
|
navigate('/simulator');
|
|
return;
|
|
}
|
|
if (key === 'abonnement') {
|
|
navigate('/abonnement');
|
|
return;
|
|
}
|
|
navigate('/beskeder');
|
|
}
|
|
|
|
const isAppLayoutMode = mode === 'dashboard' || mode === 'cv' || mode === 'jobs' || mode === 'job-detail' || mode === 'beskeder' || mode === 'ai-jobagent' || mode === 'ai-agent' || mode === 'simulator' || mode === 'abonnement';
|
|
|
|
return (
|
|
<main className={isAppLayoutMode ? 'auth-root dashboard-mode' : 'auth-root'}>
|
|
<div className="orb orb-1" />
|
|
<div className="orb orb-2" />
|
|
<div className="orb orb-3" />
|
|
|
|
{mode === 'dashboard' ? (
|
|
<DashboardPage
|
|
onLogout={logout}
|
|
onNavigate={navigateFromSidebar}
|
|
onOpenJob={(jobId, fromJobnet) =>
|
|
navigate(`/jobs/${encodeURIComponent(jobId)}/${fromJobnet ? 'jobnet' : 'arbejd'}`)
|
|
}
|
|
/>
|
|
) : mode === 'cv' ? (
|
|
<CvPage onLogout={logout} onNavigate={navigateFromSidebar} />
|
|
) : mode === 'beskeder' ? (
|
|
<BeskederPage onLogout={logout} onNavigate={navigateFromSidebar} />
|
|
) : mode === 'ai-jobagent' ? (
|
|
<AiJobAgentPage
|
|
onLogout={logout}
|
|
onNavigate={navigateFromSidebar}
|
|
onOpenJob={(jobId, fromJobnet) =>
|
|
navigate(`/jobs/${encodeURIComponent(jobId)}/${fromJobnet ? 'jobnet' : 'arbejd'}`)
|
|
}
|
|
/>
|
|
) : mode === 'ai-agent' ? (
|
|
<AiAgentPage onLogout={logout} onNavigate={navigateFromSidebar} activeNavKey="ai-agent" />
|
|
) : mode === 'simulator' ? (
|
|
<JobSimulatorPage onLogout={logout} onNavigate={navigateFromSidebar} />
|
|
) : mode === 'abonnement' ? (
|
|
<SubscriptionPage onLogout={logout} onNavigate={navigateFromSidebar} />
|
|
) : mode === 'job-detail' && jobDetailMatch ? (
|
|
<JobDetailPage
|
|
jobId={jobIdFromPath}
|
|
fromJobnet={fromJobnetFromPath}
|
|
onLogout={logout}
|
|
onNavigate={navigateFromSidebar}
|
|
/>
|
|
) : mode === 'jobs' ? (
|
|
<JobsPage
|
|
onLogout={logout}
|
|
onNavigate={navigateFromSidebar}
|
|
onOpenJob={(jobId, fromJobnet) =>
|
|
navigate(`/jobs/${encodeURIComponent(jobId)}/${fromJobnet ? 'jobnet' : 'arbejd'}`)
|
|
}
|
|
/>
|
|
) : (
|
|
<section className="auth-shell glass-panel">
|
|
<aside className="brand-panel">
|
|
<div className="brand-chip">
|
|
<span>Ar</span>
|
|
</div>
|
|
<h1>Arbejd.com</h1>
|
|
<p>
|
|
AI-assisteret jobsøgning med glasdesign og fokus på flow.
|
|
</p>
|
|
<ul className="brand-list">
|
|
<li>Log ind</li>
|
|
<li>Opret konto</li>
|
|
<li>Glemt kodeord</li>
|
|
</ul>
|
|
</aside>
|
|
|
|
<section className="form-panel glass-panel">
|
|
<div className="auth-theme-row">
|
|
<ThemeToggle />
|
|
</div>
|
|
|
|
<div className="mode-tabs">
|
|
<button
|
|
className={mode === 'login' ? 'tab-btn active' : 'tab-btn'}
|
|
onClick={() => navigate('/login')}
|
|
type="button"
|
|
>
|
|
Log ind
|
|
</button>
|
|
<button
|
|
className={mode === 'register' ? 'tab-btn active' : 'tab-btn'}
|
|
onClick={() => navigate('/register')}
|
|
type="button"
|
|
>
|
|
Opret konto
|
|
</button>
|
|
<button
|
|
className={mode === 'forgot' ? 'tab-btn active' : 'tab-btn'}
|
|
onClick={() => navigate('/forgot-password')}
|
|
type="button"
|
|
>
|
|
Glemt kode
|
|
</button>
|
|
</div>
|
|
|
|
{mode === 'login' && (
|
|
<LoginPage
|
|
isLoading={isLoading}
|
|
onSubmit={async (email, password, rememberMe) => {
|
|
const response = await login(email, password, rememberMe);
|
|
if (response.ok) {
|
|
navigate('/dashboard');
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{mode === 'register' && (
|
|
<RegisterPage
|
|
isLoading={isLoading}
|
|
onSubmit={async (payload) => {
|
|
const response = await register(payload);
|
|
if (response.ok) {
|
|
navigate('/login');
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{mode === 'forgot' && (
|
|
<ForgotPasswordPage
|
|
isLoading={isLoading}
|
|
onSubmit={async (email) => {
|
|
const response = await forgotPassword(email);
|
|
if (response.ok) {
|
|
navigate('/login');
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{result && (
|
|
<p className={result.ok ? 'status success' : 'status error'}>
|
|
{result.message}
|
|
</p>
|
|
)}
|
|
</section>
|
|
</section>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|
|
|
|
export default App;
|