391 lines
16 KiB
TypeScript
391 lines
16 KiB
TypeScript
"use client"
|
|
|
|
import { ProfilePictureUpload } from "@/components/profile-picture-upload-v2"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
import { RichTextEditor } from "@/components/rich-text-editor"
|
|
import { Save, Loader2, Building2 } from "lucide-react"
|
|
import { useEffect, useState } from "react"
|
|
import { profileApi, authApi, adminCompaniesApi, type AdminCompany } from "@/lib/api"
|
|
import { toast } from "sonner"
|
|
import { getCurrentUser, isAdminUser } from "@/lib/auth"
|
|
|
|
export default function ProfilePage() {
|
|
const [loading, setLoading] = useState(true)
|
|
const [saving, setSaving] = useState(false)
|
|
const [savingCompany, setSavingCompany] = useState(false)
|
|
const [user, setUser] = useState<any>(null)
|
|
const [company, setCompany] = useState<AdminCompany | null>(null)
|
|
const [isAdmin, setIsAdmin] = useState(false)
|
|
|
|
const [formData, setFormData] = useState({
|
|
fullName: "",
|
|
email: "",
|
|
phone: "",
|
|
bio: "",
|
|
})
|
|
|
|
const [passwordData, setPasswordData] = useState({
|
|
currentPassword: "",
|
|
newPassword: "",
|
|
confirmPassword: "",
|
|
})
|
|
|
|
const [companyData, setCompanyData] = useState({
|
|
name: "",
|
|
email: "",
|
|
phone: "",
|
|
website: "",
|
|
description: "",
|
|
})
|
|
|
|
const getInitials = (name: string) => {
|
|
const initials = name
|
|
.trim()
|
|
.split(/\s+/)
|
|
.filter(Boolean)
|
|
.map((word) => word[0])
|
|
.join("")
|
|
.toUpperCase()
|
|
return initials.slice(0, 2) || "U"
|
|
}
|
|
|
|
useEffect(() => {
|
|
const currentUser = getCurrentUser()
|
|
setIsAdmin(isAdminUser(currentUser))
|
|
loadProfile()
|
|
}, [])
|
|
|
|
const loadProfile = async () => {
|
|
console.log("[PROFILE_FLOW] Loading profile...")
|
|
try {
|
|
const userData = await authApi.getCurrentUser()
|
|
console.log("[PROFILE_FLOW] Profile loaded:", userData)
|
|
setUser(userData)
|
|
setFormData({
|
|
fullName: userData.fullName || userData.name || "",
|
|
email: userData.email || "",
|
|
phone: userData.phone || "",
|
|
bio: userData.bio || ""
|
|
})
|
|
|
|
// Load company if user has companyId
|
|
if (userData.companyId) {
|
|
try {
|
|
const companiesRes = await adminCompaniesApi.list()
|
|
const userCompany = companiesRes.data.find(c => c.id === userData.companyId)
|
|
if (userCompany) {
|
|
setCompany(userCompany)
|
|
setCompanyData({
|
|
name: userCompany.name || "",
|
|
email: userCompany.email || "",
|
|
phone: userCompany.phone || "",
|
|
website: userCompany.website || "",
|
|
description: userCompany.description || "",
|
|
})
|
|
}
|
|
} catch (err) {
|
|
console.warn("[PROFILE_FLOW] Could not load company:", err)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("[PROFILE_FLOW] Error loading profile:", error)
|
|
toast.error("Failed to load profile")
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleInputChange = (field: string, value: string) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
const handleCompanyChange = (field: string, value: string) => {
|
|
setCompanyData(prev => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
const handlePasswordChange = (field: string, value: string) => {
|
|
setPasswordData(prev => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setSaving(true)
|
|
console.log("[PROFILE_FLOW] Updating profile:", formData)
|
|
try {
|
|
await profileApi.update({
|
|
name: formData.fullName,
|
|
phone: formData.phone,
|
|
bio: formData.bio
|
|
})
|
|
console.log("[PROFILE_FLOW] Profile updated successfully")
|
|
toast.success("Profile updated")
|
|
} catch (error) {
|
|
console.error("[PROFILE_FLOW] Error updating profile:", error)
|
|
toast.error("Failed to update profile")
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}
|
|
|
|
const handleCompanySubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (!company) return
|
|
setSavingCompany(true)
|
|
console.log("[PROFILE_FLOW] Updating company:", companyData)
|
|
try {
|
|
await adminCompaniesApi.update(company.id, {
|
|
name: companyData.name,
|
|
email: companyData.email,
|
|
phone: companyData.phone,
|
|
website: companyData.website,
|
|
description: companyData.description,
|
|
})
|
|
console.log("[PROFILE_FLOW] Company updated successfully")
|
|
toast.success("Company updated")
|
|
loadProfile()
|
|
} catch (error) {
|
|
console.error("[PROFILE_FLOW] Error updating company:", error)
|
|
toast.error("Failed to update company")
|
|
} finally {
|
|
setSavingCompany(false)
|
|
}
|
|
}
|
|
|
|
const handlePasswordSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
console.log("[PROFILE_FLOW] Requesting password reset/update")
|
|
|
|
if (passwordData.newPassword !== passwordData.confirmPassword) {
|
|
toast.error("Passwords do not match")
|
|
return
|
|
}
|
|
|
|
try {
|
|
await profileApi.updatePassword({
|
|
currentPassword: passwordData.currentPassword,
|
|
newPassword: passwordData.newPassword,
|
|
})
|
|
toast.success("Password updated")
|
|
setPasswordData({
|
|
currentPassword: "",
|
|
newPassword: "",
|
|
confirmPassword: "",
|
|
})
|
|
} catch (error) {
|
|
console.error("[PROFILE_FLOW] Error updating password:", error)
|
|
toast.error("Failed to update password")
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-full">
|
|
<Loader2 className="w-8 h-8 animate-spin" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-2xl mx-auto py-8 space-y-6">
|
|
{/* Profile Card */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-2xl font-bold text-center">
|
|
Edit profile
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="flex justify-center">
|
|
<ProfilePictureUpload
|
|
fallbackText={formData.fullName ? getInitials(formData.fullName) : "U"}
|
|
size="xl"
|
|
useDatabase={false}
|
|
onImageChange={async (file, url) => {
|
|
if (file) {
|
|
try {
|
|
console.log("[PROFILE_FLOW] Uploading avatar:", { name: file.name, size: file.size })
|
|
await profileApi.uploadAvatar(file)
|
|
loadProfile()
|
|
toast.success("Avatar updated")
|
|
} catch (err) {
|
|
console.error("[PROFILE_FLOW] Avatar upload failed:", err)
|
|
toast.error("Failed to upload avatar")
|
|
}
|
|
}
|
|
}}
|
|
initialImage={user?.avatarUrl}
|
|
/>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="name">Full name</Label>
|
|
<Input
|
|
id="name"
|
|
value={formData.fullName}
|
|
onChange={(e) => handleInputChange("fullName", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="email">Email</Label>
|
|
<Input
|
|
id="email"
|
|
value={formData.email}
|
|
disabled
|
|
className="bg-muted"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="phone">Phone</Label>
|
|
<Input
|
|
id="phone"
|
|
value={formData.phone}
|
|
onChange={(e) => handleInputChange("phone", e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="bio">Bio</Label>
|
|
<RichTextEditor
|
|
value={formData.bio}
|
|
onChange={(value) => handleInputChange("bio", value)}
|
|
placeholder="Tell us about yourself..."
|
|
minHeight="140px"
|
|
/>
|
|
</div>
|
|
|
|
<Button type="submit" className="w-full" size="lg" disabled={saving}>
|
|
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
|
|
Save profile
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Password Reset</CardTitle>
|
|
<CardDescription>Update your account password securely.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handlePasswordSubmit} className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="currentPassword">Current password</Label>
|
|
<Input
|
|
id="currentPassword"
|
|
type="password"
|
|
value={passwordData.currentPassword}
|
|
onChange={(e) => handlePasswordChange("currentPassword", e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="newPassword">New password</Label>
|
|
<Input
|
|
id="newPassword"
|
|
type="password"
|
|
value={passwordData.newPassword}
|
|
onChange={(e) => handlePasswordChange("newPassword", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="confirmPassword">Confirm new password</Label>
|
|
<Input
|
|
id="confirmPassword"
|
|
type="password"
|
|
value={passwordData.confirmPassword}
|
|
onChange={(e) => handlePasswordChange("confirmPassword", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<Button type="submit" className="w-full" size="lg">
|
|
<Save className="mr-2 h-4 w-4" />
|
|
Reset password
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Company Card - Only for Admin users who have a company */}
|
|
{isAdmin && company && (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center gap-2">
|
|
<Building2 className="h-5 w-5 text-primary" />
|
|
<CardTitle>Company Information</CardTitle>
|
|
</div>
|
|
<CardDescription>Edit your company details</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleCompanySubmit} className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="companyName">Company Name</Label>
|
|
<Input
|
|
id="companyName"
|
|
value={companyData.name}
|
|
onChange={(e) => handleCompanyChange("name", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="companyEmail">Company Email</Label>
|
|
<Input
|
|
id="companyEmail"
|
|
type="email"
|
|
value={companyData.email}
|
|
onChange={(e) => handleCompanyChange("email", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="companyPhone">Phone</Label>
|
|
<Input
|
|
id="companyPhone"
|
|
value={companyData.phone}
|
|
onChange={(e) => handleCompanyChange("phone", e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="companyWebsite">Website</Label>
|
|
<Input
|
|
id="companyWebsite"
|
|
value={companyData.website}
|
|
onChange={(e) => handleCompanyChange("website", e.target.value)}
|
|
placeholder="https://example.com"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="companyDescription">Description</Label>
|
|
<Textarea
|
|
id="companyDescription"
|
|
value={companyData.description}
|
|
onChange={(e) => handleCompanyChange("description", e.target.value)}
|
|
rows={4}
|
|
placeholder="Tell us about your company..."
|
|
/>
|
|
</div>
|
|
|
|
<Button type="submit" className="w-full" size="lg" disabled={savingCompany}>
|
|
{savingCompany ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
|
|
Save company info
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|