/** biome-ignore-all lint/a11y/noStaticElementInteractions: yes maam */ "use client"; import { FileSearch2, RefreshCw, TabletSmartphone, X } from "lucide-react"; import { type ComponentProps, useEffect, useRef, useState } from "react"; import { b2sClient } from "@/app/(ui)/admin/cdn/table"; import { VirtualizedComboBox } from "@/components/combobox"; import { useFormContext } from "@/components/form"; import { SimpleTooltip } from "@/components/tooltip"; import { Button } from "@/components/ui/button"; import type { Input } from "@/components/ui/input"; import { InputGroup, InputGroupAddon, InputGroupInput, } from "@/components/ui/input-group"; import { Kbd } from "@/components/ui/kbd"; import { Label } from "@/components/ui/label"; import { MultiSelect, MultiSelectContent, MultiSelectGroup, MultiSelectItem, MultiSelectTrigger, MultiSelectValue, } from "@/components/ui/multi-select"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; import { ActionButton } from "@/components/action-button"; import { retrieveMobileUpload, startMobileUpload } from "./api"; import { sse } from "@/lib/db/sse-endpoints"; import { Dialog, DialogContent, DialogHeader } from "@/components/ui/dialog"; import QRCode from "react-qr-code"; import { Status, StatusIndicator } from "@/components/ui/status"; export function CurrencyInput(props: ComponentProps) { return ( ฿ ); } export function PriceInput() { const { values, setValue } = useFormContext(); return (
setValue("price", e.target.value)} /> บาท
); } export function ServiceSelect({ types, onValueChange, value = "", }: { types: { id: string; display: string; price: number }[]; onValueChange?: (v: string) => void; value?: string; }) { return ( onValueChange?.(v.filter(Boolean).join(","))} > {types.map((t) => ( {t.display}{" "} {t.price} บาท ))} ); } export function SlipUpload({ value: externalValue, onValueChange, }: { value?: File | null | undefined; onValueChange?: (value: File | null) => void; }) { const ref = useRef(null); const [value, setValue] = useState(externalValue); const [muUploadKey, setMuUploadKey] = useState(); const [muState, setMuState] = useState<"waiting" | "connected" | "completed">( "waiting", ); const [muReject, setMuReject] = useState<(reason?: any) => void>(() => {}); const isSelected = !!value?.name; useEffect(() => { if ( typeof externalValue === "object" && externalValue && Object.keys(externalValue).length === 0 ) // ignore {} return; setValue(externalValue); }, [externalValue]); function choose() { ref.current?.click(); } async function mobileUpload() { setMuState("waiting"); // 1. create sync entry const { trackingKey, accessKey, uploadKey } = await startMobileUpload(); // 2. watch for slip_sync.complete for that tracking ID let completedResolve: () => void; const completedPromise = new Promise( (r, j) => ((completedResolve = r), setMuReject(() => j)), ); const ev = sse.slip_sync.subMany({ complete(tid) { if (trackingKey !== tid) return; setMuState("completed"); completedResolve(); }, connected(tid) { console.log(tid, trackingKey); if (trackingKey !== tid) return; setMuState("connected"); }, }); // 3. show qr/share dialog setMuUploadKey(uploadKey); // window.open(`http://localhost:3000/donate/slip/${uploadKey}`); try { // 4. user open link on mobile // 5. user upload image // 6. image updates the sync entry and trigger slip_sync.complete await completedPromise; // 7. pc client fetch image and delete the sync entry const fd = await retrieveMobileUpload(accessKey); const f = fd.get("file") as File; const file = new File([f], fd.get("name") as string, { type: f.type, lastModified: f.lastModified, }); // 8. pc client stop watch ev.clean(); // 9. close dialog and set slip input? setMuUploadKey(undefined); console.log(file); setValue(file); onValueChange?.(file); } catch (e) { console.error(e); setMuUploadKey(undefined); ev.clean(); throw e; } } return (
{ const file = ref.current?.files?.[0] || null; setValue(file); onValueChange?.(file); }} /> state || muReject?.("user abort")} > อัพโหลดสลิปจากมือถือ
สแกนด้วยโทรศัพท์เพื่อเลือกสลิปจาก Gallery และอัพโหลดโดยตรงมาที่นี่ {muState === "waiting" ? ( <> กำลังรอการเชื่อมต่อ ) : muState === "connected" ? ( <> เชื่อมต่อแล้ว กำลังรออัพโหลดรูป ) : ( <> กำลังดาวน์โหลดเข้าเครื่องนี )}
); } export function UserSelect({ value, onValueChange, }: { value?: string; onValueChange?: (value: string) => void; }) { const [loading, setLoading] = useState(false); const [users, setUsers] = useState< { username: string; uid: string; display: string }[] >([]); const [bump, setBump] = useState(0); useEffect(() => { setLoading(true); fetch("/api/discord/users") .then((r) => r.json()) .then(setUsers) .finally(() => setLoading(false)); }, [bump]); return (
({ label: ( <> {e.display} {e.username} ), context: [e.display, e.username], value: e.uid, }))} className="grow bg-transparent! hover:bg-accent!" />
); }