= {
walletconnect: "Mobile & desktop wallets",
};
+function getFriendlyErrorMessage(id: string, err: unknown, t: (key: string) => string) {
+ const message = err instanceof Error ? err.message : String(err ?? "");
+ const normalized = message.toLowerCase();
+
+ if (
+ normalized.includes("4001") ||
+ normalized.includes("reject") ||
+ normalized.includes("declin") ||
+ normalized.includes("denied") ||
+ normalized.includes("canceled") ||
+ normalized.includes("cancelled")
+ ) {
+ return t("userRejected");
+ }
+
+ if (
+ normalized.includes("project_id") ||
+ normalized.includes("project id") ||
+ normalized.includes("walletconnect is disabled") ||
+ normalized.includes("pairing uri")
+ ) {
+ return t("walletConnectUnavailable");
+ }
+
+ if (
+ normalized.includes("not installed") ||
+ normalized.includes("not found") ||
+ normalized.includes("extension")
+ ) {
+ return t("extensionNotFound");
+ }
+
+ if (
+ normalized.includes("no stellar accounts") ||
+ normalized.includes("public key") ||
+ normalized.includes("session not established")
+ ) {
+ return t("noAccountFound");
+ }
+
+ return id === "walletconnect"
+ ? t("walletConnectFailed")
+ : t("connectionFailed");
+}
+
export default function WalletSelector({ networkPassphrase, onConnected }: WalletSelectorProps) {
const t = useTranslations("walletSelector");
const { providers, activeProvider, selectProvider } = useWallet();
@@ -65,54 +110,7 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle
return () => { cancelled = true; };
}, [providers]);
- if (activeProvider) return null;
-
- function getFriendlyErrorMessage(id: string, err: unknown) {
- const message = err instanceof Error ? err.message : String(err ?? "");
- const normalized = message.toLowerCase();
-
- if (
- normalized.includes("4001") ||
- normalized.includes("reject") ||
- normalized.includes("declin") ||
- normalized.includes("denied") ||
- normalized.includes("canceled") ||
- normalized.includes("cancelled")
- ) {
- return t("userRejected");
- }
-
- if (
- normalized.includes("project_id") ||
- normalized.includes("project id") ||
- normalized.includes("walletconnect is disabled") ||
- normalized.includes("pairing uri")
- ) {
- return t("walletConnectUnavailable");
- }
-
- if (
- normalized.includes("not installed") ||
- normalized.includes("not found") ||
- normalized.includes("extension")
- ) {
- return t("extensionNotFound");
- }
-
- if (
- normalized.includes("no stellar accounts") ||
- normalized.includes("public key") ||
- normalized.includes("session not established")
- ) {
- return t("noAccountFound");
- }
-
- return id === "walletconnect"
- ? t("walletConnectFailed")
- : t("connectionFailed");
- }
-
- async function handleSelect(id: string) {
+ const handleSelect = useCallback(async (id: string) => {
setConnectError(null);
setWcError(null);
setWcUri(null);
@@ -120,7 +118,6 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle
try {
if (id === "walletconnect") {
- setWcError(null);
const { uri, approval } = await connectWalletConnect(networkPassphrase);
setWcUri(uri);
await approval;
@@ -144,7 +141,7 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle
selectProvider(id);
onConnected();
} catch (err) {
- const msg = getFriendlyErrorMessage(id, err);
+ const msg = getFriendlyErrorMessage(id, err, t);
if (id === "walletconnect") {
setWcError(msg);
setWcUri(null);
@@ -154,7 +151,64 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle
} finally {
setConnecting(null);
}
- }
+ }, [networkPassphrase, onConnected, selectProvider, providers, t]);
+
+ const providerButtons = useMemo(() => {
+ return providers.map((p) => {
+ const isWc = p.id === "walletconnect";
+ const isConnecting = connecting === p.id;
+ const isInstalled = installed[p.id] ?? false;
+ const isDisabled = !isInstalled;
+
+ return (
+
handleSelect(p.id)}
+ aria-busy={isConnecting}
+ className="group relative flex h-16 w-full items-center gap-4 rounded-2xl border border-[#E8E8E8] bg-white px-5 text-left shadow-sm transition-[border-color,box-shadow,transform] duration-200 hover:-translate-y-px hover:border-[var(--pluto-400)] hover:shadow-[0_6px_22px_rgba(74,111,165,0.12)] active:translate-y-0 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-300)] disabled:cursor-not-allowed disabled:opacity-40"
+ >
+ {/* Icon */}
+
+ {ICONS[p.id] ?? (
+
+
+
+ )}
+
+
+ {/* Label */}
+
+ {isConnecting ? (
+
+
+ {isWc ? t("walletConnectWaiting") : "Connecting…"}
+
+ ) : (
+ <>
+ {p.name}
+
+ {isDisabled
+ ? (isWc ? t("noProjectId") : t("notInstalled"))
+ : SUBTITLES[p.id] ?? t("tapToConnect")}
+
+ >
+ )}
+
+
+ {/* Arrow */}
+ {!isConnecting && !isDisabled && (
+
+
+
+ )}
+
+ );
+ });
+ }, [providers, connecting, installed, handleSelect, t]);
+
+ if (activeProvider) return null;
return (
@@ -164,59 +218,7 @@ export default function WalletSelector({ networkPassphrase, onConnected }: Walle
- {providers.map((p) => {
- const isWc = p.id === "walletconnect";
- const isConnecting = connecting === p.id;
- const isInstalled = installed[p.id] ?? false;
- const isDisabled = !isInstalled;
-
- return (
-
handleSelect(p.id)}
- aria-busy={isConnecting}
- className="group relative flex h-16 w-full items-center gap-4 rounded-2xl border border-[#E8E8E8] bg-white px-5 text-left shadow-sm transition-[border-color,box-shadow,transform] duration-200 hover:-translate-y-px hover:border-[var(--pluto-400)] hover:shadow-[0_6px_22px_rgba(74,111,165,0.12)] active:translate-y-0 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-300)] disabled:cursor-not-allowed disabled:opacity-40"
- className="group relative flex h-16 w-full items-center gap-4 rounded-2xl border border-[#E8E8E8] bg-white px-5 text-left shadow-sm transition-[border-color,box-shadow,transform] duration-200 hover:-translate-y-px hover:border-[var(--pluto-400)] hover:shadow-[0_6px_22px_rgba(74,111,165,0.12)] active:translate-y-0 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-300)] disabled:cursor-not-allowed disabled:opacity-40"
- >
- {/* Icon */}
-
- {ICONS[p.id] ?? (
-
-
-
- )}
-
-
- {/* Label */}
-
- {isConnecting ? (
-
-
- {isWc ? t("walletConnectWaiting") : "Connecting…"}
-
- ) : (
- <>
- {p.name}
-
- {isDisabled
- ? (isWc ? t("noProjectId") : t("notInstalled"))
- : SUBTITLES[p.id] ?? t("tapToConnect")}
-
- >
- )}
-
-
- {/* Arrow */}
- {!isConnecting && !isDisabled && (
-
-
-
- )}
-
- );
- })}
+ {providerButtons}
{/* WalletConnect QR */}