From 3d13f140b9b4613e28b0f018100570b185279595 Mon Sep 17 00:00:00 2001 From: Yentec Date: Tue, 19 May 2026 11:36:44 +0200 Subject: [PATCH 01/10] feat(settings): add board settings page with slug and visibility --- app/(dashboard)/settings/page.tsx | 32 ++++++++ components/board/settings-form.tsx | 126 +++++++++++++++++++++++++++++ components/ui/switch.tsx | 33 ++++++++ lib/validators/boards.ts | 15 ++++ package.json | 2 +- server/actions/boards.ts | 73 +++++++++++++++++ 6 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 app/(dashboard)/settings/page.tsx create mode 100644 components/board/settings-form.tsx create mode 100644 components/ui/switch.tsx create mode 100644 lib/validators/boards.ts create mode 100644 server/actions/boards.ts diff --git a/app/(dashboard)/settings/page.tsx b/app/(dashboard)/settings/page.tsx new file mode 100644 index 0000000..8ef4c6e --- /dev/null +++ b/app/(dashboard)/settings/page.tsx @@ -0,0 +1,32 @@ +import { redirect } from "next/navigation"; +import { auth } from "@/auth"; +import { getBoardByOwner } from "@/server/queries/boards"; +import { SettingsForm } from "@/components/board/settings-form"; + +export default async function SettingsPage() { + const session = await auth(); + if (!session?.user?.id) redirect("/login"); + + const board = await getBoardByOwner(session.user.id); + if (!board) redirect("/login"); + + return ( +
+
+

Settings

+

+ Configure how your public board appears to visitors. +

+
+ + +
+ ); +} diff --git a/components/board/settings-form.tsx b/components/board/settings-form.tsx new file mode 100644 index 0000000..d8b6852 --- /dev/null +++ b/components/board/settings-form.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Switch } from "@/components/ui/switch"; +import { Card, CardContent } from "@/components/ui/card"; +import { updateBoard } from "@/server/actions/boards"; + +type Props = { + initial: { name: string; description: string; slug: string; isPublic: boolean }; +}; + +export function SettingsForm({ initial }: Props) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const [form, setForm] = useState(initial); + const [errors, setErrors] = useState>({}); + + const dirty = + form.name !== initial.name || + form.description !== initial.description || + form.slug !== initial.slug || + form.isPublic !== initial.isPublic; + + function handleSubmit() { + setErrors({}); + startTransition(async () => { + const result = await updateBoard({ + name: form.name, + description: form.description || null, + slug: form.slug, + isPublic: form.isPublic, + }); + if (!result.ok) { + if (result.issues) setErrors(result.issues); + toast.error(result.error); + return; + } + toast.success("Settings saved"); + if (result.data.slug !== initial.slug) { + router.refresh(); + } + }); + } + + return ( + + +
+ + setForm({ ...form, name: e.target.value })} + maxLength={60} + /> + {errors["name"] &&

{errors["name"][0]}

} +
+ +
+ +