Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,9 @@ All notable changes are documented here. Format based on [Keep a Changelog](http
- `force_cancel()` action in `StreamActions` for recipients (once contract support is merged)

### Fixed
- `withBoundedParallel` no longer manufactures and discards a fresh `AbortController` per item when the caller passes no outer signal — every handler in the batch now receives one shared, referenceable `AbortSignal` instead of a definitionally-dead one (#221)
- `checkRecipientExists` now reads the recipient's ledger entry directly and treats only an empty
result as "does not exist"; an unrelated RPC failure (a JSON-RPC `Method not found`, a 404 from a
mistyped `NEXT_PUBLIC_SOROBAN_RPC_URL`, a proxy error page) is reported as "couldn't check"
instead of telling the user the recipient account doesn't exist. The create form's recipient
check also passes its abort signal through, which it previously created and never used
- `Mutex`/`Semaphore` in `WalletContext` no longer lose the lock/permit when a queued waiter is
aborted in the same tick that dequeues it — after enough of those races the semaphore was
permanently exhausted and every `signTx` hung, and `connect()` deadlocked outright
- Deferred service-worker reloads while tracked transactions are still in flight, removed the unused
`NotificationCenter`, restored a system-theme path in `ThemeToggle`, and made unconfigured
transaction history render as a neutral coming-soon state.
- `refreshStreamData` now invalidates active queries once instead of immediately refetching the same queries a second time
- Removed the unused multisig transaction scaffold, which had no callers or tests and discarded the clipboard success result
- `scValToU64`/`scValToI128` and `streamsBySender`/`streamsByRecipient` now boundary-check the RPC
Expand Down
26 changes: 23 additions & 3 deletions app/transactions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { AlertCircle, RefreshCw, Info } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
import { Card } from '@/components/ui/Card';
import { formatTimestamp, truncateAddress } from '@/lib/format';
import { fetchTransactionHistoryWithTimeout, type TransactionRow } from '@/lib/indexer';
import {
fetchTransactionHistoryWithTimeout,
isIndexerNotConfiguredError,
type TransactionRow,
} from '@/lib/indexer';
import { useWallet } from '@/contexts/WalletContext';

const TRANSACTIONS_QUERY_KEY = ['transactions'] as const;
Expand All @@ -26,10 +30,12 @@ export default function TransactionsPage() {
queryKey: [...TRANSACTIONS_QUERY_KEY, publicKey],
queryFn: () => fetchTransactionHistoryWithTimeout(publicKey),
staleTime: 1000 * 30,
retry: 1,
retry: (failureCount, queryError) =>
!isIndexerNotConfiguredError(queryError) && failureCount < 1,
});

const isDemoData = connected && txs.length > 0;
const isIndexerComingSoon = status === 'error' && isIndexerNotConfiguredError(error);

return (
<div className="max-w-3xl mx-auto px-4 py-10">
Expand Down Expand Up @@ -59,10 +65,24 @@ export default function TransactionsPage() {
<span className="text-sm">Loading transactions…</span>
</div>
</Card>
) : isIndexerComingSoon ? (
<Card>
<div className="flex flex-col items-center justify-center gap-3 py-16 text-center">
<Info className="w-8 h-8 text-gray-400" aria-hidden="true" />
<div>
<p className="text-sm font-semibold text-black dark:text-white">
Transaction history is coming soon
</p>
<p className="text-xs text-gray-500 mt-1">
The indexer is not configured yet, so history will appear here once it is available.
</p>
</div>
</div>
</Card>
) : status === 'error' ? (
<Card>
<div className="flex flex-col items-center justify-center gap-3 py-16 text-center">
<AlertCircle className="w-8 h-8 text-red-500" aria-hidden="true" />
<AlertCircle className="w-8 h-8 text-gray-500" aria-hidden="true" />
<div>
<p className="text-sm font-semibold text-black dark:text-white">
Couldn&apos;t load transaction history
Expand Down
17 changes: 17 additions & 0 deletions components/ThemeToggle.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';
import { getNextTheme } from './ThemeToggle';

describe('ThemeToggle theme cycling', () => {
it('cycles explicit light mode to dark mode', () => {
expect(getNextTheme('light')).toBe('dark');
});

it('cycles explicit dark mode back to system mode', () => {
expect(getNextTheme('dark')).toBe('system');
});

it('cycles system or missing theme to light mode', () => {
expect(getNextTheme('system')).toBe('light');
expect(getNextTheme(undefined)).toBe('light');
});
});
30 changes: 26 additions & 4 deletions components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
'use client';

import { useTheme } from 'next-themes';
import { Sun, Moon } from 'lucide-react';
import { Sun, Moon, Monitor } from 'lucide-react';
import { useEffect, useState } from 'react';

type ThemeChoice = 'light' | 'dark' | 'system';

export function getNextTheme(theme?: string): ThemeChoice {
if (theme === 'light') return 'dark';
if (theme === 'dark') return 'system';
return 'light';
}

export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
Expand All @@ -28,14 +36,28 @@ export function ThemeToggle() {
}

const isDark = theme === 'dark' || (theme === 'system' && systemPrefersDark);
const nextTheme = getNextTheme(theme);
const label =
nextTheme === 'system'
? 'Match system theme'
: nextTheme === 'dark'
? 'Switch to dark mode'
: 'Switch to light mode';

return (
<button
onClick={() => setTheme(isDark ? 'light' : 'dark')}
onClick={() => setTheme(nextTheme)}
className="inline-flex items-center justify-center w-9 h-9 rounded text-gray-500 hover:text-black hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-800 transition-colors"
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
aria-label={label}
title={label}
>
{isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
{theme === 'system' ? (
<Monitor className="w-4 h-4" />
) : isDark ? (
<Sun className="w-4 h-4" />
) : (
<Moon className="w-4 h-4" />
)}
</button>
);
}
99 changes: 0 additions & 99 deletions components/ui/NotificationCenter.test.tsx

This file was deleted.

85 changes: 0 additions & 85 deletions components/ui/NotificationCenter.tsx

This file was deleted.

Loading