510 lines
23 KiB
TypeScript
510 lines
23 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { useNavigate } from "react-router-dom"
|
|
import { motion } from "framer-motion"
|
|
import { Camera, Save, User, Mail, MapPin, Lock, Bell, Home } from "lucide-react"
|
|
import { Link } from "react-router-dom"
|
|
|
|
interface UserProfile {
|
|
firstName: string
|
|
lastName: string
|
|
email: string
|
|
jobTitle: string
|
|
department: string
|
|
bio: string
|
|
avatar: string
|
|
}
|
|
|
|
export default function ProfileSettings() {
|
|
const [isFirstLoad, setIsFirstLoad] = useState(true)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [activeTab, setActiveTab] = useState("profile")
|
|
const [profile, setProfile] = useState<UserProfile>({
|
|
firstName: "Admin",
|
|
lastName: "User",
|
|
email: "admin@example.com",
|
|
jobTitle: "Database Administrator",
|
|
department: "IT",
|
|
bio: "Experienced database administrator with a focus on migration data management.",
|
|
avatar: "",
|
|
})
|
|
const navigate = useNavigate()
|
|
|
|
// Add a handleLogout function that clears the adminNavigation flag
|
|
const handleLogout = () => {
|
|
localStorage.removeItem("adminToken")
|
|
localStorage.removeItem("adminNavigation") // Clear navigation flag on logout
|
|
|
|
navigate("/admin/login")
|
|
}
|
|
|
|
useEffect(() => {
|
|
// Check if user is authenticated
|
|
const token = localStorage.getItem("adminToken")
|
|
if (!token) {
|
|
navigate("/admin/login")
|
|
return
|
|
}
|
|
|
|
// Check if we're navigating from another admin page
|
|
const hasVisitedAdmin = localStorage.getItem("adminNavigation")
|
|
|
|
// Skip loading if we're navigating from another admin page
|
|
if (hasVisitedAdmin) {
|
|
setIsFirstLoad(false)
|
|
} else {
|
|
// Set flag to indicate we've visited an admin page
|
|
localStorage.setItem("adminNavigation", "true")
|
|
|
|
// Only show loading state on first load
|
|
if (isFirstLoad) {
|
|
// Simulate loading profile data
|
|
const timer = setTimeout(() => {
|
|
setIsFirstLoad(false)
|
|
}, 500) // Reduced loading time for better UX
|
|
|
|
return () => clearTimeout(timer)
|
|
}
|
|
}
|
|
}, [isFirstLoad, navigate])
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const { name, value } = e.target
|
|
setProfile((prev) => ({ ...prev, [name]: value }))
|
|
}
|
|
|
|
const handleSave = () => {
|
|
setIsLoading(true)
|
|
|
|
// Simulate API call
|
|
setTimeout(() => {
|
|
setIsLoading(false)
|
|
}, 500) // Reduced loading time for better UX
|
|
}
|
|
|
|
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (file) {
|
|
const reader = new FileReader()
|
|
reader.onload = (event) => {
|
|
if (event.target?.result) {
|
|
|
|
}
|
|
}
|
|
reader.readAsDataURL(file)
|
|
}
|
|
}
|
|
|
|
// If it's the first load, show a loading state
|
|
if (isFirstLoad) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-[#E8DCCA]/10">
|
|
<div className="text-center">
|
|
<div className="w-16 h-16 border-4 border-[#9B2335] border-t-transparent rounded-full animate-spin mx-auto"></div>
|
|
<p className="mt-4 text-gray-600">Loading profile...</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen flex">
|
|
{/* Sidebar */}
|
|
<div className="w-64 bg-[#1A2A57] text-white">
|
|
<div className="p-4 border-b border-[#1A2A57]/30">
|
|
<h2 className="text-xl font-serif font-bold">Italian Migrants</h2>
|
|
<p className="text-sm text-white/70">Northern Territory DB</p>
|
|
</div>
|
|
|
|
<nav className="mt-6 px-4">
|
|
<div className="space-y-1">
|
|
<Link
|
|
to="/admin"
|
|
className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md"
|
|
>
|
|
<Home className="h-5 w-5 mr-3" />
|
|
Dashboard
|
|
</Link>
|
|
<Link
|
|
to="/admin/migrants"
|
|
className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md"
|
|
>
|
|
<User className="h-5 w-5 mr-3" />
|
|
Migrants
|
|
</Link>
|
|
<Link to="#" className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md">
|
|
<Mail className="h-5 w-5 mr-3" />
|
|
Reports
|
|
</Link>
|
|
<Link to="#" className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md">
|
|
<MapPin className="h-5 w-5 mr-3" />
|
|
Database
|
|
</Link>
|
|
<Link
|
|
to="/admin/settings/profile"
|
|
className="flex items-center px-4 py-3 text-white bg-[#1A2A57]/40 rounded-md"
|
|
>
|
|
<Lock className="h-5 w-5 mr-3" />
|
|
Settings
|
|
</Link>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Update the bottom user section in the sidebar */}
|
|
<div className="absolute bottom-0 w-64 p-4 border-t border-[#1A2A57]/30">
|
|
<button
|
|
onClick={handleLogout}
|
|
className="flex items-center w-full px-4 py-2 text-white/80 hover:bg-[#1A2A57]/40 rounded-md"
|
|
>
|
|
<div className="h-8 w-8 rounded-full bg-white text-[#1A2A57] flex items-center justify-center mr-2">
|
|
<User className="h-5 w-5" />
|
|
</div>
|
|
<span className="text-sm">Admin User</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main content */}
|
|
<div className="flex-1 overflow-auto">
|
|
{/* Header */}
|
|
<div className="bg-white shadow-sm border-b border-gray-200">
|
|
<div className="flex justify-between items-center px-6 py-4">
|
|
<h1 className="text-xl font-medium text-[#1A2A57]">Admin Portal</h1>
|
|
<div className="text-sm text-gray-600">Northern Territory</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Settings content */}
|
|
<div className="p-6">
|
|
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
|
<div className="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-800">Settings</h2>
|
|
<p className="text-gray-600">Manage your account preferences</p>
|
|
</div>
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={isLoading}
|
|
className="px-4 py-2 bg-[#01796F] text-white rounded-md hover:bg-[#01796F]/90 flex items-center disabled:opacity-70"
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
|
|
Saving...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
Save Changes
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex mb-6 border-b border-gray-200">
|
|
<button
|
|
className={`px-6 py-3 font-medium flex items-center ${
|
|
activeTab === "profile"
|
|
? "text-[#1A2A57] border-b-2 border-[#1A2A57]"
|
|
: "text-gray-500 hover:text-gray-700"
|
|
}`}
|
|
onClick={() => setActiveTab("profile")}
|
|
>
|
|
<User className="h-4 w-4 mr-2" />
|
|
Profile
|
|
</button>
|
|
<button
|
|
className={`px-6 py-3 font-medium flex items-center ${
|
|
activeTab === "security"
|
|
? "text-[#1A2A57] border-b-2 border-[#1A2A57]"
|
|
: "text-gray-500 hover:text-gray-700"
|
|
}`}
|
|
onClick={() => setActiveTab("security")}
|
|
>
|
|
<Lock className="h-4 w-4 mr-2" />
|
|
Security
|
|
</button>
|
|
<button
|
|
className={`px-6 py-3 font-medium flex items-center ${
|
|
activeTab === "notifications"
|
|
? "text-[#1A2A57] border-b-2 border-[#1A2A57]"
|
|
: "text-gray-500 hover:text-gray-700"
|
|
}`}
|
|
onClick={() => setActiveTab("notifications")}
|
|
>
|
|
<Bell className="h-4 w-4 mr-2" />
|
|
Notifications
|
|
</button>
|
|
</div>
|
|
|
|
{/* Profile Tab Content */}
|
|
{activeTab === "profile" && (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-medium text-gray-800 mb-4">Profile Information</h3>
|
|
<p className="text-gray-600 mb-6">
|
|
Update your personal information and how it appears on your profile.
|
|
</p>
|
|
|
|
<div className="flex items-start mb-8">
|
|
<div className="mr-6">
|
|
<div className="relative">
|
|
<div className="h-24 w-24 rounded-full bg-gray-200 overflow-hidden">
|
|
{profile.avatar ? (
|
|
<img
|
|
src={profile.avatar || `${import.meta.env.BASE_URL}assets/placeholder.png`}
|
|
alt="Profile"
|
|
className="h-full w-full object-cover"
|
|
/>
|
|
) : (
|
|
<div className="h-full w-full flex items-center justify-center bg-gray-300">
|
|
<User className="h-12 w-12 text-gray-400" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<label
|
|
htmlFor="avatar-upload"
|
|
className="absolute bottom-0 right-0 p-1 bg-[#9B2335] text-white rounded-full cursor-pointer"
|
|
>
|
|
<Camera className="h-4 w-4" />
|
|
<input
|
|
id="avatar-upload"
|
|
type="file"
|
|
accept="image/*"
|
|
className="hidden"
|
|
onChange={handleAvatarChange}
|
|
/>
|
|
</label>
|
|
</div>
|
|
<button className="mt-2 text-sm text-[#1A2A57] hover:underline">Change</button>
|
|
</div>
|
|
|
|
<div className="flex-1 grid grid-cols-2 gap-6">
|
|
<div>
|
|
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700 mb-1">
|
|
First Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="firstName"
|
|
name="firstName"
|
|
value={profile.firstName}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="lastName" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Last Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="lastName"
|
|
name="lastName"
|
|
value={profile.lastName}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-span-2">
|
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="email"
|
|
name="email"
|
|
value={profile.email}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="jobTitle" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Job Title
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="jobTitle"
|
|
name="jobTitle"
|
|
value={profile.jobTitle}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="department" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Department
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="department"
|
|
name="department"
|
|
value={profile.department}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="bio" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Bio
|
|
</label>
|
|
<textarea
|
|
id="bio"
|
|
name="bio"
|
|
rows={4}
|
|
value={profile.bio}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Security Tab Content */}
|
|
{activeTab === "security" && (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-medium text-gray-800 mb-4">Security Settings</h3>
|
|
<p className="text-gray-600 mb-6">Manage your password and account security preferences.</p>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h4 className="text-md font-medium mb-2">Change Password</h4>
|
|
<div className="grid grid-cols-1 gap-4">
|
|
<div>
|
|
<label htmlFor="currentPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Current Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="currentPassword"
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
|
New Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="newPassword"
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
|
Confirm New Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="confirmPassword"
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<button className="mt-4 px-4 py-2 bg-[#1A2A57] text-white rounded-md hover:bg-[#1A2A57]/90">
|
|
Update Password
|
|
</button>
|
|
</div>
|
|
|
|
<div className="pt-6 border-t border-gray-200">
|
|
<h4 className="text-md font-medium mb-2">Two-Factor Authentication</h4>
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
Add an extra layer of security to your account by enabling two-factor authentication.
|
|
</p>
|
|
<button className="px-4 py-2 bg-[#9B2335] text-white rounded-md hover:bg-[#9B2335]/90">
|
|
Enable 2FA
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Notifications Tab Content */}
|
|
{activeTab === "notifications" && (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h3 className="text-lg font-medium text-gray-800 mb-4">Notification Preferences</h3>
|
|
<p className="text-gray-600 mb-6">Manage how and when you receive notifications.</p>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h4 className="text-md font-medium mb-4">Email Notifications</h4>
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium">System Updates</p>
|
|
<p className="text-sm text-gray-600">Receive emails about system updates and maintenance</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" className="sr-only peer" defaultChecked />
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#01796F]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#01796F]"></div>
|
|
</label>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium">New Records</p>
|
|
<p className="text-sm text-gray-600">Receive emails when new migrant records are added</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" className="sr-only peer" defaultChecked />
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#01796F]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#01796F]"></div>
|
|
</label>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium">Security Alerts</p>
|
|
<p className="text-sm text-gray-600">Receive emails about security-related events</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" className="sr-only peer" defaultChecked />
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#01796F]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#01796F]"></div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-6 border-t border-gray-200">
|
|
<h4 className="text-md font-medium mb-4">In-App Notifications</h4>
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium">Record Updates</p>
|
|
<p className="text-sm text-gray-600">Receive notifications when records are updated</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" className="sr-only peer" defaultChecked />
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#01796F]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#01796F]"></div>
|
|
</label>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium">User Activity</p>
|
|
<p className="text-sm text-gray-600">Receive notifications about other users' activity</p>
|
|
</div>
|
|
<label className="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" className="sr-only peer" />
|
|
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#01796F]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#01796F]"></div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|