mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-19 19:51:02 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69789548e7 | |||
| 86ff43e206 |
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield, Smartphone } from 'lucide-react';
|
import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield, Smartphone, Loader2 } from 'lucide-react';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -29,6 +29,7 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
|
|||||||
disconnectWallet,
|
disconnectWallet,
|
||||||
api,
|
api,
|
||||||
isApiReady,
|
isApiReady,
|
||||||
|
isApiInitializing,
|
||||||
peopleApi,
|
peopleApi,
|
||||||
walletSource,
|
walletSource,
|
||||||
wcPeerName,
|
wcPeerName,
|
||||||
@@ -350,9 +351,19 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
|
|||||||
onClick={handleConnect}
|
onClick={handleConnect}
|
||||||
className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500"
|
className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={isApiInitializing}
|
||||||
>
|
>
|
||||||
<Wallet className="mr-2 h-4 w-4" />
|
{isApiInitializing ? (
|
||||||
{t('walletModal.extensionConnect')}
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
{t('walletModal.connectingBlockchain', 'Connecting to blockchain...')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Wallet className="mr-2 h-4 w-4" />
|
||||||
|
{t('walletModal.extensionConnect')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<a
|
<a
|
||||||
href="https://chromewebstore.google.com/search/pezkuwi%7B.js%7D%20extension?hl=en-GB&utm_source=ext_sidebar"
|
href="https://chromewebstore.google.com/search/pezkuwi%7B.js%7D%20extension?hl=en-GB&utm_source=ext_sidebar"
|
||||||
@@ -380,9 +391,19 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full border-purple-500/30 hover:border-purple-500/60 hover:bg-purple-500/10"
|
className="w-full border-purple-500/30 hover:border-purple-500/60 hover:bg-purple-500/10"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={isApiInitializing}
|
||||||
>
|
>
|
||||||
<Smartphone className="mr-2 h-4 w-4" />
|
{isApiInitializing ? (
|
||||||
{t('walletModal.mobileConnect')}
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
{t('walletModal.connectingBlockchain', 'Connecting to blockchain...')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Smartphone className="mr-2 h-4 w-4" />
|
||||||
|
{t('walletModal.mobileConnect')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center justify-center gap-1 text-xs text-gray-400">
|
<div className="flex items-center justify-center gap-1 text-xs text-gray-400">
|
||||||
{t('walletModal.mobileComingSoon')}
|
{t('walletModal.mobileComingSoon')}
|
||||||
|
|||||||
@@ -54,6 +54,21 @@ export function P2PIdentityProvider({ children }: { children: ReactNode }) {
|
|||||||
const uuid = await identityToUUID(fullCitizenNumber);
|
const uuid = await identityToUUID(fullCitizenNumber);
|
||||||
setUserId(uuid);
|
setUserId(uuid);
|
||||||
setVisaNumber(null);
|
setVisaNumber(null);
|
||||||
|
|
||||||
|
// If this wallet is linked to a Telegram account and p2p_user_id not set yet,
|
||||||
|
// backfill it so mini app users see the same P2P identity
|
||||||
|
if (walletAddress) {
|
||||||
|
supabase
|
||||||
|
.from('tg_users')
|
||||||
|
.select('id, p2p_user_id')
|
||||||
|
.eq('wallet_address', walletAddress)
|
||||||
|
.maybeSingle()
|
||||||
|
.then(({ data: tgUser }) => {
|
||||||
|
if (tgUser && !tgUser.p2p_user_id) {
|
||||||
|
supabase.from('tg_users').update({ p2p_user_id: uuid }).eq('id', tgUser.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (walletAddress) {
|
} else if (walletAddress) {
|
||||||
// Non-citizen: check for existing visa
|
// Non-citizen: check for existing visa
|
||||||
const { data: visa } = await supabase
|
const { data: visa } = await supabase
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ if (typeof window !== 'undefined' && !import.meta.env.DEV) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
import React, { createContext, useContext, useEffect, useState, useRef, ReactNode } from 'react';
|
||||||
import { ApiPromise, WsProvider } from '@pezkuwi/api';
|
import { ApiPromise, WsProvider } from '@pezkuwi/api';
|
||||||
import { web3Accounts, web3Enable } from '@pezkuwi/extension-dapp';
|
import { web3Accounts, web3Enable } from '@pezkuwi/extension-dapp';
|
||||||
import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
|
import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
|
||||||
@@ -49,6 +49,7 @@ interface PezkuwiContextType {
|
|||||||
assetHubApi: ApiPromise | null;
|
assetHubApi: ApiPromise | null;
|
||||||
peopleApi: ApiPromise | null;
|
peopleApi: ApiPromise | null;
|
||||||
isApiReady: boolean;
|
isApiReady: boolean;
|
||||||
|
isApiInitializing: boolean;
|
||||||
isAssetHubReady: boolean;
|
isAssetHubReady: boolean;
|
||||||
isPeopleReady: boolean;
|
isPeopleReady: boolean;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
@@ -79,6 +80,8 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
|
|||||||
const [assetHubApi, setAssetHubApi] = useState<ApiPromise | null>(null);
|
const [assetHubApi, setAssetHubApi] = useState<ApiPromise | null>(null);
|
||||||
const [peopleApi, setPeopleApi] = useState<ApiPromise | null>(null);
|
const [peopleApi, setPeopleApi] = useState<ApiPromise | null>(null);
|
||||||
const [isApiReady, setIsApiReady] = useState(false);
|
const [isApiReady, setIsApiReady] = useState(false);
|
||||||
|
const isApiReadyRef = useRef(false);
|
||||||
|
const [isApiInitializing, setIsApiInitializing] = useState(true);
|
||||||
const [isAssetHubReady, setIsAssetHubReady] = useState(false);
|
const [isAssetHubReady, setIsAssetHubReady] = useState(false);
|
||||||
const [isPeopleReady, setIsPeopleReady] = useState(false);
|
const [isPeopleReady, setIsPeopleReady] = useState(false);
|
||||||
const [accounts, setAccounts] = useState<InjectedAccountWithMeta[]>([]);
|
const [accounts, setAccounts] = useState<InjectedAccountWithMeta[]>([]);
|
||||||
@@ -120,6 +123,7 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
|
|||||||
|
|
||||||
const initApi = async () => {
|
const initApi = async () => {
|
||||||
let lastError: unknown = null;
|
let lastError: unknown = null;
|
||||||
|
setIsApiInitializing(true);
|
||||||
|
|
||||||
for (const currentEndpoint of FALLBACK_ENDPOINTS) {
|
for (const currentEndpoint of FALLBACK_ENDPOINTS) {
|
||||||
try {
|
try {
|
||||||
@@ -146,7 +150,9 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
|
|||||||
await apiInstance.isReady;
|
await apiInstance.isReady;
|
||||||
|
|
||||||
setApi(apiInstance);
|
setApi(apiInstance);
|
||||||
|
isApiReadyRef.current = true;
|
||||||
setIsApiReady(true);
|
setIsApiReady(true);
|
||||||
|
setIsApiInitializing(false);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
@@ -200,6 +206,7 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
|
|||||||
}
|
}
|
||||||
setError('Failed to connect to blockchain network. Please try again later.');
|
setError('Failed to connect to blockchain network. Please try again later.');
|
||||||
setIsApiReady(false);
|
setIsApiReady(false);
|
||||||
|
setIsApiInitializing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize Asset Hub API for PEZ token
|
// Initialize Asset Hub API for PEZ token
|
||||||
@@ -489,8 +496,20 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
|
|||||||
|
|
||||||
// Connect via WalletConnect v2 - returns pairing URI for QR code
|
// Connect via WalletConnect v2 - returns pairing URI for QR code
|
||||||
const connectWalletConnect = async (): Promise<string> => {
|
const connectWalletConnect = async (): Promise<string> => {
|
||||||
|
// Wait up to 30s for API to finish initializing instead of failing immediately
|
||||||
|
if (!isApiReady) {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const deadline = Date.now() + 30_000;
|
||||||
|
const check = () => {
|
||||||
|
if (isApiReadyRef.current) return resolve();
|
||||||
|
if (Date.now() >= deadline) return reject(new Error('Blockchain connection timed out. Please refresh and try again.'));
|
||||||
|
setTimeout(check, 300);
|
||||||
|
};
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!api || !isApiReady) {
|
if (!api || !isApiReady) {
|
||||||
throw new Error('API not ready. Please wait for blockchain connection.');
|
throw new Error('Blockchain connection failed. Please refresh and try again.');
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -581,6 +600,7 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
|
|||||||
assetHubApi,
|
assetHubApi,
|
||||||
peopleApi,
|
peopleApi,
|
||||||
isApiReady,
|
isApiReady,
|
||||||
|
isApiInitializing,
|
||||||
isAssetHubReady,
|
isAssetHubReady,
|
||||||
isPeopleReady,
|
isPeopleReady,
|
||||||
isConnected: isApiReady,
|
isConnected: isApiReady,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
|
import { identityToUUID } from '@shared/lib/identity';
|
||||||
import { Loader2, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
import { Loader2, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -51,8 +52,8 @@ export default function TelegramConnect() {
|
|||||||
|
|
||||||
// Find user by telegram_id
|
// Find user by telegram_id
|
||||||
const { data: userData, error: userError } = await supabase
|
const { data: userData, error: userError } = await supabase
|
||||||
.from('users')
|
.from('tg_users')
|
||||||
.select('id, telegram_id, wallet_address, username, first_name')
|
.select('id, telegram_id, wallet_address, p2p_user_id')
|
||||||
.eq('telegram_id', parseInt(telegramId, 10))
|
.eq('telegram_id', parseInt(telegramId, 10))
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
@@ -62,11 +63,32 @@ export default function TelegramConnect() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update wallet address if provided and different
|
// Update wallet address and resolve p2p_user_id if not already set
|
||||||
|
const updates: Record<string, unknown> = {};
|
||||||
if (walletAddress && walletAddress !== userData.wallet_address) {
|
if (walletAddress && walletAddress !== userData.wallet_address) {
|
||||||
|
updates.wallet_address = walletAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userData.p2p_user_id && walletAddress) {
|
||||||
|
// Try to find visa linked to this wallet (non-citizen users)
|
||||||
|
const { data: visa } = await supabase
|
||||||
|
.from('p2p_visa')
|
||||||
|
.select('visa_number')
|
||||||
|
.eq('wallet_address', walletAddress)
|
||||||
|
.eq('status', 'active')
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (visa?.visa_number) {
|
||||||
|
updates.p2p_user_id = await identityToUUID(visa.visa_number);
|
||||||
|
}
|
||||||
|
// Welati (citizen) users: p2p_user_id will be set when they visit pwap/web P2P
|
||||||
|
// with their wallet connected (see P2PIdentityContext → linkTelegramP2PIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
await supabase
|
await supabase
|
||||||
.from('users')
|
.from('tg_users')
|
||||||
.update({ wallet_address: walletAddress })
|
.update(updates)
|
||||||
.eq('id', userData.id);
|
.eq('id', userData.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user