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
22 changes: 22 additions & 0 deletions app/app/analytics/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import { ChartSkeleton } from '@/components/analytics/charts'

export default function AnalyticsLoading() {
return (
<div className="space-y-6">
{/* Header skeleton */}
<div className="space-y-2">
<div className="h-8 w-32 animate-pulse rounded bg-muted" />
<div className="h-4 w-96 animate-pulse rounded bg-muted" />
</div>

{/* Charts skeleton */}
<div className="grid gap-6 grid-cols-1 lg:grid-cols-2">
{Array.from({ length: 4 }).map((_, i) => (
<ChartSkeleton key={i} />
))}
</div>
</div>
)
}
6 changes: 3 additions & 3 deletions app/app/create/batch/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export default function BatchCreatePage() {
)}

{parseErrors.length > 0 && (
<div className="rounded-lg border border-yellow-500/40 bg-yellow-500/10 p-4 text-sm text-yellow-700">
<div className="rounded-lg border border-yellow-500/40 bg-yellow-500/10 p-4 text-sm text-yellow-600 dark:text-yellow-400">
<p className="font-semibold">CSV parse warnings</p>
<ul className="mt-2 list-disc pl-5 space-y-1">
{parseErrors.map((message, index) => (
Expand Down Expand Up @@ -344,7 +344,7 @@ export default function BatchCreatePage() {
{rows.map((row) => (
<tr
key={row.index}
className={row.errors.length > 0 ? 'bg-red-50' : undefined}
className={row.errors.length > 0 ? 'bg-destructive/5 dark:bg-destructive/10' : undefined}
>
<td className="py-3 pr-3 font-mono text-xs text-muted-foreground">
{row.index}
Expand All @@ -361,7 +361,7 @@ export default function BatchCreatePage() {
</td>
<td className="py-3 pr-3">
{row.errors.length === 0 ? (
<span className="rounded-full bg-emerald-100 px-2 py-1 text-emerald-700">
<span className="rounded-full bg-emerald-500/10 px-2 py-1 text-emerald-600 dark:text-emerald-400">
Valid
</span>
) : (
Expand Down
30 changes: 30 additions & 0 deletions app/app/create/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

export default function CreateStreamLoading() {
return (
<div className="space-y-6 max-w-2xl">
{/* Header skeleton */}
<div className="space-y-2">
<div className="h-8 w-48 animate-pulse rounded bg-muted" />
<div className="h-4 w-full animate-pulse rounded bg-muted" />
</div>

{/* Form section skeleton */}
<div className="rounded-lg border border-border p-6 space-y-4">
{/* Form fields skeleton */}
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="space-y-2">
<div className="h-4 w-32 animate-pulse rounded bg-muted" />
<div className="h-10 w-full animate-pulse rounded bg-muted" />
</div>
))}

{/* Button skeleton */}
<div className="flex gap-3 pt-4">
<div className="h-10 w-32 animate-pulse rounded bg-muted" />
<div className="h-10 w-32 animate-pulse rounded bg-muted" />
</div>
</div>
</div>
)
}
26 changes: 26 additions & 0 deletions app/app/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'

import { DashboardStatsSkeleton } from '@/components/streams/dashboard-stats'
import { StreamCardSkeleton } from '@/components/streams/stream-card'

export default function DashboardLoading() {
return (
<div className="space-y-6">
{/* Header skeleton */}
<div className="space-y-2">
<div className="h-8 w-32 animate-pulse rounded bg-muted" />
<div className="h-4 w-96 animate-pulse rounded bg-muted" />
</div>

{/* Stats skeleton */}
<DashboardStatsSkeleton />

{/* Stream list skeleton */}
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<StreamCardSkeleton key={i} />
))}
</div>
</div>
)
}
28 changes: 28 additions & 0 deletions app/app/settings/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client'

export default function SettingsLoading() {
return (
<div className="space-y-6 max-w-2xl">
{/* Header skeleton */}
<div className="space-y-2">
<div className="h-8 w-32 animate-pulse rounded bg-muted" />
<div className="h-4 w-96 animate-pulse rounded bg-muted" />
</div>

{/* Settings sections skeleton */}
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="rounded-lg border border-border p-6 space-y-4">
<div className="h-6 w-48 animate-pulse rounded bg-muted" />
<div className="space-y-3">
{Array.from({ length: 2 }).map((_, j) => (
<div key={j} className="space-y-2">
<div className="h-4 w-32 animate-pulse rounded bg-muted" />
<div className="h-10 w-full animate-pulse rounded bg-muted" />
</div>
))}
</div>
</div>
))}
</div>
)
}
39 changes: 39 additions & 0 deletions app/app/stream/[id]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

export default function StreamDetailLoading() {
return (
<div className="space-y-6">
{/* Back button skeleton */}
<div className="h-6 w-32 animate-pulse rounded bg-muted" />

{/* Header section skeleton */}
<div className="rounded-lg border border-border p-6 space-y-4">
<div className="h-8 w-64 animate-pulse rounded bg-muted" />
<div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="space-y-2">
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
<div className="h-6 w-32 animate-pulse rounded bg-muted" />
</div>
))}
</div>
</div>

{/* Timeline/Activity section skeleton */}
<div className="rounded-lg border border-border p-6 space-y-4">
<div className="h-6 w-40 animate-pulse rounded bg-muted" />
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="flex gap-4 p-3 border border-border rounded">
<div className="h-8 w-8 animate-pulse rounded-full bg-muted" />
<div className="flex-1 space-y-2">
<div className="h-4 w-32 animate-pulse rounded bg-muted" />
<div className="h-3 w-48 animate-pulse rounded bg-muted" />
</div>
</div>
))}
</div>
</div>
</div>
)
}
22 changes: 22 additions & 0 deletions app/app/streams/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import { StreamCardSkeleton } from '@/components/streams/stream-card'

export default function StreamsLoading() {
return (
<div className="space-y-6">
{/* Header skeleton */}
<div className="space-y-2">
<div className="h-8 w-32 animate-pulse rounded bg-muted" />
<div className="h-4 w-96 animate-pulse rounded bg-muted" />
</div>

{/* Stream list skeleton */}
<div className="space-y-3">
{Array.from({ length: 5 }).map((_, i) => (
<StreamCardSkeleton key={i} />
))}
</div>
</div>
)
}
1 change: 1 addition & 0 deletions components/streams/dashboard-stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function DashboardStats({ sent, received }: DashboardStatsProps) {
amount={topWithdrawable.amount}
token={topWithdrawable.token}
maxFractionDigits={2}
truncate
/>
) : (
<span className="text-muted-foreground">—</span>
Expand Down
10 changes: 9 additions & 1 deletion components/ui/token-amount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface TokenAmountProps {
showSymbol?: boolean
/** Display amount in compact notation (e.g., "1.2K"). @default false */
compact?: boolean
/** Truncate the display with ellipsis. @default false */
truncate?: boolean
}

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

const fullValue = `${formattedAmount}${showSymbol ? ` ${token.symbol}` : ''}`

return (
<span className={cn('font-mono tabular-nums', className)}>
<span
className={cn('font-mono tabular-nums', truncate && 'truncate', className)}
title={truncate ? fullValue : undefined}
>
{formattedAmount}
{showSymbol && (
<span className={cn('ml-1 text-muted-foreground', symbolClassName)}>
Expand Down
40 changes: 29 additions & 11 deletions components/webhooks/webhook-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useState } from 'react'
import { Plus, Trash2, ToggleLeft, ToggleRight, Send, CheckCircle2, XCircle } from 'lucide-react'
import { Plus, Trash2, ToggleLeft, ToggleRight, Send, CheckCircle2, XCircle, Webhook } from 'lucide-react'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
Expand Down Expand Up @@ -74,7 +74,7 @@ export function WebhookSettings() {
return (
<div className="space-y-8">
{/* Add webhook */}
<div className="rounded-lg border border-border p-4 space-y-4">
<div id="register-webhook" className="rounded-lg border border-border p-4 space-y-4">
<h2 className="font-medium">Register a webhook</h2>

<div className="space-y-1.5">
Expand Down Expand Up @@ -124,11 +124,28 @@ export function WebhookSettings() {
</div>

{/* Registered webhooks */}
{webhooks.length > 0 && (
<div className="space-y-3">
<h2 className="font-medium">Registered webhooks</h2>
{webhooks.map((hook) => (
<div key={hook.id} className="rounded-lg border border-border p-4 space-y-2">
<div className="space-y-3">
<h2 className="font-medium">Registered webhooks</h2>
{webhooks.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-2xl border border-dashed border-border bg-card/40 px-6 py-16 text-center">
<span className="flex size-12 items-center justify-center rounded-xl bg-secondary text-primary">
<Webhook className="size-6" />
</span>
<h3 className="mt-4 font-medium">No webhooks registered yet</h3>
<p className="mt-1 max-w-xs text-sm text-muted-foreground text-pretty">
Register a webhook above to start receiving real-time event notifications for your streams.
</p>
<Button asChild className="mt-5 gap-1.5">
<a href="#register-webhook">
<Plus className="size-4" />
Register your first webhook
</a>
</Button>
</div>
) : (
<div className="space-y-3">
{webhooks.map((hook) => (
<div key={hook.id} className="rounded-lg border border-border p-4 space-y-2">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="truncate text-sm font-mono">{hook.url}</p>
Expand Down Expand Up @@ -173,10 +190,11 @@ export function WebhookSettings() {
</Button>
</div>
</div>
</div>
))}
</div>
)}
</div>
))}
</div>
)}
</div>

{/* Delivery history */}
{history.length > 0 && (
Expand Down