"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { XIcon } from "lucide-react"; import { toast } from "sonner"; import { ArtifactSelect, CharacterSelect, WeaponSelect } from "@/components/admin/ui/catalog-select"; import { ConstellationSelect, RefinementSelect } from "@/components/admin/ui/option-select"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { FieldGroup } from "@/components/ui/field"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { commissionPrice, commissionRequestSchema, type CommissionRequest } from "@/lib/commission/request"; import { elementBackgroundColor } from "@/lib/catalog/element"; type Item = { key: string; name: string; aliases: string[]; imageUrl: string; rarity?: number | null; element?: string | null; weaponType?: string | null }; type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] }; const blankMember = (): CommissionRequest["teams"][number]["members"][number] => ({ characterKey: "", weaponKey: "", artifactKey: "", constellation: "0", refinement: "1", }); const blankTeam = () => ({ members: Array.from({ length: 4 }, blankMember) }); function requestError(request: CommissionRequest): string | null { const parsed = commissionRequestSchema.safeParse(request); if (parsed.success) return null; const path = parsed.error.issues[0]?.path; if (path?.[0] === "teams") { const teamNumber = Number(path[1]) + 1; const memberNumber = typeof path[3] === "number" ? path[3] + 1 : null; if (memberNumber && path[4] === "characterKey") return `ทีม ${teamNumber}: เลือกตัวละครที่ ${memberNumber}`; if (memberNumber && path[4] === "weaponKey") return `ทีม ${teamNumber}: เลือกอาวุธให้ตัวละครที่ ${memberNumber}`; if (memberNumber && path[4] === "artifactKey") return `ทีม ${teamNumber}: เลือกอาร์ติแฟกต์ให้ตัวละครที่ ${memberNumber}`; return `ทีม ${teamNumber}: เลือกตัวละคร 4 ตัวที่ไม่ซ้ำกัน พร้อมอาวุธและอาร์ติแฟกต์`; } if (path?.[0] === "weapons") return `ชุดเปรียบเทียบอาวุธ ${Number(path[1]) + 1}: เลือกตัวละครและอาวุธต่างกันอย่างน้อย 2 ชิ้น`; if (path?.[0] === "constellations") return `ชุดเปรียบเทียบกลุ่มดาว ${Number(path[1]) + 1}: เลือกตัวละครและกลุ่มดาวอย่างน้อย 1 ระดับ`; return "เพิ่มทีมหรือรายการเปรียบเทียบอย่างน้อยหนึ่งรายการ"; } function checkoutError(code: string | undefined, status: number): string { if (status === 401) return "เซสชันหมดอายุ กรุณาเข้าสู่ระบบอีกครั้ง"; if (status === 403) return "ไม่สามารถส่งคำขอจากหน้านี้ได้ กรุณาโหลดหน้าใหม่แล้วลองอีกครั้ง"; if (status === 429) return "ส่งคำขอบ่อยเกินไป กรุณารอสักครู่แล้วลองอีกครั้ง"; if (code === "invalid-promptpay-value" || code === "invalid-promptpay-type" || code === "COMMISSION_PROMPTPAY_VALUE-not-configured") return "ระบบยังตั้งค่าบัญชี PromptPay ไม่ถูกต้อง กรุณาแจ้งผู้ดูแลระบบ"; if (code === "invalid-commission-request") return "รายการที่เลือกไม่ตรงกับข้อมูลล่าสุด กรุณาโหลดหน้าใหม่แล้วเลือกอีกครั้ง"; return "สร้างคำขอไม่สำเร็จ กรุณาลองอีกครั้ง"; } export function CommissionRequestForm({ catalog }: { catalog: Catalog }) { const router = useRouter(); const [request, setRequest] = useState({ teams: [blankTeam()], weapons: [], constellations: [] }); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const patch = (next: Partial) => setRequest((old) => ({ ...old, ...next })); const compatibleWeapons = (characterKey: string) => { const type = catalog.characters.find((item) => item.key === characterKey)?.weaponType; return catalog.weapons.filter((item) => type && item.weaponType === type); }; async function submit() { const validationError = requestError(request); if (validationError) { setError(validationError); toast.error(validationError); return; } setBusy(true); setError(""); try { const response = await fetch("/api/commission/checkouts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request) }); if (!response.ok) { const body = await response.json().catch(() => null) as { error?: string } | null; throw new Error(checkoutError(body?.error, response.status)); } const { checkoutId } = await response.json(); router.push(`/commission/pay/${checkoutId}`); } catch (cause) { const message = cause instanceof Error ? cause.message : "สร้างคำขอไม่สำเร็จ"; setError(message); toast.error(message); } finally { setBusy(false); } } return
ทีม - ฿100 ต่อทีมเลือก 4 ตัวละคร พร้อมอาวุธและอาร์ติแฟกต์ของแต่ละตัว {request.teams.map((team, teamIndex) => ทีม {teamIndex + 1}
{team.members.map((member, memberIndex) => { const character = catalog.characters.find((item) => item.key === member.characterKey); return
#{memberIndex + 1}
{ const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex] = { ...blankMember(), characterKey }; patch({ teams }); }} />
{ const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex].weaponKey = weaponKey; teams[teamIndex].members[memberIndex].refinement = "1"; patch({ teams }); }} /> { const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex].artifactKey = artifactKey; patch({ teams }); }} />
{ const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex].constellation = constellation as typeof member.constellation; patch({ teams }); }} triggerProps={{ size: "sm", className: "h-6 min-w-0 flex-1 justify-center px-1 text-xs [&>svg]:hidden" }} /> { const teams = structuredClone(request.teams); teams[teamIndex].members[memberIndex].refinement = refinement as typeof member.refinement; patch({ teams }); }} triggerProps={{ size: "sm", className: "h-6 min-w-0 flex-1 justify-center px-1 text-xs [&>svg]:hidden" }} />

{character?.name ?? "เลือกตัวละคร"}

; })}
)}
เปรียบเทียบอาวุธ - ฿20 ต่ออาวุธเลือกตัวละครและอาวุธอย่างน้อย 2 ชิ้น หากไม่มีทีมจะมีค่าบริการพื้นฐาน ฿100 {request.weapons.map((set, index) =>
{set.weaponKeys.map((weaponKey, weaponIndex) => { return
!set.weaponKeys.includes(item.key) || item.key === weaponKey)} value={weaponKey} label={`เลือกอาวุธเปรียบเทียบชิ้นที่ ${weaponIndex + 1}`} placeholder={`อาวุธชิ้นที่ ${weaponIndex + 1}`} emptyLabel={set.characterKey ? "ไม่พบอาวุธ" : "เลือกตัวละครก่อน"} onValueChange={(next) => { const weapons = structuredClone(request.weapons); delete weapons[index].refinements?.[weaponKey]; weapons[index].weaponKeys[weaponIndex] = next; if (next) weapons[index].refinements = { ...weapons[index].refinements, [next]: "1" }; patch({ weapons }); }} />
{ const weapons = structuredClone(request.weapons); weapons[index].refinements = { ...weapons[index].refinements, [weaponKey]: refinement as "1" | "2" | "3" | "4" | "5" }; patch({ weapons }); }} triggerProps={{ size: "sm", className: "w-16 shrink-0 px-1 text-xs" }} />
; })}
{ const weapons = structuredClone(request.weapons); weapons[index] = { characterKey, weaponKeys: [""], refinements: {} }; patch({ weapons }); }} /> {set.characterKey ? : null}

item.key === set.characterKey)?.name}> {catalog.characters.find((item) => item.key === set.characterKey)?.name ?? "เลือกตัวละคร"}

)}
เปรียบเทียบกลุ่มดาว - ฿20 ต่อระดับเลือกตัวละครและกลุ่มดาวอย่างน้อย 1 ระดับ หากไม่มีทีมจะมีค่าบริการพื้นฐาน ฿100 {request.constellations.map((set, index) =>
{Array.from({ length: 7 }, (_, level) => )}
{ const constellations = structuredClone(request.constellations); constellations[index] = { characterKey, levels: [] }; patch({ constellations }); }} />

item.key === set.characterKey)?.name}> {catalog.characters.find((item) => item.key === set.characterKey)?.name ?? "เลือกตัวละคร"}

)}
รวม ฿{commissionPrice(request)} {request.teams.length === 0 && request.weapons.length + request.constellations.length > 0 ?

รวมค่าบริการพื้นฐาน ฿100

: null}
{error && {error}}
; }