= ({ onClos
Authentication Successful!
- Welcome back, Citizen #{tikiNumber}
+ Welcome back, Citizen #{citizenNumber}
Redirecting to citizen dashboard...
diff --git a/web/src/components/citizenship/NewCitizenApplication.tsx b/web/src/components/citizenship/NewCitizenApplication.tsx
index 1cf4eb35..b2c165f1 100644
--- a/web/src/components/citizenship/NewCitizenApplication.tsx
+++ b/web/src/components/citizenship/NewCitizenApplication.tsx
@@ -8,7 +8,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
-import { Loader2, AlertTriangle, CheckCircle, User, Users as UsersIcon, MapPin, Briefcase, Mail, Clock } from 'lucide-react';
+import { Loader2, AlertTriangle, CheckCircle, User, Users as UsersIcon, MapPin, Briefcase, Mail, Clock, Check, X, AlertCircle } from 'lucide-react';
import { usePolkadot } from '@/contexts/PolkadotContext';
import type { CitizenshipData, Region, MaritalStatus } from '@pezkuwi/lib/citizenship-workflow';
import { FOUNDER_ADDRESS, submitKycApplication, subscribeToKycApproval, getKycStatus } from '@pezkuwi/lib/citizenship-workflow';
@@ -16,11 +16,12 @@ import { generateCommitmentHash, generateNullifierHash, encryptData, saveLocalCi
interface NewCitizenApplicationProps {
onClose: () => void;
+ referrerAddress?: string | null;
}
type FormData = Omit;
-export const NewCitizenApplication: React.FC = ({ onClose }) => {
+export const NewCitizenApplication: React.FC = ({ onClose, referrerAddress }) => {
const { api, isApiReady, selectedAccount, connectWallet } = usePolkadot();
const { register, handleSubmit, watch, setValue, formState: { errors } } = useForm();
@@ -31,10 +32,80 @@ export const NewCitizenApplication: React.FC = ({ on
const [error, setError] = useState(null);
const [agreed, setAgreed] = useState(false);
const [checkingStatus, setCheckingStatus] = useState(false);
+ const [confirming, setConfirming] = useState(false);
+ const [applicationHash, setApplicationHash] = useState('');
const maritalStatus = watch('maritalStatus');
const childrenCount = watch('childrenCount');
+ const handleApprove = async () => {
+ if (!api || !selectedAccount) {
+ setError('Please connect your wallet first');
+ return;
+ }
+
+ setConfirming(true);
+ try {
+ const { web3FromAddress } = await import('@polkadot/extension-dapp');
+ const injector = await web3FromAddress(selectedAccount.address);
+
+ console.log('Confirming citizenship application (self-confirmation)...');
+
+ // Call confirm_citizenship() extrinsic - self-confirmation for Welati Tiki
+ const tx = api.tx.identityKyc.confirmCitizenship();
+
+ await tx.signAndSend(selectedAccount.address, { signer: injector.signer }, ({ status, events, dispatchError }) => {
+ if (dispatchError) {
+ if (dispatchError.isModule) {
+ const decoded = api.registry.findMetaError(dispatchError.asModule);
+ console.error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`);
+ setError(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`);
+ } else {
+ console.error(dispatchError.toString());
+ setError(dispatchError.toString());
+ }
+ setConfirming(false);
+ return;
+ }
+
+ if (status.isInBlock || status.isFinalized) {
+ console.log('✅ Citizenship confirmed successfully!');
+ console.log('Block hash:', status.asInBlock || status.asFinalized);
+
+ // Check for CitizenshipConfirmed event
+ events.forEach(({ event }) => {
+ if (event.section === 'identityKyc' && event.method === 'CitizenshipConfirmed') {
+ console.log('📢 CitizenshipConfirmed event detected');
+ setKycApproved(true);
+ setWaitingForApproval(false);
+
+ // Redirect to citizen dashboard after 2 seconds
+ setTimeout(() => {
+ onClose();
+ window.location.href = '/dashboard';
+ }, 2000);
+ }
+ });
+
+ setConfirming(false);
+ }
+ });
+
+ } catch (err: any) {
+ console.error('Approval error:', err);
+ setError(err.message || 'Failed to approve application');
+ setConfirming(false);
+ }
+ };
+
+ const handleReject = async () => {
+ // Cancel/withdraw the application - simply close modal and go back
+ // No blockchain interaction needed - application will remain Pending until confirmed or admin-rejected
+ console.log('Canceling citizenship application (no blockchain interaction)');
+ onClose();
+ window.location.href = '/';
+ };
+
// Check KYC status on mount
useEffect(() => {
const checkKycStatus = async () => {
@@ -139,6 +210,13 @@ export const NewCitizenApplication: React.FC = ({ on
return;
}
+ // Note: Referral initiation must be done by the REFERRER before the referee does KYC
+ // The referrer calls api.tx.referral.initiateReferral(refereeAddress) from InviteUserModal
+ // Here we just use the referrerAddress in the citizenship data if provided
+ if (referrerAddress) {
+ console.log(`KYC application with referrer: ${referrerAddress}`);
+ }
+
// Prepare complete citizenship data
const citizenshipData: CitizenshipData = {
...data,
@@ -193,6 +271,11 @@ export const NewCitizenApplication: React.FC = ({ on
console.log('✅ KYC application submitted to blockchain');
console.log('Block hash:', result.blockHash);
+ // Save block hash for display
+ if (result.blockHash) {
+ setApplicationHash(result.blockHash.slice(0, 16) + '...');
+ }
+
// Move to waiting for approval state
setSubmitted(true);
setSubmitting(false);
@@ -238,59 +321,107 @@ export const NewCitizenApplication: React.FC = ({ on
);
}
- // Waiting for approval - Loading state
+ // Waiting for self-confirmation
if (waitingForApproval) {
return (
- {/* Animated Loader with Halos */}
-
- {/* Outer halo */}
-
- {/* Middle halo */}
-
- {/* Inner spinning sun */}
-
-
-
+ {/* Icon */}
+
-
Waiting for Admin Approval
+
Confirm Your Citizenship Application
- Your application has been submitted to the blockchain and is waiting for admin approval.
- This page will automatically update when your citizenship is approved.
+ Your application has been submitted to the blockchain. Please review and confirm your identity to mint your Citizen NFT (Welati Tiki).
{/* Status steps */}
-
-
-
Application encrypted and stored on IPFS
+
+
+
+
+
+
Data Encrypted
+
Your KYC data has been encrypted and stored on IPFS
+
-
-
-
Transaction submitted to blockchain
+
+
+
+
+
+
+
Blockchain Submitted
+
Transaction hash: {applicationHash || 'Processing...'}
+
-
-
- Waiting for admin to approve KYC...
-
-
-
-
Receive Welati Tiki NFT
+
+
+
+
+
Awaiting Your Confirmation
+
Confirm or reject your application below
+
- {/* Info */}
-
-
- Note: Do not close this page. The system is monitoring the blockchain
- for approval events in real-time. You will be automatically redirected once approved.
-
-
+ {/* Action buttons */}
+
+
+
+
+
+ {error && (
+
+
+ {error}
+
+ )}
+
+
);
@@ -493,7 +624,7 @@ export const NewCitizenApplication: React.FC
= ({ on
-
+
If empty, you will be automatically linked to the Founder (Satoshi Qazi Muhammed)
diff --git a/web/src/components/p2p/CreateAd.tsx b/web/src/components/p2p/CreateAd.tsx
index 09092e57..66a533ac 100644
--- a/web/src/components/p2p/CreateAd.tsx
+++ b/web/src/components/p2p/CreateAd.tsx
@@ -172,13 +172,14 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
- setAmountCrypto(e.target.value)}
- placeholder="10.00"
+ placeholder="Amount"
+ className="placeholder:text-gray-500 placeholder:opacity-50"
/>
@@ -202,13 +203,14 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
- setFiatAmount(e.target.value)}
- placeholder="1000.00"
+ placeholder="Amount"
+ className="placeholder:text-gray-500 placeholder:opacity-50"
/>
@@ -254,6 +256,7 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
value={paymentDetails[field] || ''}
onChange={(e) => handlePaymentDetailChange(field, e.target.value)}
placeholder={placeholder}
+ className="placeholder:text-gray-500 placeholder:opacity-50"
/>
))}
@@ -270,7 +273,8 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
step="0.01"
value={minOrderAmount}
onChange={e => setMinOrderAmount(e.target.value)}
- placeholder={`Min ${token} per trade`}
+ placeholder="Minimum amount (optional)"
+ className="placeholder:text-gray-500 placeholder:opacity-50"
/>