Initial React project
This commit is contained in:
246
src/App.tsx
Normal file
246
src/App.tsx
Normal 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 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;
|
||||
Reference in New Issue
Block a user