|
- toggleSelect(a.id)} />
+ toggleSelect(a.id)}
+ />
|
{/* Progress/status icon */}
{isProcessing ? (
- {accountStep && [{accountStep.step}/{accountStep.total}]}
+ {accountStep && (
+
+ [{accountStep.step}/{accountStep.total}]
+
+ )}
) : accountResult?.status === "ok" ? (
) : accountResult?.status === "error" ? (
-
+
+
+
) : null}
|
{/* Browser login button */}
@@ -527,7 +920,9 @@ function LogpassRow({ account: a, cols, isProcessing, accountResult, accountStep
onClick={() => onOpenBrowser(a.id)}
className="px-2 py-0.5 text-xs font-medium rounded bg-blue-600/20 text-blue-400 border border-blue-500/30 hover:bg-blue-600/40 hover:text-blue-300 active:scale-95 transition"
title={t("tip.openBrowser")}
- >{t("col.browser")}
+ >
+ {t("col.browser")}
+
) : (
—
)}
@@ -535,13 +930,15 @@ function LogpassRow({ account: a, cols, isProcessing, accountResult, accountStep
)}
{/* Profile — avatar + nickname + level */}
{cols.profile && (
-
-
- {a.avatar_url &&  }
- {a.nickname || "—"}
- {a.steam_level != null && }
-
- |
+
+
+ {a.avatar_url && (
+ 
+ )}
+ {a.nickname || "—"}
+ {a.steam_level != null && }
+
+ |
)}
{/* Last online */}
{cols.last_online && (
@@ -551,46 +948,89 @@ function LogpassRow({ account: a, cols, isProcessing, accountResult, accountStep
)}
{/* Steam ID */}
{cols.steam_id && (
-
- {a.steam_id ? (
-
- {a.steam_id}
-
- ) : "—"}
- |
+
+ {a.steam_id ? (
+
+ {a.steam_id}
+
+ ) : (
+ "—"
+ )}
+ |
)}
{/* Login */}
{cols.login && {a.login} | }
{/* Password */}
{cols.password && (
-
- handleCopy(a.password)} title={t("tip.copyPassword")}>
- {masked ? dots : a.password}
-
-
- |
+
+ handleCopy(a.password)}
+ title={t("tip.copyPassword")}
+ >
+ {masked ? dots : a.password}
+
+
+ |
)}
{/* Login:Pass */}
{cols.login_pass && (
-
- handleCopy(`${a.login}:${a.password}`)} title={t("tip.copyLoginPass")}>
- {masked ? `${a.login}:${dots}` : `${a.login}:${a.password}`}
-
-
- |
+
+ handleCopy(`${a.login}:${a.password}`)}
+ title={t("tip.copyLoginPass")}
+ >
+ {masked ? `${a.login}:${dots}` : `${a.login}:${a.password}`}
+
+
+ |
)}
{/* Status */}
- {cols.status && | }
+ {cols.status && (
+
+
+ |
+ )}
{/* Ban */}
- {cols.ban && | }
+ {cols.ban && (
+
+
+ |
+ )}
{/* Prime, Trophy, Behavior */}
- {cols.prime && {a.prime ?? "—"} | }
- {cols.trophy && {a.trophy ?? "—"} | }
- {cols.behavior && {a.behavior ?? "—"} | }
+ {cols.prime && (
+ {a.prime ?? "—"} |
+ )}
+ {cols.trophy && (
+ {a.trophy ?? "—"} |
+ )}
+ {cols.behavior && (
+ {a.behavior ?? "—"} |
+ )}
{/* License — expandable on click */}
{cols.license && (
- {a.proxy ? (
- handleCopy(a.proxy!)} title={a.proxy}>
- {proxyLabel || a.proxy}
-
- ) : "—"}
- |
+
+ {a.proxy ? (
+ handleCopy(a.proxy!)}
+ title={a.proxy}
+ >
+ {proxyLabel || a.proxy}
+
+ ) : (
+ "—"
+ )}
+ |
)}
{/* Notes — inline editable */}
{cols.notes && (
-
- {editingNotes ? (
- setNotesValue(e.target.value)}
- onBlur={async () => {
- setEditingNotes(false);
- if (notesValue !== (a.notes || "")) {
- try {
- await api.updateLogpassAccount(a.id, { notes: notesValue });
- addToast("success", t("toast.noteSaved"));
- loadAccounts();
- } catch { addToast("error", t("toast.noteSaveError")); }
- }
- }}
- onKeyDown={(e) => { if (e.key === "Enter") (e.target as HTMLInputElement).blur(); if (e.key === "Escape") { setNotesValue(a.notes || ""); setEditingNotes(false); } }}
- />
- ) : (
- { setNotesValue(a.notes || ""); setEditingNotes(true); }}
- title={a.notes || t("tip.addNote")}
- >
- {a.notes || "—"}
-
- )}
- |
+
+ {editingNotes ? (
+ setNotesValue(e.target.value)}
+ onBlur={async () => {
+ setEditingNotes(false);
+ if (notesValue !== (a.notes || "")) {
+ try {
+ await api.updateLogpassAccount(a.id, { notes: notesValue });
+ addToast("success", t("toast.noteSaved"));
+ loadAccounts();
+ } catch {
+ addToast("error", t("toast.noteSaveError"));
+ }
+ }
+ }}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") (e.target as HTMLInputElement).blur();
+ if (e.key === "Escape") {
+ setNotesValue(a.notes || "");
+ setEditingNotes(false);
+ }
+ }}
+ />
+ ) : (
+ {
+ setNotesValue(a.notes || "");
+ setEditingNotes(true);
+ }}
+ title={a.notes || t("tip.addNote")}
+ >
+ {a.notes || "—"}
+
+ )}
+ |
)}
{/* Actions */}
{cols.actions && (
-
-
-
-
-
-
- |
+
+
+
+
+
+
+ |
)}
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/accounts/TokenCheckModal.tsx b/frontend/src/components/accounts/TokenCheckModal.tsx
new file mode 100644
index 0000000..df81ea5
--- /dev/null
+++ b/frontend/src/components/accounts/TokenCheckModal.tsx
@@ -0,0 +1,307 @@
+import { useEffect, useState } from "react";
+import { api } from "@/api/client";
+import type { TokenAccount, TokenCheckData } from "@/api/types";
+import { copyText } from "@/lib/clipboard";
+import { IconX, IconRefresh, IconLoader } from "@/components/shared/Icons";
+
+interface Props {
+ account: TokenAccount;
+ onClose: () => void;
+ onRecheck: () => void;
+}
+
+function Row({
+ label,
+ value,
+ good,
+ bad,
+}: {
+ label: string;
+ value: string;
+ good?: boolean;
+ bad?: boolean;
+}) {
+ return (
+