225 lines
7.1 KiB
TypeScript
225 lines
7.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { usePathname } from "next/navigation";
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarGroup,
|
|
SidebarGroupContent,
|
|
SidebarGroupLabel,
|
|
SidebarHeader,
|
|
SidebarMenu,
|
|
SidebarMenuAction,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarMenuSub,
|
|
SidebarMenuSubButton,
|
|
SidebarMenuSubItem,
|
|
} from "@/components/ui/sidebar";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import {
|
|
ArrowLeft,
|
|
ClipboardList,
|
|
ChevronRight,
|
|
Eye,
|
|
FilePenLine,
|
|
LayoutDashboard,
|
|
Settings,
|
|
} from "lucide-react";
|
|
import { ThemeToggle } from "@/components/theme-toggle";
|
|
import {
|
|
DropdownMenuAvatar,
|
|
type DiscordMemberProfile,
|
|
} from "@/components/dropdown-menu-avatar";
|
|
import { GuardedLink } from "@/components/guarded-link";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export function FormAdminSidebar({
|
|
formId,
|
|
formTitle,
|
|
isOpen,
|
|
userName,
|
|
userImage,
|
|
profile,
|
|
responseItems,
|
|
}: {
|
|
formId: string;
|
|
formTitle: string;
|
|
isOpen: boolean;
|
|
userName?: string;
|
|
userImage?: string;
|
|
profile?: DiscordMemberProfile | null;
|
|
responseItems?: {
|
|
id: string;
|
|
userName: string | null;
|
|
userDiscordId: string | null;
|
|
}[];
|
|
}) {
|
|
const pathname = usePathname();
|
|
const responseList = responseItems ?? [];
|
|
const [responsesOpen, setResponsesOpen] = useState(true);
|
|
const currentSection = pathname.startsWith(`/admin/form/${formId}/result`)
|
|
? "Responses"
|
|
: pathname.startsWith(`/admin/form/${formId}/extra`)
|
|
? "Settings"
|
|
: pathname.startsWith(`/admin/form/${formId}/preview`)
|
|
? "Preview"
|
|
: "Editor";
|
|
|
|
const items = [
|
|
{
|
|
href: `/admin/form/${formId}`,
|
|
label: "Editor",
|
|
icon: LayoutDashboard,
|
|
active: pathname === `/admin/form/${formId}`,
|
|
},
|
|
{
|
|
href: `/admin/form/${formId}/result`,
|
|
label: "Responses",
|
|
icon: ClipboardList,
|
|
active: pathname.startsWith(`/admin/form/${formId}/result`),
|
|
},
|
|
{
|
|
href: `/admin/form/${formId}/extra`,
|
|
label: "Settings",
|
|
icon: Settings,
|
|
active: pathname.startsWith(`/admin/form/${formId}/extra`),
|
|
},
|
|
{
|
|
href: `/admin/form/${formId}/preview`,
|
|
label: "Preview",
|
|
icon: Eye,
|
|
active: pathname.startsWith(`/admin/form/${formId}/preview`),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<Sidebar variant="sidebar" collapsible="icon">
|
|
<SidebarHeader className="border-b p-2">
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton
|
|
asChild
|
|
size="lg"
|
|
tooltip={`${formTitle} · ${isOpen ? "Open" : "Closed"}`}
|
|
className="h-auto min-h-14"
|
|
>
|
|
<GuardedLink
|
|
href={`/admin/form/${formId}`}
|
|
aria-label={`${formTitle}, ${isOpen ? "open" : "closed"}. Open editor`}
|
|
>
|
|
<FilePenLine />
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<span className="min-w-0 flex-1 truncate font-medium">
|
|
{formTitle}
|
|
</span>
|
|
<Badge
|
|
variant={isOpen ? "default" : "secondary"}
|
|
className="shrink-0"
|
|
>
|
|
{isOpen ? "Open" : "Closed"}
|
|
</Badge>
|
|
</div>
|
|
<span className="mt-1 block text-xs text-muted-foreground">
|
|
{currentSection}
|
|
</span>
|
|
</div>
|
|
</GuardedLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild>
|
|
<GuardedLink href="/admin/form">
|
|
<ArrowLeft />
|
|
<span>All forms</span>
|
|
</GuardedLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Workspace</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{items.map(({ href, label, icon: Icon, active }) => (
|
|
<SidebarMenuItem key={href}>
|
|
<SidebarMenuButton asChild isActive={active}>
|
|
<GuardedLink href={href}>
|
|
<Icon />
|
|
<span>{label}</span>
|
|
</GuardedLink>
|
|
</SidebarMenuButton>
|
|
{label === "Responses" && responseList.length > 0 && (
|
|
<SidebarMenuAction
|
|
onClick={() => setResponsesOpen((open) => !open)}
|
|
aria-label={`${responsesOpen ? "Collapse" : "Expand"} responses`}
|
|
aria-expanded={responsesOpen}
|
|
>
|
|
<ChevronRight
|
|
className={cn(
|
|
"transition-transform",
|
|
responsesOpen && "rotate-90"
|
|
)}
|
|
/>
|
|
</SidebarMenuAction>
|
|
)}
|
|
{label === "Responses" &&
|
|
responsesOpen &&
|
|
responseList.length > 0 && (
|
|
<ScrollArea className="max-h-80">
|
|
<SidebarMenuSub>
|
|
{responseList.map((response) => {
|
|
const responseHref = `/admin/form/${formId}/result/${response.id}`;
|
|
const name =
|
|
response.userName ||
|
|
response.userDiscordId ||
|
|
"Anonymous";
|
|
|
|
return (
|
|
<SidebarMenuSubItem key={response.id}>
|
|
<SidebarMenuSubButton
|
|
asChild
|
|
isActive={pathname === responseHref}
|
|
>
|
|
<GuardedLink href={responseHref}>
|
|
<span className="min-w-0 truncate">
|
|
{name}
|
|
</span>
|
|
</GuardedLink>
|
|
</SidebarMenuSubButton>
|
|
</SidebarMenuSubItem>
|
|
);
|
|
})}
|
|
</SidebarMenuSub>
|
|
</ScrollArea>
|
|
)}
|
|
</SidebarMenuItem>
|
|
))}
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
</SidebarContent>
|
|
<SidebarFooter className="border-t p-4">
|
|
<div className="flex items-center gap-1">
|
|
{(userName || profile) && (
|
|
<DropdownMenuAvatar
|
|
userName={userName || ""}
|
|
userImage={userImage || ""}
|
|
profile={profile || null}
|
|
showName
|
|
/>
|
|
)}
|
|
<div className="ml-auto">
|
|
<ThemeToggle />
|
|
</div>
|
|
</div>
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
);
|
|
}
|