From 546bb6dfb28644b661556f376c670279c5714698 Mon Sep 17 00:00:00 2001 From: Bianca Date: Fri, 28 Aug 2026 22:49:52 +0200 Subject: [PATCH 1/2] Merge commit '4a244e7008e15b76e1e63e846880837a7b0c43ce' --- src/components/dashboard-navigation.ts | 2 + .../group-labels/group-label-card.tsx | 217 ++++++++++++++++++ .../group-labels/group-label-color-picker.tsx | 43 ++++ .../group-labels/group-labels-page.tsx | 167 ++++++++++++++ .../group-labels/group-labels.constants.ts | 81 +++++++ .../group-labels/group-labels.functions.ts | 48 ++++ .../group-labels/group-labels.validation.ts | 24 ++ src/features/group-labels/types.ts | 10 + src/routeTree.gen.ts | 21 ++ src/routes/dashboard/web/group-labels.tsx | 15 ++ 10 files changed, 628 insertions(+) create mode 100644 src/features/group-labels/group-label-card.tsx create mode 100644 src/features/group-labels/group-label-color-picker.tsx create mode 100644 src/features/group-labels/group-labels-page.tsx create mode 100644 src/features/group-labels/group-labels.constants.ts create mode 100644 src/features/group-labels/group-labels.functions.ts create mode 100644 src/features/group-labels/group-labels.validation.ts create mode 100644 src/features/group-labels/types.ts create mode 100644 src/routes/dashboard/web/group-labels.tsx diff --git a/src/components/dashboard-navigation.ts b/src/components/dashboard-navigation.ts index 8eb40fa..2a39a61 100644 --- a/src/components/dashboard-navigation.ts +++ b/src/components/dashboard-navigation.ts @@ -8,6 +8,7 @@ import { type LucideIcon, Settings, ShieldCheck, + Tags, Users, UsersRound, } from "lucide-react" @@ -63,6 +64,7 @@ export const dashboardNavigation = [ { title: "Associations", url: "/dashboard/web/associations", icon: Users }, { title: "Guides", url: "/dashboard/web/guides", icon: BookOpen }, { title: "FAQs", url: "/dashboard/web/faqs", icon: CircleQuestionMark }, + { title: "Group labels", url: "/dashboard/web/group-labels", icon: Tags }, ], }, ] as const satisfies readonly DashboardNavigationCategory[] diff --git a/src/features/group-labels/group-label-card.tsx b/src/features/group-labels/group-label-card.tsx new file mode 100644 index 0000000..be96694 --- /dev/null +++ b/src/features/group-labels/group-label-card.tsx @@ -0,0 +1,217 @@ +import { CircleDashed, LoaderCircle, Pencil, Save, Trash2, X } from "lucide-react" +import { useState } from "react" + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { cn } from "@/lib/utils" + +import { GroupLabelColorPicker } from "./group-label-color-picker" +import { GROUP_LABEL_DESCRIPTION_MAX, GROUP_LABEL_MAX, getGroupLabelColor } from "./group-labels.constants" +import type { GroupLabel, GroupLabelFormValues } from "./types" + +type GroupLabelCardProps = { + groupLabel: GroupLabel + draft: boolean + initialEditActive: boolean + onCancelDraft: () => void + onDelete: () => Promise + onSave: (values: GroupLabelFormValues) => Promise +} + +export function GroupLabelCard({ + groupLabel, + draft, + initialEditActive, + onCancelDraft, + onDelete, + onSave, +}: GroupLabelCardProps) { + const [label, setLabel] = useState(groupLabel.label) + const [editing, setEditing] = useState(initialEditActive) + const [color, setColor] = useState(groupLabel.color) + const [description, setDescription] = useState(groupLabel.description) + const [saving, setSaving] = useState(false) + const [deleting, setDeleting] = useState(false) + const [deleteOpen, setDeleteOpen] = useState(false) + const canSave = Boolean(label.trim()) + + function resetFields() { + setLabel(groupLabel.label) + setColor(groupLabel.color) + setDescription(groupLabel.description) + } + + function cancelEdit() { + if (draft) { + onCancelDraft() + return + } + resetFields() + setEditing(false) + } + + async function save() { + if (saving || !canSave) return + setSaving(true) + try { + const saved = await onSave({ label: label.trim(), color, description: description.trim() }) + if (saved) setEditing(false) + } finally { + setSaving(false) + } + } + + async function remove() { + setDeleting(true) + try { + if (await onDelete()) setDeleteOpen(false) + } finally { + setDeleting(false) + } + } + + const swatch = getGroupLabelColor(groupLabel.color) + + return ( + <> + + + + {editing ? ( +
+ {draft && ( + + Unsaved draft + + )} +
+ + setLabel(event.target.value)} + className="bg-background text-base font-medium" + maxLength={GROUP_LABEL_MAX} + required + autoFocus={draft} + /> +
+
+ ) : ( + {groupLabel.label} + )} +
+ + {editing ? ( + <> + + + + ) : ( + <> + + + + )} + +
+ + {editing ? ( +