refactor: split frontend application features
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
83d14d23a2
commit
5d7a57fd00
32 changed files with 2563 additions and 2433 deletions
File diff suppressed because it is too large
Load diff
49
frontend/src/app/AuthenticatedApp.tsx
Normal file
49
frontend/src/app/AuthenticatedApp.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
68
frontend/src/app/DataContext.tsx
Normal file
68
frontend/src/app/DataContext.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
14
frontend/src/app/dataState.ts
Normal file
14
frontend/src/app/dataState.ts
Normal 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)
|
||||
8
frontend/src/app/useData.ts
Normal file
8
frontend/src/app/useData.ts
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue