feat: add MaybeWrap component and enhance artifact admin sidebar
MaybeWrap conditionally wraps children based on a boolean prop. Artifact sidebar now shows HoverCard with comment/UID for queue-less entries (donations) and a Bitcoin icon.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useProgress } from "@bprogress/next";
|
||||
import { Lock, Unlock } from "lucide-react";
|
||||
import { BitcoinIcon, Lock, Unlock } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -39,12 +39,25 @@ import { setLimit, toggleCheck, toggleLock } from "@/lib/api";
|
||||
import { shared } from "@/lib/comms";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MaybeWrap } from "@/components/action-button";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
|
||||
export function SidebarLink({
|
||||
submission,
|
||||
className,
|
||||
}: {
|
||||
submission: { id: string; name: string; checked: boolean; queue: number };
|
||||
submission: {
|
||||
id: string;
|
||||
name: string;
|
||||
checked: boolean;
|
||||
queue: number | null;
|
||||
comment: string;
|
||||
uid: string;
|
||||
};
|
||||
className?: string;
|
||||
}) {
|
||||
const { stop } = useProgress();
|
||||
@@ -56,32 +69,66 @@ export function SidebarLink({
|
||||
}, [submission.checked]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/artifact/admin/${submission.id}`}
|
||||
className={cn(
|
||||
className,
|
||||
id === submission.id && "bg-accent text-accent-foreground",
|
||||
<MaybeWrap
|
||||
wrap={submission.queue === null}
|
||||
wrapper={({ children }) => (
|
||||
<HoverCard openDelay={150} closeDelay={0}>
|
||||
<HoverCardTrigger
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(submission.uid);
|
||||
toast("คัดลอก UID แล้ว");
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
className="flex max-w-2xl flex-col gap-0.5"
|
||||
side="right"
|
||||
>
|
||||
<div className="font-semibold">{submission.name}</div>
|
||||
<div className="line-clamp-3">{submission.comment}</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
คิวลัด - UID ${submission.uid} (คลิ๊กเพื่อคัดลอก)
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)}
|
||||
prefetch
|
||||
onClick={(ev) => {
|
||||
if ((ev.target as HTMLButtonElement).type === "button") {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{submission.queue}. {submission.name}
|
||||
<SidebarMenuBadge className="pointer-events-auto">
|
||||
<Checkbox
|
||||
className="mr-2"
|
||||
checked={checked}
|
||||
onCheckedChange={async () => {
|
||||
setChecked((x) => !x);
|
||||
await toggleCheck(submission.id);
|
||||
stop();
|
||||
}}
|
||||
/>
|
||||
</SidebarMenuBadge>
|
||||
</Link>
|
||||
<Link
|
||||
href={
|
||||
submission.queue === null ? "" : `/artifact/admin/${submission.id}`
|
||||
}
|
||||
className={cn(
|
||||
className,
|
||||
id === submission.id && "bg-accent text-accent-foreground",
|
||||
)}
|
||||
prefetch
|
||||
onClick={(ev) => {
|
||||
if ((ev.target as HTMLButtonElement).type === "button") {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{submission.queue === null ? (
|
||||
<div className="flex items-center">
|
||||
<BitcoinIcon className="-ml-1" size={20} /> {submission.name}
|
||||
</div>
|
||||
) : (
|
||||
`${submission.queue}. ${submission.name}`
|
||||
)}
|
||||
<SidebarMenuBadge className="pointer-events-auto">
|
||||
<Checkbox
|
||||
className="mr-2"
|
||||
checked={checked}
|
||||
onCheckedChange={async () => {
|
||||
setChecked((x) => !x);
|
||||
await toggleCheck(submission.id);
|
||||
stop();
|
||||
}}
|
||||
/>
|
||||
</SidebarMenuBadge>
|
||||
</Link>
|
||||
</MaybeWrap>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,7 +239,14 @@ export function LimitManager({
|
||||
export function SubmissionList({
|
||||
subs,
|
||||
}: {
|
||||
subs: { id: string; name: string; checked: boolean; queue: number }[];
|
||||
subs: {
|
||||
id: string;
|
||||
name: string;
|
||||
checked: boolean;
|
||||
queue: number | null;
|
||||
comment: string;
|
||||
uid: string;
|
||||
}[];
|
||||
}) {
|
||||
const [query, setQuery] = useState("");
|
||||
return (
|
||||
|
||||
@@ -46,6 +46,8 @@ export default async function AdminLayout({
|
||||
name: submissions.name,
|
||||
checked: submissions.checked,
|
||||
queue: submissions.queue,
|
||||
comment: submissions.comment,
|
||||
uid: submissions.uid,
|
||||
})
|
||||
.from(submissions)
|
||||
.orderBy(submissions.queue);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { MaybePromise } from "bun";
|
||||
import { Check } from "lucide-react";
|
||||
import { ComponentProps, ReactNode, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function ActionButton({
|
||||
action,
|
||||
children,
|
||||
onClick,
|
||||
...props
|
||||
}: ComponentProps<typeof Button> & { action: () => MaybePromise<void> }) {
|
||||
const [state, setState] = useState<0 | 1 | 2>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (state !== 2) return;
|
||||
const tm = setTimeout(() => setState(0), 2000);
|
||||
return () => clearTimeout(tm);
|
||||
});
|
||||
return (
|
||||
<Button
|
||||
disabled={state > 0}
|
||||
{...props}
|
||||
onClick={async (ev) => {
|
||||
onClick?.(ev);
|
||||
setState(1);
|
||||
try {
|
||||
await action();
|
||||
setState(2);
|
||||
} catch (e) {
|
||||
toast.error("An error occured!");
|
||||
console.error(e);
|
||||
setState(0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{state === 0 ? children : state === 1 ? <Spinner /> : <Check />}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function MaybeWrap({
|
||||
wrap,
|
||||
wrapper,
|
||||
children,
|
||||
}: {
|
||||
wrap: boolean;
|
||||
wrapper: (props: { children?: ReactNode }) => ReactNode;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
return wrap ? wrapper({ children }) : children;
|
||||
}
|
||||
Reference in New Issue
Block a user