-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from 0xdevcollins/feat/treasury-deposit-history
Feat/treasury deposit history
- Loading branch information
Showing
13 changed files
with
1,106 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
'use client' | ||
|
||
import { useState } from 'react' | ||
import { Form, useForm } from 'react-hook-form' | ||
import { zodResolver } from '@hookform/resolvers/zod' | ||
import * as z from 'zod' | ||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" | ||
import { Input } from "@/components/ui/input" | ||
import { Button } from "@/components/ui/button" | ||
import { FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@/components/ui/form' | ||
import { useToast } from 'hooks/use-toast' | ||
|
||
const formSchema = z.object({ | ||
threshold: z.number().min(0, "Threshold must be a positive number"), | ||
emails: z.array(z.string().email("Invalid email address")).min(1, "At least one email is required"), | ||
phoneNumbers: z.array(z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number")).optional(), | ||
}) | ||
|
||
export default function WalletSettingsPage() { | ||
const { toast } = useToast() | ||
const [isLoading, setIsLoading] = useState(false) | ||
|
||
const form = useForm<z.infer<typeof formSchema>>({ | ||
resolver: zodResolver(formSchema), | ||
defaultValues: { | ||
threshold: 0, | ||
emails: [''], | ||
phoneNumbers: [''], | ||
}, | ||
}) | ||
|
||
async function onSubmit(values: z.infer<typeof formSchema>) { | ||
setIsLoading(true) | ||
try { | ||
const response = await fetch('', { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify(values), | ||
}) | ||
if (!response.ok) throw new Error('Failed to save settings') | ||
toast({ | ||
title: "Settings saved", | ||
description: "Your wallet alert settings have been updated successfully.", | ||
}) | ||
} catch (error) { | ||
toast({ | ||
title: "Error", | ||
description: "Failed to save settings. Please try again.", | ||
variant: "destructive", | ||
}) | ||
} finally { | ||
setIsLoading(false) | ||
} | ||
} | ||
|
||
return ( | ||
<Card className="w-full max-w-2xl mx-auto"> | ||
<CardHeader> | ||
<CardTitle>Wallet Alert Settings</CardTitle> | ||
<CardDescription>Configure low-balance alerts for the faucet wallet</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<Form {...form}> | ||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | ||
<FormField | ||
control={form.control} | ||
name="threshold" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Low Balance Threshold (BSV)</FormLabel> | ||
<FormControl> | ||
<Input type="number" step="0.00000001" {...field} onChange={e => field.onChange(parseFloat(e.target.value))} /> | ||
</FormControl> | ||
<FormDescription> | ||
Set the wallet balance threshold that triggers an alert | ||
</FormDescription> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
control={form.control} | ||
name="emails" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Alert Email Addresses</FormLabel> | ||
<FormControl> | ||
<Input {...field} placeholder="Enter email addresses separated by commas" /> | ||
</FormControl> | ||
<FormDescription> | ||
Enter the email addresses that should receive alerts | ||
</FormDescription> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
control={form.control} | ||
name="phoneNumbers" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Alert Phone Numbers (Optional)</FormLabel> | ||
<FormControl> | ||
<Input {...field} placeholder="Enter phone numbers separated by commas" /> | ||
</FormControl> | ||
<FormDescription> | ||
Enter the phone numbers that should receive SMS alerts (if enabled) | ||
</FormDescription> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<Button type="submit" disabled={isLoading}> | ||
{isLoading ? "Saving..." : "Save Settings"} | ||
</Button> | ||
</form> | ||
</Form> | ||
</CardContent> | ||
</Card> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { NextResponse } from 'next/server' | ||
|
||
export type DepositTransaction = { | ||
date: string | ||
txid: string | ||
beefTx: string | ||
vout: number | ||
txType: 'deposit' | ||
amount: number | ||
} | ||
|
||
const mockDepositHistory: DepositTransaction[] = [ | ||
{ | ||
date: '2023-05-15T10:30:00', | ||
txid: '1a2b3c4d5e6f7g8h9i0j', | ||
beefTx: 'beef1234567890abcdef', | ||
vout: 0, | ||
txType: 'deposit', | ||
amount: 1000000 | ||
}, | ||
{ | ||
date: '2023-05-14T14:45:00', | ||
txid: '2b3c4d5e6f7g8h9i0j1a', | ||
beefTx: 'beef0987654321fedcba', | ||
vout: 1, | ||
txType: 'deposit', | ||
amount: 500000 | ||
}, | ||
{ | ||
date: '2023-05-13T09:15:00', | ||
txid: '3c4d5e6f7g8h9i0j1a2b', | ||
beefTx: 'beef2468135790acegik', | ||
vout: 2, | ||
txType: 'deposit', | ||
amount: 750000 | ||
}, | ||
] | ||
|
||
export async function GET() { | ||
await new Promise(resolve => setTimeout(resolve, 1000)) | ||
|
||
// try { | ||
// const response = await fetch('', { | ||
// headers: { | ||
// 'Authorization': `Bearer ${process.env.API_TOKEN}`, | ||
// }, | ||
// }) | ||
// if (!response.ok) { | ||
// throw new Error('Failed to fetch deposit history') | ||
// } | ||
// const data: DepositTransaction[] = await response.json() | ||
// return NextResponse.json(data) | ||
// } catch (error) { | ||
// console.error('Error fetching deposit history:', error) | ||
// return NextResponse.json({ error: 'Failed to fetch deposit history' }, { status: 500 }) | ||
// } | ||
|
||
return NextResponse.json(mockDepositHistory) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { Suspense } from 'react' | ||
import { format } from 'date-fns' | ||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" | ||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" | ||
import { Skeleton } from '@/components/ui/skeleton' | ||
import { DepositTransaction } from 'app/api/deposit-history/route' | ||
import { Alert, AlertTitle, AlertDescription } from './ui/alert' | ||
import { FileWarningIcon, RefreshCcwIcon } from 'lucide-react' | ||
|
||
async function getDepositHistory(): Promise<DepositTransaction[]> { | ||
const res = await fetch('http://localhost:3001/api/deposit-history', { cache: 'no-store' }) | ||
if (!res.ok) { | ||
throw new Error('Failed to fetch deposit history') | ||
} | ||
return res.json() | ||
} | ||
|
||
function DepositHistoryTable({ depositHistory }: { depositHistory: DepositTransaction[] }) { | ||
return ( | ||
<Table> | ||
<TableHeader> | ||
<TableRow> | ||
<TableHead>Date</TableHead> | ||
<TableHead>Txid</TableHead> | ||
<TableHead>Beef TX</TableHead> | ||
<TableHead>Vout</TableHead> | ||
<TableHead>TX Type</TableHead> | ||
<TableHead className="text-right">Amount (BSV)</TableHead> | ||
</TableRow> | ||
</TableHeader> | ||
<TableBody> | ||
{depositHistory.map((transaction, index) => ( | ||
<TableRow key={index}> | ||
<TableCell>{format(new Date(transaction.date), 'yyyy-MM-dd HH:mm:ss')}</TableCell> | ||
<TableCell className="font-mono">{transaction.txid.slice(0, 10)}...</TableCell> | ||
<TableCell className="font-mono">{transaction.beefTx.slice(0, 10)}...</TableCell> | ||
<TableCell>{transaction.vout}</TableCell> | ||
<TableCell>{transaction.txType}</TableCell> | ||
<TableCell className="text-right">{(transaction.amount / 100000000).toFixed(8)}</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
) | ||
} | ||
|
||
function LoadingState() { | ||
return ( | ||
<div className="space-y-3"> | ||
<Skeleton className="h-4 w-[250px]" /> | ||
<Skeleton className="h-4 w-[200px]" /> | ||
<Skeleton className="h-4 w-[150px]" /> | ||
<Skeleton className="h-4 w-[200px]" /> | ||
</div> | ||
) | ||
} | ||
|
||
function ErrorState({ error }: { error: Error }) { | ||
return ( | ||
<Alert variant="destructive"> | ||
<FileWarningIcon className="h-4 w-4" /> | ||
<AlertTitle>Error</AlertTitle> | ||
<AlertDescription> | ||
{error.message} | ||
<button | ||
onClick={() => window.location.reload()} | ||
className="ml-2 inline-flex items-center px-2 py-1 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" | ||
> | ||
<RefreshCcwIcon className="h-4 w-4 mr-1" /> Retry | ||
</button> | ||
</AlertDescription> | ||
</Alert> | ||
) | ||
} | ||
|
||
export default async function AdminTreasuryHistory() { | ||
const depositHistoryPromise = getDepositHistory() | ||
|
||
return ( | ||
<Card className="w-full"> | ||
<CardHeader> | ||
<CardTitle className="text-2xl font-bold">Treasury - Deposit History</CardTitle> | ||
<CardDescription>View all deposit transactions in the treasury</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<Suspense fallback={<LoadingState />}> | ||
<DepositHistoryContent promise={depositHistoryPromise} /> | ||
</Suspense> | ||
</CardContent> | ||
</Card> | ||
) | ||
} | ||
|
||
async function DepositHistoryContent({ promise }: { promise: Promise<DepositTransaction[]> }) { | ||
try { | ||
const depositHistory = await promise | ||
return <DepositHistoryTable depositHistory={depositHistory} /> | ||
} catch (error) { | ||
return <ErrorState error={error instanceof Error ? error : new Error('Unknown error')} /> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import * as React from "react" | ||
import { cva, type VariantProps } from "class-variance-authority" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
const alertVariants = cva( | ||
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", | ||
{ | ||
variants: { | ||
variant: { | ||
default: "bg-background text-foreground", | ||
destructive: | ||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", | ||
}, | ||
}, | ||
defaultVariants: { | ||
variant: "default", | ||
}, | ||
} | ||
) | ||
|
||
const Alert = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> | ||
>(({ className, variant, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
role="alert" | ||
className={cn(alertVariants({ variant }), className)} | ||
{...props} | ||
/> | ||
)) | ||
Alert.displayName = "Alert" | ||
|
||
const AlertTitle = React.forwardRef< | ||
HTMLParagraphElement, | ||
React.HTMLAttributes<HTMLHeadingElement> | ||
>(({ className, ...props }, ref) => ( | ||
<h5 | ||
ref={ref} | ||
className={cn("mb-1 font-medium leading-none tracking-tight", className)} | ||
{...props} | ||
/> | ||
)) | ||
AlertTitle.displayName = "AlertTitle" | ||
|
||
const AlertDescription = React.forwardRef< | ||
HTMLParagraphElement, | ||
React.HTMLAttributes<HTMLParagraphElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn("text-sm [&_p]:leading-relaxed", className)} | ||
{...props} | ||
/> | ||
)) | ||
AlertDescription.displayName = "AlertDescription" | ||
|
||
export { Alert, AlertTitle, AlertDescription } |
Oops, something went wrong.