diff --git a/apps/cli/template/base/packages/client/package.json b/apps/cli/template/base/packages/client/package.json index ee38a20..38b4718 100644 --- a/apps/cli/template/base/packages/client/package.json +++ b/apps/cli/template/base/packages/client/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-slot": "^1.1.2", diff --git a/apps/cli/template/base/packages/client/src/components/header.tsx b/apps/cli/template/base/packages/client/src/components/header.tsx index f370e69..894fbbd 100644 --- a/apps/cli/template/base/packages/client/src/components/header.tsx +++ b/apps/cli/template/base/packages/client/src/components/header.tsx @@ -15,6 +15,15 @@ export default function Header() { > Home + + Todos +
diff --git a/apps/cli/template/base/packages/client/src/components/ui/card.tsx b/apps/cli/template/base/packages/client/src/components/ui/card.tsx new file mode 100644 index 0000000..d05bbc6 --- /dev/null +++ b/apps/cli/template/base/packages/client/src/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/apps/cli/template/base/packages/client/src/components/ui/checkbox.tsx b/apps/cli/template/base/packages/client/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..defeb01 --- /dev/null +++ b/apps/cli/template/base/packages/client/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/apps/cli/template/base/packages/client/src/routes/todos.tsx b/apps/cli/template/base/packages/client/src/routes/todos.tsx new file mode 100644 index 0000000..3fe08d3 --- /dev/null +++ b/apps/cli/template/base/packages/client/src/routes/todos.tsx @@ -0,0 +1,128 @@ +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { trpc } from "@/utils/trpc"; +import { createFileRoute } from "@tanstack/react-router"; +import { Loader2, Trash2 } from "lucide-react"; +import { useState } from "react"; + +export const Route = createFileRoute("/todos")({ + component: TodosRoute, +}); + +function TodosRoute() { + const [newTodoText, setNewTodoText] = useState(""); + + const todos = trpc.todo.getAll.useQuery(); + const createMutation = trpc.todo.create.useMutation({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + }); + const toggleMutation = trpc.todo.toggle.useMutation({ + onSuccess: () => todos.refetch(), + }); + const deleteMutation = trpc.todo.delete.useMutation({ + onSuccess: () => todos.refetch(), + }); + + const handleAddTodo = (e: React.FormEvent) => { + e.preventDefault(); + if (newTodoText.trim()) { + createMutation.mutate({ text: newTodoText }); + } + }; + + const handleToggleTodo = (id: number, completed: boolean) => { + toggleMutation.mutate({ id, completed: !completed }); + }; + + const handleDeleteTodo = (id: number) => { + deleteMutation.mutate({ id }); + }; + + return ( +
+ + + Todo List + Manage your tasks efficiently + + +
+ setNewTodoText(e.target.value)} + placeholder="Add a new task..." + disabled={createMutation.isPending} + /> + +
+ + {todos.isLoading ? ( +
+ +
+ ) : todos.data?.length === 0 ? ( +

+ No todos yet. Add one above! +

+ ) : ( +
    + {todos.data?.map((todo) => ( +
  • +
    + + handleToggleTodo(todo.id, todo.completed) + } + id={`todo-${todo.id}`} + /> + +
    + +
  • + ))} +
+ )} +
+
+
+ ); +} diff --git a/apps/cli/template/base/packages/server/src/routers/index.ts b/apps/cli/template/base/packages/server/src/routers/index.ts index b62cd06..91b444d 100644 --- a/apps/cli/template/base/packages/server/src/routers/index.ts +++ b/apps/cli/template/base/packages/server/src/routers/index.ts @@ -1,9 +1,11 @@ import { router, publicProcedure } from "../lib/trpc"; +import { todoRouter } from "./todo"; export const appRouter = router({ healthCheck: publicProcedure.query(() => { return "OK"; }), + todo: todoRouter, }); export type AppRouter = typeof appRouter; diff --git a/apps/cli/template/with-auth/packages/client/src/components/header.tsx b/apps/cli/template/with-auth/packages/client/src/components/header.tsx index 184a111..30b36b9 100644 --- a/apps/cli/template/with-auth/packages/client/src/components/header.tsx +++ b/apps/cli/template/with-auth/packages/client/src/components/header.tsx @@ -16,6 +16,15 @@ export default function Header() { > Home + + Todos + { @@ -11,6 +13,7 @@ export const appRouter = router({ user: ctx.session.user, }; }), + todo: todoRouter, }); export type AppRouter = typeof appRouter; diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/src/routers/todo.ts b/apps/cli/template/with-drizzle-postgres/packages/server/src/routers/todo.ts new file mode 100644 index 0000000..e2b1896 --- /dev/null +++ b/apps/cli/template/with-drizzle-postgres/packages/server/src/routers/todo.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import { router, publicProcedure } from "../lib/trpc"; +import { todo } from "../db/schema"; +import { eq } from "drizzle-orm"; +import { db } from "../db"; + +export const todoRouter = router({ + getAll: publicProcedure.query(async () => { + return await db.select().from(todo).all(); + }), + + create: publicProcedure + .input(z.object({ text: z.string().min(1) })) + .mutation(async ({ input }) => { + return await db + .insert(todo) + .values({ + text: input.text, + }) + .returning() + .get(); + }), + + toggle: publicProcedure + .input(z.object({ id: z.number(), completed: z.boolean() })) + .mutation(async ({ input }) => { + return await db + .update(todo) + .set({ completed: input.completed }) + .where(eq(todo.id, input.id)) + .returning() + .get(); + }), + + delete: publicProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input }) => { + return await db + .delete(todo) + .where(eq(todo.id, input.id)) + .returning() + .get(); + }), +}); diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/src/routers/todo.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/src/routers/todo.ts new file mode 100644 index 0000000..e2b1896 --- /dev/null +++ b/apps/cli/template/with-drizzle-sqlite/packages/server/src/routers/todo.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import { router, publicProcedure } from "../lib/trpc"; +import { todo } from "../db/schema"; +import { eq } from "drizzle-orm"; +import { db } from "../db"; + +export const todoRouter = router({ + getAll: publicProcedure.query(async () => { + return await db.select().from(todo).all(); + }), + + create: publicProcedure + .input(z.object({ text: z.string().min(1) })) + .mutation(async ({ input }) => { + return await db + .insert(todo) + .values({ + text: input.text, + }) + .returning() + .get(); + }), + + toggle: publicProcedure + .input(z.object({ id: z.number(), completed: z.boolean() })) + .mutation(async ({ input }) => { + return await db + .update(todo) + .set({ completed: input.completed }) + .where(eq(todo.id, input.id)) + .returning() + .get(); + }), + + delete: publicProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input }) => { + return await db + .delete(todo) + .where(eq(todo.id, input.id)) + .returning() + .get(); + }), +}); diff --git a/apps/cli/template/with-prisma-postgres/packages/server/prisma/schema.prisma b/apps/cli/template/with-prisma-postgres/packages/server/prisma/schema.prisma index 6612705..c1210cb 100644 --- a/apps/cli/template/with-prisma-postgres/packages/server/prisma/schema.prisma +++ b/apps/cli/template/with-prisma-postgres/packages/server/prisma/schema.prisma @@ -14,63 +14,3 @@ model Todo { @@map("todo") } - -model User { - id String @id @map("_id") - name String - email String - emailVerified Boolean - image String? - createdAt DateTime - updatedAt DateTime - sessions Session[] - accounts Account[] - - @@unique([email]) - @@map("user") -} - -model Session { - id String @id @map("_id") - expiresAt DateTime - token String - createdAt DateTime - updatedAt DateTime - ipAddress String? - userAgent String? - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@unique([token]) - @@map("session") -} - -model Account { - id String @id @map("_id") - accountId String - providerId String - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - accessToken String? - refreshToken String? - idToken String? - accessTokenExpiresAt DateTime? - refreshTokenExpiresAt DateTime? - scope String? - password String? - createdAt DateTime - updatedAt DateTime - - @@map("account") -} - -model Verification { - id String @id @map("_id") - identifier String - value String - expiresAt DateTime - createdAt DateTime? - updatedAt DateTime? - - @@map("verification") -} diff --git a/apps/cli/template/with-prisma-postgres/packages/server/src/routers/todo.ts b/apps/cli/template/with-prisma-postgres/packages/server/src/routers/todo.ts new file mode 100644 index 0000000..5f2d3f9 --- /dev/null +++ b/apps/cli/template/with-prisma-postgres/packages/server/src/routers/todo.ts @@ -0,0 +1,55 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import prisma from "../../prisma"; +import { publicProcedure, router } from "../lib/trpc"; + +export const todoRouter = router({ + getAll: publicProcedure.query(async () => { + return await prisma.todo.findMany({ + orderBy: { + id: "asc" + } + }); + }), + + create: publicProcedure + .input(z.object({ text: z.string().min(1) })) + .mutation(async ({ input }) => { + return await prisma.todo.create({ + data: { + text: input.text, + }, + }); + }), + + toggle: publicProcedure + .input(z.object({ id: z.number(), completed: z.boolean() })) + .mutation(async ({ input }) => { + try { + return await prisma.todo.update({ + where: { id: input.id }, + data: { completed: input.completed }, + }); + } catch (error) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Todo not found", + }); + } + }), + + delete: publicProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input }) => { + try { + return await prisma.todo.delete({ + where: { id: input.id }, + }); + } catch (error) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Todo not found", + }); + } + }), +}); diff --git a/apps/cli/template/with-prisma-sqlite/packages/server/src/routers/todo.ts b/apps/cli/template/with-prisma-sqlite/packages/server/src/routers/todo.ts new file mode 100644 index 0000000..5f2d3f9 --- /dev/null +++ b/apps/cli/template/with-prisma-sqlite/packages/server/src/routers/todo.ts @@ -0,0 +1,55 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import prisma from "../../prisma"; +import { publicProcedure, router } from "../lib/trpc"; + +export const todoRouter = router({ + getAll: publicProcedure.query(async () => { + return await prisma.todo.findMany({ + orderBy: { + id: "asc" + } + }); + }), + + create: publicProcedure + .input(z.object({ text: z.string().min(1) })) + .mutation(async ({ input }) => { + return await prisma.todo.create({ + data: { + text: input.text, + }, + }); + }), + + toggle: publicProcedure + .input(z.object({ id: z.number(), completed: z.boolean() })) + .mutation(async ({ input }) => { + try { + return await prisma.todo.update({ + where: { id: input.id }, + data: { completed: input.completed }, + }); + } catch (error) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Todo not found", + }); + } + }), + + delete: publicProcedure + .input(z.object({ id: z.number() })) + .mutation(async ({ input }) => { + try { + return await prisma.todo.delete({ + where: { id: input.id }, + }); + } catch (error) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Todo not found", + }); + } + }), +});