* Rename pre-subscription onboarding funnel to Welcome.
Move the ICP steps to /welcome, drop the social-connect checkout gate, hold unpaid members on a subscription-required screen, and keep legacy /onboarding URLs working until the post-subscription checklist lands.
Closes#237
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop legacy /onboarding ICP URL aliases.
Unfinished users re-enter Welcome via EnsureAccountReady on next login; /onboarding stays free for the post-subscription checklist.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Welcome PostHog event names.
Use welcome.persona/goals/referral and drop the unused checkout case — begin checkout stays on the frontend as checkout.started / begin_checkout.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Slim welcome goal options to match the #204 set.
Drop team_collaboration, automate_api, and track_performance so the goals step stays at nine choices.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Split Welcome AI goals into TryPost AI and MCP assistants.
Rewrite ai_content for in-app generation and add use_mcp so Claude/ChatGPT/Cursor intent is captured separately across locales.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden Welcome goals gate and drop dead checkout UI.
Treat removed goal values as incomplete so mid-funnel users re-select, remove the unused canCheckout branch, and fix the pt-BR welcome progress label.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add password visibility toggle to the login form.
Match the register eye control so users can reveal their password while signing in.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Point the sidebar community link to Discord.
Replace the X stay-updated entry with Join Discord and the trypost.it/discord invite.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Rename the sidebar Discord link to Discord community.
Softer label that matches the other support nav items.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix broken Turkish Discord community translation.
An unescaped apostrophe left a parse error in lang/tr/sidebar.php.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a connect step between persona selection and Stripe checkout: persona ->
connect >=1 network (server-enforced) -> checkout. Extract the network grid into
a shared NetworkConnectGrid used by both onboarding and the accounts page, which
is redesigned from a table into the same grid (one account per network, with
per-card connect / connected+disconnect / reconnect states). Drop the openDialog
auto-open flow now that networks are shown inline.
Pricing
- Bill per workspace ($12/mo or $120/yr each); Stripe quantity tracks the
workspace count and syncs on workspace create/delete.
- 2,500 AI credits per workspace, pooled at the account level; monthly reset
on the billing anniversary, annual granted upfront (no rollover).
- One social account per network per workspace; remove all count-based limits
(workspace/social/member) and the legacy plan tiers (single Workspace plan).
Onboarding (cloud only: SELF_HOSTED=false + PostHog)
- Replace the /subscribe plan picker with /onboarding persona selection
(Creator/Freelancer/Startup/Agency/Small business/Other), saved on the user
(users.persona) and mirrored to PostHog, then Stripe Checkout on the monthly
price. 8-day trial so Stripe displays 7.
Billing screen
- Remove the Change Plan dialog (dead with a single plan); add an annual-upgrade
banner for monthly subscribers (swapToYearly).
- Current-plan card shows the workspace count instead of the plan name.
System AI
- Brand analyzer / workspace autofill is always allowed and never debits credits
(system feature, not the user's usage).
Self-hosted (SELF_HOSTED=true) bypasses all billing, credit, limit, network,
and onboarding logic.
- StripeEventListener::handleSubscriptionCreated nulls account.trial_ends_at
when a Stripe subscription is created. Prevents 'Trial' badge from
lingering for users who convert mid-generic-trial to paid.
- Drop unused trialDays global Inertia prop (no frontend consumers after
/subscribe redesign).
Tests added (10 new, 0 regressions, 1533 total):
- AccountTest: isOnTrial + activeTrialEndsAt across 4 scenarios
(no trial, generic only, subscription only, both — subscription wins)
- StripeEventListenerTest: subscription created clears generic trial
- TrialMiddlewareAccessTest: trialing-with-card subscription passes
- BillingControllerTest: index exposes onTrial/trialEndsAt for the 3
trial states (generic-only, subscription-only, paying); subscribe
page no longer exposes trialDays prop
- Create Account model as Cashier Billable entity (stripe, plan, subscription)
- Account owns workspaces and has an owner_id (User)
- User belongs to one Account via account_id
- Workspace belongs to Account via account_id, no longer has billing fields
- Remove Brand model entirely (workspaces serve as grouping)
- Rename brand_limit to workspace_limit in plans
- Workspace roles simplified: admin/member/viewer (owner via Account)
- Invites now belong to Account with workspaces JSON array
- Pennant features scope changed from Workspace to Account
- EnsureSubscribed middleware checks Account subscription
- All controllers updated: BillingController, OnboardingController,
WorkspaceInviteController, SocialController, StripeEventListener
- Frontend: extract GoogleAuthButton component, create WorkspaceRole
enum for type-safe role checks, fix all views for new architecture
- All 1101 tests passing