mirror of
https://git.coastit.co.za/caitlin/CRM_ENVR.git
synced 2026-07-12 20:17:16 +02:00
adbcc4b9af
feat: implement conversations API with message retrieval and posting feat: add avatar URL handling for users and update user role definitions feat: create chat system tables in the database with initial seed data fix: update user roles from "sales_user" to "sales" for consistency chore: add middleware for system API route access fixed fuckups on john's side, added a benchmark Made Graphs actualy load from database and realtime data, graphs will update and percentages will be mathematically made, and made realtime added chatcart edits, made graphs glow. added in some little small home feeling fuctions added in a slide show, in login with qoutes from creators because i want to leave a print on it saying we made this shit, we built it brick for brick login page added qoutes some random, and made them shuffle from left to right one dissapears other come in adding shape to the textbox for the qoutes Fixed my chat fuckups when it comes to chats
143 lines
6.0 KiB
TypeScript
143 lines
6.0 KiB
TypeScript
"use client"
|
|
|
|
import { useRef } from "react"
|
|
import { PageHeader } from "@/components/shared/page-header"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { useUser } from "@/providers/user-provider"
|
|
import { Mail, Calendar, Shield, Activity, Camera } from "lucide-react"
|
|
import { toast } from "sonner"
|
|
|
|
export default function ProfilePage() {
|
|
const { user, updateAvatar } = useUser()
|
|
if (!user) return null
|
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
|
|
const handleAvatarChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (!file) return
|
|
const validTypes = ["image/png", "image/jpeg"]
|
|
if (!validTypes.includes(file.type)) {
|
|
toast.error("Only PNG and JPEG files are allowed")
|
|
return
|
|
}
|
|
const reader = new FileReader()
|
|
reader.onload = async () => {
|
|
const dataUrl = reader.result as string
|
|
try {
|
|
const res = await fetch("/api/users/avatar", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ avatar: dataUrl }),
|
|
})
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
updateAvatar(data.avatar)
|
|
toast.success("Avatar updated")
|
|
} else {
|
|
toast.error("Failed to save avatar")
|
|
}
|
|
} catch {
|
|
toast.error("Failed to save avatar")
|
|
}
|
|
}
|
|
reader.readAsDataURL(file)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageHeader title="Profile" description="Your account information" />
|
|
|
|
<div className="grid gap-6 lg:grid-cols-3">
|
|
<Card className="lg:col-span-1">
|
|
<CardContent className="flex flex-col items-center pt-8">
|
|
<div className="relative">
|
|
<Avatar className="h-24 w-24">
|
|
<AvatarImage src={user.avatar} />
|
|
<AvatarFallback className="text-2xl">{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
</Avatar>
|
|
<button
|
|
type="button"
|
|
onClick={() => fileInputRef.current?.click()}
|
|
className="absolute inset-0 flex items-center justify-center rounded-full bg-black/40 opacity-0 transition-opacity hover:opacity-100"
|
|
>
|
|
<Camera className="h-6 w-6 text-white" />
|
|
</button>
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept=".png,.jpeg,.jpg"
|
|
className="hidden"
|
|
onChange={handleAvatarChange}
|
|
/>
|
|
</div>
|
|
<h2 className="mt-4 text-xl font-semibold">{user.name}</h2>
|
|
<Badge variant="secondary" className="mt-1 capitalize">
|
|
{user.role}
|
|
</Badge>
|
|
<div className="mt-6 flex w-full flex-col gap-3 text-sm">
|
|
<div className="flex items-center gap-3 rounded-lg bg-muted/50 px-4 py-3">
|
|
<Mail className="h-4 w-4 text-muted-foreground" />
|
|
<span className="text-muted-foreground">{user.email}</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 rounded-lg bg-muted/50 px-4 py-3">
|
|
<Calendar className="h-4 w-4 text-muted-foreground" />
|
|
<span className="text-muted-foreground">
|
|
Joined {new Date(user.createdAt).toLocaleDateString("en-US", { month: "long", year: "numeric" })}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 rounded-lg bg-muted/50 px-4 py-3">
|
|
<Shield className="h-4 w-4 text-muted-foreground" />
|
|
<span className="text-muted-foreground capitalize">{user.role} access</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 rounded-lg bg-muted/50 px-4 py-3">
|
|
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
<span className="text-muted-foreground">{user.active ? "Active" : "Inactive"}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="lg:col-span-2 space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Account Details</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<span className="text-xs text-muted-foreground">Full Name</span>
|
|
<p className="text-sm font-medium">{user.name}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<span className="text-xs text-muted-foreground">Email</span>
|
|
<p className="text-sm font-medium">{user.email}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<span className="text-xs text-muted-foreground">Role</span>
|
|
<p className="text-sm font-medium capitalize">{user.role}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<span className="text-xs text-muted-foreground">Status</span>
|
|
<p className="text-sm font-medium">{user.active ? "Active" : "Inactive"}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<span className="text-xs text-muted-foreground">Member Since</span>
|
|
<p className="text-sm font-medium">
|
|
{new Date(user.createdAt).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<span className="text-xs text-muted-foreground">User ID</span>
|
|
<p className="text-sm font-medium font-mono">{user.id}</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|