Skip to content

Commit 870f6a5

Browse files
authored
Merge pull request #407 from adedamolamiposi/feat/accessibility-wallet-payments-fixes
2 parents 91862f7 + e26b080 commit 870f6a5

3 files changed

Lines changed: 137 additions & 17 deletions

File tree

app/(merchant)/payments/page.tsx

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"use client";
22

3-
import { useState, memo, useMemo } from 'react';
3+
import { useState, memo, useMemo, useEffect } from 'react';
4+
import { useForm } from 'react-hook-form';
5+
import { zodResolver } from '@hookform/resolvers/zod';
6+
import { editPaymentLinkSchema, type EditPaymentLinkFormValues } from '@/lib/utils/validation';
47
import { Card, CardContent, CardHeader, CardTitle, CardDescription, Button, Input, Label } from '@/components/ui';
58
import { CopyAddress, EmptyState, ErrorDisplay } from '@/components/shared';
69
import { PageHeader } from '@/components/shared/PageHeader';
@@ -166,8 +169,24 @@ export default function PaymentsPage() {
166169
resetForm();
167170
};
168171

169-
const handleEditSubmit = (e: React.FormEvent) => {
170-
e.preventDefault();
172+
const { register: registerEdit, handleSubmit: handleEditSubmitForm, reset: resetEditForm, formState: { errors: editErrors } } = useForm<EditPaymentLinkFormValues>({
173+
resolver: zodResolver(editPaymentLinkSchema),
174+
});
175+
176+
useEffect(() => {
177+
if (editingLink) {
178+
resetEditForm({
179+
label: editingLink.source ?? '',
180+
amount: editingLink.amountUsdc ? String(editingLink.amountUsdc) : '',
181+
currency: editingLink.amountUsdc ? 'USDC' : 'XLM',
182+
expiry: '',
183+
redirectUrl: '',
184+
reference: '',
185+
});
186+
}
187+
}, [editingLink, resetEditForm]);
188+
189+
const handleEditSubmit = (data: EditPaymentLinkFormValues) => {
171190
notifySuccess('Payment link updated successfully');
172191
setEditingLink(null);
173192
};
@@ -227,10 +246,16 @@ export default function PaymentsPage() {
227246

228247
<div className="space-y-3">
229248
<Label>Payment Mode</Label>
230-
<div className="flex rounded-lg border border-border/50 bg-background/50 p-0.5">
249+
<div
250+
role="tablist"
251+
aria-label="Payment mode"
252+
className="flex rounded-lg border border-border/50 bg-background/50 p-0.5"
253+
>
231254
<button
232255
type="button"
233-
className={`flex-1 rounded-md py-2 text-sm font-medium transition-colors ${
256+
role="tab"
257+
aria-selected={currencyMode === 'single'}
258+
className={`flex-1 rounded-md py-2 text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
234259
currencyMode === 'single'
235260
? 'bg-primary text-primary-foreground shadow-sm'
236261
: 'text-muted-foreground hover:text-foreground'
@@ -241,7 +266,9 @@ export default function PaymentsPage() {
241266
</button>
242267
<button
243268
type="button"
244-
className={`flex-1 rounded-md py-2 text-sm font-medium transition-colors ${
269+
role="tab"
270+
aria-selected={currencyMode === 'multi'}
271+
className={`flex-1 rounded-md py-2 text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
245272
currencyMode === 'multi'
246273
? 'bg-primary text-primary-foreground shadow-sm'
247274
: 'text-muted-foreground hover:text-foreground'
@@ -463,21 +490,86 @@ export default function PaymentsPage() {
463490

464491
{/* Edit Dialog */}
465492
<Dialog open={!!editingLink} onOpenChange={(open) => !open && setEditingLink(null)}>
466-
<DialogContent className="sm:max-w-[425px] bg-card border-border/50">
493+
<DialogContent className="sm:max-w-[425px] bg-card border-border/50 max-h-[85dvh] overflow-y-auto">
467494
<DialogHeader>
468495
<DialogTitle>Edit Payment Link</DialogTitle>
469496
<DialogDescription>Update details for {editingLink?.source}</DialogDescription>
470497
</DialogHeader>
471-
<form onSubmit={handleEditSubmit} className="space-y-4 py-2">
498+
<form onSubmit={handleEditSubmitForm(handleEditSubmit)} className="space-y-4 py-2">
472499
<div className="space-y-2">
473500
<Label htmlFor="edit-title">Title / Label</Label>
474-
<Input id="edit-title" defaultValue={editingLink?.source ?? ''} />
501+
<Input
502+
id="edit-title"
503+
className="bg-background/50 border-border/50"
504+
{...registerEdit('label')}
505+
/>
506+
{editErrors.label && (
507+
<p className="text-xs text-destructive mt-1">{editErrors.label.message}</p>
508+
)}
509+
</div>
510+
511+
<div className="grid grid-cols-2 gap-3">
512+
<div className="space-y-2">
513+
<Label htmlFor="edit-amount">Amount (Optional)</Label>
514+
<Input
515+
id="edit-amount"
516+
type="number"
517+
placeholder="0.00"
518+
className="bg-background/50 border-border/50"
519+
{...registerEdit('amount')}
520+
/>
521+
{editErrors.amount && (
522+
<p className="text-xs text-destructive mt-1">{editErrors.amount.message}</p>
523+
)}
524+
</div>
525+
<div className="space-y-2">
526+
<Label htmlFor="edit-currency">Currency</Label>
527+
<select
528+
id="edit-currency"
529+
className="flex h-10 w-full rounded-md border border-input border-border/50 bg-background/50 px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
530+
{...registerEdit('currency')}
531+
>
532+
<option value="USDC">USDC</option>
533+
<option value="XLM">XLM</option>
534+
<option value="USDT">USDT</option>
535+
</select>
536+
</div>
537+
</div>
538+
539+
<div className="space-y-2">
540+
<Label htmlFor="edit-reference">Internal Reference</Label>
541+
<Input
542+
id="edit-reference"
543+
placeholder="e.g. INV-2026-001"
544+
className="bg-background/50 border-border/50"
545+
{...registerEdit('reference')}
546+
/>
547+
</div>
548+
549+
<div className="space-y-2">
550+
<Label htmlFor="edit-expiry">Expiry Date</Label>
551+
<Input
552+
id="edit-expiry"
553+
type="date"
554+
className="bg-background/50 border-border/50"
555+
{...registerEdit('expiry')}
556+
/>
475557
</div>
558+
476559
<div className="space-y-2">
477-
<Label htmlFor="edit-amount">Amount (USDC)</Label>
478-
<Input id="edit-amount" type="number" defaultValue={editingLink?.amountUsdc ?? 0} />
560+
<Label htmlFor="edit-redirect">Redirect URL (On success)</Label>
561+
<Input
562+
id="edit-redirect"
563+
placeholder="https://yourstore.com/thank-you"
564+
className="bg-background/50 border-border/50"
565+
{...registerEdit('redirectUrl')}
566+
/>
567+
{editErrors.redirectUrl && (
568+
<p className="text-xs text-destructive mt-1">{editErrors.redirectUrl.message}</p>
569+
)}
479570
</div>
480-
<DialogFooter>
571+
572+
<DialogFooter className="pt-4">
481573
<Button type="button" variant="ghost" onClick={() => setEditingLink(null)}>Cancel</Button>
482574
<Button type="submit">Save Changes</Button>
483575
</DialogFooter>

app/(merchant)/wallet/page.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import dynamic from 'next/dynamic';
4+
import { QRCodeSVG } from 'qrcode.react';
45
import {
56
Card,
67
CardContent,
@@ -81,9 +82,20 @@ export default function WalletPage() {
8182
success("Balances updated");
8283
}, [refreshBalances, success]);
8384

84-
const primaryBalance = balances.length > 0
85-
? balances.reduce((max, b) => parseFloat(b.balance) > parseFloat(max.balance) ? b : max, balances[0])
86-
: null;
85+
const primaryBalance = useMemo(() => {
86+
if (balances.length === 0) return null;
87+
88+
const USDC_priority = balances.find((b) => b.assetCode === 'USDC');
89+
if (USDC_priority) return USDC_priority;
90+
91+
const USDT_priority = balances.find((b) => b.assetCode === 'USDT');
92+
if (USDT_priority) return USDT_priority;
93+
94+
return balances.reduce(
95+
(max, b) => parseFloat(b.balance) > parseFloat(max.balance) ? b : max,
96+
balances[0]
97+
);
98+
}, [balances]);
8799

88100
if (balancesError) {
89101
return (
@@ -344,8 +356,13 @@ export default function WalletPage() {
344356
<DialogDescription>Scan to transfer Stellar assets</DialogDescription>
345357
</DialogHeader>
346358
<div className="flex flex-col items-center py-4 space-y-4">
347-
<div className="w-48 h-48 bg-muted rounded-xl flex items-center justify-center border border-border">
348-
<QrCode className="w-32 h-32 text-foreground/80" />
359+
<div className="w-48 h-48 bg-white rounded-xl flex items-center justify-center border border-border p-2">
360+
<QRCodeSVG
361+
value={address ? `web+stellar:pay?destination=${address}` : ''}
362+
size={160}
363+
level="M"
364+
includeMargin={false}
365+
/>
349366
</div>
350367
<p className="text-xs text-muted-foreground font-mono break-all max-w-full">
351368
{address}

lib/utils/validation.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,14 @@ export const merchantProfileSchema = z.object({
3333
});
3434

3535
export type MerchantProfileFormValues = z.infer<typeof merchantProfileSchema>;
36+
37+
export const editPaymentLinkSchema = z.object({
38+
label: z.string().min(1, 'Label is required'),
39+
amount: z.string().optional(),
40+
currency: z.enum(['USDC', 'XLM', 'USDT']).default('USDC'),
41+
expiry: z.string().optional(),
42+
redirectUrl: z.string().url('Invalid URL').or(z.literal('')).optional(),
43+
reference: z.string().optional(),
44+
});
45+
46+
export type EditPaymentLinkFormValues = z.infer<typeof editPaymentLinkSchema>;

0 commit comments

Comments
 (0)