-
{currentUser.email}
+
{currentUser.email}
diff --git a/frontend/src/pages/user_account/Account.css b/frontend/src/pages/user_account/Account.css
new file mode 100644
index 0000000..1364f73
--- /dev/null
+++ b/frontend/src/pages/user_account/Account.css
@@ -0,0 +1,77 @@
+.account-page h1 {
+ font-size: 2rem;
+ font-weight: 200;
+}
+
+.account__section-title {
+ font-size: 1.4rem;
+ font-weight: 400;
+ margin-bottom: 1.25rem;
+ color: var(--color-text);
+ border-bottom: 1px solid var(--color-border-light);
+ padding-bottom: 0.5rem;
+}
+
+.account__row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.6rem 0;
+ border-bottom: 1px solid var(--color-border-light);
+}
+
+.account__row--last {
+ border-bottom: none;
+}
+
+.account__label {
+ color: var(--color-text-muted);
+ font-size: 1.1rem;
+}
+
+.account__value {
+ color: var(--color-text);
+ font-size: 1.1rem;
+ text-align: right;
+}
+
+.account__uuid {
+ font-size: 0.85rem;
+ color: var(--color-text-dimmed);
+ word-break: break-all;
+ max-width: 60%;
+}
+
+.account__status {
+ font-weight: 600;
+}
+
+.account__status--active {
+ color: var(--color-success-text);
+}
+
+.account__status--canceled {
+ color: var(--color-danger);
+}
+
+.account__status--canceling {
+ color: var(--color-warning);
+}
+
+.account__status--past_due {
+ color: var(--color-error);
+}
+
+.account__portal-section {
+ margin-top: 1.25rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--color-border-light);
+}
+
+.account__portal-hint {
+ margin-top: 0.5rem;
+}
+
+.account__password-form {
+ max-width: 400px;
+}
diff --git a/frontend/src/pages/user_account/Account.jsx b/frontend/src/pages/user_account/Account.jsx
new file mode 100644
index 0000000..b3ae918
--- /dev/null
+++ b/frontend/src/pages/user_account/Account.jsx
@@ -0,0 +1,202 @@
+import { useState, useEffect } from "react";
+import { updatePassword } from "firebase/auth";
+import { auth } from "../../firebase/config";
+import { getAccount, createPortalSession } from "../../api/account";
+import "./Account.css";
+
+export default function Account() {
+ const [account, setAccount] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const [newPassword, setNewPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+ const [passwordMsg, setPasswordMsg] = useState(null);
+ const [passwordErr, setPasswordErr] = useState(null);
+ const [changingPassword, setChangingPassword] = useState(false);
+
+ const [portalLoading, setPortalLoading] = useState(false);
+
+ useEffect(() => {
+ async function fetchAccount() {
+ try {
+ const data = await getAccount();
+ setAccount(data);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ }
+ fetchAccount();
+ }, []);
+
+ async function handlePasswordChange(e) {
+ e.preventDefault();
+ setPasswordMsg(null);
+ setPasswordErr(null);
+
+ if (newPassword.length < 6) {
+ setPasswordErr("Password must be at least 6 characters");
+ return;
+ }
+ if (newPassword !== confirmPassword) {
+ setPasswordErr("Passwords do not match");
+ return;
+ }
+
+ try {
+ setChangingPassword(true);
+ await updatePassword(auth.currentUser, newPassword);
+ setPasswordMsg("Password updated successfully");
+ setNewPassword("");
+ setConfirmPassword("");
+ } catch (err) {
+ setPasswordErr(err.message);
+ } finally {
+ setChangingPassword(false);
+ }
+ }
+
+ async function handleManageSubscription() {
+ try {
+ setPortalLoading(true);
+ const { url } = await createPortalSession();
+ window.location.href = url;
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setPortalLoading(false);
+ }
+ }
+
+ if (loading) {
+ return
Loading...
;
+ }
+
+ if (error && !account) {
+ return
Error: {error}
;
+ }
+
+ const isCanceling = account.cancel_at_period_end;
+ const statusLabel = isCanceling
+ ? "Canceling"
+ : account.subscription_status === "active"
+ ? "Active"
+ : account.subscription_status.charAt(0).toUpperCase() +
+ account.subscription_status.slice(1);
+
+ return (
+
+
Account
+
+ {error &&
{error}
}
+
+ {/* ── Profile Section ──────────────────────── */}
+
+
Profile
+
+ Email
+ {account.email}
+
+
+ User Name
+ {account.user_name}
+
+
+ User ID
+ {account.user_id}
+
+
+
+ {/* ── Subscription Section ─────────────────── */}
+
+
Subscription
+
+ Plan
+ {account.subscription_plan}
+
+
+ Status
+
+ {statusLabel}
+
+
+ {account.member_since && (
+
+ Member Since
+ {account.member_since}
+
+ )}
+ {account.next_billing_date && !isCanceling && (
+
+ Next Billing Date
+ {account.next_billing_date}
+
+ )}
+ {isCanceling && account.period_end_date && (
+
+ Access Until
+ {account.period_end_date}
+
+ )}
+
+ {isCanceling && (
+
+ Your subscription has been canceled and will not renew.
+ You have full access until {account.period_end_date}.
+
+ )}
+
+
+
+
+ Cancel subscription, update payment method, or view invoices via Stripe.
+
+
+
+
+ {/* ── Change Password Section ──────────────── */}
+
+
Change Password
+
+ {passwordMsg &&
{passwordMsg}
}
+ {passwordErr &&
{passwordErr}
}
+
+
+
+
+ );
+}