This commit is contained in:
2026-04-05 00:43:23 +05:30
commit 8be37d3e92
425 changed files with 101853 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
'use client';
import { AuthService } from '@/lib/auth';
import { useWalletConnection } from './useWalletConnection';
import { useEffect, useState } from 'react';
import { api } from '@/lib/api';
export function useAuth() {
const { address, isConnected, signMessage } = useWalletConnection();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isAuthenticating, setIsAuthenticating] = useState(false);
const [authError, setAuthError] = useState<string | null>(null);
// Check authentication status on mount and when wallet changes
useEffect(() => {
const checkAuth = () => {
const authenticated = AuthService.isAuthenticated();
setIsAuthenticated(authenticated);
// If wallet is connected but not authenticated, show warning
if (isConnected && !authenticated) {
console.warn('⚠️ Wallet connected but not authenticated');
}
};
checkAuth();
// Check auth status every 30 seconds
const interval = setInterval(checkAuth, 30000);
return () => clearInterval(interval);
}, [isConnected, address]);
const authenticate = async () => {
if (!isConnected || !address) {
setAuthError('Please connect your wallet first');
return false;
}
setIsAuthenticating(true);
setAuthError(null);
try {
// Clear any old auth before registering to avoid sending expired credentials
const oldAuthExists = AuthService.isAuthenticated();
if (!oldAuthExists) {
AuthService.clearAuth();
}
// Step 1: Register user if needed
try {
await api.users.register({
walletAddress: address,
role: 'worker',
});
console.log('✅ User registered successfully');
} catch (error: any) {
// User might already exist, which is fine
if (error.response?.status !== 409) {
console.log('Registration note:', error.response?.data?.message || error.message);
}
}
// Step 2: Generate and sign authentication message
const timestamp = Date.now();
const message = AuthService.generateAuthMessage(address, timestamp);
const signature = await signMessage(message);
// Step 3: Store authentication
AuthService.storeAuth(address, signature, message, timestamp);
setIsAuthenticated(true);
console.log('✅ Authentication successful');
return true;
} catch (error: any) {
console.error('Authentication error:', error);
setAuthError(error.message || 'Authentication failed');
AuthService.clearAuth();
setIsAuthenticated(false);
return false;
} finally {
setIsAuthenticating(false);
}
};
const clearAuth = () => {
AuthService.clearAuth();
setIsAuthenticated(false);
setAuthError(null);
};
return {
isAuthenticated,
isAuthenticating,
authError,
authenticate,
clearAuth,
};
}

View File

@@ -0,0 +1,38 @@
'use client';
import { getCUSDContract, getProvider } from '@/lib/contracts';
import { useQuery } from '@tanstack/react-query';
import { ethers } from 'ethers';
export function useCUSDBalance(address: string | null) {
return useQuery({
queryKey: ['cusd-balance', address],
queryFn: async () => {
if (!address) return '0';
const provider = getProvider();
const cUSDContract = getCUSDContract(provider);
const balance = await cUSDContract.balanceOf(address);
return ethers.formatEther(balance);
},
enabled: !!address,
refetchInterval: 10000, // Refetch every 10 seconds
});
}
export function useCUSDAllowance(owner: string | null, spender: string) {
return useQuery({
queryKey: ['cusd-allowance', owner, spender],
queryFn: async () => {
if (!owner) return '0';
const provider = getProvider();
const cUSDContract = getCUSDContract(provider);
const allowance = await cUSDContract.allowance(owner, spender);
return ethers.formatEther(allowance);
},
enabled: !!owner,
});
}

View File

@@ -0,0 +1,123 @@
'use client';
import { parseErrorMessage } from '@/lib/celo';
import { getCUSDContract, getTaskEscrowContract } from '@/lib/contracts';
import { ethers } from 'ethers';
import { useState } from 'react';
import { useWalletConnection } from './useWalletConnection';
export function useTaskContract() {
const { signer, address } = useWalletConnection();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
/**
* Create task on blockchain
*/
const createTask = async (paymentAmount: number, durationInDays: number) => {
if (!signer || !address) {
throw new Error('Wallet not connected');
}
setIsLoading(true);
setError(null);
try {
const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;
if (!contractAddress) {
throw new Error('Contract address not configured');
}
// Step 1: Approve cUSD spending
console.log('📝 Approving cUSD spending...');
const cUSDContract = getCUSDContract(signer);
const amount = ethers.parseEther(paymentAmount.toString());
const approveTx = await cUSDContract.approve(contractAddress, amount);
await approveTx.wait();
console.log('✅ cUSD approved');
// Step 2: Create task
console.log('📝 Creating task on blockchain...');
const taskContract = getTaskEscrowContract(signer);
const createTx = await taskContract.createTask(amount, durationInDays);
console.log('⏳ Waiting for confirmation...');
const receipt = await createTx.wait();
// Parse event to get taskId
const event = receipt.logs.find((log: any) => {
try {
const parsed = taskContract.interface.parseLog(log);
return parsed?.name === 'TaskCreated';
} catch {
return false;
}
});
let taskId = 0;
if (event) {
const parsedEvent = taskContract.interface.parseLog(event);
taskId = Number(parsedEvent?.args[0]);
}
console.log('✅ Task created! Task ID:', taskId);
setIsLoading(false);
return {
taskId,
txHash: receipt.hash,
};
} catch (err: any) {
const errorMessage = parseErrorMessage(err);
setError(errorMessage);
setIsLoading(false);
throw new Error(errorMessage);
}
};
/**
* Check task status on blockchain
*/
const checkTaskStatus = async (taskId: number) => {
if (!signer) {
throw new Error('Wallet not connected');
}
try {
const taskContract = getTaskEscrowContract(signer);
const task = await taskContract.getTask(taskId);
return {
taskId: Number(task.taskId),
requester: task.requester,
worker: task.worker,
paymentAmount: ethers.formatEther(task.paymentAmount),
status: Number(task.status),
createdAt: Number(task.createdAt),
expiresAt: Number(task.expiresAt),
};
} catch (err: any) {
const errorMessage = parseErrorMessage(err);
throw new Error(errorMessage);
}
};
/**
* Get current task counter
*/
const getTaskCounter = async () => {
const provider = new ethers.JsonRpcProvider(process.env.NEXT_PUBLIC_CELO_RPC_URL);
const taskContract = getTaskEscrowContract(provider);
const counter = await taskContract.taskCounter();
return Number(counter);
};
return {
createTask,
checkTaskStatus,
getTaskCounter,
isLoading,
error,
};
}

View File

@@ -0,0 +1,52 @@
'use client';
import { create } from 'zustand';
export interface Transaction {
hash: string;
status: 'pending' | 'success' | 'failed';
description: string;
timestamp: number;
}
interface TransactionState {
transactions: Transaction[];
currentTx: Transaction | null;
addTransaction: (tx: Omit<Transaction, 'timestamp'>) => void;
updateTransaction: (hash: string, updates: Partial<Transaction>) => void;
clearTransactions: () => void;
}
export const useTransactions = create<TransactionState>((set) => ({
transactions: [],
currentTx: null,
addTransaction: (tx) => {
const transaction: Transaction = {
...tx,
timestamp: Date.now(),
};
set((state) => ({
transactions: [transaction, ...state.transactions],
currentTx: transaction,
}));
},
updateTransaction: (hash, updates) => {
set((state) => ({
transactions: state.transactions.map((tx) =>
tx.hash === hash ? { ...tx, ...updates } : tx
),
currentTx:
state.currentTx?.hash === hash
? { ...state.currentTx, ...updates }
: state.currentTx,
}));
},
clearTransactions: () => {
set({ transactions: [], currentTx: null });
},
}));

View File

@@ -0,0 +1,5 @@
'use client';
// Re-export useWalletConnection as useWallet for backwards compatibility
export { useWalletConnection as useWallet } from './useWalletConnection';

View File

@@ -0,0 +1,189 @@
'use client';
import { getCurrentNetwork, parseErrorMessage } from '@/lib/celo';
import { getWalletProvider, isWalletAvailable } from '@/lib/minipay';
import { ethers } from 'ethers';
import { create } from 'zustand';
interface WalletState {
address: string | null;
chainId: number | null;
isConnected: boolean;
isConnecting: boolean;
provider: ethers.BrowserProvider | null;
signer: ethers.Signer | null;
error: string | null;
walletType: string | null;
// Actions
connect: () => Promise<void>;
disconnect: () => void;
switchNetwork: () => Promise<void>;
signMessage: (message: string) => Promise<string>;
initialize: () => Promise<void>; // Add this
}
export const useWalletConnection = create<WalletState>((set, get) => ({
address: null,
chainId: null,
isConnected: false,
isConnecting: false,
provider: null,
signer: null,
error: null,
walletType: null,
initialize: async () => {
// Check if wallet was previously connected
if (typeof window === 'undefined') return;
if (!isWalletAvailable()) return;
try {
const provider = new ethers.BrowserProvider(window.ethereum);
const accounts = await provider.listAccounts();
if (accounts.length > 0) {
// Auto-connect if previously connected
await get().connect();
}
} catch (error) {
console.log('Not previously connected');
}
},
connect: async () => {
set({ isConnecting: true, error: null });
try {
if (!isWalletAvailable()) {
throw new Error('No wallet detected. Please install MiniPay or MetaMask.');
}
const provider = new ethers.BrowserProvider(window.ethereum);
const accounts = await provider.send('eth_requestAccounts', []);
const address = accounts[0];
const network = await provider.getNetwork();
const chainId = Number(network.chainId);
const expectedChainId = getCurrentNetwork().chainId;
if (chainId !== expectedChainId) {
await get().switchNetwork();
return;
}
const signer = await provider.getSigner();
const walletType = getWalletProvider();
set({
address,
chainId,
provider,
signer,
isConnected: true,
isConnecting: false,
walletType,
});
console.log(`✅ Connected to ${walletType}:`, address);
} catch (error: any) {
const errorMessage = parseErrorMessage(error);
set({
error: errorMessage,
isConnecting: false,
});
console.error('Wallet connection error:', error);
throw error;
}
},
disconnect: () => {
set({
address: null,
chainId: null,
provider: null,
signer: null,
isConnected: false,
walletType: null,
});
console.log('🔌 Wallet disconnected');
},
switchNetwork: async () => {
try {
const targetNetwork = getCurrentNetwork();
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${targetNetwork.chainId.toString(16)}` }],
});
} catch (switchError: any) {
if (switchError.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: `0x${targetNetwork.chainId.toString(16)}`,
chainName: targetNetwork.name,
nativeCurrency: {
name: 'CELO',
symbol: 'CELO',
decimals: 18,
},
rpcUrls: [targetNetwork.rpcUrl],
blockExplorerUrls: [targetNetwork.blockExplorer],
},
],
});
} else {
throw switchError;
}
}
await get().connect();
} catch (error: any) {
const errorMessage = parseErrorMessage(error);
set({ error: errorMessage });
throw error;
}
},
signMessage: async (message: string) => {
const { signer } = get();
if (!signer) {
throw new Error('Wallet not connected');
}
try {
const signature = await signer.signMessage(message);
return signature;
} catch (error: any) {
throw new Error(parseErrorMessage(error));
}
},
}));
// Initialize on client side
if (typeof window !== 'undefined') {
// Initialize wallet connection on mount
useWalletConnection.getState().initialize();
// Listen for account changes
if (window.ethereum) {
window.ethereum.on('accountsChanged', (accounts: string[]) => {
if (accounts.length === 0) {
useWalletConnection.getState().disconnect();
} else {
useWalletConnection.getState().connect();
}
});
window.ethereum.on('chainChanged', () => {
window.location.reload();
});
}
}