mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
clean up ui
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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'>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user