diff --git a/app/admin/form/[id]/layout.tsx b/app/admin/form/[id]/layout.tsx index beef327..9e51ed5 100644 --- a/app/admin/form/[id]/layout.tsx +++ b/app/admin/form/[id]/layout.tsx @@ -6,6 +6,8 @@ import { authOptions } from "@/lib/auth-options"; import { getSessionDiscordId } from "@/lib/auth"; import { getDiscordMemberProfile } from "@/lib/discord"; import { db } from "@/db"; +import { submissions } from "@/db/schema"; +import { desc, eq } from "drizzle-orm"; import { notFound } from "next/navigation"; import { isFormAcceptingSubmissions } from "@/lib/form-availability"; @@ -33,6 +35,16 @@ export default async function AdminFormLayout( if (!form) { notFound(); } + + const responseItems = await db + .select({ + id: submissions.id, + userName: submissions.userName, + userDiscordId: submissions.userDiscordId, + }) + .from(submissions) + .where(eq(submissions.formId, params.id)) + .orderBy(desc(submissions.submittedAt), desc(submissions.id)); let profile = null; const discordId = getSessionDiscordId(session); @@ -46,6 +58,7 @@ export default async function AdminFormLayout( setUnavailable(true)} /> + + + - + Image answer {label} setUnavailable(true)} /> + + + ); diff --git a/app/admin/form/[id]/result/page.tsx b/app/admin/form/[id]/result/page.tsx index 865778e..0e36664 100644 --- a/app/admin/form/[id]/result/page.tsx +++ b/app/admin/form/[id]/result/page.tsx @@ -141,7 +141,6 @@ export default async function ResultsListPage({ Respondent Submitted - Status Note Actions @@ -149,9 +148,6 @@ export default async function ResultsListPage({ {rows.map((submission) => { const detailHref = `/admin/form/${id}/result/${submission.id}${listQuery ? `?return=${encodeURIComponent(listQuery)}` : ""}`; - const edited = - Array.isArray(submission.editHistory) && - submission.editHistory.length > 0; return ( @@ -177,11 +173,6 @@ export default async function ResultsListPage({ )} {formatDate(submission.submittedAt)} - - - {edited ? "Edited" : "Original"} - - {submission.adminNote ? (

diff --git a/app/form/client.tsx b/app/form/client.tsx index 6c48c3e..905ed0e 100644 --- a/app/form/client.tsx +++ b/app/form/client.tsx @@ -1,7 +1,8 @@ "use client"; import React, { useRef, useState, useEffect, useMemo } from "react"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; +import Link from "next/link"; import { Card, CardContent, @@ -11,6 +12,7 @@ import { CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; +import { ShieldCheck } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -61,17 +63,33 @@ export function FormLayoutClient({ userName, userImage, profile, + isAdmin, }: { userName: string; userImage: string; profile?: DiscordMemberProfile | null; + isAdmin: boolean; }) { + const pathname = usePathname(); + const formId = pathname.match(/^\/form\/([^/]+)/)?.[1]; + const adminHref = formId ? `/admin/form/${formId}` : "/admin"; + return ( - + <> + {isAdmin && ( + + )} + + ); } diff --git a/app/form/layout.tsx b/app/form/layout.tsx index 7855051..6ff420b 100644 --- a/app/form/layout.tsx +++ b/app/form/layout.tsx @@ -6,6 +6,7 @@ import { ThemeToggle } from "@/components/theme-toggle"; import { getSessionDiscordId } from "@/lib/auth"; import { getDiscordMemberProfile } from "@/lib/discord"; import Link from 'next/link'; +import { getAdminDiscordIds } from "@/lib/auth"; const siteUrl = process.env.BASE_URL; @@ -37,6 +38,7 @@ export default async function FormLayout({ }) { const session = await getServerSession(authOptions); const discordId = getSessionDiscordId(session); + const isAdmin = Boolean(discordId && getAdminDiscordIds().includes(discordId)); let profile = null; if (discordId) { @@ -57,6 +59,7 @@ export default async function FormLayout({ userName={session.user.name ?? ""} userImage={session.user.image ?? ""} profile={profile} + isAdmin={isAdmin} /> ) : ( diff --git a/app/page.tsx b/app/page.tsx index 0887c90..06f74bd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,6 +8,12 @@ import { headers } from "next/headers"; import { connection } from "next/server"; import { Users } from "lucide-react"; import { Kbd } from "@/components/ui/kbd"; +import { Button } from "@/components/ui/button"; +import { ShieldCheck } from "lucide-react"; +import Link from "next/link"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth-options"; +import { getAdminDiscordIds, getSessionDiscordId } from "@/lib/auth"; import Background from "@/public/background/space.webp"; export default async function Home() { @@ -40,6 +46,9 @@ export default async function Home() { // Use configured profile data. const profileName = config.profile.name; const profileAvatar = config.profile.avatar; + const session = await getServerSession(authOptions); + const discordId = getSessionDiscordId(session); + const isAdmin = Boolean(discordId && getAdminDiscordIds().includes(discordId)); const links1 = [...config.links]; @@ -99,6 +108,16 @@ export default async function Home() { + {isAdmin && ( +

+ +
+ )} ); diff --git a/components/form-admin-sidebar.tsx b/components/form-admin-sidebar.tsx index 7b6a61c..4bcc7ae 100644 --- a/components/form-admin-sidebar.tsx +++ b/components/form-admin-sidebar.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState } from "react"; import { usePathname } from "next/navigation"; import { Sidebar, @@ -10,12 +11,18 @@ import { 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, @@ -28,6 +35,7 @@ import { } 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, @@ -36,6 +44,7 @@ export function FormAdminSidebar({ userName, userImage, profile, + responseItems, }: { formId: string; formTitle: string; @@ -43,8 +52,15 @@ export function FormAdminSidebar({ 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`) @@ -52,6 +68,7 @@ export function FormAdminSidebar({ : pathname.startsWith(`/admin/form/${formId}/preview`) ? "Preview" : "Editor"; + const items = [ { href: `/admin/form/${formId}`, @@ -137,6 +154,50 @@ export function FormAdminSidebar({ {label} + {label === "Responses" && responseList.length > 0 && ( + setResponsesOpen((open) => !open)} + aria-label={`${responsesOpen ? "Collapse" : "Expand"} responses`} + aria-expanded={responsesOpen} + > + + + )} + {label === "Responses" && + responsesOpen && + responseList.length > 0 && ( + + + {responseList.map((response) => { + const responseHref = `/admin/form/${formId}/result/${response.id}`; + const name = + response.userName || + response.userDiscordId || + "Anonymous"; + + return ( + + + + + {name} + + + + + ); + })} + + + )} ))}