diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bae0622..4565f96f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,9 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + - run: npm install --save-dev @commitlint/cli @commitlint/config-conventional + - name: Lint commits in PR + run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose - name: Setup pnpm uses: pnpm/action-setup@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bff61609..9eb85f96 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,11 @@ jobs: working-directory: . run: | NOTES=$(npx --yes conventional-changelog-cli -p angular -r 1 --outfile /dev/stdout 2>/dev/null) + # Store as multiline env var + { + echo "RELEASE_NOTES<(); + const { address } = useWallet(); + const searchParams = useSearchParams(); + const t = useTranslations("apps"); + + const scVerified = searchParams.get("sc_verified") === "true"; + const scWallet = searchParams.get("sc_wallet"); + const activeWallet = address ?? scWallet ?? null; + const isPreview = usePreviewMode(); + + const protocol = getProtocol(id); + + const [statuses, setStatuses] = useState([]); + const [checked, setChecked] = useState(false); + const [inputValue, setInputValue] = useState(protocol?.inputDefault ?? ""); + const [showQr, setShowQr] = useState(false); + + useEffect(() => { + if (!protocol) return; + setStatuses(protocol.requirements.map(() => false)); + }, [protocol]); + + useEffect(() => { + if (isPreview && protocol) { + setChecked(true); + setStatuses(protocol.requirements.map(() => true)); + return; + } + if (!activeWallet || !protocol) { + setChecked(false); + setStatuses(protocol?.requirements.map(() => false) ?? []); + return; + } + let cancelled = false; + (async () => { + try { + const results = await Promise.all( + protocol.requirements.map((r) => checkClaim(activeWallet, r.type, r.minThreshold)), + ); + if (!cancelled) setStatuses(results); + } catch { + // contracts not deployed — requirements stay unmet + } finally { + if (!cancelled) setChecked(true); + } + })(); + return () => { cancelled = true; }; + }, [activeWallet, protocol]); + + if (!protocol) { + return ( +
+

{t("protocolNotFound")}

+ + {t("backToApps")} + +
+ ); + } + + const eligible = statuses.length > 0 && statuses.every(Boolean); + function ProtocolDetailBody({ protocol, activeWallet, @@ -58,7 +122,7 @@ function ProtocolDetailBody({ textDecoration: "none", }} > - Apps + {t("backToApps")}
{protocol.icon} @@ -89,8 +153,8 @@ function ProtocolDetailBody({ > - Verification complete.{" "} - You were returned here from StellarCred automatically. + {t("verificationComplete")}{" "} + {t("returnedFrom")}
)} @@ -126,6 +190,7 @@ function ProtocolDetailBody({ + {t("requirements")} Requirements @@ -154,6 +219,8 @@ function ProtocolDetailBody({ {r.label} + {statuses[i] ? ( + {t("proved")} {checking ? ( Checking ) : state === "error" ? ( @@ -161,12 +228,20 @@ function ProtocolDetailBody({ ) : statuses[i] ? ( Proved ) : ( - Needed + {t("needed")} )} ))} + {checked && !eligible && ( + + {t("getVerified")} + + + )} + {!activeWallet && ( + {checked && !eligible && !isPreview && ( {state === "error" && ( ); })} {selectedClaims.size > 0 && ( - )} @@ -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} +
@@ -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)} /> ) : ( +