diff --git a/.vscode/settings.json b/.vscode/settings.json index 0967ef4..118a34c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,3 @@ -{} +{ + "wake.compiler.solc.remappings": [] +} diff --git a/__mocks__/@sharibo/client.ts b/__mocks__/@sharibo/client.ts index 6befbd0..bf94ee3 100644 --- a/__mocks__/@sharibo/client.ts +++ b/__mocks__/@sharibo/client.ts @@ -148,5 +148,14 @@ export const getCircle = vi.fn( size: 5, round: 0, pot: 0n, + contributors: [], + cancelled: false, + }), +); + +export const cancelCircle = vi.fn( + async (_client: SharaboClient, _args: unknown): Promise> => ({ + result: undefined, + hash: "mockCancelHash", }), ); diff --git a/app/package.json b/app/package.json index f45c232..416a9c2 100644 --- a/app/package.json +++ b/app/package.json @@ -20,7 +20,8 @@ "scripts": { "sync-circuit": "node scripts/sync-circuit.mjs", "dev:circuits": "node scripts/sync-circuit.mjs --watch", - "dev": "npm run sync-circuit && vite", + "dev": "vite", + "dev:full": "npm run sync-circuit && vite", "build": "npm run sync-circuit && vite build", "preview": "vite preview", "test": "vitest run" diff --git a/app/src/App.tsx b/app/src/App.tsx index 8a959af..091e972 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useCallback } from "react"; import { Keypair } from "@stellar/stellar-sdk"; import { isConnected, @@ -18,6 +18,7 @@ import { createCircle, fund, claim, + cancelCircle, getCircle, hasClaimed, TREE_LEVELS, @@ -30,12 +31,37 @@ import { } from "@sharibo/client"; import { config, configError } from "./config"; import { useI18n } from "./i18n"; +import { usePoliteLiveRegion, LiveRegion } from "./usePoliteLiveRegion"; +import { explorerTx, short, explorerAccount, explorerContract } from "./lib/explorer"; import { friendbotFund as fundWithFriendbot, FriendbotRetryableError, FRIEND_BOT_RATE_LIMIT_MESSAGE, } from "./lib/friendbot"; +function LanguageSwitcher({ className }: { className?: string }) { + const { locale, locales, setLocale } = useI18n(); + return ( +
+ + +
+ ); +} + const BIGINT_MARKER = 'BIGINT::'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function replacer(key: string, value: any) { @@ -106,15 +132,7 @@ function toUiError(error: unknown): string { return "Something went wrong. Please retry."; } -function explorerAccount(address: string): string { - return `https://stellar.expert/explorer/testnet/account/${address}`; -} -function explorerContract(): string { - return `https://stellar.expert/explorer/testnet/contract/${NETWORK.contractId}`; -} -function short(address: string): string { - return `${address.slice(0, 4)}…${address.slice(-4)}`; -} + // Every truncated value on screen (addresses, tx hashes) needs to be // pasteable in full somewhere else — a CLI call, an explorer search — so @@ -159,6 +177,7 @@ interface Member { funded: boolean; fundHash?: string; freighterKey?: string; + pending?: boolean; // Optimistic flag while transaction is in flight } interface ClaimResult { @@ -244,22 +263,7 @@ function Stepper({ step }: { step: 0 | 1 | 2 | 3 }) { ); } -function NetworkBanner() { - const isTestnet = NETWORK.networkPassphrase.toLowerCase().includes("test"); - if (!isTestnet) return null; - return ( -
- Stellar testnet — no real funds ·{" "} - - limitations ↗ - -
- ); -} + // Purely presentational: after a claim, none of the 5 nodes are highlighted // as "the one that claimed" — that's the point. From outside the ring, all @@ -281,7 +285,7 @@ function useRingRadius(): number { return radius; } -function MemberRing({ members, revealed }: { members: { funded: boolean }[]; revealed: boolean }) { +function MemberRing({ members, revealed }: { members: { funded: boolean; pending?: boolean }[]; revealed: boolean }) { const radius = useRingRadius(); const fundedCount = members.filter((m) => m.funded).length; @@ -318,7 +322,7 @@ function MemberRing({ members, revealed }: { members: { funded: boolean }[]; rev