diff --git a/apps/backoffice/src/app/(protected)/hackathon-users/_components/modal-user-detail.tsx b/apps/backoffice/src/app/(protected)/hackathon-users/_components/modal-user-detail.tsx index dd0a304..a678d51 100644 --- a/apps/backoffice/src/app/(protected)/hackathon-users/_components/modal-user-detail.tsx +++ b/apps/backoffice/src/app/(protected)/hackathon-users/_components/modal-user-detail.tsx @@ -1,43 +1,647 @@ -import { FC } from 'react'; +import { FC, useState, useEffect, useMemo, useRef } from 'react'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { cn } from '@imphnen-frontend-service/utils'; +import { + UserOutlined, + EnvironmentOutlined, + CalendarOutlined, + SaveOutlined, + CloseOutlined, + ExclamationOutlined, + CameraOutlined, + DeleteOutlined, + UploadOutlined, +} from '@ant-design/icons'; + +interface UserType { + id: string; + avatar?: string; + fullname: string; + bio?: string; + location: string; + is_active: boolean; + skills: string[]; + created_at: string; + updated_at: string; +} interface ModalProps { isOpen: boolean; onClose: () => void; + user: UserType | null; } -const ModalUserDetail: FC = ({ isOpen, onClose }) => { - if (!isOpen) return null; +const ModalUserDetail: FC = ({ isOpen, onClose, user }) => { + const [formData, setFormData] = useState(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [showAvatarMenu, setShowAvatarMenu] = useState(false); + const fileInputRef = useRef(null); + + // Initialize form data when modal opens + useEffect(() => { + if (isOpen) { + if (user) { + // Edit existing user + setFormData({ ...user }); + } else { + // Create new user + setFormData({ + id: '', // Will be generated by backend + fullname: '', + bio: '', + location: '', + is_active: true, + skills: [], + avatar: undefined, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }); + } + } + }, [isOpen, user]); + + // Check if form has changes + const hasChanges = useMemo(() => { + if (!formData) return false; + if (!user) return true; // New user always has changes + return ( + formData.fullname !== user.fullname || + formData.location !== user.location || + formData.is_active !== user.is_active || + formData.avatar !== user.avatar || + JSON.stringify(formData.skills) !== JSON.stringify(user.skills) || + formData.bio !== user.bio + ); + }, [formData, user]); + + // Check if required fields are filled + const isFormValid = useMemo(() => { + if (!formData) return false; + return formData.fullname.trim() !== '' && formData.location.trim() !== ''; + }, [formData]); + + const canSave = hasChanges && isFormValid; + + if (!isOpen || !formData) return null; + + const handleInputChange = ( + field: keyof UserType, + value: string | boolean | string[] | undefined + ) => { + setFormData((prev) => (prev ? { ...prev, [field]: value } : null)); + }; + + const handleSkillsChange = (skills: string[]) => { + setFormData((prev) => (prev ? { ...prev, skills } : null)); + }; + + const handleSave = () => { + if (!formData) return; + + if (user) { + // Update existing user + console.log('Update user data:', formData); + } else { + // Create new user + console.log('Create new user:', formData); + } + // Here you would typically make an API call to save the data + onClose(); + }; + + const handleCancel = () => { + if (user) { + setFormData({ ...user }); // Reset to original for edit mode + } + onClose(); + }; + + const handleDeleteAccount = () => { + if (!user) return; // Can't delete new user + console.log('Delete user:', user.id); + setShowDeleteConfirm(false); + onClose(); + }; + + const handleAvatarUpload = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + // Validate file type + if (!file.type.startsWith('image/')) { + alert('Please select an image file'); + return; + } + + // Validate file size (max 5MB) + if (file.size > 5 * 1024 * 1024) { + alert('Image size must be less than 5MB'); + return; + } + + // Create preview URL + const reader = new FileReader(); + reader.onload = (e) => { + const avatarUrl = e.target?.result as string; + handleInputChange('avatar', avatarUrl); + setShowAvatarMenu(false); + }; + reader.readAsDataURL(file); + } + }; + + const handleRemoveAvatar = () => { + handleInputChange('avatar', undefined); + setShowAvatarMenu(false); + }; + + const triggerFileUpload = () => { + fileInputRef.current?.click(); + }; + + const availableSkills = [ + 'Frontend Developer', + 'Backend Developer', + 'Full Stack Developer', + 'DevOps Engineer', + 'UI/UX Designer', + 'Product Manager', + 'Data Scientist', + 'Mobile Developer', + ]; + return (
-
+
{ + setShowAvatarMenu(false); + onClose(); + }} + />
-
-
-

User Detail

- + + {/* Avatar Menu Dropdown */} + {showAvatarMenu && ( +
+ + {formData.avatar && ( + + )} +
+ )} +
+
+
+

+ {user ? 'Edit User Profile' : 'Create New User'} +

+ {user && ( + + Hover avatar to change + + )} +
+
+ {user + ? `Make changes to ${ + formData.fullname || 'this user' + }'s profile information` + : 'Fill in the information below to create a new user account'} +
+
+
+
-
-
-

Account Profile

-
- Email, provider, createdAt + + {/* Content */} +
setShowAvatarMenu(false)}> +
+ {/* Left Column - Basic Info */} +
+
+

+ Basic Information +

+
+ {/* Full Name - Required */} +
+ +
+ + + handleInputChange('fullname', e.target.value) + } + className={cn( + 'w-full border rounded-lg px-3 py-2 text-sm focus:border-primary-500 focus:outline-none', + formData.fullname.trim() === '' + ? 'border-red-300 bg-red-50' + : 'border-neutral-300' + )} + placeholder="Enter full name" + /> + {formData.fullname.trim() === '' && ( +

+ Full name is required +

+ )} +
+
+ + {/* Location - Required */} +
+ +
+ + + {formData.location.trim() === '' && ( +

+ Location is required +

+ )} +
+
+ + {/* Joined Date - Read Only - Only show for existing users */} + {user && ( +
+ +
+

+ Joined Date +

+

+ {new Date(formData.created_at).toLocaleDateString( + 'en-US', + { + year: 'numeric', + month: 'long', + day: 'numeric', + } + )} +

+
+
+ )} +
+
+ + {/* Bio Section - Optional */} +
+

+ Bio{' '} + + (Optional) + +

+