From 67447c5d01c8834ddf9eb6e793d484ee688435ed Mon Sep 17 00:00:00 2001 From: azeeee05 Date: Mon, 19 Jan 2026 11:39:48 +0530 Subject: [PATCH] added profile page --- src/application/profile/ProfilePage.tsx | 679 ++++++++++++++++++------ src/application/profile/ProfileTypes.ts | 13 + 2 files changed, 540 insertions(+), 152 deletions(-) create mode 100644 src/application/profile/ProfileTypes.ts diff --git a/src/application/profile/ProfilePage.tsx b/src/application/profile/ProfilePage.tsx index d1f2415..8d3e88a 100644 --- a/src/application/profile/ProfilePage.tsx +++ b/src/application/profile/ProfilePage.tsx @@ -1,192 +1,567 @@ -import React, { useEffect, useState } from "react"; -import { toast } from "react-toastify"; +import React, { useState, useEffect } from "react"; import { useAuth } from "../../context/AuthContext"; -import { useTheme } from "../../context/ThemeContext"; +import { CustomLoader, CustomModal, CustomInput, CustomButton } from "../../components/custom"; +import { useTranslation } from "react-i18next"; import { authApi } from "../Authentication/AuthApi"; +import { Key, Pencil } from "lucide-react"; +import type { PasswordForm, FormatDateFunction, FormatNameFunction, RenderStatusBadgeFunction } from "./ProfileTypes"; +import { useTheme } from "../../context/ThemeContext"; import { paletteApi } from "../theme/PaletteApi"; import type { ColorPalette } from "../theme/ThemeTypes"; -import CustomButton from "../../components/custom/CustomButton"; -import { Loader } from "../../components/custom/CustomLoader"; -import { Check, User } from "lucide-react"; + +type ProfileForm = { + firstName: string; + lastName: string; + phoneNumber: string; +}; + +const formatDate: FormatDateFunction = (value: string) => { + const date = new Date(value); + if (Number.isNaN(date.getTime())) return value; + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +}; + +const formatName: FormatNameFunction = (firstName: string, lastName?: string | null) => + [firstName, lastName].filter(Boolean).join(" "); + +const renderStatusBadge: RenderStatusBadgeFunction = (status?: string) => ( + + {status ? status.charAt(0).toUpperCase() + status.slice(1) : "Unknown"} + +); const ProfilePage: React.FC = () => { - const { user, refreshUser } = useAuth(); + const { t } = useTranslation(['profile', 'common']); + const { user, isLoading, refreshUser } = useAuth(); + + const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false); + const [passwordForm, setPasswordForm] = useState({ + currentPassword: "", + newPassword: "", + confirmPassword: "", + }); + const [passwordError, setPasswordError] = useState(""); + const [passwordSuccess, setPasswordSuccess] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [profileForm, setProfileForm] = useState({ + firstName: "", + lastName: "", + phoneNumber: "", + }); + const [profileError, setProfileError] = useState(""); + const [profileSuccess, setProfileSuccess] = useState(""); + const [isProfileSubmitting, setIsProfileSubmitting] = useState(false); + + // Theme Logic const { currentPalette, setTheme } = useTheme(); - const [palettes, setPalettes] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [isSaving, setIsSaving] = useState(false); - - // Form states - const [firstName, setFirstName] = useState(user?.first_name || ""); - const [lastName, setLastName] = useState(user?.last_name || ""); - const [phone, setPhone] = useState(user?.phone_number || ""); + const [isThemesLoading, setIsThemesLoading] = useState(false); useEffect(() => { const fetchPalettes = async () => { - setIsLoading(true); + setIsThemesLoading(true); try { const data = await paletteApi.getAllPalettes(); setPalettes(data); } catch (error) { console.error("Failed to fetch palettes", error); } finally { - setIsLoading(false); + setIsThemesLoading(false); } }; - fetchPalettes(); }, []); - // Update form when user data changes - useEffect(() => { - if (user) { - setFirstName(user.first_name || ""); - setLastName(user.last_name || ""); - setPhone(user.phone_number || ""); - } - }, [user]); - - const handleThemeSelect = (palette: ColorPalette) => { - setTheme(palette); - toast.success("Theme updated successfully"); - }; - - const handleProfileUpdate = async (e: React.FormEvent) => { - e.preventDefault(); - if (!user) return; - - setIsSaving(true); - try { - await authApi.updateProfile(user.id, { - first_name: firstName, - last_name: lastName, - phone_number: phone, - }); - await refreshUser(); - toast.success("Profile updated successfully"); - } catch (error) { - console.error("Failed to update profile", error); - toast.error("Failed to update profile"); - } finally { - setIsSaving(false); - } - }; - - if (isLoading && palettes.length === 0) { + if (isLoading) { return ( -
- +
+
); } + if (!user) { + return ( +
+ {t('messages.loadError')} +
+ ); + } + + const fullName = formatName(user.first_name, user.last_name); + const initials = + user.first_name && user.last_name + ? `${user.first_name[0]}${user.last_name[0]}` + : user.first_name + ? user.first_name[0] + : "U"; + + const handlePasswordChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setPasswordForm((prev) => ({ ...prev, [name]: value })); + setPasswordError(""); + }; + + const handlePasswordSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setPasswordError(""); + setPasswordSuccess(""); + + // Client-side validation + if (!passwordForm.currentPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) { + setPasswordError(t('messages.requiredFields')); + return; + } + + if (passwordForm.newPassword.length < 8) { + setPasswordError(t('messages.passwordLength')); + return; + } + + if (passwordForm.newPassword !== passwordForm.confirmPassword) { + setPasswordError(t('messages.passwordMismatch')); + return; + } + + setIsSubmitting(true); + + try { + const response = await authApi.resetPassword( + passwordForm.currentPassword, + passwordForm.newPassword + ); + setPasswordSuccess(response.message || t('messages.passwordSuccess')); + setPasswordForm({ + currentPassword: "", + newPassword: "", + confirmPassword: "", + }); + + // Close modal after 1.5 seconds + setTimeout(() => { + setIsPasswordModalOpen(false); + setPasswordSuccess(""); + }, 1500); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to update password"; + setPasswordError(errorMessage); + } finally { + setIsSubmitting(false); + } + }; + + const handlePasswordModalClose = () => { + setIsPasswordModalOpen(false); + setPasswordForm({ + currentPassword: "", + newPassword: "", + confirmPassword: "", + }); + setPasswordError(""); + setPasswordSuccess(""); + }; + + // Edit Profile handlers + const handleOpenEditModal = () => { + if (user) { + setProfileForm({ + firstName: user.first_name || "", + lastName: user.last_name || "", + phoneNumber: user.phone_number || "", + }); + } + setIsEditModalOpen(true); + }; + + const handleProfileChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setProfileForm((prev) => ({ ...prev, [name]: value })); + setProfileError(""); + }; + + const handleProfileSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setProfileError(""); + setProfileSuccess(""); + + if (!user) return; + + if (!profileForm.firstName.trim()) { + setProfileError(t('messages.firstNameRequired')); + return; + } + + setIsProfileSubmitting(true); + + try { + await authApi.updateProfile(user.id, { + first_name: profileForm.firstName.trim(), + last_name: profileForm.lastName.trim() || undefined, + phone_number: profileForm.phoneNumber.trim() || undefined, + }); + setProfileSuccess(t('messages.profileSuccess')); + + // Refresh user data + if (refreshUser) { + await refreshUser(); + } + + // Close modal after 1.5 seconds + setTimeout(() => { + setIsEditModalOpen(false); + setProfileSuccess(""); + }, 1500); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to update profile"; + setProfileError(errorMessage); + } finally { + setIsProfileSubmitting(false); + } + }; + + const handleEditModalClose = () => { + setIsEditModalOpen(false); + setProfileError(""); + setProfileSuccess(""); + }; + return ( -
+
{/* Header */}
-

Profile Settings

-

Manage your account settings and preferences.

+

{t('title')}

+

+ {t('subTitle')} +

- {/* Profile Information Section */} -
-
-
- -
-
-

Personal Information

-

Update your personal details.

-
-
- -
-
-
- - setFirstName(e.target.value)} - className="w-full px-3 py-2 rounded-lg border border-[var(--card-border)] bg-[var(--background)] text-[var(--text-primary)] focus:ring-2 focus:ring-[var(--primary)] outline-none" - required - /> -
-
- - setLastName(e.target.value)} - className="w-full px-3 py-2 rounded-lg border border-[var(--card-border)] bg-[var(--background)] text-[var(--text-primary)] focus:ring-2 focus:ring-[var(--primary)] outline-none" - /> -
-
- - -
-
- - setPhone(e.target.value)} - className="w-full px-3 py-2 rounded-lg border border-[var(--card-border)] bg-[var(--background)] text-[var(--text-primary)] focus:ring-2 focus:ring-[var(--primary)] outline-none" - /> -
-
- -
- - Save Changes - -
-
-
- - {/* Theme Selection Section */} -
-
-

Theme Preferences

-

Choose a color theme for your interface.

-
- -
- {palettes.map((palette) => ( - - ))} + {/* User Details Section */} +
+

+ {t('sections.accountInfo')} +

+
+ {/* First Name */} +
+

+ {t('fields.firstName')} +

+

+ {user.first_name} +

+
+ + {/* Last Name */} +
+

+ {t('fields.lastName')} +

+

+ {user.last_name || "--"} +

+
+ + {/* Email */} +
+

+ {t('fields.email')} +

+

+ {user.email} +

+
+ + {/* Phone Number */} +
+

+ {t('fields.phone')} +

+

+ {user.phone_number || "--"} +

+
+ + {/* Tenant */} +
+

+ {t('fields.tenant')} +

+

+ {user.tenant_name || "--"} +

+
+ + {/* Role */} +
+

+ {t('fields.role')} +

+

+ {user.role?.role_name || "--"} +

+
+ + {/* Account Created */} +
+

+ {t('fields.accountCreated')} +

+

+ {formatDate(user.created_at)} +

+
+ + {/* Last Updated */} +
+

+ {t('fields.lastUpdated')} +

+

+ {formatDate(user.updated_at)} +

+
+
+ + {/* Appearance / Theme Section */} +
+

+ {t('sections.appearance')} +

+

+ {t('sections.appearanceDesc')} +

+ + {isThemesLoading ? ( +
+ +
+ ) : ( +
+ {palettes.map((palette) => ( + + ))} +
+ )} +
+ + {/* Change Password Modal */} + + + {t('common:actions.cancel')} + + + {t('buttons.updatePassword')} + + + } + > +
+ + + + + {passwordError && ( +
+ {passwordError} +
+ )} + + {passwordSuccess && ( +
+ {passwordSuccess} +
+ )} + +
+ + {/* Edit Profile Modal */} + + + {t('common:actions.cancel')} + + + {t('buttons.saveChanges')} + + + } + > +
+ + + + + {profileError && ( +
+ {profileError} +
+ )} + + {profileSuccess && ( +
+ {profileSuccess} +
+ )} + +
); }; diff --git a/src/application/profile/ProfileTypes.ts b/src/application/profile/ProfileTypes.ts new file mode 100644 index 0000000..312bc70 --- /dev/null +++ b/src/application/profile/ProfileTypes.ts @@ -0,0 +1,13 @@ +import type { JSX } from "react/jsx-dev-runtime"; + +export interface PasswordForm { + currentPassword: string; + newPassword: string; + confirmPassword: string; +} + +export type FormatDateFunction = (value: string) => string; + +export type FormatNameFunction = (firstName: string, lastName?: string | null) => string; + +export type RenderStatusBadgeFunction = (status?: string) => JSX.Element;