import MDInput from "@tensamin/markdown/input"; import { useMTP } from "@tensamin/mtp"; import { mtp } from "@tensamin/shared/data"; import { useStorage } from "@tensamin/storage/context"; import { Avatar, AvatarFallback, AvatarImage, Button, cn, Input, useIsMobile, } from "@methanium/ui"; import { useUser, useUserFields, type SelectedUser, } from "@tensamin/identity/context"; import { Check } from "lucide-react"; import { useEffect, useRef, useState } from "react"; const PROFILE_FIELDS = [ "Avatar", "Display", "Username", "About", "UserId", ] as const; type ProfileDraft = Partial< Omit, "UserId"> >; async function prepImage( file: File, size = 300, quality = 0.8, ): Promise { const bitmap = await createImageBitmap(file); const canvas = document.createElement("canvas"); canvas.width = size; canvas.height = size; const context = canvas.getContext("2d"); if (!context) throw new Error("Could not get canvas context"); const scale = Math.max(size / bitmap.width, size / bitmap.height); const width = bitmap.width * scale; const height = bitmap.height * scale; try { context.drawImage( bitmap, (size - width) / 2, (size - height) / 2, width, height, ); } finally { bitmap.close(); } return canvas.toDataURL("image/webp", quality); } export default function Page() { const { updateProfile } = useUser(); const { load } = useStorage(); const { send } = useMTP(); const isMobile = useIsMobile(); const [userId, setUserId] = useState(null); const { data: currentUser } = useUserFields(userId, PROFILE_FIELDS); const [draftUser, setDraftUser] = useState({}); const [errorMessage, setErrorMessage] = useState(""); const [saveSucceeded, setSaveSucceeded] = useState(false); const avatarUploadRef = useRef(null); const draftInitializedRef = useRef(false); const effectiveAvatar = draftUser.Avatar === "none" ? undefined : draftUser.Avatar; const updateDraftUser = ( updater: (previous: ProfileDraft) => ProfileDraft, ) => { setSaveSucceeded(false); setErrorMessage(""); setDraftUser(updater); }; useEffect(() => { void load("user_id").then(setUserId); }, [load]); useEffect(() => { if (!currentUser || draftInitializedRef.current) return; setDraftUser({ Avatar: currentUser.Avatar, Display: currentUser.Display, Username: currentUser.Username, About: currentUser.About, }); draftInitializedRef.current = true; }, [currentUser]); async function handleAvatarUpload(file: File) { const avatar = await prepImage(file); updateDraftUser((previous) => ({ ...previous, Avatar: avatar })); if (avatarUploadRef.current) avatarUploadRef.current.value = ""; } if (!currentUser) return

Loading...

; return ( <> event.target.files?.[0] && handleAvatarUpload(event.target.files[0]) } type="file" />
{draftUser.Display?.slice(0, 2).toUpperCase() || currentUser.Display.slice(0, 2).toUpperCase()}

Avatar

GIFs are supported in decentralised mode or with Tensamin Premium.
Maximum file size is 16mb.

updateDraftUser((previous) => ({ ...previous, Display: event.target.value, })) } placeholder="Display Name" value={draftUser.Display || ""} /> updateDraftUser((previous) => ({ ...previous, Username: event.target.value, })) } placeholder="Username" value={draftUser.Username || ""} /> updateDraftUser((previous) => ({ ...previous, About: value })) } value={draftUser.About || ""} /> {errorMessage && (

{errorMessage}

)}
); }