diff --git a/.gitignore b/.gitignore index f650315..94b396d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts + +.pnpm-store/ +.history/* diff --git a/app/(app)/mint/page.tsx b/app/(app)/mint/page.tsx index 7643518..3df6ca1 100644 --- a/app/(app)/mint/page.tsx +++ b/app/(app)/mint/page.tsx @@ -19,13 +19,12 @@ import { import { Skeleton } from '@/components/ui/skeleton'; import { ArrowDown, ArrowUp, ArrowLeft } from 'lucide-react'; import { useApiOpts } from '@/hooks/use-api'; +import { useBalance } from '@/hooks/use-balance'; import * as ratesApi from '@/lib/api/rates'; import * as mintApi from '@/lib/api/mint'; import * as burnApi from '@/lib/api/burn'; import type { RatesResponse } from '@/types/api'; import { formatAmount } from '@/lib/utils'; - -const BALANCE_PLACEHOLDER = "—"; const MINT_NETWORK_FEE_TEXT = "Estimated at confirmation"; const BURN_PROCESSING_FEE_TEXT = "Estimated at confirmation"; @@ -34,6 +33,7 @@ const BURN_PROCESSING_FEE_TEXT = "Estimated at confirmation"; */ export default function MintPage() { const opts = useApiOpts(); + const { balance, loading: balanceLoading } = useBalance(); const [activeTab, setActiveTab] = useState<'mint' | 'burn' | 'rates'>('mint'); const [step, setStep] = useState<'input' | 'confirm' | 'success'>('input'); const [usdcAmount, setUsdcAmount] = useState(''); @@ -47,9 +47,6 @@ export default function MintPage() { const [mintError, setMintError] = useState(''); const [txId, setTxId] = useState(null); const [executing, setExecuting] = useState(false); - const burnReceiveText = burnAmount - ? `Local currency payout to ${BURN_DESTINATION_LABELS[burnDestination] ?? 'selected destination'}` - : '—'; useEffect(() => { if (activeTab !== "rates") return; @@ -153,7 +150,7 @@ export default function MintPage() { AFK Balance

- AFK {formatAmount(BALANCE_PLACEHOLDER)} + {balanceLoading ? '...' : `AFK ${formatAmount(balance)}`}

Native ACBU Currency @@ -335,7 +332,7 @@ export default function MintPage() {

Available: AFK{" "} - {formatAmount(BALANCE_PLACEHOLDER)} + {balanceLoading ? '...' : formatAmount(balance)}

diff --git a/app/(app)/page.tsx b/app/(app)/page.tsx index ffb0e2c..aa64dfa 100644 --- a/app/(app)/page.tsx +++ b/app/(app)/page.tsx @@ -19,12 +19,11 @@ import { PageContainer } from '@/components/layout/page-container'; import { SkeletonList } from '@/components/ui/skeleton-list'; import { EmptyState } from '@/components/ui/empty-state'; import { useApiOpts } from '@/hooks/use-api'; +import { useBalance } from '@/hooks/use-balance'; import * as transfersApi from '@/lib/api/transfers'; import type { TransferItem } from '@/types/api'; import { cn, formatAmount } from '@/lib/utils'; -const BALANCE_PLACEHOLDER = '—'; // TODO: GET /users/me/balance when available - const features = [ { title: 'Send', description: 'Transfer money', icon: Send, href: '/send', color: 'bg-blue-100 dark:bg-blue-900/30', iconColor: 'text-blue-600 dark:text-blue-400' }, { title: 'Mint', description: 'Create ACBU', icon: Coins, href: '/mint', color: 'bg-purple-100 dark:bg-purple-900/30', iconColor: 'text-purple-600 dark:text-purple-400' }, @@ -47,6 +46,7 @@ function formatDate(iso: string) { */ export default function Home() { const [showBalance, setShowBalance] = useState(true); + const { balance, loading: balanceLoading, error: balanceError } = useBalance(); const opts = useApiOpts(); const [transfers, setTransfers] = useState([]); const [loading, setLoading] = useState(true); @@ -87,7 +87,11 @@ export default function Home() {

Total Balance

- {showBalance ? `ACBU ${BALANCE_PLACEHOLDER}` : '••••••'} + {!showBalance + ? '••••••' + : balanceLoading + ? '...' + : `ACBU ${formatAmount(balance)}`}

- {showBalance && BALANCE_PLACEHOLDER === '—' && ( -
- Balance from backend when available + {showBalance && balanceError && ( +
+ {balanceError}
)}
diff --git a/app/(app)/send/page.tsx b/app/(app)/send/page.tsx index a18344b..f679eaf 100644 --- a/app/(app)/send/page.tsx +++ b/app/(app)/send/page.tsx @@ -25,9 +25,9 @@ import { import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsTrigger, TabsList } from "@/components/ui/tabs"; import { SkeletonList } from "@/components/ui/skeleton-list"; -import { Skeleton } from "@/components/ui/skeleton"; import { Plus, Check, AlertCircle, ArrowRight } from "lucide-react"; import { useApiOpts } from "@/hooks/use-api"; +import { useBalance } from "@/hooks/use-balance"; import * as transfersApi from "@/lib/api/transfers"; import * as userApi from "@/lib/api/user"; import type { TransferItem, ContactItem } from "@/types/api"; @@ -39,10 +39,6 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { PageContainer } from "@/components/layout/page-container"; - -const BALANCE_PLACEHOLDER = "—"; - function formatDate(iso: string) { const d = new Date(iso); const today = new Date(); @@ -58,6 +54,7 @@ function formatDate(iso: string) { */ export default function SendPage() { const opts = useApiOpts(); + const { balance, loading: balanceLoading, refetch: refetchBalance } = useBalance(); const [activeTab, setActiveTab] = useState("send"); const [showSendDialog, setShowSendDialog] = useState(false); const [showConfirmDialog, setShowConfirmDialog] = useState(false); @@ -120,6 +117,7 @@ export default function SendPage() { opts, ); loadTransfers(); + refetchBalance(); setShowConfirmDialog(false); setShowSendDialog(false); setLastSentAmount(amount); @@ -151,9 +149,13 @@ const getStatusColor = (status: string) => { } }; + const exceedsBalance = + balance !== null && amount !== "" && parseFloat(amount) > balance; + const isFormValid = () => amount && parseFloat(amount) > 0 && + !exceedsBalance && ((useContact && selectedContact) || (!useContact && customRecipient.trim())); @@ -200,77 +202,51 @@ const getStatusColor = (status: string) => { - - - - -
-

- Recent Transfers -

- {loadingTransfers ? ( - - ) : transfers.length === 0 ? ( -
-

- No transfers yet -

-
- ) : ( -
- {transfers.map((t: TransferItem) => ( - -
-

- Transfer -

-

- {formatDate(t.created_at)} -

-
-
-

- AFK {formatAmount(t.amount_acbu)} -

- - {t.status === "completed" && ( - - )} - {t.status === "pending" && ( - - )} - {t.status.charAt(0).toUpperCase() + t.status.slice(1)} - -
- - ))} -
-
+
-

Recent Transfers

+

+ Recent Transfers +

{loadingTransfers ? ( ) : transfers.length === 0 ? ( -

No transfers yet

+
+

+ No transfers yet +

+
) : (
{transfers.map((t: TransferItem) => ( - -

Transfer

{formatDate(t.created_at)}

+ +
+

+ Transfer +

+

+ {formatDate(t.created_at)} +

+
-

ACBU {formatAmount(t.amount_acbu)}

- - {t.status === 'completed' && } - {t.status === 'pending' && } +

+ ACBU {formatAmount(t.amount_acbu)} +

+ + {t.status === "completed" && ( + + )} + {t.status === "pending" && ( + + )} {t.status.charAt(0).toUpperCase() + t.status.slice(1)}
@@ -352,7 +328,9 @@ const getStatusColor = (status: string) => { />
{exceedsBalance &&

Insufficient balance.

} -

Available: ACBU {formatAmount(BALANCE_PLACEHOLDER)}

+

+ Available: ACBU {balanceLoading ? '...' : formatAmount(balance)} +

diff --git a/hooks/use-balance.ts b/hooks/use-balance.ts new file mode 100644 index 0000000..c731052 --- /dev/null +++ b/hooks/use-balance.ts @@ -0,0 +1,55 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useApiOpts } from '@/hooks/use-api'; +import * as userApi from '@/lib/api/user'; + +interface UseBalanceReturn { + balance: number | null; + loading: boolean; + error: string; + refetch: () => void; +} + +/** + * Fetches the authenticated user's ACBU wallet balance from GET /users/me/balance. + * Returns a numeric balance (null while unknown), loading flag, error string, and refetch fn. + */ +export function useBalance(): UseBalanceReturn { + const opts = useApiOpts(); + const [balance, setBalance] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [tick, setTick] = useState(0); + + const refetch = useCallback(() => setTick((t) => t + 1), []); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(''); + + userApi + .getBalance(opts) + .then((data) => { + if (cancelled) return; + const raw = data.balance; + const num = typeof raw === 'number' ? raw : parseFloat(raw); + setBalance(Number.isNaN(num) ? null : num); + }) + .catch((e) => { + if (cancelled) return; + setBalance(null); + setError(e instanceof Error ? e.message : 'Failed to load balance'); + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); + + return () => { + cancelled = true; + }; + }, [opts.token, tick]); + + return { balance, loading, error, refetch }; +} diff --git a/lib/api/user.ts b/lib/api/user.ts index 52cd00f..f1fa41d 100644 --- a/lib/api/user.ts +++ b/lib/api/user.ts @@ -1,6 +1,6 @@ import { get, post, patch, del } from './client'; import type { RequestOptions } from './client'; -import type { UserMe, PatchMeBody, ReceiveResponse, ContactItem, GuardianItem } from '@/types/api'; +import type { UserMe, PatchMeBody, ReceiveResponse, BalanceResponse, ContactItem, GuardianItem } from '@/types/api'; export async function getMe(opts?: RequestOptions): Promise { return get('/users/me', opts); @@ -10,6 +10,10 @@ export async function patchMe(data: PatchMeBody, opts?: RequestOptions): Promise return patch('/users/me', data, opts); } +export async function getBalance(opts?: RequestOptions): Promise { + return get('/users/me/balance', opts); +} + export async function getReceive(opts?: RequestOptions): Promise { return get('/users/me/receive', opts); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fafb0d..71917ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,6 +122,9 @@ importers: next: specifier: 16.0.10 version: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next-intl: + specifier: ^4.8.3 + version: 4.8.3(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(typescript@5.9.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -215,6 +218,24 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@formatjs/bigdecimal@0.2.0': + resolution: {integrity: sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==} + + '@formatjs/ecma402-abstract@3.2.0': + resolution: {integrity: sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==} + + '@formatjs/fast-memoize@3.1.1': + resolution: {integrity: sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==} + + '@formatjs/icu-messageformat-parser@3.5.3': + resolution: {integrity: sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==} + + '@formatjs/icu-skeleton-parser@2.1.3': + resolution: {integrity: sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==} + + '@formatjs/intl-localematcher@0.8.2': + resolution: {integrity: sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==} + '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -424,6 +445,88 @@ packages: cpu: [x64] os: [win32] + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} @@ -1080,9 +1183,99 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@schummar/icu-type-parser@1.21.5': + resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==} + + '@swc/core-darwin-arm64@1.15.21': + resolution: {integrity: sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.21': + resolution: {integrity: sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.21': + resolution: {integrity: sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.21': + resolution: {integrity: sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.15.21': + resolution: {integrity: sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-ppc64-gnu@1.15.21': + resolution: {integrity: sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==} + engines: {node: '>=10'} + cpu: [ppc64] + os: [linux] + + '@swc/core-linux-s390x-gnu@1.15.21': + resolution: {integrity: sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==} + engines: {node: '>=10'} + cpu: [s390x] + os: [linux] + + '@swc/core-linux-x64-gnu@1.15.21': + resolution: {integrity: sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.15.21': + resolution: {integrity: sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.15.21': + resolution: {integrity: sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.21': + resolution: {integrity: sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.21': + resolution: {integrity: sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.21': + resolution: {integrity: sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/types@0.1.26': + resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + '@tailwindcss/node@4.1.18': resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} @@ -1366,6 +1559,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + icu-minify@4.8.3: + resolution: {integrity: sha512-65Av7FLosNk7bPbmQx5z5XG2Y3T2GFppcjiXh4z1idHeVgQxlDpAmkGoYI0eFzAvrOnjpWTL5FmPDhsdfRMPEA==} + input-otp@1.4.1: resolution: {integrity: sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==} peerDependencies: @@ -1376,6 +1572,17 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + intl-messageformat@11.2.0: + resolution: {integrity: sha512-IhghAA8n4KSlXuWKzYsWyWb82JoYTzShfyvdSF85oJPnNOjvv4kAo7S7Jtkm3/vJ53C7dQNRO+Gpnj3iWgTjBQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -1473,6 +1680,23 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + next-intl-swc-plugin-extractor@4.8.3: + resolution: {integrity: sha512-YcaT+R9z69XkGhpDarVFWUprrCMbxgIQYPUaXoE6LGVnLjGdo8hu3gL6bramDVjNKViYY8a/pXPy7Bna0mXORg==} + + next-intl@4.8.3: + resolution: {integrity: sha512-PvdBDWg+Leh7BR7GJUQbCDVVaBRn37GwDBWc9sv0rVQOJDQ5JU1rVzx9EEGuOGYo0DHAl70++9LQ7HxTawdL7w==} + peerDependencies: + next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: @@ -1500,6 +1724,9 @@ packages: sass: optional: true + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -1510,6 +1737,13 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + po-parser@2.1.1: + resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -1695,6 +1929,11 @@ packages: '@types/react': optional: true + use-intl@4.8.3: + resolution: {integrity: sha512-nLxlC/RH+le6g3amA508Itnn/00mE+J22ui21QhOWo5V9hCEC43+WtnRAITbJW0ztVZphev5X9gvOf2/Dk9PLA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + use-sidecar@1.1.3: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} @@ -1752,6 +1991,29 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@formatjs/bigdecimal@0.2.0': {} + + '@formatjs/ecma402-abstract@3.2.0': + dependencies: + '@formatjs/bigdecimal': 0.2.0 + '@formatjs/fast-memoize': 3.1.1 + '@formatjs/intl-localematcher': 0.8.2 + + '@formatjs/fast-memoize@3.1.1': {} + + '@formatjs/icu-messageformat-parser@3.5.3': + dependencies: + '@formatjs/ecma402-abstract': 3.2.0 + '@formatjs/icu-skeleton-parser': 2.1.3 + + '@formatjs/icu-skeleton-parser@2.1.3': + dependencies: + '@formatjs/ecma402-abstract': 3.2.0 + + '@formatjs/intl-localematcher@0.8.2': + dependencies: + '@formatjs/fast-memoize': 3.1.1 + '@hookform/resolvers@3.10.0(react-hook-form@7.71.1(react@19.2.0))': dependencies: react-hook-form: 7.71.1(react@19.2.0) @@ -1898,6 +2160,66 @@ snapshots: '@next/swc-win32-x64-msvc@16.0.10': optional: true + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + '@radix-ui/number@1.1.0': {} '@radix-ui/primitive@1.1.1': {} @@ -2590,10 +2912,72 @@ snapshots: '@radix-ui/rect@1.1.0': {} + '@schummar/icu-type-parser@1.21.5': {} + + '@swc/core-darwin-arm64@1.15.21': + optional: true + + '@swc/core-darwin-x64@1.15.21': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.21': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.21': + optional: true + + '@swc/core-linux-arm64-musl@1.15.21': + optional: true + + '@swc/core-linux-ppc64-gnu@1.15.21': + optional: true + + '@swc/core-linux-s390x-gnu@1.15.21': + optional: true + + '@swc/core-linux-x64-gnu@1.15.21': + optional: true + + '@swc/core-linux-x64-musl@1.15.21': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.21': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.21': + optional: true + + '@swc/core-win32-x64-msvc@1.15.21': + optional: true + + '@swc/core@1.15.21': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.26 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.21 + '@swc/core-darwin-x64': 1.15.21 + '@swc/core-linux-arm-gnueabihf': 1.15.21 + '@swc/core-linux-arm64-gnu': 1.15.21 + '@swc/core-linux-arm64-musl': 1.15.21 + '@swc/core-linux-ppc64-gnu': 1.15.21 + '@swc/core-linux-s390x-gnu': 1.15.21 + '@swc/core-linux-x64-gnu': 1.15.21 + '@swc/core-linux-x64-musl': 1.15.21 + '@swc/core-win32-arm64-msvc': 1.15.21 + '@swc/core-win32-ia32-msvc': 1.15.21 + '@swc/core-win32-x64-msvc': 1.15.21 + + '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 + '@swc/types@0.1.26': + dependencies: + '@swc/counter': 0.1.3 + '@tailwindcss/node@4.1.18': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2837,6 +3221,10 @@ snapshots: graceful-fs@4.2.11: {} + icu-minify@4.8.3: + dependencies: + '@formatjs/icu-messageformat-parser': 3.5.3 + input-otp@1.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 @@ -2844,6 +3232,18 @@ snapshots: internmap@2.0.3: {} + intl-messageformat@11.2.0: + dependencies: + '@formatjs/ecma402-abstract': 3.2.0 + '@formatjs/fast-memoize': 3.1.1 + '@formatjs/icu-messageformat-parser': 3.5.3 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + jiti@2.6.1: {} js-tokens@4.0.0: {} @@ -2913,6 +3313,27 @@ snapshots: nanoid@3.3.11: {} + negotiator@1.0.0: {} + + next-intl-swc-plugin-extractor@4.8.3: {} + + next-intl@4.8.3(next@16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + dependencies: + '@formatjs/intl-localematcher': 0.8.2 + '@parcel/watcher': 2.5.6 + '@swc/core': 1.15.21 + icu-minify: 4.8.3 + negotiator: 1.0.0 + next: 16.0.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next-intl-swc-plugin-extractor: 4.8.3 + po-parser: 2.1.1 + react: 19.2.0 + use-intl: 4.8.3(react@19.2.0) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@swc/helpers' + next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 @@ -2941,12 +3362,18 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-addon-api@7.1.1: {} + node-releases@2.0.27: {} object-assign@4.1.1: {} picocolors@1.1.1: {} + picomatch@4.0.4: {} + + po-parser@2.1.1: {} + postcss-value-parser@4.2.0: {} postcss@8.4.31: @@ -3139,6 +3566,14 @@ snapshots: optionalDependencies: '@types/react': 19.2.10 + use-intl@4.8.3(react@19.2.0): + dependencies: + '@formatjs/fast-memoize': 3.1.1 + '@schummar/icu-type-parser': 1.21.5 + icu-minify: 4.8.3 + intl-messageformat: 11.2.0 + react: 19.2.0 + use-sidecar@1.1.3(@types/react@19.2.10)(react@19.2.0): dependencies: detect-node-es: 1.1.0 diff --git a/types/api.ts b/types/api.ts index 9a790f8..31459cd 100644 --- a/types/api.ts +++ b/types/api.ts @@ -44,6 +44,12 @@ export interface ReceiveResponse { [key: string]: unknown; } +// Balance +export interface BalanceResponse { + balance: string | number; + currency?: string; +} + // Contacts & guardians export interface ContactItem { id: string;