no separate login/register page, use modal throughout
This commit is contained in:
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
import { AuthProvider } from './contexts/AuthContext';
|
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||||
import Navbar from './components/Navbar';
|
import Navbar from './components/Navbar';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
|
import AuthModal from './components/AuthModal';
|
||||||
import Home from './pages/Home';
|
import Home from './pages/Home';
|
||||||
import Login from './pages/Login';
|
|
||||||
import Register from './pages/Register';
|
|
||||||
import GoogleCallback from './pages/GoogleCallback';
|
import GoogleCallback from './pages/GoogleCallback';
|
||||||
import VerifyEmail from './pages/VerifyEmail';
|
import VerifyEmail from './pages/VerifyEmail';
|
||||||
import ItemList from './pages/ItemList';
|
import ItemList from './pages/ItemList';
|
||||||
@@ -27,17 +26,17 @@ import EarningsDashboard from './pages/EarningsDashboard';
|
|||||||
import PrivateRoute from './components/PrivateRoute';
|
import PrivateRoute from './components/PrivateRoute';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
const AppContent: React.FC = () => {
|
||||||
|
const { showAuthModal, authModalMode, closeAuthModal } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
<>
|
||||||
<Router>
|
<Router>
|
||||||
<div className="d-flex flex-column min-vh-100">
|
<div className="d-flex flex-column min-vh-100">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex-grow-1">
|
<main className="flex-grow-1">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/login" element={<Login />} />
|
|
||||||
<Route path="/register" element={<Register />} />
|
|
||||||
<Route path="/auth/google/callback" element={<GoogleCallback />} />
|
<Route path="/auth/google/callback" element={<GoogleCallback />} />
|
||||||
<Route path="/verify-email" element={<VerifyEmail />} />
|
<Route path="/verify-email" element={<VerifyEmail />} />
|
||||||
<Route path="/items" element={<ItemList />} />
|
<Route path="/items" element={<ItemList />} />
|
||||||
@@ -138,6 +137,20 @@ function App() {
|
|||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
<AuthModal
|
||||||
|
show={showAuthModal}
|
||||||
|
onHide={closeAuthModal}
|
||||||
|
initialMode={authModalMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<AuthProvider>
|
||||||
|
<AppContent />
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
41
frontend/src/components/AuthButton.tsx
Normal file
41
frontend/src/components/AuthButton.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
|
|
||||||
|
interface AuthButtonProps {
|
||||||
|
mode: 'login' | 'signup';
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
asLink?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthButton: React.FC<AuthButtonProps> = ({ mode, className = '', children, asLink = false }) => {
|
||||||
|
const { openAuthModal } = useAuth();
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
openAuthModal(mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (asLink) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={handleClick}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleClick}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthButton;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import PasswordStrengthMeter from "./PasswordStrengthMeter";
|
import PasswordStrengthMeter from "./PasswordStrengthMeter";
|
||||||
import PasswordInput from "./PasswordInput";
|
import PasswordInput from "./PasswordInput";
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import AuthModal from "./AuthModal";
|
|
||||||
|
|
||||||
const Navbar: React.FC = () => {
|
const Navbar: React.FC = () => {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout, openAuthModal } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
||||||
const [authModalMode, setAuthModalMode] = useState<"login" | "signup">(
|
|
||||||
"login"
|
|
||||||
);
|
|
||||||
const [searchFilters, setSearchFilters] = useState({
|
const [searchFilters, setSearchFilters] = useState({
|
||||||
search: "",
|
search: "",
|
||||||
location: "",
|
location: "",
|
||||||
@@ -20,11 +15,6 @@ const Navbar: React.FC = () => {
|
|||||||
navigate("/");
|
navigate("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
const openAuthModal = (mode: "login" | "signup") => {
|
|
||||||
setAuthModalMode(mode);
|
|
||||||
setShowAuthModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearch = (e: React.FormEvent) => {
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@@ -204,12 +194,6 @@ const Navbar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<AuthModal
|
|
||||||
show={showAuthModal}
|
|
||||||
onHide={() => setShowAuthModal(false)}
|
|
||||||
initialMode={authModalMode}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { Navigate, useLocation } from 'react-router-dom';
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
|
||||||
|
|
||||||
interface PrivateRouteProps {
|
interface PrivateRouteProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children }) => {
|
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children }) => {
|
||||||
const { user, loading } = useAuth();
|
const { user, loading, openAuthModal } = useAuth();
|
||||||
const location = useLocation();
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !user) {
|
||||||
|
openAuthModal("login");
|
||||||
|
}
|
||||||
|
}, [loading, user, openAuthModal]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="d-flex justify-content-center align-items-center" style={{ minHeight: '80vh' }}>
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center"
|
||||||
|
style={{ minHeight: "80vh" }}
|
||||||
|
>
|
||||||
<div className="spinner-border text-primary" role="status">
|
<div className="spinner-border text-primary" role="status">
|
||||||
<span className="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,7 +27,21 @@ const PrivateRoute: React.FC<PrivateRouteProps> = ({ children }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return user ? <>{children}</> : <Navigate to="/login" state={{ from: location }} replace />;
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center"
|
||||||
|
style={{ minHeight: "80vh" }}
|
||||||
|
>
|
||||||
|
<div className="text-center">
|
||||||
|
<i className="bi bi-lock display-1 text-muted mb-3"></i>
|
||||||
|
<h3>Please log in or sign up to access this page.</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrivateRoute;
|
export default PrivateRoute;
|
||||||
@@ -9,7 +9,6 @@ import React, {
|
|||||||
import { User } from "../types";
|
import { User } from "../types";
|
||||||
import {
|
import {
|
||||||
authAPI,
|
authAPI,
|
||||||
userAPI,
|
|
||||||
fetchCSRFToken,
|
fetchCSRFToken,
|
||||||
resetCSRFToken,
|
resetCSRFToken,
|
||||||
} from "../services/api";
|
} from "../services/api";
|
||||||
@@ -23,6 +22,10 @@ interface AuthContextType {
|
|||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
updateUser: (user: User) => void;
|
updateUser: (user: User) => void;
|
||||||
checkAuth: () => Promise<void>;
|
checkAuth: () => Promise<void>;
|
||||||
|
showAuthModal: boolean;
|
||||||
|
authModalMode: "login" | "signup";
|
||||||
|
openAuthModal: (mode: "login" | "signup") => void;
|
||||||
|
closeAuthModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
@@ -42,6 +45,8 @@ interface AuthProviderProps {
|
|||||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||||
|
const [authModalMode, setAuthModalMode] = useState<"login" | "signup">("login");
|
||||||
const isAuthenticating = useRef(false);
|
const isAuthenticating = useRef(false);
|
||||||
|
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
@@ -132,6 +137,15 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
setUser(user);
|
setUser(user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openAuthModal = (mode: "login" | "signup") => {
|
||||||
|
setAuthModalMode(mode);
|
||||||
|
setShowAuthModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAuthModal = () => {
|
||||||
|
setShowAuthModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider
|
<AuthContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -143,6 +157,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
logout,
|
logout,
|
||||||
updateUser,
|
updateUser,
|
||||||
checkAuth,
|
checkAuth,
|
||||||
|
showAuthModal,
|
||||||
|
authModalMode,
|
||||||
|
openAuthModal,
|
||||||
|
closeAuthModal,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useAuth } from '../contexts/AuthContext';
|
|||||||
import { itemAPI } from '../services/api';
|
import { itemAPI } from '../services/api';
|
||||||
import { Item } from '../types';
|
import { Item } from '../types';
|
||||||
import ItemCard from '../components/ItemCard';
|
import ItemCard from '../components/ItemCard';
|
||||||
|
import AuthButton from '../components/AuthButton';
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@@ -48,9 +49,9 @@ const Home: React.FC = () => {
|
|||||||
List Your Items
|
List Your Items
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<Link to="/register" className="btn btn-outline-light">
|
<AuthButton mode="signup" className="btn btn-outline-light">
|
||||||
Start Earning
|
Start Earning
|
||||||
</Link>
|
</AuthButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -292,9 +293,9 @@ const Home: React.FC = () => {
|
|||||||
List an Item
|
List an Item
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<Link to="/register" className="btn btn-outline-light btn-lg">
|
<AuthButton mode="signup" className="btn btn-outline-light btn-lg">
|
||||||
Sign Up Free
|
Sign Up Free
|
||||||
</Link>
|
</AuthButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useAuth } from '../contexts/AuthContext';
|
|||||||
import { itemRequestAPI } from '../services/api';
|
import { itemRequestAPI } from '../services/api';
|
||||||
import { ItemRequest, ItemRequestResponse } from '../types';
|
import { ItemRequest, ItemRequestResponse } from '../types';
|
||||||
import RequestResponseModal from '../components/RequestResponseModal';
|
import RequestResponseModal from '../components/RequestResponseModal';
|
||||||
|
import AuthButton from '../components/AuthButton';
|
||||||
|
|
||||||
const ItemRequestDetail: React.FC = () => {
|
const ItemRequestDetail: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
@@ -330,9 +331,9 @@ const ItemRequestDetail: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : !user ? (
|
) : !user ? (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link to="/login" className="btn btn-outline-primary">
|
<AuthButton mode="login" className="btn btn-outline-primary">
|
||||||
Log in to Respond
|
Log in to Respond
|
||||||
</Link>
|
</AuthButton>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useAuth } from '../contexts/AuthContext';
|
|||||||
import { itemRequestAPI } from '../services/api';
|
import { itemRequestAPI } from '../services/api';
|
||||||
import { ItemRequest } from '../types';
|
import { ItemRequest } from '../types';
|
||||||
import ItemRequestCard from '../components/ItemRequestCard';
|
import ItemRequestCard from '../components/ItemRequestCard';
|
||||||
|
import AuthButton from '../components/AuthButton';
|
||||||
|
|
||||||
const ItemRequests: React.FC = () => {
|
const ItemRequests: React.FC = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@@ -200,7 +201,7 @@ const ItemRequests: React.FC = () => {
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="alert alert-info" role="alert">
|
<div className="alert alert-info" role="alert">
|
||||||
<i className="bi bi-info-circle me-2"></i>
|
<i className="bi bi-info-circle me-2"></i>
|
||||||
<Link to="/login" className="alert-link">Log in</Link> to create your own item requests or respond to existing ones.
|
<AuthButton mode="login" className="alert-link" asLink>Log in</AuthButton> to create your own item requests or respond to existing ones.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
|
||||||
import PasswordInput from '../components/PasswordInput';
|
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
|
||||||
const [email, setEmail] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { login } = useAuth();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const from = location.state?.from?.pathname || '/';
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setError('');
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await login(email, password);
|
|
||||||
navigate(from, { replace: true });
|
|
||||||
} catch (err: any) {
|
|
||||||
setError(err.response?.data?.error || 'Failed to login');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mt-5">
|
|
||||||
<div className="row justify-content-center">
|
|
||||||
<div className="col-md-6 col-lg-5">
|
|
||||||
<div className="card shadow">
|
|
||||||
<div className="card-body p-4">
|
|
||||||
<h2 className="text-center mb-4">Login</h2>
|
|
||||||
{error && (
|
|
||||||
<div className="alert alert-danger" role="alert">
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="email" className="form-label">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
className="form-control"
|
|
||||||
id="email"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<PasswordInput
|
|
||||||
id="password"
|
|
||||||
label="Password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary w-100"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? 'Logging in...' : 'Login'}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<div className="text-center mt-3">
|
|
||||||
<p className="mb-0">
|
|
||||||
Don't have an account?{' '}
|
|
||||||
<Link to="/register" state={{ from: location.state?.from }} className="text-decoration-none">
|
|
||||||
Sign up
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Login;
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { itemRequestAPI } from '../services/api';
|
import { itemRequestAPI } from '../services/api';
|
||||||
import { ItemRequest, ItemRequestResponse } from '../types';
|
import { ItemRequest, ItemRequestResponse } from '../types';
|
||||||
import ConfirmationModal from '../components/ConfirmationModal';
|
import ConfirmationModal from '../components/ConfirmationModal';
|
||||||
|
|
||||||
const MyRequests: React.FC = () => {
|
const MyRequests: React.FC = () => {
|
||||||
const { user } = useAuth();
|
const { user, openAuthModal } = useAuth();
|
||||||
const navigate = useNavigate();
|
|
||||||
const [requests, setRequests] = useState<ItemRequest[]>([]);
|
const [requests, setRequests] = useState<ItemRequest[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -20,9 +19,9 @@ const MyRequests: React.FC = () => {
|
|||||||
if (user) {
|
if (user) {
|
||||||
fetchMyRequests();
|
fetchMyRequests();
|
||||||
} else {
|
} else {
|
||||||
navigate('/login');
|
openAuthModal('login');
|
||||||
}
|
}
|
||||||
}, [user, navigate]);
|
}, [user, openAuthModal]);
|
||||||
|
|
||||||
const fetchMyRequests = async () => {
|
const fetchMyRequests = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
|
||||||
import PasswordInput from '../components/PasswordInput';
|
|
||||||
|
|
||||||
const Register: React.FC = () => {
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
firstName: '',
|
|
||||||
lastName: '',
|
|
||||||
phone: ''
|
|
||||||
});
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { register } = useAuth();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const from = location.state?.from?.pathname || '/';
|
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
[e.target.name]: e.target.value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setError('');
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await register(formData);
|
|
||||||
navigate(from, { replace: true });
|
|
||||||
} catch (err: any) {
|
|
||||||
setError(err.response?.data?.error || 'Failed to create account');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mt-5">
|
|
||||||
<div className="row justify-content-center">
|
|
||||||
<div className="col-md-6 col-lg-5">
|
|
||||||
<div className="card shadow">
|
|
||||||
<div className="card-body p-4">
|
|
||||||
<h2 className="text-center mb-4">Create Account</h2>
|
|
||||||
{error && (
|
|
||||||
<div className="alert alert-danger" role="alert">
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-md-6 mb-3">
|
|
||||||
<label htmlFor="firstName" className="form-label">
|
|
||||||
First Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
id="firstName"
|
|
||||||
name="firstName"
|
|
||||||
value={formData.firstName}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6 mb-3">
|
|
||||||
<label htmlFor="lastName" className="form-label">
|
|
||||||
Last Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
id="lastName"
|
|
||||||
name="lastName"
|
|
||||||
value={formData.lastName}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="username" className="form-label">
|
|
||||||
Username
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
id="username"
|
|
||||||
name="username"
|
|
||||||
value={formData.username}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="email" className="form-label">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
className="form-control"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="phone" className="form-label">
|
|
||||||
Phone (optional)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
className="form-control"
|
|
||||||
id="phone"
|
|
||||||
name="phone"
|
|
||||||
value={formData.phone}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<PasswordInput
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
label="Password"
|
|
||||||
value={formData.password}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary w-100"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? 'Creating Account...' : 'Sign Up'}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<div className="text-center mt-3">
|
|
||||||
<p className="mb-0">
|
|
||||||
Already have an account?{' '}
|
|
||||||
<Link to="/login" className="text-decoration-none">
|
|
||||||
Login
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Register;
|
|
||||||
Reference in New Issue
Block a user