refactor: split frontend application features

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Daniel Noulet 2026-09-20 08:08:16 +02:00
parent 83d14d23a2
commit 5d7a57fd00
32 changed files with 2563 additions and 2433 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
import { useState } from 'react'
import { Navigate, Route, Routes } from 'react-router-dom'
import { authStorage } from '../api/client'
import { AppFooter } from '../components/layout/AppFooter'
import { GoGuessHeader } from '../components/layout/GoGuessHeader'
import { WorkspaceUtilities } from '../components/layout/WorkspaceUtilities'
import { NotFound } from '../components/ui/NotFound'
import { CalendarPage, InboxPage, ScheduledInterviewPage } from '../features/interviews'
import { CreateJobPage, JobDetailPage, JobsPage } from '../features/jobs'
import {
CreateParticipantPage,
ParticipantDetailPage,
ParticipantsPage,
} from '../features/participants'
import { QuestionLibraryPage, QuestionPage } from '../features/questions'
import { ProfilePage, UsersPage } from '../features/users'
export function AuthenticatedApp({ onLogout }: { onLogout: () => void }) {
const [open, setOpen] = useState(false)
const user = authStorage.user()
return (
<div className="app-shell">
<GoGuessHeader setOpen={setOpen} open={open} onLogout={onLogout} />
<main className="main-content">
<WorkspaceUtilities user={user} />
<div className="workspace-content">
<Routes>
<Route index element={<Navigate to="/jobs" replace />} />
<Route path="/jobs" element={<JobsPage />} />
<Route path="/jobs/new" element={<CreateJobPage />} />
<Route path="/jobs/:jobId" element={<JobDetailPage />} />
<Route path="/jobs/:jobId/questions/new" element={<QuestionPage />} />
<Route path="/questions" element={<QuestionLibraryPage />} />
<Route path="/participants" element={<ParticipantsPage />} />
<Route path="/participants/new" element={<CreateParticipantPage />} />
<Route path="/participants/:participantId" element={<ParticipantDetailPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/inbox" element={<InboxPage />} />
<Route path="/calendar" element={<CalendarPage />} />
<Route path="/scheduled-interviews/:id" element={<ScheduledInterviewPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
<AppFooter />
</main>
</div>
)
}

View file

@ -0,0 +1,68 @@
import { type ReactNode, useEffect, useState } from 'react'
import { api } from '../api/client'
import { errorMessage } from '../components/actions'
import type { CreateJobInput, CreateParticipantInput, Job, Participant } from '../types'
import { DataContext } from './dataState'
export function DataProvider({ children }: { children: ReactNode }) {
const [jobs, setJobs] = useState<Job[]>([])
const [participants, setParticipants] = useState<Participant[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
async function reload() {
setLoading(true)
setError('')
try {
const [jobItems, participantItems] = await Promise.all([
api.jobs.list(),
api.participants.list(),
])
setJobs(jobItems)
setParticipants(participantItems)
} catch (requestError) {
setError(errorMessage(requestError))
} finally {
setLoading(false)
}
}
useEffect(() => {
let active = true
void Promise.all([api.jobs.list(), api.participants.list()])
.then(([jobItems, participantItems]) => {
if (!active) return
setJobs(jobItems)
setParticipants(participantItems)
})
.catch((requestError: unknown) => {
if (active) setError(errorMessage(requestError))
})
.finally(() => {
if (active) setLoading(false)
})
return () => {
active = false
}
}, [])
async function createJob(input: CreateJobInput) {
const created = await api.jobs.create(input)
setJobs((current) => [created, ...current])
return created
}
async function createParticipant(input: CreateParticipantInput) {
const created = await api.participants.create(input)
setParticipants((current) => [created, ...current])
return created
}
return (
<DataContext.Provider
value={{ jobs, participants, loading, error, createJob, createParticipant, reload }}
>
{children}
</DataContext.Provider>
)
}

View file

@ -0,0 +1,14 @@
import { createContext } from 'react'
import type { CreateJobInput, CreateParticipantInput, Job, Participant } from '../types'
export interface AppData {
jobs: Job[]
participants: Participant[]
loading: boolean
error: string
createJob: (input: CreateJobInput) => Promise<Job>
createParticipant: (input: CreateParticipantInput) => Promise<Participant>
reload: () => Promise<void>
}
export const DataContext = createContext<AppData | null>(null)

View file

@ -0,0 +1,8 @@
import { useContext } from 'react'
import { DataContext } from './dataState'
export function useData() {
const value = useContext(DataContext)
if (!value) throw new Error('useData must be used within DataProvider')
return value
}