From bf496f4a397917139d5a8f9c553f6318c1b7a1ea Mon Sep 17 00:00:00 2001
From: "ALMAZROUEI Shamma (2021) WKIS203"
 <shamma.almazrouei.2021@live.rhul.ac.uk>
Date: Thu, 13 Mar 2025 00:44:56 +0530
Subject: [PATCH] Build profile page

---
 golden-crust-bakery/package-lock.json         |  31 +
 golden-crust-bakery/package.json              |   1 +
 .../src/components/ui/tabs.jsx                |  41 ++
 golden-crust-bakery/src/main.jsx              |   5 +
 golden-crust-bakery/src/pages/Profile.jsx     | 556 ++++++++++++++++++
 5 files changed, 634 insertions(+)
 create mode 100644 golden-crust-bakery/src/components/ui/tabs.jsx
 create mode 100644 golden-crust-bakery/src/pages/Profile.jsx

diff --git a/golden-crust-bakery/package-lock.json b/golden-crust-bakery/package-lock.json
index 4130bbf..30fcb51 100644
--- a/golden-crust-bakery/package-lock.json
+++ b/golden-crust-bakery/package-lock.json
@@ -17,6 +17,7 @@
         "@radix-ui/react-radio-group": "^1.2.3",
         "@radix-ui/react-select": "^2.1.6",
         "@radix-ui/react-slot": "^1.1.2",
+        "@radix-ui/react-tabs": "^1.1.3",
         "canvas-confetti": "^1.9.3",
         "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
@@ -1810,6 +1811,36 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-tabs": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz",
+      "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-direction": "1.1.0",
+        "@radix-ui/react-id": "1.1.0",
+        "@radix-ui/react-presence": "1.1.2",
+        "@radix-ui/react-primitive": "2.0.2",
+        "@radix-ui/react-roving-focus": "1.1.2",
+        "@radix-ui/react-use-controllable-state": "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-use-callback-ref": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
diff --git a/golden-crust-bakery/package.json b/golden-crust-bakery/package.json
index f016c50..bac8b20 100644
--- a/golden-crust-bakery/package.json
+++ b/golden-crust-bakery/package.json
@@ -19,6 +19,7 @@
     "@radix-ui/react-radio-group": "^1.2.3",
     "@radix-ui/react-select": "^2.1.6",
     "@radix-ui/react-slot": "^1.1.2",
+    "@radix-ui/react-tabs": "^1.1.3",
     "canvas-confetti": "^1.9.3",
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
diff --git a/golden-crust-bakery/src/components/ui/tabs.jsx b/golden-crust-bakery/src/components/ui/tabs.jsx
new file mode 100644
index 0000000..b674eb9
--- /dev/null
+++ b/golden-crust-bakery/src/components/ui/tabs.jsx
@@ -0,0 +1,41 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef(({ className, ...props }, ref) => (
+  <TabsPrimitive.List
+    ref={ref}
+    className={cn(
+      "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
+      className
+    )}
+    {...props} />
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
+  <TabsPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
+      className
+    )}
+    {...props} />
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
+  <TabsPrimitive.Content
+    ref={ref}
+    className={cn(
+      "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+      className
+    )}
+    {...props} />
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/golden-crust-bakery/src/main.jsx b/golden-crust-bakery/src/main.jsx
index 84ec698..7597294 100644
--- a/golden-crust-bakery/src/main.jsx
+++ b/golden-crust-bakery/src/main.jsx
@@ -20,6 +20,7 @@ import Cart from './pages/Cart';
 import Orders from './pages/Orders';
 import Checkout from './pages/Checkout';
 import Confirmation from './pages/Confirmation';
+import Profile from './pages/Profile';
 
 const router = createBrowserRouter([
   {
@@ -67,6 +68,10 @@ const router = createBrowserRouter([
         path: '/menu',
         element: <Menu />,
       },
+      {
+        path: '/profile',
+        element: <Profile />,
+      },
       {
         path: '/menu/:id',
         element: <MenuItem />,
diff --git a/golden-crust-bakery/src/pages/Profile.jsx b/golden-crust-bakery/src/pages/Profile.jsx
new file mode 100644
index 0000000..7d712b3
--- /dev/null
+++ b/golden-crust-bakery/src/pages/Profile.jsx
@@ -0,0 +1,556 @@
+import { useState, useEffect } from 'react';
+import { toast } from 'sonner';
+import { useNavigate, Link } from 'react-router-dom';
+import { ArrowLeft, Key, Save, Trash2, 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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardFooter,
+  CardHeader,
+  CardTitle,
+} from '@/components/ui/card';
+import {
+  AlertDialog,
+  AlertDialogAction,
+  AlertDialogCancel,
+  AlertDialogContent,
+  AlertDialogDescription,
+  AlertDialogFooter,
+  AlertDialogHeader,
+  AlertDialogTitle,
+  AlertDialogTrigger,
+} from '@/components/ui/alert-dialog';
+
+export default function Profile() {
+  const navigate = useNavigate();
+  const { user, isAuthenticated, updateUser, logout } = useAuthStore();
+
+  const [profileData, setProfileData] = useState({
+    name: '',
+    email: '',
+    phone: '',
+    address: '',
+    city: '',
+    zipCode: '',
+  });
+
+  const [passwordData, setPasswordData] = useState({
+    currentPassword: '',
+    newPassword: '',
+    confirmPassword: '',
+  });
+
+  const [errors, setErrors] = useState({});
+  const [passwordErrors, setPasswordErrors] = useState({});
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const [isPasswordSubmitting, setIsPasswordSubmitting] = useState(false);
+
+  useEffect(() => {
+    // Redirect to login if not authenticated
+    if (!isAuthenticated) {
+      toast.error('Authentication Required', {
+        description: 'Please sign in to view your profile.',
+      });
+      navigate('/login');
+      return;
+    }
+
+    // Load user data
+    if (user) {
+      // Get additional user data from localStorage if available
+      const users = JSON.parse(localStorage.getItem('users') || '[]');
+      const userData = users.find((u) => u.id === user.id) || {};
+
+      setProfileData({
+        name: user.name || '',
+        email: user.email || '',
+        phone: userData.phone || '',
+        address: userData.address || '',
+        city: userData.city || '',
+        zipCode: userData.zipCode || '',
+      });
+    }
+  }, [isAuthenticated, navigate, user]);
+
+  // If not authenticated, don't render the profile content
+  if (!isAuthenticated) {
+    return null;
+  }
+
+  const handleProfileChange = (e) => {
+    const { name, value } = e.target;
+    setProfileData((prev) => ({
+      ...prev,
+      [name]: value,
+    }));
+
+    // Clear error when field is being edited
+    if (errors[name]) {
+      setErrors((prev) => ({
+        ...prev,
+        [name]: '',
+      }));
+    }
+  };
+
+  const handlePasswordChange = (e) => {
+    const { name, value } = e.target;
+    setPasswordData((prev) => ({
+      ...prev,
+      [name]: value,
+    }));
+
+    // Clear error when field is being edited
+    if (passwordErrors[name]) {
+      setPasswordErrors((prev) => ({
+        ...prev,
+        [name]: '',
+      }));
+    }
+  };
+
+  const validateProfileForm = () => {
+    const newErrors = {};
+
+    if (!profileData.name.trim()) newErrors.name = 'Name is required';
+    if (!profileData.email.trim()) {
+      newErrors.email = 'Email is required';
+    } else if (!/\S+@\S+\.\S+/.test(profileData.email)) {
+      newErrors.email = 'Email is invalid';
+    }
+
+    setErrors(newErrors);
+    return Object.keys(newErrors).length === 0;
+  };
+
+  const validatePasswordForm = () => {
+    const newErrors = {};
+
+    if (!passwordData.currentPassword) {
+      newErrors.currentPassword = 'Current password is required';
+    }
+
+    if (!passwordData.newPassword) {
+      newErrors.newPassword = 'New password is required';
+    } else if (passwordData.newPassword.length < 6) {
+      newErrors.newPassword = 'Password must be at least 6 characters';
+    }
+
+    if (passwordData.newPassword !== passwordData.confirmPassword) {
+      newErrors.confirmPassword = 'Passwords do not match';
+    }
+
+    setPasswordErrors(newErrors);
+    return Object.keys(newErrors).length === 0;
+  };
+
+  const handleProfileSubmit = (e) => {
+    e.preventDefault();
+
+    if (!validateProfileForm()) return;
+
+    setIsSubmitting(true);
+
+    // Simulate profile update
+    setTimeout(() => {
+      // Update user in localStorage
+      const users = JSON.parse(localStorage.getItem('users') || '[]');
+      const updatedUsers = users.map((u) => {
+        if (u.id === user.id) {
+          return {
+            ...u,
+            name: profileData.name,
+            email: profileData.email,
+            phone: profileData.phone,
+            address: profileData.address,
+            city: profileData.city,
+            zipCode: profileData.zipCode,
+          };
+        }
+        return u;
+      });
+
+      localStorage.setItem('users', JSON.stringify(updatedUsers));
+
+      // Update user in auth store
+      updateUser({
+        name: profileData.name,
+        email: profileData.email,
+      });
+
+      toast.success('Profile Updated', {
+        description: 'Your profile information has been updated successfully.',
+      });
+
+      setIsSubmitting(false);
+    }, 1000);
+  };
+
+  const handlePasswordSubmit = (e) => {
+    e.preventDefault();
+
+    if (!validatePasswordForm()) return;
+
+    setIsPasswordSubmitting(true);
+
+    // Simulate password update
+    setTimeout(() => {
+      // Verify current password
+      const users = JSON.parse(localStorage.getItem('users') || '[]');
+      const currentUser = users.find((u) => u.id === user.id);
+
+      if (
+        !currentUser ||
+        currentUser.password !== passwordData.currentPassword
+      ) {
+        setPasswordErrors({
+          currentPassword: 'Current password is incorrect',
+        });
+        setIsPasswordSubmitting(false);
+        return;
+      }
+
+      // Update password in localStorage
+      const updatedUsers = users.map((u) => {
+        if (u.id === user.id) {
+          return {
+            ...u,
+            password: passwordData.newPassword,
+          };
+        }
+        return u;
+      });
+
+      localStorage.setItem('users', JSON.stringify(updatedUsers));
+
+      toast.success('Password Updated', {
+        description: 'Your password has been changed successfully.',
+      });
+
+      // Reset password fields
+      setPasswordData({
+        currentPassword: '',
+        newPassword: '',
+        confirmPassword: '',
+      });
+
+      setIsPasswordSubmitting(false);
+    }, 1000);
+  };
+
+  const handleDeleteAccount = () => {
+    // Remove user from localStorage
+    const users = JSON.parse(localStorage.getItem('users') || '[]');
+    const updatedUsers = users.filter((u) => u.id !== user.id);
+    localStorage.setItem('users', JSON.stringify(updatedUsers));
+
+    // Logout user
+    logout();
+
+    toast.success('Account Deleted', {
+      description: 'Your account has been deleted successfully.',
+    });
+
+    navigate('/');
+  };
+
+  return (
+    <div className='container mx-auto px-4 py-8 md:py-12'>
+      <Button
+        variant='ghost'
+        asChild
+        className='mb-6 text-gray-600 hover:text-gray-800'
+      >
+        <Link to='/'>
+          <ArrowLeft className='mr-2 h-4 w-4' /> Back to Home
+        </Link>
+      </Button>
+
+      <div className='max-w-4xl mx-auto'>
+        <div className='text-center mb-8'>
+          <h1 className='text-3xl font-bold text-gray-800 mb-2'>My Profile</h1>
+          <p className='text-gray-600'>
+            Manage your account information and preferences
+          </p>
+        </div>
+
+        <Tabs defaultValue='profile' className='space-y-6'>
+          <TabsList className='grid w-full grid-cols-2'>
+            <TabsTrigger value='profile'>Profile Information</TabsTrigger>
+            <TabsTrigger value='security'>Security</TabsTrigger>
+          </TabsList>
+
+          <TabsContent value='profile'>
+            <Card>
+              <CardHeader>
+                <CardTitle>Profile Information</CardTitle>
+                <CardDescription>
+                  Update your personal information and delivery details
+                </CardDescription>
+              </CardHeader>
+              <form onSubmit={handleProfileSubmit}>
+                <CardContent className='space-y-6'>
+                  <div className='space-y-4'>
+                    <div className='grid grid-cols-1 md:grid-cols-2 gap-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={profileData.name}
+                            onChange={handleProfileChange}
+                            className={`pl-10 rounded-lg ${
+                              errors.name ? 'border-red-500' : 'border-gray-200'
+                            }`}
+                          />
+                        </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>
+                        <Input
+                          id='email'
+                          name='email'
+                          type='email'
+                          value={profileData.email}
+                          onChange={handleProfileChange}
+                          className={`rounded-lg ${
+                            errors.email ? 'border-red-500' : 'border-gray-200'
+                          }`}
+                        />
+                        {errors.email && (
+                          <p className='text-red-500 text-sm'>{errors.email}</p>
+                        )}
+                      </div>
+                    </div>
+
+                    <div className='space-y-2'>
+                      <Label htmlFor='phone'>Phone Number</Label>
+                      <Input
+                        id='phone'
+                        name='phone'
+                        value={profileData.phone}
+                        onChange={handleProfileChange}
+                        className='rounded-lg border-gray-200'
+                      />
+                    </div>
+
+                    <div className='space-y-2'>
+                      <Label htmlFor='address'>Address</Label>
+                      <Input
+                        id='address'
+                        name='address'
+                        value={profileData.address}
+                        onChange={handleProfileChange}
+                        className='rounded-lg border-gray-200'
+                      />
+                    </div>
+
+                    <div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
+                      <div className='space-y-2'>
+                        <Label htmlFor='city'>City</Label>
+                        <Input
+                          id='city'
+                          name='city'
+                          value={profileData.city}
+                          onChange={handleProfileChange}
+                          className='rounded-lg border-gray-200'
+                        />
+                      </div>
+
+                      <div className='space-y-2'>
+                        <Label htmlFor='zipCode'>ZIP Code</Label>
+                        <Input
+                          id='zipCode'
+                          name='zipCode'
+                          value={profileData.zipCode}
+                          onChange={handleProfileChange}
+                          className='rounded-lg border-gray-200'
+                        />
+                      </div>
+                    </div>
+                  </div>
+                </CardContent>
+                <CardFooter className='flex justify-end'>
+                  <Button
+                    type='submit'
+                    disabled={isSubmitting}
+                    className='bg-pink-500 hover:bg-pink-600 text-white rounded-full'
+                  >
+                    {isSubmitting ? (
+                      'Saving...'
+                    ) : (
+                      <>
+                        <Save className='mr-2 h-4 w-4' /> Save Changes
+                      </>
+                    )}
+                  </Button>
+                </CardFooter>
+              </form>
+            </Card>
+          </TabsContent>
+
+          <TabsContent value='security'>
+            <div className='space-y-6'>
+              <Card>
+                <CardHeader>
+                  <CardTitle>Change Password</CardTitle>
+                  <CardDescription>
+                    Update your password to keep your account secure
+                  </CardDescription>
+                </CardHeader>
+                <form onSubmit={handlePasswordSubmit}>
+                  <CardContent className='space-y-4'>
+                    <div className='space-y-2'>
+                      <Label htmlFor='currentPassword'>Current Password</Label>
+                      <div className='relative'>
+                        <Key className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5' />
+                        <Input
+                          id='currentPassword'
+                          name='currentPassword'
+                          type='password'
+                          value={passwordData.currentPassword}
+                          onChange={handlePasswordChange}
+                          className={`pl-10 rounded-lg ${
+                            passwordErrors.currentPassword
+                              ? 'border-red-500'
+                              : 'border-gray-200'
+                          }`}
+                        />
+                      </div>
+                      {passwordErrors.currentPassword && (
+                        <p className='text-red-500 text-sm'>
+                          {passwordErrors.currentPassword}
+                        </p>
+                      )}
+                    </div>
+
+                    <div className='space-y-2'>
+                      <Label htmlFor='newPassword'>New Password</Label>
+                      <div className='relative'>
+                        <Key className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5' />
+                        <Input
+                          id='newPassword'
+                          name='newPassword'
+                          type='password'
+                          value={passwordData.newPassword}
+                          onChange={handlePasswordChange}
+                          className={`pl-10 rounded-lg ${
+                            passwordErrors.newPassword
+                              ? 'border-red-500'
+                              : 'border-gray-200'
+                          }`}
+                        />
+                      </div>
+                      {passwordErrors.newPassword && (
+                        <p className='text-red-500 text-sm'>
+                          {passwordErrors.newPassword}
+                        </p>
+                      )}
+                    </div>
+
+                    <div className='space-y-2'>
+                      <Label htmlFor='confirmPassword'>
+                        Confirm New Password
+                      </Label>
+                      <div className='relative'>
+                        <Key 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={passwordData.confirmPassword}
+                          onChange={handlePasswordChange}
+                          className={`pl-10 rounded-lg ${
+                            passwordErrors.confirmPassword
+                              ? 'border-red-500'
+                              : 'border-gray-200'
+                          }`}
+                        />
+                      </div>
+                      {passwordErrors.confirmPassword && (
+                        <p className='text-red-500 text-sm'>
+                          {passwordErrors.confirmPassword}
+                        </p>
+                      )}
+                    </div>
+                  </CardContent>
+                  <CardFooter className='flex justify-end'>
+                    <Button
+                      type='submit'
+                      disabled={isPasswordSubmitting}
+                      className='bg-pink-500 hover:bg-pink-600 text-white rounded-full'
+                    >
+                      {isPasswordSubmitting ? 'Updating...' : 'Update Password'}
+                    </Button>
+                  </CardFooter>
+                </form>
+              </Card>
+
+              <Card className='border-red-100'>
+                <CardHeader>
+                  <CardTitle className='text-red-600'>Delete Account</CardTitle>
+                  <CardDescription>
+                    Permanently delete your account and all associated data
+                  </CardDescription>
+                </CardHeader>
+                <CardContent>
+                  <p className='text-gray-600'>
+                    This action cannot be undone. Once you delete your account,
+                    all your data will be permanently removed from our system.
+                  </p>
+                </CardContent>
+                <CardFooter>
+                  <AlertDialog>
+                    <AlertDialogTrigger asChild>
+                      <Button
+                        variant='outline'
+                        className='border-red-300 text-red-600 hover:bg-red-50 rounded-full'
+                      >
+                        <Trash2 className='mr-2 h-4 w-4' /> Delete Account
+                      </Button>
+                    </AlertDialogTrigger>
+                    <AlertDialogContent>
+                      <AlertDialogHeader>
+                        <AlertDialogTitle>
+                          Are you absolutely sure?
+                        </AlertDialogTitle>
+                        <AlertDialogDescription>
+                          This action cannot be undone. This will permanently
+                          delete your account and remove all your data from our
+                          servers.
+                        </AlertDialogDescription>
+                      </AlertDialogHeader>
+                      <AlertDialogFooter>
+                        <AlertDialogCancel>Cancel</AlertDialogCancel>
+                        <AlertDialogAction
+                          onClick={handleDeleteAccount}
+                          className='bg-red-500 hover:bg-red-600 text-white'
+                        >
+                          Yes, Delete My Account
+                        </AlertDialogAction>
+                      </AlertDialogFooter>
+                    </AlertDialogContent>
+                  </AlertDialog>
+                </CardFooter>
+              </Card>
+            </div>
+          </TabsContent>
+        </Tabs>
+      </div>
+    </div>
+  );
+}
-- 
GitLab