Skip to content

Hide billing button until user subscribes, move membership to top#321

Merged
bokelley merged 2 commits into
mainfrom
bokelley/hide-billing-unsub
Dec 16, 2025
Merged

Hide billing button until user subscribes, move membership to top#321
bokelley merged 2 commits into
mainfrom
bokelley/hide-billing-unsub

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Improves dashboard UX for new users by hiding the "Manage Billing" button until they've subscribed at least once, and by moving the "Become a Member" membership section prominently to the top of the dashboard where it's visible and not hidden at the bottom of the page.

Changes

  • Hide "Manage Billing" button until user has an active, past_due, canceled, or unpaid subscription (i.e., when status is not 'none')
  • Move "Become a Member" section above the main dashboard grid so non-subscribers see it immediately
  • Reduces confusion by keeping billing operations contextual to subscription status

🤖 Generated with Claude Code

bokelley and others added 2 commits December 15, 2025 21:05
- Hide "Manage Billing" button until user has subscribed at least once (avoids confusion for new users)
- Move "Become a Member" section to top of dashboard so it's visible and not hidden at bottom of page
- Non-subscribers now see clear membership call-to-action prominently before profile/team sections

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@bokelley
bokelley merged commit 1d6df30 into main Dec 16, 2025
5 checks passed
bokelley added a commit that referenced this pull request May 7, 2026
…) (#4179)

* feat(member-profile): self-service Linked Domains card

GET /api/me/organization/domains lists verified org domains; PUT
/api/me/organization/domains/:domain/primary lets owners/admins switch the
primary. The PUT writes BOTH organization_domains.is_primary AND
member_profiles.primary_brand_domain in one transaction so members don't have
to know about the two-primary distinction (Media.net escalation #321 root cause).

Adds a Linked Domains card to the member-profile UI for company orgs, mirroring
what admins see in admin-account-detail.html. Closes #4158 (Stage 2 of the
domain-cleanup series).

POST (add domain → WorkOS verification challenge) and DELETE deferred to a
follow-up since they require WorkOS-side wiring that's materially more work
than the read+set paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(member-profile): remove broken is_personal gate for Linked Domains card

E2E caught: section never rendered because the conditional always
evaluated false (the /api/me/member-profile response does not include
is_personal). The empty-state path in loadLinkedDomains already handles
personal orgs (zero verified domains keeps the section hidden), so the
gate was unnecessary anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(member-profile): address expert review on Linked Domains PR

- Restrict brand-primary dual-write to source='workos' rows only —
  admin-imported / manual 'verified' rows aren't DNS-proven and shouldn't
  seed brand identity. Org-membership inference primary still flips for
  any verified domain. Matches the trust boundary the
  backfill-primary-brand-domain script applies. (security-reviewer)
- Add SELECT 1 ... FOR UPDATE on the organizations row to serialize
  against concurrent WorkOS upsertOrganizationDomain webhooks that take
  the same lock. (code-reviewer)
- Canonicalize input :domain via canonicalizeBrandDomain instead of
  lowercase+trim — rejects malformed shapes early and matches the GET
  path's claimable check. (code-reviewer)
- Length-cap input domain at 253 chars (RFC 1035). (code-reviewer)
- Log ROLLBACK failure instead of silently swallowing. (code-reviewer)
- UI: keep the section hidden when 0 domains rather than rendering an
  empty-state copy that's confusing for personal orgs. (code-reviewer)
- Test cleanup: drop redundant inline auth middleware in buildApp; the
  vi.mock of requireAuth is what makes auth bypass work.
- Test coverage: add a case asserting source='import' rows can flip the
  org-membership primary but not the brand-identity primary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(member-profile): drop error message from invalid_domain response (semgrep)

Caught by CI semgrep rule error-message-leak. Log the underlying error
server-side, return only the error code to the caller.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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