Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function StreamingWidgetStoryShell({
dataTestId: string
}) {
return (
<YStack data-testid={dataTestId} style={{ width: 400, minHeight: '100vh' }}>
<YStack data-testid={dataTestId} width={400} minHeight="100vh">
<StreamingWidget provider={provider} environment="development" />
</YStack>
)
Expand All @@ -45,7 +45,7 @@ function InjectedWalletStory() {

if (!usableProvider) {
return (
<YStack data-testid="StreamingWidget-no-wallet" style={{ width: 400 }} gap="$3">
<YStack data-testid="StreamingWidget-no-wallet" width={400} gap="$3">
<strong>No injected wallet found</strong>
<span>
Install/enable MetaMask (or another EIP-1193 wallet) in this browser, then refresh
Expand Down Expand Up @@ -77,7 +77,7 @@ function CustodialLocalFixtureStory() {
)
} catch (error: unknown) {
return (
<YStack data-testid="StreamingWidget-custodial-config-error" style={{ width: 400 }}>
<YStack data-testid="StreamingWidget-custodial-config-error" width={400}>
<strong>Custodial fixture not configured</strong>
<span>
{error instanceof Error
Expand Down Expand Up @@ -123,3 +123,195 @@ export const CustodialLocalFixture: Story = {
export const NoWallet: Story = {
render: () => <NoWalletStory />,
}

// ---------------------------------------------------------------------------
// Wrong-chain story — provider reports an unsupported chain (Ethereum mainnet)
// ---------------------------------------------------------------------------
function WrongChainStory() {
const mockProvider = {
on: () => {},
removeListener: () => {},
request: async ({ method }: { method: string }) => {
if (method === 'eth_accounts' || method === 'eth_requestAccounts') {
return ['0x1234567890123456789012345678901234567890']
}
if (method === 'eth_chainId') return '0x1' // Ethereum mainnet (unsupported)
if (method === 'net_version') return '1'
return null
},
}

return (
<StreamingWidgetStoryShell
provider={mockProvider}
dataTestId="StreamingWidget-wrong-chain"
/>
)
}

/**
* Wallet connected but on an unsupported chain (Ethereum mainnet).
* Shows the "Unsupported network" prompt with chain-switch buttons.
*/
export const WrongChain: Story = {
render: () => <WrongChainStory />,
}

// ---------------------------------------------------------------------------
// Loading state story — blocks all RPC/subgraph calls to keep widget loading
// ---------------------------------------------------------------------------
function LoadingStateStory() {
// Use custodial provider but block RPC in a separate story via Playwright routing
try {
const provider = createCustodialEip1193Provider()
return (
<StreamingWidgetStoryShell
provider={provider}
dataTestId="StreamingWidget-loading-state"
/>
)
} catch {
return (
<YStack data-testid="StreamingWidget-loading-config-error" width={400}>
<strong>Custodial fixture not configured</strong>
</YStack>
)
}
}

/**
* Widget in loading state — RPC/subgraph calls are routed to hang in Playwright tests.
* Shows loading spinners across all tabs.
*/
export const LoadingState: Story = {
render: () => <LoadingStateStory />,
}

// ---------------------------------------------------------------------------
// Error state story — blocks all RPC calls to force error state
// ---------------------------------------------------------------------------
function ErrorStateStory() {
// Same as loading; Playwright routes handle the actual error forcing
try {
const provider = createCustodialEip1193Provider()
return (
<StreamingWidgetStoryShell
provider={provider}
dataTestId="StreamingWidget-error-state"
/>
)
} catch {
return (
<YStack data-testid="StreamingWidget-error-config-error" width={400}>
<strong>Custodial fixture not configured</strong>
</YStack>
)
}
}

/**
* Widget in error state — RPC calls are aborted in Playwright tests.
* Shows error messages and retry buttons.
*/
export const ErrorState: Story = {
render: () => <ErrorStateStory />,
}

// ---------------------------------------------------------------------------
// Pool claim story — custodial fixture on Celo showing pool memberships
// with claimable amounts and claim action. Playwright routes can mock
// claimable balances for deterministic screenshots.
// ---------------------------------------------------------------------------
function PoolClaimStory() {
try {
const provider = createCustodialEip1193Provider()
return (
<StreamingWidgetStoryShell
provider={provider}
dataTestId="StreamingWidget-pool-claim"
/>
)
} catch {
return (
<YStack data-testid="StreamingWidget-pool-claim-config-error" width={400}>
<strong>Custodial fixture not configured</strong>
</YStack>
)
}
}

/**
* Pool claim scenario — shows GDA pool memberships with claimable amounts
* and the Claim action button. Navigate to the Pools tab to view.
*/
export const PoolClaim: Story = {
render: () => <PoolClaimStory />,
}

// ---------------------------------------------------------------------------
// Base SUP reserve story — mock provider on Base chain (8453) to show
// the SUP reserve balance section.
// ---------------------------------------------------------------------------
function BaseSupReserveStory() {
const mockProvider = {
on: () => {},
removeListener: () => {},
request: async ({ method }: { method: string }) => {
if (method === 'eth_accounts' || method === 'eth_requestAccounts') {
return ['0x1234567890123456789012345678901234567890']
}
if (method === 'eth_chainId') return '0x2105' // Base mainnet (8453)
if (method === 'net_version') return '8453'
return null
},
}

return (
<StreamingWidgetStoryShell
provider={mockProvider}
dataTestId="StreamingWidget-base-sup-reserve"
/>
)
}

/**
* Base chain SUP reserve — wallet connected on Base shows the SUP Reserve
* (Staked) section with reserve balance. Navigate to the Balances tab.
*/
export const BaseSupReserve: Story = {
render: () => <BaseSupReserveStory />,
}

// ---------------------------------------------------------------------------
// Base SUP balance story — mock provider on Base chain showing Super Token
// balance for SUP on Base.
// ---------------------------------------------------------------------------
function BaseSupBalanceStory() {
const mockProvider = {
on: () => {},
removeListener: () => {},
request: async ({ method }: { method: string }) => {
if (method === 'eth_accounts' || method === 'eth_requestAccounts') {
return ['0xabcdefabcdefabcdefabcdefabcdefabcdefabcd']
}
if (method === 'eth_chainId') return '0x2105' // Base mainnet (8453)
if (method === 'net_version') return '8453'
return null
},
}

return (
<StreamingWidgetStoryShell
provider={mockProvider}
dataTestId="StreamingWidget-base-sup-balance"
/>
)
}

/**
* Base chain SUP balance — wallet connected on Base shows the Super Token
* balance for SUP. Navigate to the Balances tab.
*/
export const BaseSupBalance: Story = {
render: () => <BaseSupBalanceStory />,
}
49 changes: 43 additions & 6 deletions packages/streaming-widget/src/StreamingWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,16 +452,24 @@ function PoolCard({
pool,
connectStatus,
connectError,
claimStatus,
claimError,
onConnect,
onDisconnect,
onClaim,
}: {
pool: PoolMembershipItem
connectStatus: WriteStatus
connectError: string | null
claimStatus: WriteStatus
claimError: string | null
onConnect: (poolAddress: Address) => void
onDisconnect: (poolAddress: Address) => void
onClaim: (poolAddress: Address) => void
}) {
const isPending = connectStatus === 'pending'
const isConnectPending = connectStatus === 'pending'
const isClaimPending = claimStatus === 'pending'
const hasClaimable = pool.claimableAmount > 0n

return (
<PoolRow>
Expand All @@ -479,23 +487,49 @@ function PoolCard({
<Text>{formatUnits(pool.totalAmountClaimed, 18)}</Text>
</XStack>

{hasClaimable && (
<XStack justifyContent="space-between" alignItems="center">
<Text variant="caption" secondary>
Claimable
</Text>
<Text fontWeight="600">{formatUnits(pool.claimableAmount, 18)}</Text>
</XStack>
)}

{connectError && (
<Text color="$error" variant="caption">
{connectError}
</Text>
)}
{claimError && (
<Text color="$error" variant="caption">
{claimError}
</Text>
)}

<XStack gap="$2" alignItems="center">
<XStack gap="$2" alignItems="center" flexWrap="wrap">
<WriteStatusBadge status={connectStatus} />
{pool.isConnected ? (
<Button disabled={isPending} onPress={() => onDisconnect(pool.poolId)}>
{isPending ? <Spinner size="sm" /> : <ButtonText>Disconnect</ButtonText>}
<Button disabled={isConnectPending} onPress={() => onDisconnect(pool.poolId)}>
{isConnectPending ? <Spinner size="sm" /> : <ButtonText>Disconnect</ButtonText>}
</Button>
) : (
<Button disabled={isPending} onPress={() => onConnect(pool.poolId)}>
{isPending ? <Spinner size="sm" /> : <ButtonText>Connect</ButtonText>}
<Button disabled={isConnectPending} onPress={() => onConnect(pool.poolId)}>
{isConnectPending ? <Spinner size="sm" /> : <ButtonText>Connect</ButtonText>}
</Button>
)}
{pool.isConnected && hasClaimable && (
<>
<WriteStatusBadge status={claimStatus} />
<Button
disabled={isClaimPending}
onPress={() => onClaim(pool.poolId)}
variant="secondary"
>
{isClaimPending ? <Spinner size="sm" /> : <ButtonText>Claim</ButtonText>}
</Button>
</>
)}
</XStack>
</PoolRow>
)
Expand Down Expand Up @@ -548,8 +582,11 @@ function PoolsTab({
pool={pool}
connectStatus={state.poolConnectStatus[pool.poolId] ?? 'idle'}
connectError={state.poolConnectError[pool.poolId] ?? null}
claimStatus={state.poolClaimStatus[pool.poolId] ?? 'idle'}
claimError={state.poolClaimError[pool.poolId] ?? null}
onConnect={actions.connectToPool}
onDisconnect={actions.disconnectFromPool}
onClaim={actions.claimFromPool}
/>
))}
</StreamingTabContent>
Expand Down
Loading