import { useState } from "react"; import { X, Copy, Loader2, Trash2 } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { acceptShareCode, createShareCode, deleteShareCode, listShareCodes, queryShareCode, resolveShareDeviceModel } from "@/lib/luxsinApi"; import type { PeqEqUi } from "../constants"; import { resolveShareApiMessage } from "../shareMessages"; import { eqInterp } from "../utils"; import { PeqOverwriteConfirmDialog } from "./PeqOverwriteConfirmDialog"; import { PEQ_PRESET_NAME_MAX_LENGTH } from "../constants"; import { clampPeqPresetName, isPeqPresetNameTooLong } from "../peqPresetName"; import { PeqPresetNameLengthHint } from "./PeqPresetNameLengthHint"; type PeqItem = { name: string; brand?: string; model?: string; form?: string; filters?: any[] | string; autoPre?: number; preamp?: number; canDel?: number; }; type PeqCardLabels = { share: string; }; type ImportedEqData = { name: string; brand?: string; model?: string; target?: string; form?: string; filters?: unknown; preamp?: number; autoPre?: number; }; export function ShareDialog({ open, onClose, peqItems, headphoneIdx, eqUi, peqCardLabels, mac, device, isDemoMode, onImportEq, }: { open: boolean; onClose: () => void; peqItems: PeqItem[]; headphoneIdx: number; eqUi: PeqEqUi; peqCardLabels: PeqCardLabels; mac: string; device: string; isDemoMode: boolean; onImportEq: (data: ImportedEqData, shareCode: string) => void | Promise; }) { const [shareDialogTab, setShareDialogTab] = useState<"share" | "import" | "myShares">("share"); const [shareSelectedIdx, setShareSelectedIdx] = useState(headphoneIdx); const [shareCode, setShareCode] = useState(null); const [shareGenerating, setShareGenerating] = useState(false); const [importCodeInputs, setImportCodeInputs] = useState(["", "", "", "", ""]); const [importQuerying, setImportQuerying] = useState(false); const [importSaving, setImportSaving] = useState(false); const [importPresetName, setImportPresetName] = useState(""); const [queriedShareCode, setQueriedShareCode] = useState(""); const [importedEqData, setImportedEqData] = useState(null); const [importedSourceModel, setImportedSourceModel] = useState(null); const [shareCodeExpireAt, setShareCodeExpireAt] = useState(null); const [mySharesList, setMySharesList] = useState }>>([]); const [mySharesLoading, setMySharesLoading] = useState(false); const [deleteConfirmCode, setDeleteConfirmCode] = useState(null); const [deletingShareCode, setDeletingShareCode] = useState(null); const handleDeleteConfirm = () => { const code = deleteConfirmCode; if (!code || !mac || deletingShareCode) return; setDeleteConfirmCode(null); setDeletingShareCode(code); void (async () => { try { const res = await deleteShareCode(mac, code); if (res.code !== 200) { toast.error(resolveShareApiMessage("delete", res, eqUi)); return; } setMySharesList((prev) => prev.filter((item) => item.share_code !== code)); toast.success(eqUi.mySharesDeleteSuccess); } catch { toast.error(resolveShareApiMessage("delete", { code: 500 }, eqUi)); } finally { setDeletingShareCode(null); } })(); }; const handleImportClick = async () => { if (importSaving || !importedEqData || !queriedShareCode) return; const nextName = importPresetName.trim(); if (!nextName) return; if (isPeqPresetNameTooLong(nextName)) { toast.error(eqUi.addPresetNameTooLong); return; } setImportSaving(true); try { if (!isDemoMode && mac) { const shareDeviceModel = resolveShareDeviceModel(device); if (!shareDeviceModel) { toast.error(eqUi.shareInvalidParams); return; } const acceptRes = await acceptShareCode(mac, shareDeviceModel, queriedShareCode); if (acceptRes.code !== 200) { toast.error(resolveShareApiMessage("accept", acceptRes, eqUi)); return; } } await onImportEq({ ...importedEqData, name: nextName }, queriedShareCode); } finally { setImportSaving(false); } }; if (!open) return null; /* ── clipboard helper ── */ const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); toast.success(eqUi.shareCopied); } catch { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); toast.success(eqUi.shareCopied); } }; return ( <>
{ if (e.target !== e.currentTarget) return; if (importSaving || deleteConfirmCode !== null || deletingShareCode) return; onClose(); }} >
e.stopPropagation()} > {/* header */}
{/* tab bar */}
{/* ── Tab: Share EQ ── */} {shareDialogTab === "share" && ( <> {/* hint */}

{eqUi.shareSelectHint}

{/* EQ preset list */}
{peqItems.length === 0 && (
)} {peqItems.map((item, idx) => { const active = idx === shareSelectedIdx; return ( ); })}
{/* confirm button */} {/* share code display */} {shareCode && (

{eqUi.shareCodeLabel}

{shareCode.split("").map((ch, i) => (
{ch}
))}
{/* expiration time */} {shareCodeExpireAt && (

{eqUi.shareExpireAt}: {shareCodeExpireAt}

)}
)} )} {/* ── Tab: Import EQ ── */} {shareDialogTab === "import" && ( <> {/* hint */}

{eqUi.importCodeHint}

{/* 5 input boxes */}
{importCodeInputs.map((ch, i) => ( { const val = e.target.value.replace(/[^a-zA-Z0-9]/g, "").slice(-1).toUpperCase(); const next = [...importCodeInputs]; next[i] = val; setImportCodeInputs(next); setImportedEqData(null); setImportedSourceModel(null); setImportPresetName(""); setQueriedShareCode(""); // auto-focus next box if (val && i < 4) { const el = e.target.nextElementSibling as HTMLInputElement | null; el?.focus(); } }} onKeyDown={(e) => { // backspace: clear current & focus previous if (e.key === "Backspace" && !importCodeInputs[i] && i > 0) { const next = [...importCodeInputs]; next[i - 1] = ""; setImportCodeInputs(next); setImportedEqData(null); setImportedSourceModel(null); setImportPresetName(""); setQueriedShareCode(""); const prev = (e.target as HTMLElement).previousElementSibling as HTMLInputElement | null; prev?.focus(); } }} onPaste={(e) => { e.preventDefault(); const text = (e.clipboardData.getData("text") || "").replace(/[^a-zA-Z0-9]/g, "").toUpperCase().slice(0, 5); if (!text) return; const next = [...importCodeInputs]; for (let j = 0; j < 5; j++) { next[j] = text[j] ?? ""; } setImportCodeInputs(next); setImportedEqData(null); setImportedSourceModel(null); setImportPresetName(""); setQueriedShareCode(""); // focus last filled or last box const focusIdx = Math.min(text.length, 4); const inputs = (e.target as HTMLElement).parentElement?.querySelectorAll("input"); (inputs?.[focusIdx] as HTMLInputElement | undefined)?.focus(); }} ref={() => { // stash ref for focus management if needed later }} /> ))}
{/* query button */} {/* imported EQ result */} {importedEqData && (
{importedSourceModel && (

{eqUi.importSourceModel}: {importedSourceModel}

)}

{eqUi.importEqName} ({eqUi.importEqNameHint})

setImportPresetName(clampPeqPresetName(e.target.value))} disabled={importSaving} maxLength={PEQ_PRESET_NAME_MAX_LENGTH} className="h-10 w-full rounded-[10px] border border-white/12 bg-[#15171b] px-3 text-[14px] text-white/90 outline-none placeholder:text-white/30 focus:border-[#00FFF6]/40 disabled:opacity-60" placeholder={eqUi.importEqName} />
)} )} {/* ── Tab: My Shares ── */} {shareDialogTab === "myShares" && ( <> {mySharesLoading && mySharesList.length === 0 ? (
{eqUi.mySharesLoading}
) : mySharesList.length === 0 ? (
{eqUi.mySharesEmpty}
) : (
{mySharesList.map((item, idx) => (
{/* share code */}
{item.share_code.split("").map((ch: string, ci: number) => ( {ch} ))}
{/* EQ name + expiration */}
{(item.eq_data?.name as string) ?? "—"}
{item.expire_at && (

{eqUi.shareExpireAt}: {item.expire_at}

)}
{/* copy + delete */}
))}
)} )}
setDeleteConfirmCode(null)} /> ); }