From 3b9b7c2643e1d5dbab0e827ead18c554be483926 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Thu, 9 Apr 2026 03:52:21 +0300 Subject: [PATCH] feat: add Bank and TaxZekat pages, activate finance buttons on web --- web/src/App.tsx | 4 + web/src/components/MobileHomeLayout.tsx | 4 +- web/src/pages/finance/BankPage.tsx | 218 ++++++++++++++++ web/src/pages/finance/TaxZekatPage.tsx | 323 ++++++++++++++++++++++++ 4 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 web/src/pages/finance/BankPage.tsx create mode 100644 web/src/pages/finance/TaxZekatPage.tsx diff --git a/web/src/App.tsx b/web/src/App.tsx index 7f2f6e99..e31eaa51 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -64,6 +64,8 @@ const ForumTopic = lazy(() => import('@/pages/ForumTopic')); const Telemetry = lazy(() => import('@/pages/Telemetry')); const Subdomains = lazy(() => import('@/pages/Subdomains')); const Messaging = lazy(() => import('@/pages/Messaging')); +const TaxZekatPage = lazy(() => import('@/pages/finance/TaxZekatPage')); +const BankPage = lazy(() => import('@/pages/finance/BankPage')); // Network pages const Mainnet = lazy(() => import('@/pages/networks/Mainnet')); @@ -226,6 +228,8 @@ function App() { } /> + } /> + } /> } /> } /> } /> diff --git a/web/src/components/MobileHomeLayout.tsx b/web/src/components/MobileHomeLayout.tsx index d58a03f7..6d085c67 100644 --- a/web/src/components/MobileHomeLayout.tsx +++ b/web/src/components/MobileHomeLayout.tsx @@ -53,11 +53,11 @@ const APP_SECTIONS: AppSection[] = [ borderColor: 'border-l-green-500', apps: [ { title: 'mobile.app.wallet', icon: '👛', route: '/wallet' }, - { title: 'mobile.app.bank', icon: '🏦', route: '/wallet', comingSoon: true }, + { title: 'mobile.app.bank', icon: '🏦', route: '/finance/bank' }, { title: 'mobile.app.exchange', icon: '💱', route: '/dex', requiresAuth: true }, { title: 'mobile.app.p2p', icon: '🤝', route: '/p2p', requiresAuth: true }, { title: 'mobile.app.b2b', icon: '🤖', route: '/bereketli', requiresAuth: true }, - { title: 'mobile.app.bacZekat', icon: '💰', route: '/wallet', comingSoon: true }, + { title: 'mobile.app.bacZekat', icon: '💰', route: '/finance/zekat' }, { title: 'mobile.app.launchpad', icon: '🚀', route: '/launchpad' }, ], }, diff --git a/web/src/pages/finance/BankPage.tsx b/web/src/pages/finance/BankPage.tsx new file mode 100644 index 00000000..58f4ff95 --- /dev/null +++ b/web/src/pages/finance/BankPage.tsx @@ -0,0 +1,218 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface SavingsAccount { + id: string; + nameKu: string; + name: string; + apy: string; + minDeposit: string; + lockPeriod: string; + totalDeposited: string; + emoji: string; +} + +interface LendingPool { + id: string; + nameKu: string; + name: string; + borrowRate: string; + collateralRatio: string; + available: string; + emoji: string; +} + +const SAVINGS: SavingsAccount[] = [ + { id: 's1', emoji: '🌱', nameKu: 'Teserûfa Destpêk', name: 'Starter Savings', apy: '4.5%', minDeposit: '100 HEZ', lockPeriod: 'Tune / None', totalDeposited: '125,430 HEZ' }, + { id: 's2', emoji: '🌿', nameKu: 'Teserûfa Navîn', name: 'Growth Savings', apy: '8.2%', minDeposit: '1,000 HEZ', lockPeriod: '90 roj / days', totalDeposited: '892,100 HEZ' }, + { id: 's3', emoji: '🌳', nameKu: 'Teserûfa Zêrîn', name: 'Gold Savings', apy: '12.0%', minDeposit: '10,000 HEZ', lockPeriod: '365 roj / days', totalDeposited: '2,340,000 HEZ' }, +]; + +const LENDING: LendingPool[] = [ + { id: 'l1', emoji: '💳', nameKu: 'Deyna Bilez', name: 'Flash Loan', borrowRate: '0.1%', collateralRatio: 'Tune / None', available: '50,000 HEZ' }, + { id: 'l2', emoji: '🏠', nameKu: 'Deyna Kesane', name: 'Personal Loan', borrowRate: '5.5%', collateralRatio: '150%', available: '200,000 HEZ' }, + { id: 'l3', emoji: '🏢', nameKu: 'Deyna Karsaziyê', name: 'Business Loan', borrowRate: '3.8%', collateralRatio: '200%', available: '500,000 HEZ' }, +]; + +const TREASURY_ALLOCATIONS = [ + { label: 'Perwerde / Education', pct: '25%', color: '#00A94F' }, + { label: 'Teknolojî / Technology', pct: '30%', color: '#2196F3' }, + { label: 'Ewlehî / Security', pct: '15%', color: '#EE2A35' }, + { label: 'Civak / Community', pct: '20%', color: '#9C27B0' }, + { label: 'Pareztî / Reserve', pct: '10%', color: '#FFD700' }, +]; + +type Tab = 'savings' | 'lending' | 'treasury'; + +export default function BankPage() { + const navigate = useNavigate(); + const [activeTab, setActiveTab] = useState('savings'); + + const tabs: { key: Tab; label: string }[] = [ + { key: 'savings', label: 'Teserûf' }, + { key: 'lending', label: 'Deyn' }, + { key: 'treasury', label: 'Xezîne' }, + ]; + + return ( +
+ {/* Header */} +
+
+ + Finance +
+
+ 🏦 +

Banka Dijîtal

+

Digital Bank of Kurdistan

+
+
+

Bihaya Giştî / Total Value

+

24,580.00 HEZ

+

~ $1,229.00

+
+
+ + {/* Tabs */} +
+
+ {tabs.map(tab => ( + + ))} +
+
+ + {/* Content */} +
+ + {/* Savings */} + {activeTab === 'savings' && ( + <> +
+

Hesabên Teserûfê / Savings Accounts

+

+ Tokenên xwe stake bikin û xelatên salane bistînin. · Stake your tokens and earn annual rewards. +

+
+ {SAVINGS.map(account => ( +
+
+ {account.emoji} +
+

{account.nameKu}

+

{account.name}

+
+
+ {account.apy} APY +
+
+
+
+

Kêmtirîn / Min

+

{account.minDeposit}

+
+
+

Kilîtkirin / Lock

+

{account.lockPeriod}

+
+
+

Giştî / Total

+

{account.totalDeposited}

+
+
+ +
+ ))} + + )} + + {/* Lending */} + {activeTab === 'lending' && ( + <> +
+

Hovzên Deyndanê / Lending Pools

+

+ Bi rêya peymana zîrek deyn bistînin. · Borrow through smart contracts with collateral. +

+
+ {LENDING.map(pool => ( +
+
+ {pool.emoji} +
+

{pool.nameKu}

+

{pool.name}

+
+
+ {pool.borrowRate} +
+
+
+
+

Rêje / Rate

+

{pool.borrowRate}

+
+
+

Garantî / Collateral

+

{pool.collateralRatio}

+
+
+

Peyda / Available

+

{pool.available}

+
+
+ +
+ ))} + + )} + + {/* Treasury */} + {activeTab === 'treasury' && ( + <> +
+

Xezîneya Civakê / Community Treasury

+

+ Xezîneya giştî ya Komara Dijîtal a Kurdistanê. · The public treasury of the Digital Kurdistan Republic. +

+
+
+

Bihaya Xezîneyê / Treasury Balance

+

12,450,000 HEZ

+

~ $622,500

+
+
+

Dabeşkirin / Allocation

+
+ {TREASURY_ALLOCATIONS.map((item, i) => ( +
+
+ {item.label} + {item.pct} +
+ ))} +
+
+ + )} +
+ +
+
+ ); +} diff --git a/web/src/pages/finance/TaxZekatPage.tsx b/web/src/pages/finance/TaxZekatPage.tsx new file mode 100644 index 00000000..c4e58fcf --- /dev/null +++ b/web/src/pages/finance/TaxZekatPage.tsx @@ -0,0 +1,323 @@ +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { usePezkuwi } from '@/contexts/PezkuwiContext'; + +type ContributionType = 'zekat' | 'tax'; + +interface AllocationItem { + id: string; + nameKu: string; + nameEn: string; + icon: string; + percentage: number; +} + +const DEFAULT_ALLOCATIONS: AllocationItem[] = [ + { id: 'shahid', nameKu: 'Binemalin Şehîda', nameEn: 'Martyr Families', icon: '🏠', percentage: 0 }, + { id: 'education', nameKu: 'Projeyin Perwerde', nameEn: 'Education Projects', icon: '📚', percentage: 0 }, + { id: 'health', nameKu: 'Tenduristî', nameEn: 'Health Services', icon: '🏥', percentage: 0 }, + { id: 'orphans', nameKu: 'Sêwî û Feqîr', nameEn: 'Orphans & Poor', icon: '👶', percentage: 0 }, + { id: 'infrastructure', nameKu: 'Binesazî', nameEn: 'Infrastructure', icon: '🏗️', percentage: 0 }, + { id: 'defense', nameKu: 'Parastina Welat', nameEn: 'National Defense', icon: '🛡️', percentage: 0 }, + { id: 'diaspora', nameKu: 'Diaspora', nameEn: 'Diaspora Support', icon: '🌍', percentage: 0 }, + { id: 'culture', nameKu: 'Çand û Huner', nameEn: 'Culture & Arts', icon: '🎭', percentage: 0 }, +]; + +export default function TaxZekatPage() { + const navigate = useNavigate(); + const { api, selectedAccount, getKeyPair } = usePezkuwi(); + + const [contributionType, setContributionType] = useState('zekat'); + const [amount, setAmount] = useState(''); + const [allocations, setAllocations] = useState(DEFAULT_ALLOCATIONS); + const [termsAccepted, setTermsAccepted] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + const [result, setResult] = useState<{ ok: boolean; msg: string } | null>(null); + + const totalPercentage = useMemo(() => + allocations.reduce((sum, a) => sum + a.percentage, 0), [allocations]); + + const isFormValid = useMemo(() => { + const n = parseFloat(amount); + return n > 0 && totalPercentage === 100 && termsAccepted && !!selectedAccount; + }, [amount, totalPercentage, termsAccepted, selectedAccount]); + + const updateAllocation = (id: string, value: string) => { + const n = Math.min(100, Math.max(0, parseInt(value) || 0)); + setAllocations(prev => prev.map(a => a.id === id ? { ...a, percentage: n } : a)); + }; + + const calcAmount = (pct: number) => + ((parseFloat(amount) || 0) * pct / 100).toFixed(2); + + const confirmAndSend = async () => { + setShowConfirm(false); + setIsSubmitting(true); + setResult(null); + try { + if (!api || !selectedAccount) throw new Error('Wallet not connected'); + const keyPair = await getKeyPair(selectedAccount.address); + if (!keyPair) throw new Error('Could not retrieve key pair'); + + const allocationData = allocations + .filter(a => a.percentage > 0) + .map(a => `${a.id}:${a.percentage}`) + .join(','); + + const remarkMessage = JSON.stringify({ + type: contributionType, + allocations: allocationData, + timestamp: Date.now(), + }); + + const amountInUnits = BigInt(Math.floor(parseFloat(amount) * 1e12)); + + const treasuryOpt = await api.query.pezTreasury.governmentPotAccountId(); + if (!treasuryOpt || (treasuryOpt as { isEmpty?: boolean }).isEmpty) { + throw new Error('Government treasury account not found'); + } + const treasuryAccount = treasuryOpt.toString(); + + const txs = [ + api.tx.balances.transferKeepAlive(treasuryAccount, amountInUnits.toString()), + api.tx.system.remark(remarkMessage), + ]; + + await new Promise((resolve, reject) => { + api.tx.utility + .batch(txs) + .signAndSend(keyPair, { nonce: -1 }, ({ status, dispatchError }: { status: { isInBlock?: boolean; isFinalized?: boolean }; dispatchError?: unknown }) => { + if (status.isInBlock || status.isFinalized) { + if (dispatchError) { reject(new Error('Transaction failed')); return; } + resolve(); + } + }) + .catch(reject); + }); + + setResult({ ok: true, msg: `${amount} HEZ başarıyla gönderildi. Spas!` }); + setAmount(''); + setAllocations(DEFAULT_ALLOCATIONS); + setTermsAccepted(false); + } catch (e) { + setResult({ ok: false, msg: e instanceof Error ? e.message : 'An error occurred' }); + } finally { + setIsSubmitting(false); + } + }; + + const isZekat = contributionType === 'zekat'; + const accentColor = isZekat ? '#00A94F' : '#D4A017'; + + return ( +
+ {/* Header */} +
+ +
+

+ {isZekat ? '🤲 Zekat' : '📜 Bac / Tax'} +

+

Komara Dijîtal a Kurdistanê

+
+
+ +
+ + {/* Result banner */} + {result && ( +
+ {result.ok ? '✅' : '❌'} {result.msg} +
+ )} + + {/* Description */} +
+

+ Beşdariya xwe ya bi dilxwazî ji Komara Dijîtaliya Kurdistanê re bişînin. +

+

+ Send your voluntary contribution to the Digital Kurdistan Republic. +

+
+ + {/* Type selector */} +
+

Cureyê Beşdariyê / Contribution Type

+
+ {(['zekat', 'tax'] as ContributionType[]).map(type => ( + + ))} +
+
+ + {/* Amount */} +
+

Miqdar / Amount

+
+ setAmount(e.target.value)} + placeholder="0.00" + className="flex-1 bg-transparent px-4 py-3 text-2xl font-semibold text-white outline-none" + /> + HEZ +
+ {!selectedAccount && ( +

⚠️ Cüzdan bağlı değil / Wallet not connected

+ )} +
+ + {/* Allocation */} +
+
+

Dabeşkirina Fonê / Fund Allocation

+ 100 ? 'bg-red-900 text-red-400' : 'bg-gray-800 text-gray-400' + }`}> + {totalPercentage}% + +
+

Divê bêkêmasî %100 be / Must equal exactly 100%

+ +
+ {allocations.map(item => ( +
+ {item.icon} +
+

{item.nameKu}

+

{item.nameEn}

+
+
+ 0 ? item.percentage : ''} + onChange={e => updateAllocation(item.id, e.target.value)} + placeholder="0" + className="w-10 bg-transparent text-center text-sm font-bold text-white outline-none py-1" + min={0} + max={100} + /> + % +
+ {item.percentage > 0 && parseFloat(amount) > 0 && ( + + {calcAmount(item.percentage)} HEZ + + )} +
+ ))} +
+
+ + {/* Terms */} +
+
+ {isZekat ? '🤲' : '📜'} +

SOZNAME / COMMITMENT

+
+ {isZekat ? ( +

+ Komara Dîjîtal a Kurdistanê SOZ DIDE ku zekata we BI TEMAMÎ li gorî rêjeyên ku we destnîşan kirine dê bê xerckirin, li gorî rêgez û qaîdeyên Îslamî. +

+ The Digital Republic of Kurdistan COMMITS to spending your zekat EXACTLY according to the ratios you specify, in accordance with Islamic principles. +

+ ) : ( +

+ Komara Dîjîtal a Kurdistanê SOZ DIDE ku beşdariyên baca we BI QASÎ KU MIMKUN BE li gorî rêjeyên ku we destnîşan kirine dê bê xerckirin. Hemû lêçûn dê bi şefafî li ser blockchain werin tomar kirin. +

+ The Digital Republic of Kurdistan COMMITS to using your tax contributions AS CLOSELY AS POSSIBLE according to the ratios you specify. All expenses will be transparently recorded on the blockchain. +

+ )} + + +
+ + {/* Submit */} + + +
+
+ + {/* Confirm Modal */} + {showConfirm && ( +
+
+

Piştrast bike / Confirm

+ +
+
+ Cure / Type: + {isZekat ? 'Zekat' : 'Bac / Tax'} +
+
+ Miqdar / Amount: + {amount} HEZ +
+
+

Dabeşkirin / Allocation:

+ {allocations.filter(a => a.percentage > 0).map(a => ( +
+ {a.icon} {a.nameKu} + {calcAmount(a.percentage)} HEZ ({a.percentage}%) +
+ ))} +
+
+ +
+ + +
+
+
+ )} +
+ ); +}