|
| 1 | +"use client" |
| 2 | + |
| 3 | +import type { User } from "better-auth" |
| 4 | +import { Pencil, Upload, UserIcon, X } from "lucide-react" |
| 5 | +import { useRouter } from "next/navigation" |
| 6 | +import { forwardRef, useRef } from "react" |
| 7 | +import { toast } from "sonner" |
| 8 | +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" |
| 9 | +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" |
| 10 | +import { auth, useSession } from "@/lib/auth" |
| 11 | +import { getInitials } from "@/lib/utils" |
| 12 | +import { updateProfilePic } from "@/server/actions/users" |
| 13 | + |
| 14 | +type Props = { |
| 15 | + user: User |
| 16 | +} |
| 17 | + |
| 18 | +export function ProfilePic({ user }: Props) { |
| 19 | + const uploadRef = useRef<HTMLInputElement>(null) |
| 20 | + const router = useRouter() |
| 21 | + const { refetch } = useSession() |
| 22 | + |
| 23 | + async function handleRemove() { |
| 24 | + const ok = confirm("Are you sure you want to reset your profile picture?") |
| 25 | + if (!ok) return |
| 26 | + |
| 27 | + const r = await auth.updateUser({ image: null }) |
| 28 | + if (r.error) { |
| 29 | + toast.error("There was an error") |
| 30 | + console.error(r.error) |
| 31 | + return |
| 32 | + } |
| 33 | + |
| 34 | + toast.success("Profile picture removed successfully'") |
| 35 | + await refetch() |
| 36 | + router.refresh() |
| 37 | + } |
| 38 | + |
| 39 | + return ( |
| 40 | + <div className="flex flex-col items-center gap-2"> |
| 41 | + <Avatar className="h-32 w-32 rounded-full relative group peer"> |
| 42 | + <DropdownMenu> |
| 43 | + <DropdownMenuTrigger> |
| 44 | + <div className="absolute top-0 left-0 w-full h-full group-hover:opacity-100 opacity-0 bg-background/70 backdrop-blur-[1px] transition-all cursor-pointer z-10 grid place-content-center duration-100"> |
| 45 | + <Pencil size={32} /> |
| 46 | + </div> |
| 47 | + </DropdownMenuTrigger> |
| 48 | + <DropdownMenuContent> |
| 49 | + <DropdownMenuItem onClick={() => uploadRef.current?.click()}> |
| 50 | + <Upload /> Upload |
| 51 | + </DropdownMenuItem> |
| 52 | + <DropdownMenuItem onClick={handleRemove} variant="destructive" disabled={!user.image}> |
| 53 | + <X /> Remove |
| 54 | + </DropdownMenuItem> |
| 55 | + </DropdownMenuContent> |
| 56 | + </DropdownMenu> |
| 57 | + |
| 58 | + <UploadProfilePicture ref={uploadRef} /> |
| 59 | + |
| 60 | + <AvatarImage src={user.image || undefined} alt={`propic of ${user.name}`} /> |
| 61 | + <AvatarFallback className="rounded-full text-3xl text-foreground"> |
| 62 | + {user.name ? getInitials(user.name) : <UserIcon size={48} />} |
| 63 | + </AvatarFallback> |
| 64 | + </Avatar> |
| 65 | + <p className="peer-hover:opacity-100 opacity-0 text-xs">Max 1MB</p> |
| 66 | + </div> |
| 67 | + ) |
| 68 | +} |
| 69 | + |
| 70 | +const UploadProfilePicture = forwardRef<HTMLInputElement>((_, ref) => { |
| 71 | + const router = useRouter() |
| 72 | + const { refetch } = useSession() |
| 73 | + |
| 74 | + const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => { |
| 75 | + const files = event.target.files |
| 76 | + if (files && files.length > 0) { |
| 77 | + const selectedFile = files[0] |
| 78 | + if (!selectedFile) return |
| 79 | + |
| 80 | + if (selectedFile.size > 1024 * 1024) { |
| 81 | + toast.error("The image must be no larger than 1 MB") |
| 82 | + return |
| 83 | + } |
| 84 | + |
| 85 | + if (selectedFile.type !== "image/png" && selectedFile.type !== "image/jpeg") { |
| 86 | + toast.error("The image must be jpeg or png") |
| 87 | + return |
| 88 | + } |
| 89 | + |
| 90 | + const { success } = await updateProfilePic(selectedFile) |
| 91 | + if (success) toast.success("Image changed successfully!") |
| 92 | + else toast.error("There was an error, try again") |
| 93 | + await refetch() |
| 94 | + router.refresh() |
| 95 | + } |
| 96 | + } |
| 97 | + return ( |
| 98 | + <input |
| 99 | + className="hidden" |
| 100 | + type="file" |
| 101 | + ref={ref} |
| 102 | + onChange={handleFileChange} |
| 103 | + multiple={false} |
| 104 | + accept="image/png,image/jpeg" |
| 105 | + maxLength={1024 * 1024} |
| 106 | + /> |
| 107 | + ) |
| 108 | +}) |
0 commit comments