clean up ui

This commit is contained in:
9qeklajc
2026-03-23 22:20:36 +01:00
parent 4ee654331f
commit 750193e99d
5 changed files with 260 additions and 30 deletions
+39
View File
@@ -0,0 +1,39 @@
'use client';
import { useState, useEffect } from 'react';
import * as React from 'react';
import { Input } from '@/components/ui/input';
interface ApiKeyInputProps extends React.ComponentProps<'input'> {
onApiKeyChange: (apiKey: string) => void;
}
export function ApiKeyInput({
value,
onApiKeyChange,
...props
}: ApiKeyInputProps) {
const [internalValue, setInternalValue] = useState(value || '');
useEffect(() => {
setInternalValue(value || '');
}, [value]);
useEffect(() => {
const handler = setTimeout(() => {
onApiKeyChange(internalValue as string);
}, 300);
return () => clearTimeout(handler);
}, [internalValue, onApiKeyChange]);
return (
<Input
value={internalValue}
onChange={(e) => setInternalValue(e.target.value)}
placeholder='sk-...'
className='font-mono text-sm'
{...props}
/>
);
}
+17 -6
View File
@@ -2,6 +2,7 @@
import { useState } from 'react';
import { WalletService } from '@/lib/api/services/wallet';
import { ApiKeyInput } from './api-key-input';
import { Button } from '@/components/ui/button';
import {
Card,
@@ -250,12 +251,22 @@ export function ChildKeyCreator({
<Label className='text-muted-foreground text-[0.7rem] tracking-wider'>
Parent API Key
</Label>
<Input
value={activeApiKey}
onChange={(e) => handleApiKeyChange(e.target.value)}
placeholder='sk-...'
className='font-mono text-sm'
/>
<div className='flex gap-2'>
<div className='flex-1'>
<ApiKeyInput
value={activeApiKey}
onApiKeyChange={handleApiKeyChange}
/>
</div>
<Button
variant='outline'
size='icon'
onClick={() => navigator.clipboard.writeText(activeApiKey)}
disabled={!activeApiKey}
>
<Copy className='h-4 w-4' />
</Button>
</div>
</div>
)}
@@ -11,6 +11,7 @@ import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { KeyOptions } from '@/components/key-options';
import { WalletBalanceStats } from './wallet-balance-stats';
import { ApiKeyInput } from '../api-key-input';
import type { ChildKeyInfo, WalletSnapshot } from './key-info-details';
export type RefundReceipt = {
@@ -271,7 +272,7 @@ export function CashuPaymentWorkflow({
onWalletInfoUpdated?.(null);
}
},
[apiKey]
[apiKey, onWalletInfoUpdated]
);
const showManageDetails = hasInteractedManage || Boolean(walletInfo);
@@ -342,11 +343,9 @@ export function CashuPaymentWorkflow({
)}
</header>
<div className='flex flex-col gap-2 sm:flex-row'>
<Input
<ApiKeyInput
value={apiKeyInput}
onChange={(event) => handleApiKeyChange(event.target.value)}
placeholder='sk-...'
className='font-mono text-sm'
onApiKeyChange={handleApiKeyChange}
onFocus={() => setHasInteractedManage(true)}
/>
<div className='flex gap-2'>
+2 -19
View File
@@ -4,6 +4,7 @@ import { type JSX, useState, useCallback, useEffect } from 'react';
import { Copy, RefreshCcw, RotateCcw, Trash2 } from 'lucide-react';
import type { RefundReceipt } from './cashu-payment-workflow';
import { toast } from 'sonner';
import { ApiKeyInput } from '../api-key-input';
import {
Card,
CardContent,
@@ -13,7 +14,6 @@ import {
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { WalletService } from '@/lib/api/services/wallet';
export type ChildKeyInfo = {
@@ -188,12 +188,7 @@ export function KeyInfoDetails({
</CardHeader>
<CardContent>
<div className='flex flex-col gap-2 sm:flex-row'>
<Input
value={apiKeyInput}
onChange={(e) => handleKeyChange(e.target.value)}
placeholder='sk-...'
className='font-mono text-sm'
/>
<ApiKeyInput value={apiKeyInput} onApiKeyChange={handleKeyChange} />
<div className='flex gap-2'>
<Button
variant='outline'
@@ -425,18 +420,6 @@ export function KeyInfoDetails({
)}
<div className='flex justify-center gap-4'>
<Button
variant='ghost'
size='sm'
onClick={handleRefresh}
disabled={isRefreshing}
className='text-muted-foreground'
>
<RefreshCcw
className={`mr-2 h-3 w-3 ${isRefreshing ? 'animate-spin' : ''}`}
/>
Last synced: {new Date().toLocaleTimeString()}
</Button>
<Button
onClick={handleRefund}
disabled={isRefunding || !apiKeyInput}
+198
View File
@@ -0,0 +1,198 @@
'use client';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Copy, RotateCcw } from 'lucide-react';
import type { WalletSnapshot } from './key-info-details';
import { toast } from 'sonner';
interface KeyInfoDisplayProps {
walletInfo: WalletSnapshot;
onResetSpent?: (childKey: string) => Promise<void>;
isResetting?: string | null;
}
const formatSats = (msats: number) =>
new Intl.NumberFormat('en-US').format(Math.floor(msats / 1000));
const formatMsats = (msats: number) =>
new Intl.NumberFormat('en-US').format(msats);
const formatDate = (timestamp: number | null) =>
timestamp ? new Date(timestamp * 1000).toLocaleDateString() : 'Never';
export function KeyInfoDisplay({
walletInfo,
onResetSpent,
isResetting,
}: KeyInfoDisplayProps) {
const handleCopy = (value: string) => {
navigator.clipboard.writeText(value);
toast.success('Copied to clipboard');
};
return (
<div className='space-y-4'>
<div className='grid gap-4 md:grid-cols-2'>
<Card>
<CardHeader className='pb-2'>
<CardTitle className='text-lg'>Status & Identity</CardTitle>
</CardHeader>
<CardContent className='space-y-4'>
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>Type</span>
<Badge variant={walletInfo.isChild ? 'secondary' : 'default'}>
{walletInfo.isChild ? 'Child Key' : 'Parent Key'}
</Badge>
</div>
{walletInfo.parentKey && (
<div className='space-y-1'>
<span className='text-muted-foreground text-xs tracking-wider'>
Parent Key
</span>
<div className='flex items-center gap-2'>
<code className='bg-muted flex-1 rounded px-2 py-1 font-mono text-xs break-all'>
{walletInfo.parentKey}
</code>
<Button
variant='ghost'
size='icon'
className='h-8 w-8'
onClick={() => handleCopy(walletInfo.parentKey!)}
>
<Copy className='h-4 w-4' />
</Button>
</div>
</div>
)}
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>Validity</span>
<span className='text-sm font-medium'>
{formatDate(walletInfo.validityDate)}
</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className='pb-2'>
<CardTitle className='text-lg'>Infos</CardTitle>
</CardHeader>
<CardContent className='space-y-4'>
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>
Spendable Balance
</span>
<span className='text-primary font-mono text-sm font-medium'>
{formatSats(walletInfo.balanceMsats)} sats
</span>
</div>
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>
Total Requests
</span>
<span className='font-mono text-sm font-medium'>
{walletInfo.totalRequests}
</span>
</div>
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>Total Spent</span>
<div className='text-right'>
<p className='font-mono text-sm font-medium'>
{formatSats(walletInfo.totalSpent)} sats
</p>
<p className='text-muted-foreground font-mono text-[0.6rem]'>
{formatMsats(walletInfo.totalSpent)} msats
</p>
</div>
</div>
{walletInfo.balanceLimit !== null && (
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>
Spend Limit
</span>
<span className='font-mono text-sm font-medium'>
{formatSats(walletInfo.balanceLimit)} sats
</span>
</div>
{walletInfo.balanceLimitReset && (
<div className='flex items-center justify-between'>
<span className='text-muted-foreground text-sm'>
Reset Policy
</span>
<Badge variant='outline' className='capitalize'>
{walletInfo.balanceLimitReset}
</Badge>
</div>
)}
</div>
)}
</CardContent>
</Card>
</div>
{!walletInfo.isChild &&
walletInfo.childKeys &&
walletInfo.childKeys.length > 0 && (
<Card>
<CardHeader>
<CardTitle className='text-lg'>
Child Keys ({walletInfo.childKeys.length})
</CardTitle>
<CardDescription>
Secondary keys using this account&apos;s balance
</CardDescription>
</CardHeader>
<CardContent>
<div className='space-y-4'>
{walletInfo.childKeys.map((ck) => (
<div
key={ck.api_key}
className='space-y-3 rounded-lg border p-4'
>
<div className='flex items-center justify-between gap-4'>
<code className='bg-muted flex-1 rounded px-2 py-1 font-mono text-xs break-all'>
{ck.api_key}
</code>
<div className='flex gap-1'>
<Button
variant='ghost'
size='icon'
className='h-8 w-8'
onClick={() => handleCopy(ck.api_key)}
>
<Copy className='h-4 w-4' />
</Button>
{onResetSpent && (
<Button
variant='ghost'
size='icon'
className='text-destructive h-8 w-8'
title='Reset consumption'
disabled={isResetting === ck.api_key}
onClick={() => onResetSpent(ck.api_key)}
>
{isResetting === ck.api_key ? (
<RotateCcw className='h-4 w-4 animate-spin' />
) : (
<RotateCcw className='h-4 w-4' />
)}
</Button>
)}
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
);
}