diff --git a/admin_panel/admin_panel/page/account_hub/account_hub.js b/admin_panel/admin_panel/page/account_hub/account_hub.js
index d28c446..30e6df5 100644
--- a/admin_panel/admin_panel/page/account_hub/account_hub.js
+++ b/admin_panel/admin_panel/page/account_hub/account_hub.js
@@ -56,11 +56,46 @@ const ACCOUNT_STATUSES = {
CLOSED: "CLOSED",
};
+// ENG-516 nomenclature: Pro and International are retired, Merchant is now
+// Business, and levels are internal. The account leads with one headline word
+// (Trial → Verified → Business, the "light headline status" decision) with
+// capability badges as supporting detail. L1 and L2 both read "Verified" —
+// bank payout is a badge, not a tier.
const ACCOUNT_LEVEL_LABELS = {
[ACCOUNT_LEVELS.ZERO]: "Trial",
- [ACCOUNT_LEVELS.ONE]: "Personal",
- [ACCOUNT_LEVELS.TWO]: "Pro",
- [ACCOUNT_LEVELS.THREE]: "Merchant",
+ [ACCOUNT_LEVELS.ONE]: "Verified",
+ [ACCOUNT_LEVELS.TWO]: "Verified",
+ [ACCOUNT_LEVELS.THREE]: "Business",
+};
+
+// On an upgrade request, requested_level reads as the capability being
+// requested, not a tier the user picked.
+const REQUESTED_LEVEL_LABELS = {
+ [ACCOUNT_LEVELS.ZERO]: "Trial",
+ [ACCOUNT_LEVELS.ONE]: "Verified",
+ [ACCOUNT_LEVELS.TWO]: "Bank payout",
+ [ACCOUNT_LEVELS.THREE]: "Business",
+};
+
+const STATUS_HEADLINE_LABELS = {
+ TRIAL: "Trial",
+ VERIFIED: "Verified",
+ BUSINESS: "Business",
+};
+
+const STATUS_HEADLINE_BADGES = {
+ TRIAL: "badge-trial",
+ VERIFIED: "badge-personal",
+ BUSINESS: "badge-merchant",
+};
+
+// Internal levels for the admin change-level action — disambiguated with the
+// level number because L1 and L2 share the "Verified" headline.
+const ADMIN_LEVEL_OPTIONS = {
+ [ACCOUNT_LEVELS.ZERO]: "Trial (L0)",
+ [ACCOUNT_LEVELS.ONE]: "Verified (L1)",
+ [ACCOUNT_LEVELS.TWO]: "Verified + Bank payout (L2)",
+ [ACCOUNT_LEVELS.THREE]: "Business (L3)",
};
const ACCOUNT_LEVEL_BADGES = {
@@ -90,6 +125,36 @@ function getLevelLabel(level) {
return ACCOUNT_LEVEL_LABELS[level] || level;
}
+function getRequestedLevelLabel(level) {
+ return REQUESTED_LEVEL_LABELS[level] || level;
+}
+
+// Headline status straight from the backend when available (ENG-516);
+// falls back to the stored level for older backends.
+function getHeadlineLabel(account) {
+ if (account.statusHeadline) {
+ return STATUS_HEADLINE_LABELS[account.statusHeadline] || account.statusHeadline;
+ }
+ return getLevelLabel(account.level);
+}
+
+function getHeadlineBadge(account) {
+ if (account.statusHeadline) {
+ return STATUS_HEADLINE_BADGES[account.statusHeadline] || "badge-trial";
+ }
+ return getLevelBadge(account.level);
+}
+
+// Supporting capability badges next to the headline. verified/business are
+// already the headline; bankPayout and usdAccount are the orthogonal extras.
+function capabilityBadgesHtml(capabilities) {
+ if (!capabilities) return "";
+ const badges = [];
+ if (capabilities.bankPayout) badges.push("Bank payout");
+ if (capabilities.usdAccount) badges.push("USD account");
+ return badges.map((b) => `${b}`).join(" ");
+}
+
const RESULT_STATUS_TONE = {
// account statuses (UPPERCASE) — account search results
[ACCOUNT_STATUSES.ACTIVE]: "ok",
@@ -353,6 +418,7 @@ class AccountHub {
.badge-personal { background: var(--ah-accent-soft); color: var(--ah-accent-ink); opacity: 0.85; }
.badge-business { background: var(--ah-accent-soft); color: var(--ah-accent-ink); }
.badge-merchant { background: var(--ah-accent); color: #fff; }
+ .badge-capability { background: var(--ah-line-soft); color: var(--ah-ink2); }
.badge-pending { background: var(--ah-warn-bg); color: var(--ah-warn); }
.badge-approved { background: var(--ah-accent-soft); color: var(--ah-accent-ink); }
.badge-rejected { background: var(--ah-serious-bg); color: var(--ah-serious); }
@@ -454,6 +520,7 @@ class AccountHub {
+
@@ -570,6 +637,7 @@ class AccountHub {
detailContent: main.find(".ah-detail-content"),
detailUsername: main.find(".detail-username-display"),
detailLevelBadge: main.find(".detail-level-badge"),
+ detailCapBadges: main.find(".detail-cap-badges"),
detailStatusBadge: main.find(".detail-status-badge"),
identMeta: main.find(".detail-ident-meta"),
identBalances: main.find(".detail-ident-balances"),
@@ -693,7 +761,7 @@ class AccountHub {
[account.phone_number, account.email].filter(Boolean).join(" · ") || "—";
const level = account.requested_level || "ZERO";
const initial = (displayName || "?")[0].toUpperCase();
- const levelLabel = getLevelLabel(level);
+ const levelLabel = getRequestedLevelLabel(level);
const levelBadge = getLevelBadge(level);
const dotHtml = statusDotHtml(account.status);
@@ -896,8 +964,8 @@ class AccountHub {
account.owner?.email?.address ||
account.username ||
account.id;
- const levelLabel = getLevelLabel(account.level);
- const levelBadge = getLevelBadge(account.level);
+ const levelLabel = getHeadlineLabel(account);
+ const levelBadge = getHeadlineBadge(account);
const item = $(`
@@ -934,8 +1002,9 @@ class AccountHub {
// Update header
this.$.detailUsername.text(account.username || "Unknown");
this.$.detailLevelBadge
- .text(getLevelLabel(account.level))
- .attr("class", "ah-badge " + getLevelBadge(account.level));
+ .text(getHeadlineLabel(account))
+ .attr("class", "ah-badge " + getHeadlineBadge(account));
+ this.$.detailCapBadges.html(capabilityBadgesHtml(account.capabilities));
this.$.detailStatusBadge
.text(getStatusLabel(account.status))
.attr("class", "ah-badge " + getStatusBadge(account.status));
@@ -1039,8 +1108,8 @@ class AccountHub {
// Account State
this.$.ovLevelBadge
- .text(getLevelLabel(account.level))
- .attr("class", "ah-badge " + getLevelBadge(account.level));
+ .text(getHeadlineLabel(account))
+ .attr("class", "ah-badge " + getHeadlineBadge(account));
this.$.ovStatusBadge
.text(getStatusLabel(account.status))
.attr("class", "ah-badge " + getStatusBadge(account.status));
@@ -1321,7 +1390,7 @@ class AccountHub {
requests.forEach((r) => {
const statusLabel = getStatusLabel(r.status || "PENDING");
const statusBadge = getStatusBadge(r.status || "PENDING");
- const levelLabel = getLevelLabel(r.requested_level);
+ const levelLabel = getRequestedLevelLabel(r.requested_level);
const levelBadge = getLevelBadge(r.requested_level);
const row = $(`
@@ -1359,10 +1428,10 @@ class AccountHub {
const account = this.current_account;
const currentLevel = account.level;
- const options = Object.keys(ACCOUNT_LEVEL_LABELS)
+ const options = Object.keys(ADMIN_LEVEL_OPTIONS)
.filter((k) => k !== currentLevel)
.map((k) => ({
- label: ACCOUNT_LEVEL_LABELS[k],
+ label: ADMIN_LEVEL_OPTIONS[k],
value: k,
}));
@@ -1405,7 +1474,7 @@ class AccountHub {
title: "ERP Party Required",
indicator: "orange",
message: `${
- ACCOUNT_LEVEL_LABELS[selectedOption.value]
+ ADMIN_LEVEL_OPTIONS[selectedOption.value]
} accounts require an ERP party, and this account has none. Approve an account upgrade request instead — that flow creates the ERP Customer, Address, and Bank Account records.`,
});
return;
@@ -1433,7 +1502,7 @@ class AccountHub {
frappe.show_alert(
{
message: `Account level updated to ${
- ACCOUNT_LEVEL_LABELS[selectedOption.value]
+ ADMIN_LEVEL_OPTIONS[selectedOption.value]
}`,
indicator: "green",
},
diff --git a/admin_panel/admin_panel/page/account_management/account_management.js b/admin_panel/admin_panel/page/account_management/account_management.js
index dbc0e1a..e321a5e 100644
--- a/admin_panel/admin_panel/page/account_management/account_management.js
+++ b/admin_panel/admin_panel/page/account_management/account_management.js
@@ -40,11 +40,15 @@ const AccountStatus = {
CLOSED: "Closed",
};
+// ENG-516 nomenclature: Pro and International are retired, Merchant is now
+// Business, L1 is Verified. Levels are internal, so this admin page shows the
+// new name with the level number for disambiguation (L1 and L2 share the
+// "Verified" headline — L2 adds the bank-payout capability).
const ACCOUNT_LEVEL_MAP = {
- [AccountLevels.TRIAL]: "Trial",
- [AccountLevels.PERSONAL]: "Personal",
- [AccountLevels.PRO]: "Pro",
- [AccountLevels.MERCHANT]: "Merchant",
+ [AccountLevels.TRIAL]: "Trial (L0)",
+ [AccountLevels.PERSONAL]: "Verified (L1)",
+ [AccountLevels.PRO]: "Verified + Bank payout (L2)",
+ [AccountLevels.MERCHANT]: "Business (L3)",
};
const LEVEL_BADGE_MAP = {
@@ -344,10 +348,10 @@ class FlashAccountManager {
diff --git a/admin_panel/api/graphql_client.py b/admin_panel/api/graphql_client.py
index 632922e..171faa7 100644
--- a/admin_panel/api/graphql_client.py
+++ b/admin_panel/api/graphql_client.py
@@ -133,6 +133,13 @@ def execute_and_extract(
username
npub
level
+ statusHeadline
+ capabilities {
+ verified
+ bankPayout
+ business
+ usdAccount
+ }
status
title
erpParty