From e27ca25c6405be1baaffe4edb627eaec86f46226 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Mon, 28 Oct 2024 08:01:03 +0100 Subject: [PATCH 1/2] feat(wallet-settings): add wallet alert configuration page --- .../admin/wallet-settings/page.tsx | 121 +++++++++++ components/ui/form.tsx | 178 ++++++++++++++++ components/ui/label.tsx | 26 +++ components/ui/toast.tsx | 129 ++++++++++++ components/ui/toaster.tsx | 35 ++++ hooks/use-toast.ts | 194 ++++++++++++++++++ 6 files changed, 683 insertions(+) create mode 100644 app/(dashboard)/admin/wallet-settings/page.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 hooks/use-toast.ts diff --git a/app/(dashboard)/admin/wallet-settings/page.tsx b/app/(dashboard)/admin/wallet-settings/page.tsx new file mode 100644 index 0000000..7743965 --- /dev/null +++ b/app/(dashboard)/admin/wallet-settings/page.tsx @@ -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>({ + resolver: zodResolver(formSchema), + defaultValues: { + threshold: 0, + emails: [''], + phoneNumbers: [''], + }, + }) + + async function onSubmit(values: z.infer) { + 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 ( + + + Wallet Alert Settings + Configure low-balance alerts for the faucet wallet + + +
+ + ( + + Low Balance Threshold (BSV) + + field.onChange(parseFloat(e.target.value))} /> + + + Set the wallet balance threshold that triggers an alert + + + + )} + /> + ( + + Alert Email Addresses + + + + + Enter the email addresses that should receive alerts + + + + )} + /> + ( + + Alert Phone Numbers (Optional) + + + + + Enter the phone numbers that should receive SMS alerts (if enabled) + + + + )} + /> + + + +
+
+ ) +} \ No newline at end of file diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 0000000..ce264ae --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,178 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +