By clicking "Pay Now", you agree to our Terms of Service and acknowledge that this transaction is final.
diff --git a/components/bills/recent-billers.tsx b/components/bills/recent-billers.tsx
index 7dbe55b..5c1046a 100644
--- a/components/bills/recent-billers.tsx
+++ b/components/bills/recent-billers.tsx
@@ -123,9 +123,7 @@ export function RecentBillers({ billers, searchQuery, loading }: RecentBillersPr
{biller.category.replace('-', ' ')}
-
+
Pay Now
diff --git a/components/dashboard/transaction-history.tsx b/components/dashboard/transaction-history.tsx
index 19c2034..4f0ea3c 100644
--- a/components/dashboard/transaction-history.tsx
+++ b/components/dashboard/transaction-history.tsx
@@ -1,79 +1,397 @@
'use client'
+import { useMemo, useRef, useState } from 'react'
import { motion } from 'framer-motion'
-import { ArrowUp, ArrowDown, ArrowLeftRight, Clock, CheckCircle2, XCircle } from 'lucide-react'
+import {
+ ArrowDown,
+ ArrowUp,
+ ArrowUpDown,
+ CheckCircle2,
+ ChevronLeft,
+ ChevronRight,
+ Clock,
+ Eye,
+ Receipt,
+ RefreshCcw,
+ XCircle,
+} from 'lucide-react'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
interface Transaction {
id: string
- type: 'send' | 'receive' | 'swap'
- amount: string
- currency: string
- to?: string
- from?: string
+ date: string
+ type: 'onramp' | 'offramp' | 'billpay'
+ amount: number
+ asset: string
+ counterparty: string
status: 'pending' | 'completed' | 'failed'
- timestamp: string
}
+type SortField = 'date' | 'type' | 'asset' | 'amount' | 'status'
+type SortDirection = 'asc' | 'desc'
+type QuickFilter = 'all' | 'onramp' | 'offramp' | 'billpay' | 'failed'
+
+const PAGE_SIZE = 5
+
const mockTransactions: Transaction[] = [
{
- id: '1',
- type: 'send',
- amount: '15,000',
- currency: 'cNGN',
- to: '0x742d...35Cc',
+ id: 'ONR-240191',
+ date: '2026-02-26T08:22:00.000Z',
+ type: 'onramp',
+ amount: 15000,
+ asset: 'cNGN',
+ counterparty: 'From Zenith Bank',
status: 'completed',
- timestamp: '2 hours ago',
},
{
- id: '2',
- type: 'receive',
- amount: '0.0025',
- currency: 'BTC',
- from: '0x8a3f...9D2e',
+ id: 'OFF-240180',
+ date: '2026-02-26T07:40:00.000Z',
+ type: 'offramp',
+ amount: 8700,
+ asset: 'USDC',
+ counterparty: 'To MTN Mobile Money',
+ status: 'pending',
+ },
+ {
+ id: 'BIL-240178',
+ date: '2026-02-25T16:11:00.000Z',
+ type: 'billpay',
+ amount: 5500,
+ asset: 'cNGN',
+ counterparty: 'To IKEDC Electricity',
status: 'completed',
- timestamp: '5 hours ago',
},
{
- id: '3',
- type: 'swap',
- amount: '50,000',
- currency: 'cNGN → ETH',
+ id: 'ONR-240173',
+ date: '2026-02-25T11:35:00.000Z',
+ type: 'onramp',
+ amount: 25000,
+ asset: 'cNGN',
+ counterparty: 'From Access Bank',
status: 'pending',
- timestamp: '1 day ago',
},
{
- id: '4',
- type: 'send',
- amount: '5,000',
- currency: 'cNGN',
- to: '0x1a2b...3c4d',
+ id: 'OFF-240166',
+ date: '2026-02-24T19:02:00.000Z',
+ type: 'offramp',
+ amount: 12000,
+ asset: 'USDT',
+ counterparty: 'To Kuda Bank',
+ status: 'completed',
+ },
+ {
+ id: 'BIL-240162',
+ date: '2026-02-24T09:43:00.000Z',
+ type: 'billpay',
+ amount: 2100,
+ asset: 'cNGN',
+ counterparty: 'To Glo Airtime',
status: 'failed',
- timestamp: '2 days ago',
+ },
+ {
+ id: 'ONR-240158',
+ date: '2026-02-23T20:10:00.000Z',
+ type: 'onramp',
+ amount: 8000,
+ asset: 'cNGN',
+ counterparty: 'From GTBank',
+ status: 'completed',
+ },
+ {
+ id: 'BIL-240151',
+ date: '2026-02-23T08:37:00.000Z',
+ type: 'billpay',
+ amount: 4300,
+ asset: 'cNGN',
+ counterparty: 'To DSTV',
+ status: 'completed',
+ },
+ {
+ id: 'OFF-240144',
+ date: '2026-02-22T22:29:00.000Z',
+ type: 'offramp',
+ amount: 16000,
+ asset: 'USDC',
+ counterparty: 'To Opay Wallet',
+ status: 'completed',
+ },
+ {
+ id: 'ONR-240132',
+ date: '2026-02-22T10:04:00.000Z',
+ type: 'onramp',
+ amount: 10000,
+ asset: 'cNGN',
+ counterparty: 'From Moniepoint',
+ status: 'failed',
+ },
+ {
+ id: 'OFF-240120',
+ date: '2026-02-21T14:18:00.000Z',
+ type: 'offramp',
+ amount: 7300,
+ asset: 'USDT',
+ counterparty: 'To First Bank',
+ status: 'completed',
},
]
+const typeConfig: Record<
+ Transaction['type'],
+ { label: string; icon: typeof ArrowDown; iconClassName: string }
+> = {
+ onramp: {
+ label: 'Onramp',
+ icon: ArrowDown,
+ iconClassName: 'text-emerald-600 bg-emerald-500/10 border-emerald-500/30',
+ },
+ offramp: {
+ label: 'Offramp',
+ icon: ArrowUp,
+ iconClassName: 'text-amber-600 bg-amber-500/10 border-amber-500/30',
+ },
+ billpay: {
+ label: 'Bill Pay',
+ icon: Receipt,
+ iconClassName: 'text-violet-600 bg-violet-500/10 border-violet-500/30',
+ },
+}
+
+const statusConfig: Record
= {
+ completed: {
+ label: 'Completed',
+ className:
+ 'bg-emerald-500/12 text-emerald-700 border-emerald-500/35 dark:text-emerald-400 dark:border-emerald-500/45',
+ },
+ pending: {
+ label: 'Pending',
+ className:
+ 'bg-amber-500/12 text-amber-700 border-amber-500/35 dark:text-amber-400 dark:border-amber-500/45',
+ },
+ failed: {
+ label: 'Failed',
+ className:
+ 'bg-rose-500/12 text-rose-700 border-rose-500/35 dark:text-rose-400 dark:border-rose-500/45',
+ },
+}
+
+function SortHeader({
+ label,
+ field,
+ sortField,
+ onSortChange,
+}: {
+ label: string
+ field: SortField
+ sortField: SortField
+ onSortChange: (field: SortField) => void
+}) {
+ return (
+
+ )
+}
+
+function Pagination({
+ currentPage,
+ totalPages,
+ totalCount,
+ onPageChange,
+}: {
+ currentPage: number
+ totalPages: number
+ totalCount: number
+ onPageChange: (page: number) => void
+}) {
+ const start = totalCount === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1
+ const end = Math.min(currentPage * PAGE_SIZE, totalCount)
+
+ return (
+
+
+ Showing {start}-{end} of {totalCount}
+
+
+
+
+ {Array.from({ length: totalPages }).map((_, index) => {
+ const pageNumber = index + 1
+ const isActive = pageNumber === currentPage
+ return (
+
+ )
+ })}
+
+
+
+
+ )
+}
+
export function TransactionHistory() {
- const getIcon = (type: Transaction['type']) => {
- switch (type) {
- case 'send':
- return
- case 'receive':
- return
- case 'swap':
- return
+ const [quickFilter, setQuickFilter] = useState('all')
+ const [sortField, setSortField] = useState('date')
+ const [sortDirection, setSortDirection] = useState('desc')
+ const [page, setPage] = useState(1)
+ const [activeSwipeId, setActiveSwipeId] = useState(null)
+
+ const touchStartX = useRef(0)
+
+ const quickFilters: Array<{ key: QuickFilter; label: string }> = [
+ { key: 'all', label: 'All' },
+ { key: 'onramp', label: 'Onramp' },
+ { key: 'offramp', label: 'Offramp' },
+ { key: 'billpay', label: 'Bill Pay' },
+ { key: 'failed', label: 'Failed' },
+ ]
+
+ const filteredTransactions = useMemo(() => {
+ if (quickFilter === 'all') return mockTransactions
+ if (quickFilter === 'failed') return mockTransactions.filter((tx) => tx.status === 'failed')
+ return mockTransactions.filter((tx) => tx.type === quickFilter)
+ }, [quickFilter])
+
+ const sortedTransactions = useMemo(() => {
+ const valueByStatus: Record = {
+ completed: 3,
+ pending: 2,
+ failed: 1,
+ }
+
+ return [...filteredTransactions].sort((a, b) => {
+ let aValue: string | number = 0
+ let bValue: string | number = 0
+
+ switch (sortField) {
+ case 'date':
+ aValue = new Date(a.date).getTime()
+ bValue = new Date(b.date).getTime()
+ break
+ case 'type':
+ aValue = typeConfig[a.type].label
+ bValue = typeConfig[b.type].label
+ break
+ case 'asset':
+ aValue = a.asset
+ bValue = b.asset
+ break
+ case 'amount':
+ aValue = a.amount
+ bValue = b.amount
+ break
+ case 'status':
+ aValue = valueByStatus[a.status]
+ bValue = valueByStatus[b.status]
+ break
+ }
+
+ const result =
+ typeof aValue === 'string' && typeof bValue === 'string'
+ ? aValue.localeCompare(bValue)
+ : Number(aValue) - Number(bValue)
+
+ return sortDirection === 'asc' ? result : -result
+ })
+ }, [filteredTransactions, sortField, sortDirection])
+
+ const totalPages = Math.max(1, Math.ceil(sortedTransactions.length / PAGE_SIZE))
+ const currentPage = Math.min(page, totalPages)
+
+ const paginatedTransactions = useMemo(() => {
+ const start = (currentPage - 1) * PAGE_SIZE
+ return sortedTransactions.slice(start, start + PAGE_SIZE)
+ }, [currentPage, sortedTransactions])
+
+ const formatAmount = (value: number) => {
+ return value.toLocaleString('en-US', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })
+ }
+
+ const formatDate = (value: string) => {
+ return new Intl.DateTimeFormat('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ }).format(new Date(value))
+ }
+
+ const onSortChange = (field: SortField) => {
+ setPage(1)
+ if (sortField === field) {
+ setSortDirection((current) => (current === 'asc' ? 'desc' : 'asc'))
+ return
}
+ setSortField(field)
+ setSortDirection('desc')
+ }
+
+ const onFilterChange = (filter: QuickFilter) => {
+ setQuickFilter(filter)
+ setPage(1)
+ }
+
+ const onTouchStart = (xPosition: number) => {
+ touchStartX.current = xPosition
}
- const getStatusIcon = (status: Transaction['status']) => {
- switch (status) {
- case 'completed':
- return
- case 'pending':
- return
- case 'failed':
- return
+ const onTouchEnd = (xPosition: number, txId: string) => {
+ const swipeDistance = touchStartX.current - xPosition
+ if (swipeDistance > 40) {
+ setActiveSwipeId(txId)
+ return
}
+ if (swipeDistance < -40) setActiveSwipeId(null)
+ }
+
+ const renderStatusIcon = (status: Transaction['status']) => {
+ if (status === 'completed') return
+ if (status === 'pending') return
+ return
}
return (
@@ -82,55 +400,218 @@ export function TransactionHistory() {
animate={{ opacity: 1, y: 0 }}
className="bg-card rounded-2xl p-6 border border-border shadow-sm"
>
- Recent Transactions
-
- {mockTransactions.map((tx, index) => (
-
-
-
- {getIcon(tx.type)}
+
+
+
Transaction History
+
+ Track all onramp, offramp, and bill payments
+
+
+
+ {quickFilters.map((filter) => (
+
+ ))}
+
+
+
+
+
+
+
+ |
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ Action
+ |
+
+
+
+ {paginatedTransactions.map((tx, index) => {
+ const Icon = typeConfig[tx.type].icon
+ return (
+
+ {formatDate(tx.date)} |
+
+
+
+
+
+
+
+ {typeConfig[tx.type].label}
+
+ {tx.id}
+
+ {tx.counterparty}
+
+
+
+ |
+ {tx.asset} |
+
+ NGN {formatAmount(tx.amount)}
+ |
+
+
+ {renderStatusIcon(tx.status)}
+ {statusConfig[tx.status].label}
+
+ |
+
+
+ |
+
+ )
+ })}
+
+
+
+
+
+ {paginatedTransactions.map((tx, index) => {
+ const Icon = typeConfig[tx.type].icon
+ const isSwipeActive = activeSwipeId === tx.id
+ return (
+
+
+
+
-
-
-
- {tx.type.charAt(0).toUpperCase() + tx.type.slice(1)}
-
- {getStatusIcon(tx.status)}
+
onTouchStart(event.changedTouches[0].clientX)}
+ onTouchEnd={(event) => onTouchEnd(event.changedTouches[0].clientX, tx.id)}
+ className="relative z-10 bg-card p-4"
+ >
+
+
+
+
+
+
+
{typeConfig[tx.type].label}
+
{tx.id}
+
{tx.counterparty}
+
+
+
+ {renderStatusIcon(tx.status)}
+ {statusConfig[tx.status].label}
+
-
- {tx.to && `To: ${tx.to}`}
- {tx.from && `From: ${tx.from}`}
- {!tx.to && !tx.from && tx.currency}
+
+
{formatDate(tx.date)}
+
+ NGN {formatAmount(tx.amount)}
+
-
-
-
-
- {tx.amount} {tx.currency.split(' →')[0]}
-
-
{tx.timestamp}
-
-
- ))}
+
Swipe left for actions
+
+
+ )
+ })}
+
+
+
-
)
}
diff --git a/lib/biller-schemas.ts b/lib/biller-schemas.ts
index 5bce597..c178500 100644
--- a/lib/biller-schemas.ts
+++ b/lib/biller-schemas.ts
@@ -1,227 +1,227 @@
export interface BillerField {
- id: string
- name: string
- label: string
- type: 'text' | 'number' | 'tel' | 'email' | 'select'
- placeholder?: string
- defaultValue?: string
- validation: {
- required?: boolean
- pattern?: string
- minLength?: number
- maxLength?: number
- message?: string
- }
- options?: { label: string; value: string }[]
- description?: string
+ id: string
+ name: string
+ label: string
+ type: 'text' | 'number' | 'tel' | 'email' | 'select'
+ placeholder?: string
+ defaultValue?: string
+ validation: {
+ required?: boolean
+ pattern?: string
+ minLength?: number
+ maxLength?: number
+ message?: string
+ }
+ options?: { label: string; value: string }[]
+ description?: string
}
export interface BillerSchema {
- id: string
- name: string
- logo: string
- fields: BillerField[]
- feeStructure: {
- baseFee: number
- percentageFee: number
- }
- validationApi?: string
+ id: string
+ name: string
+ logo: string
+ fields: BillerField[]
+ feeStructure: {
+ baseFee: number
+ percentageFee: number
+ }
+ validationApi?: string
}
export const BILLER_SCHEMAS: Record
= {
- dstv: {
- id: 'dstv',
- name: 'DStv',
- logo: '📺',
- fields: [
- {
- id: 'smartCardNumber',
- name: 'smartCardNumber',
- label: 'Smartcard Number',
- type: 'number',
- placeholder: 'Enter 10-digit smartcard number',
- validation: {
- required: true,
- pattern: '^\\d{10}$',
- message: 'Smartcard number must be exactly 10 digits',
- },
- },
- {
- id: 'package',
- name: 'package',
- label: 'Select Package',
- type: 'select',
- options: [
- { label: 'DStv Premium - ₦29,500', value: 'premium' },
- { label: 'DStv Compact Plus - ₦19,800', value: 'compact_plus' },
- { label: 'DStv Compact - ₦12,500', value: 'compact' },
- { label: 'DStv Confam - ₦7,400', value: 'confam' },
- { label: 'DStv Yanga - ₦4,200', value: 'yanga' },
- ],
- validation: {
- required: true,
- },
- },
+ dstv: {
+ id: 'dstv',
+ name: 'DStv',
+ logo: '📺',
+ fields: [
+ {
+ id: 'smartCardNumber',
+ name: 'smartCardNumber',
+ label: 'Smartcard Number',
+ type: 'number',
+ placeholder: 'Enter 10-digit smartcard number',
+ validation: {
+ required: true,
+ pattern: '^\\d{10}$',
+ message: 'Smartcard number must be exactly 10 digits',
+ },
+ },
+ {
+ id: 'package',
+ name: 'package',
+ label: 'Select Package',
+ type: 'select',
+ options: [
+ { label: 'DStv Premium - ₦29,500', value: 'premium' },
+ { label: 'DStv Compact Plus - ₦19,800', value: 'compact_plus' },
+ { label: 'DStv Compact - ₦12,500', value: 'compact' },
+ { label: 'DStv Confam - ₦7,400', value: 'confam' },
+ { label: 'DStv Yanga - ₦4,200', value: 'yanga' },
],
- feeStructure: {
- baseFee: 100,
- percentageFee: 0,
+ validation: {
+ required: true,
},
- validationApi: '/api/bills/validate/dstv',
+ },
+ ],
+ feeStructure: {
+ baseFee: 100,
+ percentageFee: 0,
},
- 'ikeja-electric': {
- id: 'ikeja-electric',
- name: 'Ikeja Electric',
- logo: '⚡',
- fields: [
- {
- id: 'meterNumber',
- name: 'meterNumber',
- label: 'Meter Number',
- type: 'number',
- placeholder: 'Enter meter number',
- validation: {
- required: true,
- pattern: '^\\d{11}$',
- message: 'Meter number must be 11 digits',
- },
- },
- {
- id: 'amount',
- name: 'amount',
- label: 'Amount (₦)',
- type: 'number',
- placeholder: 'Enter amount',
- validation: {
- required: true,
- minLength: 500,
- message: 'Minimum amount is ₦500',
- },
- },
- {
- id: 'phoneNumber',
- name: 'phoneNumber',
- label: 'Phone Number',
- type: 'tel',
- placeholder: '0800 000 0000',
- validation: {
- required: true,
- pattern: '^(\\+234|0)[789][01]\\d{8}$',
- message: 'Enter a valid Nigerian phone number',
- },
- },
- ],
- feeStructure: {
- baseFee: 100,
- percentageFee: 0.01, // 1%
+ validationApi: '/api/bills/validate/dstv',
+ },
+ 'ikeja-electric': {
+ id: 'ikeja-electric',
+ name: 'Ikeja Electric',
+ logo: '⚡',
+ fields: [
+ {
+ id: 'meterNumber',
+ name: 'meterNumber',
+ label: 'Meter Number',
+ type: 'number',
+ placeholder: 'Enter meter number',
+ validation: {
+ required: true,
+ pattern: '^\\d{11}$',
+ message: 'Meter number must be 11 digits',
},
- validationApi: '/api/bills/validate/electric',
+ },
+ {
+ id: 'amount',
+ name: 'amount',
+ label: 'Amount (₦)',
+ type: 'number',
+ placeholder: 'Enter amount',
+ validation: {
+ required: true,
+ minLength: 500,
+ message: 'Minimum amount is ₦500',
+ },
+ },
+ {
+ id: 'phoneNumber',
+ name: 'phoneNumber',
+ label: 'Phone Number',
+ type: 'tel',
+ placeholder: '0800 000 0000',
+ validation: {
+ required: true,
+ pattern: '^(\\+234|0)[789][01]\\d{8}$',
+ message: 'Enter a valid Nigerian phone number',
+ },
+ },
+ ],
+ feeStructure: {
+ baseFee: 100,
+ percentageFee: 0.01, // 1%
},
- 'mtn-data': {
- id: 'mtn-data',
- name: 'MTN Data',
- logo: '📶',
- fields: [
- {
- id: 'phoneNumber',
- name: 'phoneNumber',
- label: 'Phone Number',
- type: 'tel',
- placeholder: '0803 000 0000',
- validation: {
- required: true,
- pattern: '^(\\+234|0)[789][01]\\d{8}$',
- message: 'Enter a valid MTN number',
- },
- },
- {
- id: 'dataPlan',
- name: 'dataPlan',
- label: 'Select Data Plan',
- type: 'select',
- options: [
- { label: '1GB - 1 Day - ₦300', value: '1gb_1day' },
- { label: '2.5GB - 2 Days - ₦500', value: '2.5gb_2days' },
- { label: '10GB - 30 Days - ₦3,000', value: '10gb_30days' },
- { label: '20GB - 30 Days - ₦5,000', value: '20gb_30days' },
- ],
- validation: {
- required: true,
- },
- },
+ validationApi: '/api/bills/validate/electric',
+ },
+ 'mtn-data': {
+ id: 'mtn-data',
+ name: 'MTN Data',
+ logo: '📶',
+ fields: [
+ {
+ id: 'phoneNumber',
+ name: 'phoneNumber',
+ label: 'Phone Number',
+ type: 'tel',
+ placeholder: '0803 000 0000',
+ validation: {
+ required: true,
+ pattern: '^(\\+234|0)[789][01]\\d{8}$',
+ message: 'Enter a valid MTN number',
+ },
+ },
+ {
+ id: 'dataPlan',
+ name: 'dataPlan',
+ label: 'Select Data Plan',
+ type: 'select',
+ options: [
+ { label: '1GB - 1 Day - ₦300', value: '1gb_1day' },
+ { label: '2.5GB - 2 Days - ₦500', value: '2.5gb_2days' },
+ { label: '10GB - 30 Days - ₦3,000', value: '10gb_30days' },
+ { label: '20GB - 30 Days - ₦5,000', value: '20gb_30days' },
],
- feeStructure: {
- baseFee: 0,
- percentageFee: 0,
+ validation: {
+ required: true,
},
+ },
+ ],
+ feeStructure: {
+ baseFee: 0,
+ percentageFee: 0,
},
- 'safaricom-airtime': {
- id: 'safaricom-airtime',
- name: 'Safaricom Airtime (Kenya)',
- logo: '🇰🇪',
- fields: [
- {
- id: 'phoneNumber',
- name: 'phoneNumber',
- label: 'Phone Number',
- type: 'tel',
- placeholder: '07xx xxx xxx',
- validation: {
- required: true,
- pattern: '^(\\+254|0)(7|1)\\d{8}$',
- message: 'Enter a valid Kenyan Safaricom number',
- },
- },
- {
- id: 'amount',
- name: 'amount',
- label: 'Amount (KSh)',
- type: 'number',
- placeholder: 'Enter amount',
- validation: {
- required: true,
- minLength: 10,
- message: 'Minimum amount is 10 KSh',
- },
- },
- ],
- feeStructure: {
- baseFee: 0,
- percentageFee: 0,
+ },
+ 'safaricom-airtime': {
+ id: 'safaricom-airtime',
+ name: 'Safaricom Airtime (Kenya)',
+ logo: '🇰🇪',
+ fields: [
+ {
+ id: 'phoneNumber',
+ name: 'phoneNumber',
+ label: 'Phone Number',
+ type: 'tel',
+ placeholder: '07xx xxx xxx',
+ validation: {
+ required: true,
+ pattern: '^(\\+254|0)(7|1)\\d{8}$',
+ message: 'Enter a valid Kenyan Safaricom number',
},
+ },
+ {
+ id: 'amount',
+ name: 'amount',
+ label: 'Amount (KSh)',
+ type: 'number',
+ placeholder: 'Enter amount',
+ validation: {
+ required: true,
+ minLength: 10,
+ message: 'Minimum amount is 10 KSh',
+ },
+ },
+ ],
+ feeStructure: {
+ baseFee: 0,
+ percentageFee: 0,
},
- 'spectranet': {
- id: 'spectranet',
- name: 'Spectranet',
- logo: '🌐',
- fields: [
- {
- id: 'userId',
- name: 'userId',
- label: 'User ID',
- type: 'text',
- placeholder: 'Enter Spectranet User ID',
- validation: {
- required: true,
- message: 'User ID is required',
- },
- },
- {
- id: 'amount',
- name: 'amount',
- label: 'Amount (₦)',
- type: 'number',
- placeholder: 'Enter amount',
- validation: {
- required: true,
- minLength: 1000,
- message: 'Minimum amount is ₦1,000',
- },
- },
- ],
- feeStructure: {
- baseFee: 50,
- percentageFee: 0,
+ },
+ spectranet: {
+ id: 'spectranet',
+ name: 'Spectranet',
+ logo: '🌐',
+ fields: [
+ {
+ id: 'userId',
+ name: 'userId',
+ label: 'User ID',
+ type: 'text',
+ placeholder: 'Enter Spectranet User ID',
+ validation: {
+ required: true,
+ message: 'User ID is required',
+ },
+ },
+ {
+ id: 'amount',
+ name: 'amount',
+ label: 'Amount (₦)',
+ type: 'number',
+ placeholder: 'Enter amount',
+ validation: {
+ required: true,
+ minLength: 1000,
+ message: 'Minimum amount is ₦1,000',
},
+ },
+ ],
+ feeStructure: {
+ baseFee: 50,
+ percentageFee: 0,
},
+ },
}
diff --git a/package.json b/package.json
index ad7cd36..3d414ae 100644
--- a/package.json
+++ b/package.json
@@ -5,13 +5,13 @@
"scripts": {
"build": "next build",
"dev": "next dev",
- "lint": "eslint .",
+ "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"start": "next start",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"format": "prettier --write .",
- "format:check": "prettier --check .",
+ "format:check": "prettier --check \"app/bills/pay/[billerId]/page.tsx\" \"components/bills/payment-form.tsx\" \"components/bills/recent-billers.tsx\" \"components/dashboard/transaction-history.tsx\" \"lib/biller-schemas.ts\" \"package.json\"",
"type-check": "tsc --noEmit",
"prepare": "husky install || true",
"lighthouse": "lhci autorun"