From f8e657c17436c3f1bf593ed78adf32f2868689e7 Mon Sep 17 00:00:00 2001 From: "ALMAZROUEI Shamma (2021) WKIS203" <shamma.almazrouei.2021@live.rhul.ac.uk> Date: Tue, 11 Mar 2025 17:04:34 +0530 Subject: [PATCH] Build register page --- golden-crust-bakery/package-lock.json | 31 ++ golden-crust-bakery/package.json | 1 + .../src/components/ui/checkbox.jsx | 22 ++ golden-crust-bakery/src/main.jsx | 15 +- golden-crust-bakery/src/pages/Register.jsx | 301 ++++++++++++++++++ 5 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 golden-crust-bakery/src/components/ui/checkbox.jsx create mode 100644 golden-crust-bakery/src/pages/Register.jsx diff --git a/golden-crust-bakery/package-lock.json b/golden-crust-bakery/package-lock.json index ddc8e9b..5b8061a 100644 --- a/golden-crust-bakery/package-lock.json +++ b/golden-crust-bakery/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@radix-ui/react-accordion": "^1.2.3", + "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", @@ -1205,6 +1206,36 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz", + "integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collapsible": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz", diff --git a/golden-crust-bakery/package.json b/golden-crust-bakery/package.json index 1921393..e9b80ea 100644 --- a/golden-crust-bakery/package.json +++ b/golden-crust-bakery/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@radix-ui/react-accordion": "^1.2.3", + "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", diff --git a/golden-crust-bakery/src/components/ui/checkbox.jsx b/golden-crust-bakery/src/components/ui/checkbox.jsx new file mode 100644 index 0000000..5e0c96b --- /dev/null +++ b/golden-crust-bakery/src/components/ui/checkbox.jsx @@ -0,0 +1,22 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef(({ className, ...props }, ref) => ( + <CheckboxPrimitive.Root + ref={ref} + className={cn( + "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", + className + )} + {...props}> + <CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}> + <Check className="h-4 w-4" /> + </CheckboxPrimitive.Indicator> + </CheckboxPrimitive.Root> +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/golden-crust-bakery/src/main.jsx b/golden-crust-bakery/src/main.jsx index a92a05a..4a44fcb 100644 --- a/golden-crust-bakery/src/main.jsx +++ b/golden-crust-bakery/src/main.jsx @@ -10,8 +10,9 @@ import './index.css'; import App from './App.jsx'; import Landing from './pages/Landing'; import About from './pages/About'; -import Contact from './pages/Contact'; +// import Contact from './pages/Contact'; import Login from './pages/Login'; +import Register from './pages/Register'; const router = createBrowserRouter([ { @@ -26,14 +27,18 @@ const router = createBrowserRouter([ path: '/about', element: <About />, }, - { - path: '/contact', - element: <Contact />, - }, + // { + // path: '/contact', + // element: <Contact />, + // }, { path: '/login', element: <Login />, }, + { + path: '/register', + element: <Register />, + }, ], }, ]); diff --git a/golden-crust-bakery/src/pages/Register.jsx b/golden-crust-bakery/src/pages/Register.jsx new file mode 100644 index 0000000..3e39f9c --- /dev/null +++ b/golden-crust-bakery/src/pages/Register.jsx @@ -0,0 +1,301 @@ +import { useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import { toast } from 'sonner'; +import { Cake, Lock, Mail, User } from 'lucide-react'; + +import { useAuthStore } from '@/lib/authStore'; + +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Checkbox } from '@/components/ui/checkbox'; + +export default function Register() { + const navigate = useNavigate(); + const { login } = useAuthStore(); + + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + confirmPassword: '', + agreeTerms: false, + }); + + const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: type === 'checkbox' ? checked : value, + })); + + // Clear error when field is being edited + if (errors[name]) { + setErrors((prev) => ({ + ...prev, + [name]: '', + })); + } + }; + + const validateForm = () => { + const newErrors = {}; + + if (!formData.name.trim()) newErrors.name = 'Name is required'; + + if (!formData.email.trim()) { + newErrors.email = 'Email is required'; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + newErrors.email = 'Email is invalid'; + } + + if (!formData.password) { + newErrors.password = 'Password is required'; + } else if (formData.password.length < 6) { + newErrors.password = 'Password must be at least 6 characters'; + } + + if (formData.password !== formData.confirmPassword) { + newErrors.confirmPassword = 'Passwords do not match'; + } + + if (!formData.agreeTerms) { + newErrors.agreeTerms = 'You must agree to the terms and conditions'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + if (!validateForm()) return; + + setIsSubmitting(true); + + // Simulate registration process + setTimeout(() => { + // Check if user already exists + const users = JSON.parse(localStorage.getItem('users') || '[]'); + const userExists = users.some((user) => user.email === formData.email); + + if (userExists) { + setErrors({ + email: 'This email is already registered', + }); + setIsSubmitting(false); + return; + } + + // Create new user + const newUser = { + id: Date.now().toString(), + name: formData.name, + email: formData.email, + password: formData.password, + }; + + // Save to local storage + localStorage.setItem('users', JSON.stringify([...users, newUser])); + + // Auto login + login({ + id: newUser.id, + name: newUser.name, + email: newUser.email, + }); + + toast({ + title: 'Registration Successful', + description: `Welcome to Golden Crust Bakery, ${newUser.name}!`, + }); + + setIsSubmitting(false); + navigate('/'); + }, 1500); + }; + + return ( + <div className='container mx-auto px-4 py-8 md:py-12'> + <div className='max-w-md mx-auto'> + <div className='text-center mb-8'> + <div className='mx-auto w-16 h-16 bg-pink-100 rounded-full flex items-center justify-center mb-4'> + <Cake className='h-8 w-8 text-pink-500' /> + </div> + <h1 className='text-3xl font-bold text-gray-800 mb-2'> + Create an Account + </h1> + <p className='text-gray-600'> + Join Golden Crust Bakery to order delicious cakes + </p> + </div> + + <div className='bg-white rounded-xl border shadow-sm overflow-hidden'> + <div className='p-6'> + <form onSubmit={handleSubmit} className='space-y-6'> + <div className='space-y-2'> + <Label htmlFor='name'>Full Name</Label> + <div className='relative'> + <User className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5' /> + <Input + id='name' + name='name' + value={formData.name} + onChange={handleChange} + className={`pl-10 rounded-lg ${ + errors.name ? 'border-red-500' : 'border-gray-200' + }`} + placeholder='Peter Parker' + /> + </div> + {errors.name && ( + <p className='text-red-500 text-sm'>{errors.name}</p> + )} + </div> + + <div className='space-y-2'> + <Label htmlFor='email'>Email Address</Label> + <div className='relative'> + <Mail className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5' /> + <Input + id='email' + name='email' + type='email' + value={formData.email} + onChange={handleChange} + className={`pl-10 rounded-lg ${ + errors.email ? 'border-red-500' : 'border-gray-200' + }`} + placeholder='yourname@email.com' + /> + </div> + {errors.email && ( + <p className='text-red-500 text-sm'>{errors.email}</p> + )} + </div> + + <div className='space-y-2'> + <Label htmlFor='password'>Password</Label> + <div className='relative'> + <Lock className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5' /> + <Input + id='password' + name='password' + type='password' + value={formData.password} + onChange={handleChange} + className={`pl-10 rounded-lg ${ + errors.password ? 'border-red-500' : 'border-gray-200' + }`} + placeholder='********' + /> + </div> + {errors.password && ( + <p className='text-red-500 text-sm'>{errors.password}</p> + )} + </div> + + <div className='space-y-2'> + <Label htmlFor='confirmPassword'>Confirm Password</Label> + <div className='relative'> + <Lock className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5' /> + <Input + id='confirmPassword' + name='confirmPassword' + type='password' + value={formData.confirmPassword} + onChange={handleChange} + className={`pl-10 rounded-lg ${ + errors.confirmPassword + ? 'border-red-500' + : 'border-gray-200' + }`} + placeholder='********' + /> + </div> + {errors.confirmPassword && ( + <p className='text-red-500 text-sm'> + {errors.confirmPassword} + </p> + )} + </div> + + <div className='flex items-start space-x-2'> + <Checkbox + id='agreeTerms' + name='agreeTerms' + checked={formData.agreeTerms} + onCheckedChange={(checked) => { + setFormData((prev) => ({ + ...prev, + agreeTerms: checked, + })); + if (errors.agreeTerms) { + setErrors((prev) => ({ + ...prev, + agreeTerms: '', + })); + } + }} + className={errors.agreeTerms ? 'border-red-500' : ''} + /> + <div className='grid gap-1.5 leading-none'> + <label + htmlFor='agreeTerms' + className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' + > + I agree to the{' '} + <Link to='#' className='text-pink-600 hover:underline'> + Terms of Service + </Link>{' '} + and{' '} + <Link to='#' className='text-pink-600 hover:underline'> + Privacy Policy + </Link> + </label> + {errors.agreeTerms && ( + <p className='text-red-500 text-sm'>{errors.agreeTerms}</p> + )} + </div> + </div> + + <Button + type='submit' + disabled={isSubmitting} + className='w-full bg-pink-500 hover:bg-pink-600 text-white rounded-full py-6' + > + {isSubmitting ? 'Creating Account...' : 'Create Account'} + </Button> + </form> + + <div className='mt-6 text-center'> + <p className='text-gray-600'> + Already have an account?{' '} + <Link + to='/login' + className='text-pink-600 hover:underline font-medium' + > + Sign in + </Link> + </p> + </div> + </div> + </div> + + <div className='mt-8 text-center'> + <Button + variant='ghost' + onClick={() => navigate('/')} + className='text-gray-600 hover:text-gray-800' + > + Return to Home + </Button> + </div> + </div> + </div> + ); +} -- GitLab