'use client'; import { updateProfile } from '@services/settings/profile' import { getUser } from '@services/users/users' import React, { useEffect, useState, useCallback, useMemo } from 'react' import { Formik, Form } from 'formik' import { useLHSession } from '@components/Contexts/LHSessionContext' import { ArrowBigUpDash, Check, FileWarning, Info, UploadCloud, AlertTriangle, LogOut, Briefcase, GraduationCap, MapPin, Building2, Globe, Laptop2, Award, BookOpen, Link, Users, Calendar, Lightbulb } from 'lucide-react' import UserAvatar from '@components/Objects/UserAvatar' import { updateUserAvatar } from '@services/users/users' import { constructAcceptValue } from '@/lib/constants' import * as Yup from 'yup' import { Input } from "@components/ui/input" import { Textarea } from "@components/ui/textarea" import { Button } from "@components/ui/button" import { Label } from "@components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@components/ui/select" import { toast } from 'react-hot-toast' import { signOut } from 'next-auth/react' import { getUriWithoutOrg } from '@services/config/config'; import { useDebounce } from '@/hooks/useDebounce'; const SUPPORTED_FILES = constructAcceptValue(['image']) const AVAILABLE_ICONS = [ { name: 'briefcase', label: 'Briefcase', component: Briefcase }, { name: 'graduation-cap', label: 'Education', component: GraduationCap }, { name: 'map-pin', label: 'Location', component: MapPin }, { name: 'building-2', label: 'Organization', component: Building2 }, { name: 'speciality', label: 'Speciality', component: Lightbulb }, { name: 'globe', label: 'Website', component: Globe }, { name: 'laptop-2', label: 'Tech', component: Laptop2 }, { name: 'award', label: 'Achievement', component: Award }, { name: 'book-open', label: 'Book', component: BookOpen }, { name: 'link', label: 'Link', component: Link }, { name: 'users', label: 'Community', component: Users }, { name: 'calendar', label: 'Calendar', component: Calendar }, ] as const; const IconComponent = ({ iconName }: { iconName: string }) => { const iconConfig = AVAILABLE_ICONS.find(i => i.name === iconName); if (!iconConfig) return null; const IconElement = iconConfig.component; return ; }; interface DetailItem { id: string; label: string; icon: string; text: string; } interface FormValues { username: string; first_name: string; last_name: string; email: string; bio: string; details: { [key: string]: DetailItem; }; } const DETAIL_TEMPLATES = { general: [ { id: 'title', label: 'Title', icon: 'briefcase', text: '' }, { id: 'affiliation', label: 'Affiliation', icon: 'building-2', text: '' }, { id: 'location', label: 'Location', icon: 'map-pin', text: '' }, { id: 'website', label: 'Website', icon: 'globe', text: '' }, { id: 'linkedin', label: 'LinkedIn', icon: 'link', text: '' } ], academic: [ { id: 'institution', label: 'Institution', icon: 'building-2', text: '' }, { id: 'department', label: 'Department', icon: 'graduation-cap', text: '' }, { id: 'research', label: 'Research Area', icon: 'book-open', text: '' }, { id: 'academic-title', label: 'Academic Title', icon: 'award', text: '' } ], professional: [ { id: 'company', label: 'Company', icon: 'building-2', text: '' }, { id: 'industry', label: 'Industry', icon: 'briefcase', text: '' }, { id: 'expertise', label: 'Expertise', icon: 'laptop-2', text: '' }, { id: 'community', label: 'Community', icon: 'users', text: '' } ] } as const; const validationSchema = Yup.object().shape({ email: Yup.string().email('Invalid email').required('Email is required'), username: Yup.string().required('Username is required'), first_name: Yup.string().required('First name is required'), last_name: Yup.string().required('Last name is required'), bio: Yup.string().max(400, 'Bio must be 400 characters or less'), details: Yup.object().shape({}) }); // Memoized detail card component for better performance const DetailCard = React.memo(({ id, detail, onUpdate, onRemove, onLabelChange }: { id: string; detail: DetailItem; onUpdate: (id: string, field: keyof DetailItem, value: string) => void; onRemove: (id: string) => void; onLabelChange: (id: string, newLabel: string) => void; }) => { // Add local state for label input const [localLabel, setLocalLabel] = useState(detail.label); // Debounce the label change handler const debouncedLabelChange = useDebounce((newLabel: string) => { if (newLabel !== detail.label) { onLabelChange(id, newLabel); } }, 500); // Memoize handlers to prevent unnecessary re-renders const handleLabelChange = useCallback((e: React.ChangeEvent) => { const newLabel = e.target.value; setLocalLabel(newLabel); debouncedLabelChange(newLabel); }, [debouncedLabelChange]); const handleIconChange = useCallback((value: string) => { onUpdate(id, 'icon', value); }, [id, onUpdate]); const handleTextChange = useCallback((e: React.ChangeEvent) => { onUpdate(id, 'text', e.target.value); }, [id, onUpdate]); const handleRemove = useCallback(() => { onRemove(id); }, [id, onRemove]); // Update local label when prop changes useEffect(() => { setLocalLabel(detail.label); }, [detail.label]); return (
); }); DetailCard.displayName = 'DetailCard'; // Form component to handle the details section const UserEditForm = ({ values, setFieldValue, handleChange, errors, touched, isSubmitting, profilePicture }: { values: FormValues; setFieldValue: (field: string, value: any) => void; handleChange: (e: React.ChangeEvent) => void; errors: any; touched: any; isSubmitting: boolean; profilePicture: { error: string | undefined; success: string; isLoading: boolean; localAvatar: File | null; handleFileChange: (event: any) => Promise; }; }) => { // Memoize template handlers const templateHandlers = useMemo(() => Object.entries(DETAIL_TEMPLATES).reduce((acc, [key, template]) => ({ ...acc, [key]: () => { const currentIds = new Set(Object.keys(values.details)); const newDetails = { ...values.details }; template.forEach((item) => { if (!currentIds.has(item.id)) { newDetails[item.id] = { ...item }; } }); setFieldValue('details', newDetails); } }), {} as Record void>) , [values.details, setFieldValue]); // Memoize detail handlers const detailHandlers = useMemo(() => ({ handleDetailUpdate: (id: string, field: keyof DetailItem, value: string) => { const newDetails = { ...values.details }; newDetails[id] = { ...newDetails[id], [field]: value }; setFieldValue('details', newDetails); }, handleDetailRemove: (id: string) => { const newDetails = { ...values.details }; delete newDetails[id]; setFieldValue('details', newDetails); } }), [values.details, setFieldValue]); return (

Account Settings

Manage your personal information and preferences

{/* Profile Information Section */}
{touched.email && errors.email && (

{errors.email}

)} {values.email !== values.email && (
You will be logged out after changing your email
)}
{touched.username && errors.username && (

{errors.username}

)}
{touched.first_name && errors.first_name && (

{errors.first_name}

)}
{touched.last_name && errors.last_name && (

{errors.last_name}

)}