diff --git a/drizzle/20260401114858_cultured_lady_mastermind/snapshot.json b/drizzle/20260401114858_cultured_lady_mastermind/snapshot.json
index e8663ab..fb368f0 100644
--- a/drizzle/20260401114858_cultured_lady_mastermind/snapshot.json
+++ b/drizzle/20260401114858_cultured_lady_mastermind/snapshot.json
@@ -2,9 +2,7 @@
"version": "8",
"dialect": "postgres",
"id": "91ea16cc-3353-493c-bc2c-4fc9dea2fce7",
- "prevIds": [
- "00000000-0000-0000-0000-000000000000"
- ],
+ "prevIds": ["00000000-0000-0000-0000-000000000000"],
"ddl": [
{
"isRlsEnabled": false,
@@ -78,9 +76,7 @@
"table": "todo"
},
{
- "columns": [
- "id"
- ],
+ "columns": ["id"],
"nameExplicit": false,
"name": "todo_pkey",
"schema": "public",
@@ -89,4 +85,4 @@
}
],
"renames": []
-}
\ No newline at end of file
+}
diff --git a/src/components/Error.tsx b/src/components/Error.tsx
index f25b194..193d098 100644
--- a/src/components/Error.tsx
+++ b/src/components/Error.tsx
@@ -1,3 +1,40 @@
-export function ErrorComponent() {
- return
An unhandled error happened!
+export const ErrorComponent = ({ error, reset }: { error: Error; reset: () => void }) => {
+ return (
+
+
+
+
+
出错了
+
{error.message}
+
+
+
+
+ )
}
diff --git a/src/components/NotFound.tsx b/src/components/NotFound.tsx
index fec55b7..d448827 100644
--- a/src/components/NotFound.tsx
+++ b/src/components/NotFound.tsx
@@ -1,3 +1,23 @@
-export function NotFoundComponent() {
- return 404 - Not Found
+import { Link } from '@tanstack/react-router'
+
+export const NotFoundComponent = () => {
+ return (
+
+
+
+ 404
+
+
+
页面不存在
+
您访问的页面可能已被移除或地址有误
+
+
+ 返回首页
+
+
+
+ )
}
diff --git a/src/components/TodoForm.tsx b/src/components/TodoForm.tsx
new file mode 100644
index 0000000..3f92f6b
--- /dev/null
+++ b/src/components/TodoForm.tsx
@@ -0,0 +1,41 @@
+import type { SubmitEventHandler } from 'react'
+import { useState } from 'react'
+
+interface TodoFormProps {
+ onSubmit: (title: string) => void
+ isPending: boolean
+}
+
+export const TodoForm = ({ onSubmit, isPending }: TodoFormProps) => {
+ const [title, setTitle] = useState('')
+
+ const handleSubmit: SubmitEventHandler = (e) => {
+ e.preventDefault()
+ if (title.trim()) {
+ onSubmit(title.trim())
+ setTitle('')
+ }
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx
new file mode 100644
index 0000000..1963ff3
--- /dev/null
+++ b/src/components/TodoItem.tsx
@@ -0,0 +1,77 @@
+import type { RouterOutputs } from '@/server/api/types'
+
+type Todo = RouterOutputs['todo']['list'][number]
+
+interface TodoItemProps {
+ todo: Todo
+ onToggle: (id: string, completed: boolean) => void
+ onDelete: (id: string) => void
+}
+
+export const TodoItem = ({ todo, onToggle, onDelete }: TodoItemProps) => {
+ return (
+
+
+
+
+
+
+
+ {new Date(todo.createdAt).toLocaleDateString('zh-CN')}
+
+
+
+
+ )
+}
diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx
index b7f758f..5dea9b1 100644
--- a/src/routes/__root.tsx
+++ b/src/routes/__root.tsx
@@ -34,8 +34,8 @@ export const Route = createRootRouteWithContext()({
],
}),
shellComponent: RootDocument,
- errorComponent: () => ,
- notFoundComponent: () => ,
+ errorComponent: ErrorComponent,
+ notFoundComponent: NotFoundComponent,
})
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index fa57a89..d3d052e 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -1,8 +1,8 @@
import { useMutation, useSuspenseQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
-import type { ChangeEventHandler, SubmitEventHandler } from 'react'
-import { useState } from 'react'
import { orpc } from '@/client/orpc'
+import { TodoForm } from '@/components/TodoForm'
+import { TodoItem } from '@/components/TodoItem'
export const Route = createFileRoute('/')({
component: Todos,
@@ -12,36 +12,11 @@ export const Route = createFileRoute('/')({
})
function Todos() {
- const [newTodoTitle, setNewTodoTitle] = useState('')
-
const listQuery = useSuspenseQuery(orpc.todo.list.queryOptions())
const createMutation = useMutation(orpc.todo.create.mutationOptions())
const updateMutation = useMutation(orpc.todo.update.mutationOptions())
const deleteMutation = useMutation(orpc.todo.remove.mutationOptions())
- const handleCreateTodo: SubmitEventHandler = (e) => {
- e.preventDefault()
- if (newTodoTitle.trim()) {
- createMutation.mutate({ title: newTodoTitle.trim() })
- setNewTodoTitle('')
- }
- }
-
- const handleInputChange: ChangeEventHandler = (e) => {
- setNewTodoTitle(e.target.value)
- }
-
- const handleToggleTodo = (id: string, currentCompleted: boolean) => {
- updateMutation.mutate({
- id,
- data: { completed: !currentCompleted },
- })
- }
-
- const handleDeleteTodo = (id: string) => {
- deleteMutation.mutate({ id })
- }
-
const todos = listQuery.data
const completedCount = todos.filter((todo) => todo.completed).length
const totalCount = todos.length
@@ -65,28 +40,9 @@ function Todos() {
- {/* Add Todo Form */}
-
+ createMutation.mutate({ title })} isPending={createMutation.isPending} />
- {/* Progress Bar (Only visible when there are tasks) */}
+ {/* Progress Bar */}
{totalCount > 0 && (
) : (
todos.map((todo) => (
-
-
-
-
-
-
-
- {new Date(todo.createdAt).toLocaleDateString('zh-CN')}
-
-
-
-
+ todo={todo}
+ onToggle={(id, completed) => updateMutation.mutate({ id, data: { completed: !completed } })}
+ onDelete={(id) => deleteMutation.mutate({ id })}
+ />
))
)}
diff --git a/src/server/api/routers/index.ts b/src/server/api/routers/index.ts
index 02a11fe..31a82ed 100644
--- a/src/server/api/routers/index.ts
+++ b/src/server/api/routers/index.ts
@@ -1,4 +1,4 @@
-import { os } from '../server'
+import { os } from '@/server/api/server'
import * as todo from './todo.router'
export const router = os.router({
diff --git a/src/server/api/routers/todo.router.ts b/src/server/api/routers/todo.router.ts
index 40c988f..523e999 100644
--- a/src/server/api/routers/todo.router.ts
+++ b/src/server/api/routers/todo.router.ts
@@ -1,8 +1,8 @@
import { ORPCError } from '@orpc/server'
import { eq } from 'drizzle-orm'
+import { db } from '@/server/api/middlewares'
+import { os } from '@/server/api/server'
import { todoTable } from '@/server/db/schema'
-import { db } from '../middlewares'
-import { os } from '../server'
export const list = os.todo.list.use(db).handler(async ({ context }) => {
const todos = await context.db.query.todoTable.findMany({
diff --git a/src/server/db/index.ts b/src/server/db/index.ts
index e7fd5f0..fc8e6be 100644
--- a/src/server/db/index.ts
+++ b/src/server/db/index.ts
@@ -13,11 +13,7 @@ export type DB = ReturnType
export const getDB = (() => {
let db: DB | null = null
- return (singleton = true): DB => {
- if (!singleton) {
- return createDB()
- }
-
+ return (): DB => {
db ??= createDB()
return db
}