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
127 changes: 58 additions & 69 deletions src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,75 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { authService, LoginCredentials, RegisterCredentials, User } from '../services/auth.service';
import toast from 'react-hot-toast';

interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (credentials: LoginCredentials) => Promise<void>;
register: (credentials: RegisterCredentials) => Promise<void>;
logout: () => void;
user: any;
login: () => Promise<void>;
logout: () => void;
isLoading?: boolean; // Added ? to prevent strict type errors
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
// Checking for stored token and user on mount
const storedUser = localStorage.getItem('user');
const token = localStorage.getItem('token');
const checkWalletConnected = async () => {
try {
const { ethereum } = window as any;
if (ethereum && ethereum.selectedAddress) {
setUser({ name: 'Farmer', address: ethereum.selectedAddress });
}
} catch (error) {
console.error("Error checking wallet:", error);
}
};

useEffect(() => {
checkWalletConnected();
}, []);

if (storedUser && token) {
try {
setUser(JSON.parse(storedUser));
} catch (e) {
console.error('Failed to parse stored user', e);
localStorage.removeItem('user');
localStorage.removeItem('token');
}
}
setIsLoading(false);
}, []);
const login = async () => {
setIsLoading(true);
const { ethereum } = window as any;

const login = async (credentials: LoginCredentials) => {
setIsLoading(true);
try {
const response = await authService.login(credentials);
setUser(response.user);
localStorage.setItem('user', JSON.stringify(response.user));
localStorage.setItem('token', response.token);
} finally {
setIsLoading(false);
}
};
if (!ethereum) {
toast.error("Metamask not found!");
setIsLoading(false);
return;
}

const register = async (credentials: RegisterCredentials) => {
setIsLoading(true);
try {
const response = await authService.register(credentials);
setUser(response.user);
localStorage.setItem('user', JSON.stringify(response.user));
localStorage.setItem('token', response.token);
} finally {
setIsLoading(false);
}
};
try {
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
setUser({ name: 'Farmer', address: accounts[0] });
toast.success("Connected!");
} catch (error: any) {
if (error.code === 4001) {
toast.error("Connection rejected");
} else {
toast.error("Connection failed");
}
} finally {
setIsLoading(false);
}
};

const logout = () => {
setUser(null);
localStorage.removeItem('user');
localStorage.removeItem('token');
};
const logout = () => {
setUser(null);
toast.success("Logged out");
};

return (
<AuthContext.Provider value={{
user,
isAuthenticated: !!user,
isLoading,
login,
register,
logout
}}>
{children}
</AuthContext.Provider>
);
return (
<AuthContext.Provider value={{ user, login, logout, isLoading }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
169 changes: 57 additions & 112 deletions src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,120 +1,65 @@
import React, { useState } from 'react';
import { useNavigate, Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { LayoutDashboard, Wallet, Loader2 } from 'lucide-react';
import { useAuth } from '../context/AuthContext';
import { LogIn, Mail, Lock, Loader } from 'lucide-react';

const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);

const { t } = useTranslation();
const { login } = useAuth();
const navigate = useNavigate();
const location = useLocation();

const from = location.state?.from?.pathname || '/';

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsSubmitting(true);

try {
await login({ email, password });
navigate(from, { replace: true });
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to login. Please check your credentials.');
} finally {
setIsSubmitting(false);
}
};

return (
<div className="max-w-md mx-auto mt-12 px-4">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl overflow-hidden">
<div className="bg-gradient-to-r from-green-600 to-green-800 p-6 text-center">
<div className="mx-auto bg-white/20 w-16 h-16 rounded-full flex items-center justify-center mb-4 backdrop-blur-sm">
<LogIn className="w-8 h-8 text-white" />
</div>
<h2 className="text-2xl font-bold text-white">{t('auth.loginTitle')}</h2>
<p className="text-green-100 mt-2">Sign in to your CropChain account</p>
</div>

<div className="p-8">
{error && (
<div className="mb-6 p-4 bg-red-100 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-lg text-red-600 dark:text-red-300 text-sm">
{error}
</div>
)}
import { useTranslation } from 'react-i18next';

<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t('auth.email')}
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail className="h-5 w-5 text-gray-400" />
</div>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100 transition-colors"
placeholder="you@example.com"
/>
</div>
</div>
const Login = () => {
const { login, user, isLoading } = useAuth(); // <--- This will work now
const navigate = useNavigate();
const { t } = useTranslation();

<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t('auth.password')}
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100 transition-colors"
placeholder="••••••••"
/>
</div>
</div>
useEffect(() => {
if (user) {
navigate('/dashboard');
}
}, [user, navigate]);

<button
type="submit"
disabled={isSubmitting}
className="w-full flex items-center justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-sm font-medium text-white bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-[1.02] transition-all duration-200"
>
{isSubmitting ? (
<>
<Loader className="animate-spin -ml-1 mr-2 h-4 w-4" />
{t('common.loading')}
</>
) : (
t('auth.loginButton')
)}
</button>
</form>
return (
<div className="min-h-[80vh] flex items-center justify-center">
<div className="bg-white dark:bg-gray-800 p-8 rounded-2xl shadow-xl w-full max-w-md">
<div className="text-center mb-8">
<div className="bg-green-100 dark:bg-green-900 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<LayoutDashboard className="h-8 w-8 text-green-600 dark:text-green-400" />
</div>
<h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-2">
{t('auth.welcomeBack')}
</h2>
<p className="text-gray-600 dark:text-gray-300">
{t('auth.loginSubtitle')}
</p>
</div>

<div className="mt-6 text-center text-sm">
<span className="text-gray-500 dark:text-gray-400">{t('auth.noAccount')}</span>{' '}
<Link to="/register" className="font-medium text-green-600 hover:text-green-500 dark:text-green-400 dark:hover:text-green-300">
{t('nav.register')}
</Link>
</div>
</div>
</div>
<div className="space-y-4">
<button
onClick={login}
disabled={isLoading}
className={`w-full flex items-center justify-center space-x-2 py-3 px-4 rounded-lg font-semibold transition-all duration-200 ${
isLoading
? 'bg-gray-400 cursor-not-allowed opacity-70'
: 'bg-green-600 hover:bg-green-700 text-white hover:scale-105 shadow-md'
}`}
>
{isLoading ? (
<>
<Loader2 className="h-5 w-5 animate-spin" />
<span>Connecting...</span>
</>
) : (
<>
<Wallet className="h-5 w-5" />
<span>{t('auth.connectWallet')}</span>
</>
)}
</button>

<div className="text-center text-sm text-gray-500 dark:text-gray-400 mt-4">
<p>Make sure you have MetaMask installed</p>
</div>
</div>
);
</div>
</div>
);
};

export default Login;
export default Login;
Loading