feat : update
This commit is contained in:
@@ -12,7 +12,7 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateForm } from "@/app/admin/form/actions";
|
import { updateForm } from "@/app/(ui)/admin/form/actions";
|
||||||
import {
|
import {
|
||||||
createQuestion,
|
createQuestion,
|
||||||
updateQuestion,
|
updateQuestion,
|
||||||
@@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { testDiscordWebhook, updateForm } from "@/app/admin/form/actions";
|
import { testDiscordWebhook, updateForm } from "@/app/(ui)/admin/form/actions";
|
||||||
import { Loader2, ChevronDown, Send } from "lucide-react";
|
import { Loader2, ChevronDown, Send } from "lucide-react";
|
||||||
import { getGuildRoles } from "@/app/actions/discord";
|
import { getGuildRoles } from "@/app/actions/discord";
|
||||||
import type { DiscordRole } from "@/lib/discord/discord";
|
import type { DiscordRole } from "@/lib/discord/discord";
|
||||||
@@ -45,7 +45,7 @@ export default async function AdminFormLayout(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<div className="flex min-h-svh w-full bg-background text-foreground">
|
<div className="flex min-h-svh w-full bg-transparent text-foreground">
|
||||||
<FormAdminSidebar
|
<FormAdminSidebar
|
||||||
formId={params.id}
|
formId={params.id}
|
||||||
formTitle={form.title}
|
formTitle={form.title}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { FormFillerClient } from "@/app/form/client";
|
import { FormFillerClient } from "@/app/(ui)/form/client";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
+1
-1
@@ -42,7 +42,7 @@ import { stripHtml } from "@/lib/utils";
|
|||||||
import { HtmlDisplay } from "@/components/html-display";
|
import { HtmlDisplay } from "@/components/html-display";
|
||||||
import { formatStoredAnswer } from "@/lib/form/form-answer-values";
|
import { formatStoredAnswer } from "@/lib/form/form-answer-values";
|
||||||
import { normalizeStoredAnswerSnapshot } from "@/lib/form/submission-intake";
|
import { normalizeStoredAnswerSnapshot } from "@/lib/form/submission-intake";
|
||||||
import { updateSubmissionAdminNote } from "@/app/admin/form/actions";
|
import { updateSubmissionAdminNote } from "@/app/(ui)/admin/form/actions";
|
||||||
import {
|
import {
|
||||||
useDebouncedAutoSave,
|
useDebouncedAutoSave,
|
||||||
} from "@/hooks/use-debounced-autosave";
|
} from "@/hooks/use-debounced-autosave";
|
||||||
+1
-1
@@ -40,7 +40,7 @@ import { GuardedLink } from "@/components/guarded-link";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { getResponseNavigation } from "@/app/admin/form/actions";
|
import { getResponseNavigation } from "@/app/(ui)/admin/form/actions";
|
||||||
|
|
||||||
type ResponseNavigationData = Awaited<ReturnType<typeof getResponseNavigation>>;
|
type ResponseNavigationData = Awaited<ReturnType<typeof getResponseNavigation>>;
|
||||||
|
|
||||||
@@ -2,8 +2,33 @@
|
|||||||
|
|
||||||
import { useState, type ReactNode } from "react";
|
import { useState, type ReactNode } from "react";
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||||
|
import { Toaster } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { CardContent } from "@/components/ui/card";
|
import { CardContent } from "@/components/ui/card";
|
||||||
|
import { PageProgress } from "@/components/page-progress";
|
||||||
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
import { VersionCheck } from "@/components/version-check";
|
||||||
|
|
||||||
|
export function UiProviders({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageProgress />
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="dark"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Toaster richColors closeButton position="bottom-right" theme="dark" />
|
||||||
|
<VersionCheck />
|
||||||
|
{children}
|
||||||
|
</TooltipProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function LinkCarousel({
|
export function LinkCarousel({
|
||||||
primary,
|
primary,
|
||||||
@@ -47,7 +47,7 @@ export default async function FormLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex min-h-screen w-full flex-1 flex-col overflow-x-hidden bg-background text-foreground">
|
<div className="relative flex min-h-svh w-full flex-1 flex-col overflow-x-hidden">
|
||||||
<header className="relative z-10 flex shrink-0 items-center justify-between border-b border-border bg-background/90 px-6 py-3 backdrop-blur">
|
<header className="relative z-10 flex shrink-0 items-center justify-between border-b border-border bg-background/90 px-6 py-3 backdrop-blur">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<span className="font-semibold text-sm">
|
<span className="font-semibold text-sm">
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import Background from "@/public/background/space.webp";
|
||||||
|
import { UiProviders } from "./client";
|
||||||
|
|
||||||
|
export default function UiLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<UiProviders>
|
||||||
|
<div className="relative isolate min-h-svh overflow-x-clip text-foreground">
|
||||||
|
<Image
|
||||||
|
src={Background}
|
||||||
|
alt=""
|
||||||
|
priority
|
||||||
|
sizes="100vw"
|
||||||
|
className="pointer-events-none fixed inset-0 -z-20 size-full object-cover opacity-40"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none fixed inset-0 -z-10 bg-background/40"
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</UiProviders>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ export default function LeaderboardLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen overflow-hidden bg-background text-foreground">
|
<main className="relative min-h-svh overflow-hidden">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -8,7 +8,6 @@ import { Finger_Paint } from "next/font/google";
|
|||||||
import { connection } from "next/server";
|
import { connection } from "next/server";
|
||||||
import { getFollowerCounts } from "@/lib/followers";
|
import { getFollowerCounts } from "@/lib/followers";
|
||||||
import { Share2, Trophy } from "lucide-react";
|
import { Share2, Trophy } from "lucide-react";
|
||||||
import Background from "@/public/background/space.webp";
|
|
||||||
import {
|
import {
|
||||||
formatCount,
|
formatCount,
|
||||||
getCountForLink,
|
getCountForLink,
|
||||||
@@ -128,18 +127,11 @@ export default async function Home() {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="relative flex min-h-screen flex-col items-center justify-center bg-background p-4 text-foreground">
|
<main className="relative flex min-h-svh flex-col items-center justify-center p-4">
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
/>
|
/>
|
||||||
<Image
|
|
||||||
src={Background}
|
|
||||||
alt="Background"
|
|
||||||
fill
|
|
||||||
priority
|
|
||||||
className="fixed top-0 left-0 z-0 h-fit min-h-dvh w-full object-cover opacity-40"
|
|
||||||
/>
|
|
||||||
<Card className="relative z-10 w-full max-w-sm bg-card/90 backdrop-blur md:max-w-lg" role="article">
|
<Card className="relative z-10 w-full max-w-sm bg-card/90 backdrop-blur md:max-w-lg" role="article">
|
||||||
<CardHeader className="flex flex-col items-center gap-4 text-center">
|
<CardHeader className="flex flex-col items-center gap-4 text-center">
|
||||||
<div className="relative w-24 h-24 overflow-hidden rounded-full">
|
<div className="relative w-24 h-24 overflow-hidden rounded-full">
|
||||||
@@ -13,7 +13,7 @@ export const metadata: Metadata = {
|
|||||||
|
|
||||||
export default function PrivacyPolicyPage() {
|
export default function PrivacyPolicyPage() {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen flex flex-col items-center p-4 md:p-8">
|
<main className="flex flex-col items-center p-4 md:p-8">
|
||||||
<div className="w-full max-w-2xl space-y-6">
|
<div className="w-full max-w-2xl space-y-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
@@ -9,7 +9,7 @@ export default function ProfileLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen overflow-hidden bg-background text-foreground">
|
<main className="relative min-h-svh overflow-hidden">
|
||||||
<div className="relative mx-auto flex w-full max-w-5xl flex-col gap-6 px-4 py-6 sm:px-6 lg:py-10">
|
<div className="relative mx-auto flex w-full max-w-5xl flex-col gap-6 px-4 py-6 sm:px-6 lg:py-10">
|
||||||
<nav aria-label="ทางลัด" className="flex items-center justify-between gap-3">
|
<nav aria-label="ทางลัด" className="flex items-center justify-between gap-3">
|
||||||
<Button variant="ghost" asChild>
|
<Button variant="ghost" asChild>
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
CopyShareActions,
|
CopyShareActions,
|
||||||
DeleteCommentButton,
|
DeleteCommentButton,
|
||||||
DeleteShareButton,
|
DeleteShareButton,
|
||||||
} from "@/app/share/[id]/client";
|
} from "./client";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { ArrowLeft, List, Plus } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { DropdownMenuAvatar } from "@/components/avatar";
|
import { DropdownMenuAvatar } from "@/components/avatar";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
@@ -22,7 +22,7 @@ export default async function ShareLayout({ children }: { children: React.ReactN
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background text-foreground">
|
<div className="min-h-svh">
|
||||||
<header className="sticky top-0 z-10 border-b bg-background/90 backdrop-blur">
|
<header className="sticky top-0 z-10 border-b bg-background/90 backdrop-blur">
|
||||||
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-4 sm:px-6">
|
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-4 sm:px-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -2,7 +2,7 @@ import { getServerSession } from "next-auth";
|
|||||||
import { LoginDialog } from "@/components/login-dialog";
|
import { LoginDialog } from "@/components/login-dialog";
|
||||||
import { authOptions } from "@/lib/auth/auth-options";
|
import { authOptions } from "@/lib/auth/auth-options";
|
||||||
import { getSessionDiscordId } from "@/lib/auth/auth";
|
import { getSessionDiscordId } from "@/lib/auth/auth";
|
||||||
import { ShareUploadClient } from "@/app/share/client";
|
import { ShareUploadClient } from "./client";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ export const metadata: Metadata = {
|
|||||||
|
|
||||||
export default function TermsOfServicePage() {
|
export default function TermsOfServicePage() {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen flex flex-col items-center p-4 md:p-8">
|
<main className="flex flex-col items-center p-4 md:p-8">
|
||||||
<div className="w-full max-w-2xl space-y-6">
|
<div className="w-full max-w-2xl space-y-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
+2
-26
@@ -1,7 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Anuphan, Inter } from "next/font/google";
|
import { Anuphan, Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { config } from "@/lib/config";
|
import { config } from "@/lib/config";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
@@ -65,12 +64,6 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
||||||
import { Toaster } from "sonner";
|
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
|
||||||
import { PageProgress } from "@/components/page-progress";
|
|
||||||
import { VersionCheck } from "@/components/version-check";
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -79,28 +72,11 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="th"
|
lang="th"
|
||||||
className={cn(
|
className={`dark h-full antialiased ${inter.variable} ${anuphan.variable}`}
|
||||||
"h-full",
|
|
||||||
"antialiased",
|
|
||||||
inter.variable,
|
|
||||||
anuphan.variable,
|
|
||||||
)}
|
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body className="font-app" suppressHydrationWarning>
|
<body className="font-app" suppressHydrationWarning>
|
||||||
<PageProgress />
|
{children}
|
||||||
<ThemeProvider
|
|
||||||
attribute="class"
|
|
||||||
defaultTheme="dark"
|
|
||||||
enableSystem
|
|
||||||
disableTransitionOnChange
|
|
||||||
>
|
|
||||||
<TooltipProvider>
|
|
||||||
<Toaster richColors closeButton position="bottom-right" theme="dark" />
|
|
||||||
<VersionCheck />
|
|
||||||
{children}
|
|
||||||
</TooltipProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -99,9 +99,13 @@ export function AdminShell({
|
|||||||
}) {
|
}) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
if (/^\/admin\/form\/[^/]+/.test(pathname)) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<div className="flex min-h-screen w-full bg-background text-foreground">
|
<div className="flex min-h-svh w-full bg-transparent text-foreground">
|
||||||
<Sidebar variant="sidebar" collapsible="icon">
|
<Sidebar variant="sidebar" collapsible="icon">
|
||||||
<SidebarHeader className="border-b border-border py-4 px-4">
|
<SidebarHeader className="border-b border-border py-4 px-4">
|
||||||
<Link href="/admin">
|
<Link href="/admin">
|
||||||
@@ -157,7 +161,7 @@ export function AdminShell({
|
|||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<SidebarInset>
|
<SidebarInset className="bg-transparent">
|
||||||
<div className="md:hidden p-2">
|
<div className="md:hidden p-2">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Trash2, Loader2 } from "lucide-react";
|
import { Trash2, Loader2 } from "lucide-react";
|
||||||
import { deleteForm } from "@/app/admin/form/actions";
|
import { deleteForm } from "@/app/(ui)/admin/form/actions";
|
||||||
import { deleteSubmission, deleteAllSubmissions } from "@/app/actions/submissions";
|
import { deleteSubmission, deleteAllSubmissions } from "@/app/actions/submissions";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ describe("share UI contracts", () => {
|
|||||||
test("keeps the extracted login dialog non-dismissible and shared", async () => {
|
test("keeps the extracted login dialog non-dismissible and shared", async () => {
|
||||||
const [dialog, sharePage, formPage] = await Promise.all([
|
const [dialog, sharePage, formPage] = await Promise.all([
|
||||||
Bun.file(new URL("../../components/login-dialog.tsx", import.meta.url)).text(),
|
Bun.file(new URL("../../components/login-dialog.tsx", import.meta.url)).text(),
|
||||||
Bun.file(new URL("../../app/share/page.tsx", import.meta.url)).text(),
|
Bun.file(new URL("../../app/(ui)/share/page.tsx", import.meta.url)).text(),
|
||||||
Bun.file(new URL("../../app/form/[id]/page.tsx", import.meta.url)).text(),
|
Bun.file(new URL("../../app/(ui)/form/[id]/page.tsx", import.meta.url)).text(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(dialog).toContain("เข้าสู่ระบบก่อนกรอกแบบฟอร์ม");
|
expect(dialog).toContain("เข้าสู่ระบบก่อนกรอกแบบฟอร์ม");
|
||||||
|
|||||||
Reference in New Issue
Block a user