Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,64 @@
import { Route, Routes } from 'react-router';
import LoginPage from './pages/LoginPage';
import React from 'react';
import AuthContext from './context/AuthContext';
import connectToBlockchain from './config';

function App() {
const auth = React.useContext(AuthContext);

const { dispatch } = auth;

React.useEffect(() => {
let isMounted = true;

const init = async () => {
if (isMounted) {
const blockchainConnection = await connectToBlockchain();
if (!blockchainConnection) return;

const { contractInstance, signer } = blockchainConnection;
const account = await signer.getAddress();

let is_admin = false;
let is_voter = true;

try {
is_admin = await contractInstance.isAdmin(account);
} catch (error) {
console.error('Error while fetching user data:', error);
}

if (!is_admin) {
try {
const voter = await contractInstance.getVoterdetails(account);
if (voter?.votername) {
is_voter = false;
}
} catch (error) {
console.error('Error while fetching user data:', error);
}
}

dispatch({
type: 'LOGIN',
payload: { account, instance: contractInstance, is_admin, flag: is_voter },
});
}
};

init();

return () => {
isMounted = false;
};
}, [dispatch]);

return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<LoginPage />} />
<Route path="/" element={<div>home</div>} />
<Route path="*" element={<div>not found return 404</div>} />
</Routes>
);
}
Expand Down
16 changes: 16 additions & 0 deletions src/components/shared/Form-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AlertTriangle } from 'lucide-react';

type FormErrorProps = {
message?: string;
};

export const FormError = ({ message }: FormErrorProps) => {
if (!message) return null;

return (
<div className="bg-destructive/15 p-3 rounded-md flex items-center gap-x-2 text-sm text-destructive">
<AlertTriangle className="h-4 w-4" />
<p>{message}</p>
</div>
);
};
174 changes: 72 additions & 102 deletions src/components/shared/auth/Login-form.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,89 @@
import { Vote } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import connectToBlockchain from '@/config';
import { useContext, useState } from 'react';
import React from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { LoginSchema } from '@/schemas';
import { zodResolver } from '@hookform/resolvers/zod';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { FormError } from '../Form-error';
import AuthContext from '@/context/AuthContext';
import { toast } from 'sonner';

export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
const auth = useContext(AuthContext);
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);

if (!auth) {
return <p>Loading...</p>;
}
export function LoginForm() {
const [error, setError] = React.useState<string | undefined>('');
const [success, setSuccess] = React.useState<string | undefined>('');

Check failure on line 22 in src/components/shared/auth/Login-form.tsx

View workflow job for this annotation

GitHub Actions / Lint, Format, and Build

'success' is assigned a value but never used
const [isPending, startTransition] = React.useTransition();
const auth = React.useContext(AuthContext);

const { dispatch } = auth;

const connectWallet = async (e: React.FormEvent) => {
e.preventDefault(); // Prevent form submission
setLoading(true);

try {
const blockchainConnection = await connectToBlockchain();
console.log('blockchainConnection', blockchainConnection);
if (!blockchainConnection) {
setLoading(false);
return;
}

const { contractInstance, signer } = blockchainConnection;
console.log('contractInstacne', contractInstance);
console.log('signer', signer);
const account = await signer.getAddress();
console.log('account', signer);
console.log('account1', account);

console.log('contractInstance', contractInstance);

toast.success(`Connected to ${account}`);

let isAdmin = false;
let isVoter = true;
const form = useForm<z.infer<typeof LoginSchema>>({
resolver: zodResolver(LoginSchema),
defaultValues: {
name: '',
},
});

try {
isAdmin = await contractInstance.isAdmin(account);
console.log('isAdmin', isAdmin);
} catch (err) {
console.error('Error checking admin status:', err);
}

if (!isAdmin) {
try {
const voter = await contractInstance.getVoterdetails(account);
if (voter?.votername) {
isVoter = false;
}
} catch (error) {
console.error('Error fetching voter details:', error);
}
}

dispatch({
type: 'login',
payload: { account, instance: contractInstance, is_admin: isAdmin, flag: isVoter },
});

toast.success('Wallet connected successfully!');
} catch (error) {
console.error('MetaMask connection error:', error);
toast.error('Failed to connect wallet.');
} finally {
setLoading(false);
}
const onSubmit = (values: z.infer<typeof LoginSchema>) => {
setError('');
setSuccess('');
startTransition(async () => {
console.log('values', values);
console.log('dispatch', dispatch);
});
};

return (
<div className={cn('flex flex-col gap-6', className)} {...props}>
<form onSubmit={connectWallet}>
<div className="flex flex-col gap-6">
<div className="flex flex-col items-center gap-2">
<a href="#" className="flex flex-col items-center gap-2 font-medium">
<div className="flex h-8 w-8 items-center justify-center rounded-md">
<Vote className="size-6" />
</div>
<span className="sr-only">VoteChain</span>
</a>
<h1 className="text-xl font-bold">Welcome to VoteChain.</h1>
<div className="text-center text-sm">
Don&apos;t have an account?{' '}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</div>
</div>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
type="text"
placeholder="Your name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<Button type="submit" disabled={loading} className="w-full">
{loading ? 'Connecting...' : 'Login with MetaMask'}
</Button>
<Form {...form}>
<form className={cn('flex flex-col gap-6')} onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex flex-col items-center gap-2 text-center">
<h1 className="text-2xl font-bold">Login to your account</h1>
<p className="text-balance text-sm text-muted-foreground">
Enter your Name below to login to your account
</p>
</div>
<div className="grid gap-6">
<div className="grid gap-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="name">Name</FormLabel>
<FormControl>
<Input
{...field}
disabled={isPending}
id="name"
placeholder="atharv sawant"
type="text"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormError message={error} />
<Button type="submit" className="w-full" disabled={isPending}>
Login
</Button>
</div>
<div className="text-center text-sm">
Don&apos;t have an account?{' '}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</div>
</form>
</div>
</Form>
);
}
96 changes: 88 additions & 8 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,108 @@ import { ethers } from 'ethers';
import { ContractABI } from '@/utils/components';
import { toast } from 'sonner';

const contractAddress = '0x75d4f9C120f8B41EA49244e9a543fCA830e0eb22';
const contractAddress = '0x75d4f9C120f8B41EA49244e9a543fCA830e0eb22' as const;

interface BlockchainConnection {
provider: ethers.providers.Web3Provider;
contractInstance: ethers.Contract;
signer: ethers.Signer;
account: string;
}

let cachedConnection: BlockchainConnection | null = null;

const loadCachedConnection = async (): Promise<BlockchainConnection | null> => {
const storedData = sessionStorage.getItem('cachedBlockchainConnection');
if (!storedData) return null;

const connectToBlockchain = async () => {
try {
const ethereum = window.ethereum;
const { account } = JSON.parse(storedData);

if (!window.ethereum) return null;

const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contractInstance = new ethers.Contract(contractAddress, ContractABI, signer);

if (!ethereum) {
cachedConnection = { provider, contractInstance, signer, account };
return cachedConnection;
} catch (error) {
console.error('Failed to restore blockchain connection:', error);
sessionStorage.removeItem('cachedBlockchainConnection');
return null;
}
};

const connectToBlockchain = async (): Promise<BlockchainConnection | null> => {
try {
if (cachedConnection) {
console.log('Using cachedConnection:', cachedConnection);
return cachedConnection;
}

const existingConnection = await loadCachedConnection();
if (existingConnection) {
console.log('Restored connection from sessionStorage:', existingConnection);
return existingConnection;
}

if (!window.ethereum) {
toast.error('Ethereum not found, please install MetaMask');
return null;
}

const provider = new ethers.providers.Web3Provider(ethereum);
await provider.send('eth_requestAccounts', []);
const provider = new ethers.providers.Web3Provider(window.ethereum);
const accounts = await provider.listAccounts();
if (accounts.length === 0) {
await provider.send('eth_requestAccounts', []);
}

const signer = provider.getSigner();
const account = await signer.getAddress();
const contractInstance = new ethers.Contract(contractAddress, ContractABI, signer);

return { provider, contractInstance, signer };
cachedConnection = { provider, contractInstance, signer, account };
sessionStorage.setItem('cachedBlockchainConnection', JSON.stringify({ account }));

window.ethereum.on('accountsChanged', (accounts: string[]) => {
if (accounts.length === 0) {
cachedConnection = null;
sessionStorage.removeItem('cachedBlockchainConnection');
toast.info('Disconnected from MetaMask');
} else {
window.location.reload();
}
});

window.ethereum.on('chainChanged', () => {
cachedConnection = null;
sessionStorage.removeItem('cachedBlockchainConnection');
window.location.reload();
});

toast.success('Connected to blockchain successfully');
return cachedConnection;
} catch (error) {
console.error('Connection error:', error);
const ethersError = error as { code?: number; message?: string };
if (ethersError.code === 4001) {
toast.error('User rejected the connection request');
} else {
console.error('Failed to connect to blockchain');
}
toast.error('Failed to connect to blockchain');
console.error(error);
return null;
}
};

export const getCurrentConnection = (): BlockchainConnection | null => {
return cachedConnection;
};

export const disconnectFromBlockchain = (): void => {
cachedConnection = null;
sessionStorage.removeItem('cachedBlockchainConnection');
};

export default connectToBlockchain;
Loading
Loading