+
setSearch(e.target.value)}
- style={{
- width: "100%",
- padding: "0.65rem 0.75rem 0.65rem 2.25rem",
- borderRadius: "var(--radius)",
- border: "1px solid var(--border)",
- background: "var(--bg-raised)",
- color: "var(--text)",
- fontSize: "0.875rem",
- }}
+ style={{ width: "100%", padding: "0.65rem 0.75rem 0.65rem 2.25rem", borderRadius: "var(--radius)", border: "1px solid var(--border)", background: "var(--bg-raised)", color: "var(--text)", fontSize: "0.875rem" }}
/>
@@ -359,40 +361,13 @@ function AppsInner() {
{CREDENTIAL_TYPES.map((claim) => {
const isActive = selectedClaims.has(claim);
return (
- toggleClaim(claim)}
- type="button"
- style={{
- padding: "0.3rem 0.7rem",
- borderRadius: "999px",
- fontSize: "0.72rem",
- fontWeight: 500,
- border: `1px solid ${isActive ? "var(--accent)" : "var(--border)"}`,
- background: isActive ? "rgba(62,207,142,0.12)" : "transparent",
- color: isActive ? "var(--accent)" : "var(--muted)",
- cursor: "pointer",
- transition: "all 0.15s ease",
- }}
- >
+ toggleClaim(claim)} type="button" style={{ padding: "0.3rem 0.7rem", borderRadius: "999px", fontSize: "0.72rem", fontWeight: 500, border: `1px solid ${isActive ? "var(--accent)" : "var(--border)"}`, background: isActive ? "rgba(62,207,142,0.12)" : "transparent", color: isActive ? "var(--accent)" : "var(--muted)", cursor: "pointer", transition: "all 0.15s ease" }}>
{CLAIM_LABELS[claim] || claim}
);
})}
{selectedClaims.size > 0 && (
- setSelectedClaims(new Set())}
- type="button"
- style={{
- padding: "0.3rem 0.7rem",
- borderRadius: "999px",
- fontSize: "0.72rem",
- border: "1px solid var(--border)",
- background: "transparent",
- color: "var(--faint)",
- cursor: "pointer",
- }}
- >
+ setSelectedClaims(new Set())} type="button" style={{ padding: "0.3rem 0.7rem", borderRadius: "999px", fontSize: "0.72rem", border: "1px solid var(--border)", background: "transparent", color: "var(--faint)", cursor: "pointer" }}>
Clear filters
)}
@@ -400,15 +375,10 @@ function AppsInner() {
{filtered.length === 0 ? (
-
+
-
No apps match
-
- Try adjusting your search or removing claim filters.
-
+
{t("noResults")}
+
{t("noResultsHint")}
) : (
diff --git a/frontend/app/holder/page.tsx b/frontend/app/holder/page.tsx
index b03ad2ce..d39c0a65 100644
--- a/frontend/app/holder/page.tsx
+++ b/frontend/app/holder/page.tsx
@@ -1,5 +1,7 @@
"use client";
+import { useEffect, useRef, useState } from "react";
+import { useTranslations } from "next-intl";
import { Suspense, useEffect, useRef, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import {
@@ -128,6 +130,7 @@ function CredCard({
};
}) {
const status = proofStatus(c);
+ const t = useTranslations("holder");
const { events } = useProofTimeline(c);
const [showHistory, setShowHistory] = useState(false);
@@ -141,6 +144,97 @@ function CredCard({
{c.claim}
+ {c.issuer} ·
{truncateHash(c.commitment)}
+
+ {c.issuer} ·
{truncateHash(c.commitment)}
+
+
+
+ {status === "proved" && (
+ <>
+ {" · "}
+
+ {t("expiresIn", { days: daysRemaining(c) })}
+ expires in {daysRemaining(c)}d
+
+ {c.provedTxHash && (
+ <>
+ {" · "}
+
+ {c.provedTxHash.slice(0, 6)}…
+
+ >
+ )}
+ >
+ )}
+ {status === "expired" && (
+ <> ·
{t("expired")} >
+ )}
+ {/* left: selection + credential info */}
+
+ {selection && (
+
+ )}
+
+
+ {c.title}
+ {c.claim}
+
+
+ {c.issuer} ·
{truncateHash(c.commitment)}
+ {status === "proved" && (
+ <>
+ {" · "}
+
+ expires in {daysRemaining(c)}d
+
+ {c.provedTxHash && (
+ <>
+ {" · "}
+
+ {c.provedTxHash.slice(0, 6)}…
+
+ >
+ )}
+ >
+ )}
+ {status === "expired" && (
+ <> ·
expired >
+ )}
+
+ <> ·
expired >
+ )}
{c.issuer} · {truncateHash(c.commitment)}
{status === "proved" && (
@@ -181,19 +275,33 @@ function CredCard({
{/* right: badges + button + trash */}
+
+
{t("held")}
+ {status === "proved" &&
{t("onChain")} }
{isPreview &&
Preview }
Held
{status === "proved" &&
On-chain }
- {status === "proved" ? "Re-prove" :
- status === "expired" ? "Re-prove" :
- "Generate proof"}
+ {status === "proved" ? t("reprove") :
+ status === "expired" ? t("reprove") :
+ t("generateProof")}
+
+
+
([]);
const [view, setView] = useState({ kind: "list" });
const [importing, setImporting] = useState(false);
+ const t = useTranslations("holder");
+ const [transferCred, setTransferCred] = useState(null);
+ const [importScanning, setImportScanning] = useState(false);
+ const [importPayload, setImportPayload] = useState(null);
const [detailCred, setDetailCred] = useState(null);
useEffect(() => setCreds(loadCredentials()), []);
@@ -366,8 +478,8 @@ function HolderInner() {
<>
- Holder
-
Your credentials
+ {t("eyebrow")}
+ {t("title")}
@@ -422,13 +534,12 @@ function HolderInner() {
style={{ textAlign: "center", padding: "3.5rem 1.5rem", borderStyle: "dashed" }}
>
- No credentials yet
+ {t("emptyTitle")}
- Get a credential from a trusted issuer, then generate a
- zero-knowledge proof to verify it on-chain.
+ {t("emptyBody")}
- Get a credential
+ {t("emptyCta")}
@@ -437,7 +548,7 @@ function HolderInner() {
{/* ── Credentials to prove ── */}
{unproved.length > 0 && (
-
Ready to prove
+
{t("sectionReady")}
{unproved.map((c) => (
0 && (
-
On-chain · active proofs
+
{t("sectionOnChain")}
{proved.map((c) => (
0 && (
- Connect a wallet to generate and submit proofs.
+ {t("connectWalletToProve")}
)}
@@ -544,6 +655,14 @@ function HolderInner() {
onCancel={() => setImporting(false)}
/>
) : (
+ setImporting(true)}
+ >
+
+ {t("importJson")}
+
void; onCancel: () => void }) {
const [json, setJson] = useState("");
const [error, setError] = useState("");
+ const t = useTranslations("holder");
function onAdd() {
try { onImport(parseCredential(json)); }
@@ -589,18 +709,18 @@ function ImportPanel({ onImport, onCancel }: { onImport: (c: Credential) => void
return (
-
Import credential
+
{t("importTitle")}
);
@@ -728,6 +848,7 @@ function ProofFlow({
const [showRaw, setShowRaw] = useState(false);
const [elapsed, setElapsed] = useState(0);
const timerRef = useRef | null>(null);
+ const t = useTranslations("holder");
const toast = useToast();
const { networkMismatch } = useWallet();
const { addEvent } = useProofTimeline(cred);
@@ -836,14 +957,14 @@ function ProofFlow({
- All credentials
+ {t("allCredentials")}
{/* credential header */}
- Proving
+ {t("proving")}
{cred.title}
{cred.claim}
@@ -851,6 +972,23 @@ function ProofFlow({
{/* step list */}
+
}
+ title={t("proofFlowWitnessTitle")}
+ subtitle={t("proofFlowWitnessSubtitle")}
+ state={
+ stage === "witness" ? "active" :
+ stage === "error" ? "idle" : "done"
+ }
+ detail={
+ stage === "witness" ?
: null
+ }
+ />
+
+
}
+ title={t("proofFlowProvingTitle")}
+ subtitle={t("proofFlowProvingSubtitle")}