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
4 changes: 2 additions & 2 deletions app/app/create/create-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ export function CreateForm() {
const [recurrenceCadence, setRecurrenceCadence] =
useState<RecurrenceCadence>("none");

// Issue #29: balance state
// Tracks the connected wallet's token balance for validation
const [tokenBalance, setTokenBalance] = useState<bigint | null>(null);
const [balanceLoading, setBalanceLoading] = useState(false);

// Issue #103: recipient account validation
// Validates recipient account existence, funding status, and transaction history
const [recipientAccountInfo, setRecipientAccountInfo] = useState<{
exists: boolean;
funded: boolean;
Expand Down
14 changes: 14 additions & 0 deletions components/ui/countdown-timer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { useEffect, useState } from 'react'
import { cn } from '@/lib/utils'
import { useNow } from '@/hooks/use-now'
import { formatTimeRemaining } from '@/lib/stream-utils'
Expand All @@ -9,17 +10,30 @@ interface CountdownTimerProps {
target: bigint
className?: string
endedLabel?: string
/** Callback when a significant state change occurs (e.g., timer expires). */
onStateChange?: (state: 'expired' | 'active') => void
}

/** Live "2d 4h 13m" countdown to a target timestamp. */
export function CountdownTimer({
target,
className,
endedLabel = 'Ended',
onStateChange,
}: CountdownTimerProps) {
const now = useNow(1000)
const ended = Number(target) <= now

const [lastState, setLastState] = useState<'expired' | 'active'>(ended ? 'expired' : 'active')

useEffect(() => {
const newState = ended ? 'expired' : 'active'
if (newState !== lastState) {
setLastState(newState)
onStateChange?.(newState)
}
}, [ended, lastState, onStateChange])

return (
<span className={cn('font-mono tabular-nums', className)}>
{ended ? endedLabel : formatTimeRemaining(target, now)}
Expand Down
11 changes: 10 additions & 1 deletion components/ui/progress-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ interface ProgressBarProps {
/** Optional secondary marker (0–1), e.g. withdrawn portion. */
marker?: number
indeterminateShimmer?: boolean
/** Height variant. @default 'default' */
size?: 'sm' | 'default' | 'lg'
}

/** Shows the % of a stream that has unlocked, with an optional withdrawn marker. */
Expand All @@ -15,7 +17,13 @@ export function ProgressBar({
className,
marker,
indeterminateShimmer,
size = 'default',
}: ProgressBarProps) {
const sizeClasses = {
sm: 'h-1',
default: 'h-2',
lg: 'h-3',
}
const pct = Math.min(Math.max(value, 0), 1) * 100
const markerPct =
marker != null ? Math.min(Math.max(marker, 0), 1) * 100 : null
Expand All @@ -27,7 +35,8 @@ export function ProgressBar({
aria-valuemin={0}
aria-valuemax={100}
className={cn(
'relative h-2 w-full overflow-hidden rounded-full bg-secondary',
'relative w-full overflow-hidden rounded-full bg-secondary',
sizeClasses[size],
className,
)}
>
Expand Down
11 changes: 9 additions & 2 deletions components/ui/token-amount.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cn } from '@/lib/utils'
import { formatTokenAmount } from '@/lib/stream-utils'
import { formatTokenAmount, formatCompactAmount } from '@/lib/stream-utils'
import type { TokenInfo } from '@/types/stream'

interface TokenAmountProps {
Expand All @@ -9,6 +9,8 @@ interface TokenAmountProps {
symbolClassName?: string
maxFractionDigits?: number
showSymbol?: boolean
/** Display amount in compact notation (e.g., "1.2K"). @default false */
compact?: boolean
}

/** Formats a raw bigint token amount with correct decimals + symbol. */
Expand All @@ -19,10 +21,15 @@ export function TokenAmount({
symbolClassName,
maxFractionDigits = 4,
showSymbol = true,
compact = false,
}: TokenAmountProps) {
const formattedAmount = compact
? formatCompactAmount(amount, token.decimals)
: formatTokenAmount(amount, token.decimals, maxFractionDigits)

return (
<span className={cn('font-mono tabular-nums', className)}>
{formatTokenAmount(amount, token.decimals, maxFractionDigits)}
{formattedAmount}
{showSymbol && (
<span className={cn('ml-1 text-muted-foreground', symbolClassName)}>
{token.symbol}
Expand Down
3 changes: 1 addition & 2 deletions hooks/use-batch-create.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client'

import { useState, useCallback, useRef } from 'react'
import { createStreamsBatch as createStreamsBatchCall } from '@/lib/contract'
import { useState, useCallback, useRef, useEffect } from 'react'
import { createStreamsBatch as createStreamsBatchCall } from '@/lib/contract'
import { createStream as createStreamCall } from '@/lib/contract'
import { invalidateStreams } from '@/hooks/use-streams'
import { useWallet } from '@/hooks/use-wallet'
Expand Down
Loading