agentic.email/frontend/src/components/AccountProfile.jsx

151 lines
5.3 KiB
JavaScript

import { useRef, useState } from "react";
import { updateAccountProfile } from "../api";
import { getAccessToken, saveStoredAccount } from "../auth";
const MAX_AVATAR_BYTES = 512 * 1024;
const ALLOWED_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);
function accountInitials(account) {
return `${account?.first_name?.[0] || ""}${account?.last_name?.[0] || ""}`.toUpperCase() || "AG";
}
function displayPhone(phone) {
if (!phone) return "Not set";
return phone.replace(/^\+/, "");
}
export default function AccountProfile({ account, onUpdated }) {
const token = getAccessToken();
const inputRef = useRef(null);
const [status, setStatus] = useState({ loading: false, error: "", notice: "" });
const fullName = `${account?.first_name || ""} ${account?.last_name || ""}`.trim();
const isCompany = account?.account_type === "company";
const hasPhoto = Boolean(account?.profile_picture_url);
async function handleFileChange(event) {
const file = event.target.files?.[0];
event.target.value = "";
if (!file) return;
if (!ALLOWED_TYPES.has(file.type)) {
setStatus({ loading: false, error: "Use a JPG, PNG, or WebP image.", notice: "" });
return;
}
if (file.size > MAX_AVATAR_BYTES) {
setStatus({ loading: false, error: "Profile picture must be 512 KB or smaller.", notice: "" });
return;
}
setStatus({ loading: true, error: "", notice: "" });
try {
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = () => reject(new Error("Could not read that image."));
reader.readAsDataURL(file);
});
const updated = await updateAccountProfile({ profile_picture_url: dataUrl }, token);
saveStoredAccount(updated);
onUpdated?.(updated);
setStatus({ loading: false, error: "", notice: "Saved" });
} catch (error) {
setStatus({ loading: false, error: error.message, notice: "" });
}
}
async function removePhoto() {
setStatus({ loading: true, error: "", notice: "" });
try {
const updated = await updateAccountProfile({ profile_picture_url: null }, token);
saveStoredAccount(updated);
onUpdated?.(updated);
setStatus({ loading: false, error: "", notice: "Saved" });
} catch (error) {
setStatus({ loading: false, error: error.message, notice: "" });
}
}
return (
<section className="account-profile-page">
<header className="account-profile-head">
<p className="account-profile-eyebrow">{isCompany ? "Company workspace" : "Personal workspace"}</p>
<h1>Profile</h1>
<p>Manage your personal information and how you appear across Gentic.</p>
<p className="account-profile-meta">
{isCompany ? "Company account" : "Personal account"} · {isCompany ? "Company" : "Individual"}
</p>
</header>
<div className="account-profile-section">
<div className="account-profile-identity">
<div className="account-profile-avatar">
{hasPhoto ? (
<img src={account.profile_picture_url} alt="" />
) : (
<span>{accountInitials(account)}</span>
)}
</div>
<div className="account-profile-identity-copy">
<strong>{fullName}</strong>
<div className="account-profile-identity-actions">
<button
type="button"
className="account-profile-link"
disabled={status.loading}
onClick={() => inputRef.current?.click()}
>
{hasPhoto ? "Change photo" : "Upload photo"}
</button>
{hasPhoto && (
<>
<span aria-hidden="true">·</span>
<button
type="button"
className="account-profile-link subtle"
disabled={status.loading}
onClick={removePhoto}
>
Remove
</button>
</>
)}
</div>
<input
ref={inputRef}
type="file"
accept="image/jpeg,image/png,image/webp"
hidden
onChange={handleFileChange}
/>
</div>
</div>
<div className="account-profile-fields">
<label>
<span>Name</span>
<input value={fullName} readOnly disabled />
</label>
<label>
<span>Email</span>
<input value={account?.email || ""} readOnly disabled />
<small>Your Gentic email address</small>
</label>
<label>
<span>Phone</span>
<input value={displayPhone(account?.phone_number)} readOnly disabled />
</label>
</div>
<div className="account-profile-footer">
{status.error && <p className="form-error" role="alert">{status.error}</p>}
{status.notice && !status.error && (
<p className="account-profile-saved" role="status">{status.notice}</p>
)}
{status.loading && !status.notice && (
<p className="account-profile-saved" role="status">Saving…</p>
)}
</div>
</div>
</section>
);
}