Initial React project

This commit is contained in:
Johan
2026-02-14 10:46:50 +01:00
commit 6c1f178ba9
5884 changed files with 1701440 additions and 0 deletions

246
src/App.tsx Normal file
View File

@@ -0,0 +1,246 @@
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 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;