2026 10 15 account settings design - xopherdeep/do-it-for-the-xp GitHub Wiki

Account Settings

Status: approved in conversation, 2026-10-15. Owner: "rn since we link the account via townhall. maybe this is the more 'through the app menu' way to do it? i dont have settings to link the google/apple accounts yet so we can put that on the roadmap." Parked page: Settings, Account (Coming soon, commit d97779a7); the develop branch's original is kept at tools/reference/develop/AccountSettings.vue.

1. What the account is

The family's account is the Game Master's Town Hall link: one WordPress account on doit.forthexp.com, linked through WordPress's authorize-application page and stored on the device as login, display name and an Application Password (TownHallLink, src/lib/store/stores/townHall.ts). Heroes have no accounts (COPPA decision, town-hall-wp-bridge spec). The plan is the XP Pro licence (src/lib/store/stores/pro.ts). There is no other sign-in and no new backend.

Settings, Account is the menu's way to the same link. The Town Hall page keeps its clerk conversation (that is where a hero bumps into needing it); both call one composable, so the flow has one implementation.

2. The shared link flow (useTownHallLink)

Today the flow lives inside src/app/Console/MyPortal/HomeTown/TownHall/TownHall.ts (goToAuthorizePage, registerFamily, finishLinkReturn, announceLink, the native lastLinkResult watch). It moves to src/app/Console/MyPortal/HomeTown/TownHall/components/useTownHallLink.ts with this shape:

useTownHallLink({
  store,                       // useTownHallStore()
  heroId: () => string,        // whose state is remembered (the hero on screen, or the current user in Settings)
  returnRoute: () => string,   // "/town-hall/<id>" or "/xp-settings/account": where WordPress sends the web parent back
  isGameMaster: () => boolean,
  announce: (result: LinkResult) => Promise<void>,   // the page's own voice: the clerk's prompt, or a settings toast
}) => {
  loadLink(): Promise<void>;         // store.init, errors logged
  goToAuthorizePage(): Promise<void>; // beginLink, authorizeUrl, web navigates / native opens the Browser
  unlink(): Promise<UnlinkResult>;
  finishReturn(): Promise<boolean>;   // takeLinkReturn + completeLink + announce; false on an ordinary visit (web)
  // native: watches store.lastLinkResult and announces it (the page need not)
}
  • townHallReturnUrl(userId, loc, hash) in townHallLink.ts becomes returnUrlFor(route, loc, hash) (same output for /town-hall/<id>); the boot capture (pendingReturn.ts) is already route-agnostic and keeps working.
  • The native return (main.ts → completeLink → lastLinkResult) is unchanged; the composable's watch replaces the page's.
  • The Town Hall keeps registerFamily's clerk prompts and offerRegistration; it only calls the composable underneath. Its behaviour does not change, and the existing specs (townHallLink, townHallPendingReturn, townHallStore, townHallRegistrationOffer) stay green. New spec: tests/unit/useTownHallLink.spec.ts (web return announced once, an ordinary visit announces nothing, native result announced, authorize URL carries the page's return route).

3. The store and the site

  • townHallStore.fetchProfile(): Promise<TownHallProfile | null> reads wp/v2/users/me?context=edit with the link's auth and returns { login, displayName, email }; null when not linked or when the site refuses (nothing is stored, the page just shows what the link already holds). Spec in townHallStore.spec.ts.
  • townHallSite.ts gains profileUrl(site) → <site>/wp-admin/profile.php and passwordResetUrl(site) → <site>/wp-login.php?action=lostpassword. Email and password change on the site: WordPress confirms an email change by mail and a new password does not touch the Application Password, so the app never edits them.

4. The page (src/app/SideMenu/XpSettings/components/AccountSettings.vue, Game Master only)

A hero sees one card: "Only a Game Master manages the family's account." (canManageFamily(), as Chores does).

  1. Family account card.
    • Linked: display name as the title, "Signed in as <login>", the email from fetchProfile (a dash while loading or when the site did not answer). Buttons in thumb reach, outline style like XP Pro's: Edit on the site (profileUrl), Change password (passwordResetUrl), both window.open(..., "_blank", "noopener") with the line "You'll sign in on the site."; Unlink this device (confirm, then unlink(); the result as an rpg-toast, the removed-here-only case naming the site like the clerk does).
    • Not linked: "Link your For the XP account to post for your family from any device." Link account (goToAuthorizePage) and Create one (SIGNUP_URL). When signedInThroughWebsite, the first line says so.
    • On mount: loadLink(), then finishReturn(); a result shows as an rpg-toast (middle, close button).
  2. Plan card: "XP Pro is on, <plan label>" or "XP Pro is off", one button XP Pro → /xp-settings/pro. PLAN_NAMES moves to where both pages can import it (src/lib/services/pro/), no copy.
  3. Sign-in options card: XpComingSoonCard with ACCOUNT_COMING_SOON reduced to Google and Apple.

Tile (XpSettingsHome.ts): soon off, description "The family's Town Hall account, plan and sign-in options." Help (helpContent.ts): the data chapter's line and the FAQ entry say what the page does now (link, unlink, where to change email and password). AccountSettingsSoon.spec.ts becomes AccountSettings.spec.ts and checks the route, the reduced planned list and the help lines.

5. Google and Apple (roadmap, owner)

A site job: a social login plugin on the network (owner-installed, rehearsed on the local Docker WP first) shows Google and Apple on the sign-in and authorize pages; Apple also needs an Apple Developer account and Sign in with Apple set up. The app's flow then uses them for free; the page lists them as linked sign-ins. No app-side OAuth.

6. Order of work

  1. AC1 the composable (section 2) and AC2 the store and site helpers (section 3), in parallel.
  2. AC3 the page, tile, help, spec (section 4).
  3. Review, then one typecheck + lint + the touched specs; push and read the PR's Tests check.
⚠️ **GitHub.com Fallback** ⚠️