feat: add frontend app routing
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import { DesignSystemPreview } from '@/features/design-system/DesignSystemPreview'
|
import { AppShell } from './layout/AppShell'
|
||||||
|
import { Router } from './router'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return <DesignSystemPreview />
|
return (
|
||||||
|
<AppShell>
|
||||||
|
<Router />
|
||||||
|
</AppShell>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function AppShell({ children }: AppShellProps) {
|
|||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="min-h-screen lg:pl-64">
|
<div className="min-h-screen lg:pl-64">
|
||||||
<Topbar />
|
<Topbar />
|
||||||
<main>{children}</main>
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
|
const navItems = [
|
||||||
|
{ href: '#/tickets', label: 'Tickets' },
|
||||||
|
{ href: '#/tickets/new', label: 'Create Ticket' },
|
||||||
|
{ href: '#/solutions', label: 'Solutions' },
|
||||||
|
{ href: '#/design-system', label: 'Design System' }
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-surface lg:block">
|
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-surface lg:block">
|
||||||
<div className="border-b border-border px-4 py-3">
|
<div className="border-b border-border px-4 py-3">
|
||||||
<p className="text-sm font-semibold text-foreground">AI Ticket Platform</p>
|
<p className="text-sm font-semibold text-foreground">AI Ticket Platform</p>
|
||||||
</div>
|
</div>
|
||||||
|
<nav className="flex flex-col gap-1 p-3">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
className="rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
||||||
|
href={item.href}
|
||||||
|
key={item.href}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
import { App } from './App'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { DesignSystemPreview } from '@/features/design-system/DesignSystemPreview'
|
||||||
|
|
||||||
|
function getRoute() {
|
||||||
|
return window.location.hash.replace(/^#/, '') || '/tickets'
|
||||||
|
}
|
||||||
|
|
||||||
export function Router() {
|
export function Router() {
|
||||||
return <App />
|
const [route, setRoute] = React.useState(getRoute)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleHashChange = () => setRoute(getRoute())
|
||||||
|
window.addEventListener('hashchange', handleHashChange)
|
||||||
|
return () => window.removeEventListener('hashchange', handleHashChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (route === '/design-system') {
|
||||||
|
return <DesignSystemPreview />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="min-h-screen bg-background p-6 text-foreground">
|
||||||
|
<div className="mx-auto max-w-6xl">
|
||||||
|
<p className="text-sm text-muted-foreground">Route</p>
|
||||||
|
<h1 className="mt-2 text-xl font-semibold">{route}</h1>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
17
frontend/src/lib/api.ts
Normal file
17
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8000/api'
|
||||||
|
|
||||||
|
export async function apiRequest<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
|
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
...init?.headers
|
||||||
|
},
|
||||||
|
...init
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API request failed: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<T>
|
||||||
|
}
|
||||||
6
frontend/src/lib/format.ts
Normal file
6
frontend/src/lib/format.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function formatDateTime(value: string) {
|
||||||
|
return new Intl.DateTimeFormat('zh-CN', {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short'
|
||||||
|
}).format(new Date(value))
|
||||||
|
}
|
||||||
1
frontend/src/vite-env.d.ts
vendored
Normal file
1
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
Reference in New Issue
Block a user