add:planned feats
This commit is contained in:
@@ -1,76 +1,79 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Modal } from './components/ui/Modal.tsx'
|
||||
import { Button } from './components/ui/Button.tsx'
|
||||
import { TicketTable } from './components/tickets/TicketTable.tsx'
|
||||
import { NewTicketForm } from './components/tickets/NewTicketForm.tsx'
|
||||
import { useModal } from './hooks/useModal.ts'
|
||||
import { useStorageMode } from './hooks/useStorageMode.ts'
|
||||
import { getStorage } from './lib/storage.ts'
|
||||
import type { Ticket } from './lib/types.ts'
|
||||
import { useState } from 'react'
|
||||
import { Layout } from './components/ui/Layout.tsx'
|
||||
import { PlusIcon } from './components/icons/plus.tsx'
|
||||
import { Tabs } from './components/ui/Tabs.tsx'
|
||||
import { UserPage } from './pages/UserPage.tsx'
|
||||
import { AdminPage } from './pages/AdminPage.tsx'
|
||||
import { LoginPage } from './pages/LoginPage.tsx'
|
||||
import { useStorageMode } from './hooks/useStorageMode.ts'
|
||||
import { useAuth } from './hooks/useAuth.ts'
|
||||
import { AuthBar } from './components/ui/AuthBar.tsx'
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||
import { NotFound } from './pages/NotFound.tsx'
|
||||
|
||||
function TicketApp({ storageMode }: { storageMode: 'local' | 'remote' }) {
|
||||
const storage = getStorage(storageMode)
|
||||
const [tickets, setTickets] = useState<Ticket[]>([])
|
||||
const newTicketModal = useModal()
|
||||
type TabValue = 'tickets' | 'admin'
|
||||
|
||||
// load tickets — handles both sync (local) and async (remote)
|
||||
useEffect(() => {
|
||||
const result = storage.getTickets()
|
||||
if (result instanceof Promise) {
|
||||
result.then(setTickets)
|
||||
} else {
|
||||
setTickets(result)
|
||||
}
|
||||
}, [storageMode])
|
||||
|
||||
const handleCreateTicket = async (form: Pick<Ticket, 'subject' | 'description' | 'type'>) => {
|
||||
const result = storage.createTicket(form)
|
||||
const ticket = result instanceof Promise ? await result : result
|
||||
setTickets(prev => [ticket, ...prev])
|
||||
newTicketModal.close()
|
||||
}
|
||||
const handleDeleteTicket = async (id: string) => {
|
||||
const result = storage.deleteTicket(id)
|
||||
if (result instanceof Promise) await result
|
||||
setTickets(prev => prev.filter(t => t.id !== id))
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-fg-100">Support Tickets</h1>
|
||||
<p className="mt-0.5 text-sm text-fg-300">
|
||||
{tickets.length} {tickets.length === 1 ? 'ticket' : 'tickets'} total
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={newTicketModal.open}>
|
||||
<PlusIcon className="size-3" />
|
||||
New Ticket
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<TicketTable tickets={tickets} onDelete={handleDeleteTicket} />
|
||||
|
||||
<Modal isOpen={newTicketModal.isOpen} onClose={newTicketModal.close} title="New Ticket">
|
||||
<NewTicketForm onSubmit={handleCreateTicket} />
|
||||
</Modal>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
function SupportApp() {
|
||||
const [activeTab, setActiveTab] = useState<TabValue>('tickets')
|
||||
const [showLogin, setShowLogin] = useState(false)
|
||||
const { user, authState, logout } = useAuth()
|
||||
const storageMode = useStorageMode()
|
||||
|
||||
if (storageMode === 'pending') {
|
||||
const urlError = new URLSearchParams(window.location.search).get('error')
|
||||
|
||||
if (showLogin || urlError) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<Layout
|
||||
user={user}>
|
||||
<LoginPage
|
||||
error={urlError}
|
||||
onBack={() => {
|
||||
setShowLogin(false)
|
||||
window.history.replaceState({}, '', window.location.pathname)
|
||||
}}
|
||||
/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
if (authState === 'pending' || storageMode === 'pending') {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-bg-100">
|
||||
<p className="text-sm text-fg-300">Loading...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <TicketApp storageMode={storageMode} />
|
||||
const isGuest = authState === 'unauthenticated'
|
||||
|
||||
const tabs: { value: TabValue; label: string }[] = [
|
||||
{
|
||||
value: 'tickets',
|
||||
label: user ? `${user.username}'s Tickets` : 'My Tickets',
|
||||
},
|
||||
{ value: 'admin', label: 'Admin' },
|
||||
]
|
||||
|
||||
return (
|
||||
<Layout
|
||||
user={user}
|
||||
subHeader={
|
||||
<AuthBar isGuest={isGuest} onLogin={() => setShowLogin(true)} onLogout={logout} user={user} />
|
||||
}
|
||||
>
|
||||
<Tabs tabs={tabs} active={activeTab} onChange={setActiveTab} />
|
||||
{activeTab === 'tickets' && <UserPage storageMode={storageMode} />}
|
||||
{activeTab === 'admin' && <AdminPage storageMode={storageMode} />}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<SupportApp />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user