Skip to content

Settings: profile + billing on top, everything else behind More - #113

Merged
ehsan6sha merged 1 commit into
mainfrom
web-settings-profile-billing-top
Aug 25, 2026
Merged

Settings: profile + billing on top, everything else behind More#113
ehsan6sha merged 1 commit into
mainfrom
web-settings-profile-billing-top

Conversation

@ehsan6sha

Copy link
Copy Markdown
Member

Makes the web Settings page simpler: the profile and billing sit at the top, everything else is one click away.

Before / after

The page rendered nine sections in one flat column, so the two things people actually open Settings for -- who they are signed in as, and what their storage costs -- sat buried among Share ID, wallet keys and endpoint config.

It is now three entries:

PROFILE the old ACCOUNT section, larger avatar. Identity, sub-line and Sign out unchanged.
BILLING new row, opens cloud.fx.land. Subtitle shows live usage when available.
More collapsed tile holding the other seven sections verbatim -- Share ID, NFT Wallet, Security, API Configuration, Integrations, Uploads, Other settings, About. Expanding it restores exactly the previous page.

Notes on the three judgement calls

The billing link. There is no in-app billing screen on web, so the row hands off to <issuerBaseUrl>/login?returnTo=%2Fbilling. Checked against pinning-service/pinning-webui: App.tsx has a real billing route, and Login.tsx navigates to any returnTo beginning with a slash. Resolving through AuthCore.issuerBaseUrl() rather than hardcoding means a user-configured billing server keeps working.

The subtitle is deliberately quiet. getStorageAndCredits() fails fast when there is no JWT -- its _ensureConfigured throws before any HTTP -- and that is the normal state both when signed out and for a tokenless Mode-C vault user. A plain FutureBuilder would therefore have parked BillingApiException: JWT Token is not configured at the very top of the page this PR exists to simplify. Pending and failed both fall back to static copy: no spinner, no error row. Same treatment the home screen and rank badge already give this fetch.

Suppressing ExpansionTile's rules. Done with shape / collapsedShape, not a transparent dividerColor -- the latter would also have erased the Dividers between the sections nested inside the tile. Verified against the SDK (expansion_tile.dart:768-783): the default expanded shape is Border(top: dividerColor, bottom: dividerColor), which const Border() overrides exactly.

Expansion state rides on the tile's own State (no _moreExpanded field), which survives the setState the Security section fires from inside it -- the tile keeps a fixed position and runtime type in the parent Column, so Flutter reuses the same Element. The PageStorageKey caveat in the ExpansionTile docs applies to lazily-built ListViews, not this SingleChildScrollView + Column.

Two incidental fixes to the same file

  • It was the only one of 394 .dart files under lib/ carrying a UTF-8 BOM.
  • It held four mojibake ellipsis sequences in user-visible strings -- the app was rendering a garbled Generating... and Vault 1234abcd... on screen. The BOM is the likely cause.

Verification

flutter analyze on this file returned No issues found! locally, though on a slightly earlier revision of the change; the deltas since (the shape/collapsedShape swap, dropping a now-unused field, one comment reword) were checked by grep for dangling references and against the SDK source. The local machine then became unusable for further runs -- an unrelated process is leaking ~1M OS handles and starving process launches -- so CI is the authoritative check here.

Not verified in a real browser yet: the collapsed/expanded layout and the live billing subtitle both want a look on files.fx.land/app/ after deploy.

Reviewed by an independent advisor (Google Antigravity), which found no defects.

Generated with Claude Code

https://claude.ai/code/session_01X6TQxyiZN6cv1NJrXkX5Ps

The web Settings page rendered nine sections in one flat column, so the
two things people actually open it for -- who they are signed in as, and
what their storage costs -- sat buried among Share ID, wallet keys and
endpoint config.

It is now three entries: PROFILE (was ACCOUNT, larger avatar), a new
BILLING row, and a collapsed "More" tile holding the other seven
sections verbatim, so expanding it restores exactly the previous page.

Billing has no in-app screen on web, so the row opens cloud.fx.land at
<issuerBaseUrl>/login?returnTo=%2Fbilling -- pinning-webui has a real
billing route, and its Login honours any returnTo beginning with a
slash. Resolving through issuerBaseUrl keeps a user-configured billing
server working instead of hardcoding the default.

That row's subtitle is deliberately quiet. getStorageAndCredits fails
fast when there is no JWT, which is the normal state both when signed
out and for a tokenless Mode-C vault user, so a plain FutureBuilder
would have parked a BillingApiException error row at the very top of the
page this change exists to simplify. Pending and failed both fall back
to static copy -- no spinner, no error row -- matching how the home
screen and the rank badge already treat this same fetch.

ExpansionTile's own top/bottom rules are suppressed via shape and
collapsedShape rather than a transparent dividerColor, which would also
have erased the Dividers between the sections nested inside the tile.
Expansion state rides on the tile's own State, which survives the
setState the Security section fires from inside it.

Two incidental fixes to the same file: it was the only one of 394 .dart
files under lib/ carrying a UTF-8 BOM, and it held four mojibake ellipsis
sequences in user-visible strings, so the app was rendering a garbled
"Generating..." on screen. The BOM is the likely cause of both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6TQxyiZN6cv1NJrXkX5Ps
@ehsan6sha
ehsan6sha merged commit e94b769 into main Aug 25, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant