From 00c48fc915069723056bec5fcea68d616143a9de Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 12:59:55 -0300 Subject: [PATCH 01/17] Add post-subscription onboarding design spec. Co-authored-by: Cursor --- ...-24-post-subscription-onboarding-design.md | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 .superpowers/specs/2026-07-24-post-subscription-onboarding-design.md diff --git a/.superpowers/specs/2026-07-24-post-subscription-onboarding-design.md b/.superpowers/specs/2026-07-24-post-subscription-onboarding-design.md new file mode 100644 index 00000000..ec1b5ca9 --- /dev/null +++ b/.superpowers/specs/2026-07-24-post-subscription-onboarding-design.md @@ -0,0 +1,198 @@ +# Post-Subscription Onboarding Design + +**Date:** 2026-07-24 +**Branch:** `feature/post-subscription-onboarding` +**Status:** Approved for planning + +## Problem + +TryPost’s current “onboarding” is ICP qualification (persona, goals, referral source) plus a mandatory social connect before Stripe. After payment, `/billing/processing` sends the user to `/accounts`. There is no activation moment — no guided path to connect an MCP agent, connect networks in context, or create a first post. + +The ICP for TryPost is people who create content via MCP clients (Claude, ChatGPT). Without a dedicated post-subscription activation experience, users miss the AHA moment. + +## Goals + +1. Rename and clarify **Welcome** (pre-Stripe ICP) vs **Onboarding** (post-Stripe activation). +2. After Stripe, land on a **single-page** activation checklist at `/onboarding`. +3. AHA = MCP connected + first post created (**draft is enough**; publish optional). +4. Flow is **skippable**, with a dismissible residual checklist in the app. +5. Move social connect out of pre-Stripe into post-Stripe onboarding. +6. v1 MCP clients: **Claude** and **ChatGPT** only. First-post step is dual: MCP primary + UI fallback. + +## Non-goals (v1) + +- Hard-gating the app until onboarding is complete. +- Multi-step wizard for activation (no `/onboarding/step-*`). +- Cursor (or other MCP clients) as first-class cards. +- Requiring publish as the AHA. +- Changing the pre-Stripe ICP questions themselves (only routes/names/structure). + +## End-to-end flow + +``` +/register + → /register/success + → /welcome (redirect → /welcome/persona) + → /welcome/persona + → /welcome/goals + → /welcome/referral-source + → /welcome/subscribe (plan + Stripe CTA; no social required) + → Stripe Checkout + → /billing/processing?session_id=… + → /onboarding (single-page checklist) + → Skip or complete → /calendar +``` + +Self-hosted: skip Welcome and activation Onboarding (same policy as today’s onboarding skip → calendar). + +## URL map + +### Welcome (ICP, pre-subscription) + +| Method | Path | Name | Purpose | +|--------|------|------|---------| +| GET | `/welcome` | `app.welcome` | Redirect to `app.welcome.persona` | +| GET/POST | `/welcome/persona` | `app.welcome.persona` / `.store` | Persona | +| GET/POST | `/welcome/goals` | `app.welcome.goals` / `.store` | Goals | +| GET/POST | `/welcome/referral-source` | `app.welcome.referral-source` / `.store` | Referral | +| GET | `/welcome/subscribe` | `app.welcome.subscribe` | Plan summary + subscribe CTA | +| POST | `/welcome/checkout` | `app.welcome.checkout` | Start Stripe Checkout | + +Legacy redirects (temporary): + +- `/onboarding` → `/welcome/persona` (only while unsubscribed / for old bookmarks; after activation ships, subscribed users hitting old paths should not bounce into Welcome) +- `/onboarding/goals` → `/welcome/goals` +- `/onboarding/referral-source` → `/welcome/referral-source` +- `/onboarding/connect` → `/welcome/subscribe` + +`EnsureAccountReady`: unsubscribed users → `app.welcome.persona` (not activation `/onboarding`). + +### Onboarding (activation, post-subscription) + +| Method | Path | Name | Purpose | +|--------|------|------|---------| +| GET | `/onboarding` | `app.onboarding` | Single-page activation checklist | +| POST | `/onboarding/dismiss` | `app.onboarding.dismiss` | Skip / do later | +| POST | `/onboarding/complete` | `app.onboarding.complete` | Explicit complete when all steps done (optional if auto-complete navigates) | + +`/billing/processing` success redirect target changes from `/accounts` to `/onboarding`. + +## Code rename (Welcome) + +| Today | After | +|-------|-------| +| `OnboardingController` | `WelcomeController` | +| `app/Http/Requests/App/Onboarding/*` | `app/Http/Requests/App/Welcome/*` (`StoreWelcomePersonaRequest`, `StoreWelcomeGoalsRequest`, `StoreWelcomeReferralSourceRequest`) | +| `resources/js/pages/onboarding/{Index,Goals,ReferralSource,Connect}.vue` | `resources/js/pages/welcome/{Persona,Goals,ReferralSource,Subscribe}.vue` | +| `OnboardingLayout.vue` | `WelcomeLayout.vue` | +| `lang/*/onboarding.php` (ICP strings) | `lang/*/welcome.php` | +| `tests/Feature/Onboarding/OnboardingControllerTest.php` | `tests/Feature/Welcome/WelcomeControllerTest.php` | +| `app.onboarding.*` (ICP) | `app.welcome.*` | + +`Connect.vue` is removed from Welcome. Social connect lives on the activation page (and remains available at `/accounts`). + +Checkout no longer requires an existing social account. + +## Onboarding page (single page, not a step form) + +One dedicated full-page screen with a **3-item checklist** on the same view: + +1. **Connect an agent** — Claude and ChatGPT cards with MCP URL / deep-link / copy instructions (link to docs where needed). +2. **Connect a social network** — reuse existing connect UI patterns (platforms + connected accounts), inlined on this page. +3. **Create your first post** — primary: ready-made MCP prompt (“open Claude/ChatGPT and ask…”); fallback: button to create in TryPost UI. Draft counts. + +### Live completion + +Steps auto-check from real state (poll or Inertia partial reload): + +| Step | Done when | +|------|-----------| +| MCP | User has a non-revoked OAuth access token used for MCP (not a Personal Access Token / API key) | +| Social | Current workspace has ≥ 1 social account | +| First post | Current workspace has ≥ 1 post (any status, any `created_via`) | + +Primary CTA: continue into the app when all three are done (or celebrate in place then go to calendar). +Secondary: **Skip / I’ll do this later**. + +### Gating policy + +- **Not blocking.** After the post-checkout land, users can skip and use the product. +- No middleware that traps subscribed users on `/onboarding` on every request. +- Residual checklist in the app shell until completed or dismissed. + +## Persistence + +Welcome ICP fields stay on `User`: `persona`, `goals`, `referral_source`. + +Activation flags on `Account` (billing/subscription owner — preferred over User so workspace members don’t each own account-level activation): + +- `onboarding_completed_at` (nullable timestamp) +- `onboarding_dismissed_at` (nullable timestamp) + +Semantics: + +- Steps themselves are derived from state, not stored booleans. +- `onboarding_completed_at` set when all three steps are true (server-side on dismiss-complete path or when status is loaded and all true). +- `onboarding_dismissed_at` set on Skip. +- Residual UI shows when account is subscribed and both timestamps are null. +- Dismiss hides residual even if steps remain incomplete. +- Completing all three sets `completed_at` and clears residual. + +Existing accounts already subscribed: treat as dismissed or completed (migration strategy: backfill `onboarding_dismissed_at = now()` for accounts that already have an active subscription, so we don’t force legacy users into activation). + +## Residual checklist + +Dismissible banner or compact checklist in the authenticated app layout (not a modal trap): + +- Visible when subscribed + `completed_at` null + `dismissed_at` null. +- Link back to `/onboarding`. +- Dismiss action hits `app.onboarding.dismiss`. + +Exact UI placement (sidebar vs top banner) is an implementation detail; prefer the lightest pattern that matches existing app chrome. + +## Analytics (PostHog) + +Follow existing enum pattern under `App\Enums\PostHog\` (e.g. `WelcomeEvent`, `OnboardingEvent`). + +Suggested events: + +- Welcome: `welcome.persona_saved`, `welcome.goals_saved`, `welcome.referral_saved`, `welcome.checkout_started` +- Onboarding: `onboarding.viewed`, `onboarding.step_completed` (property `step`: `mcp` | `social` | `first_post`), `onboarding.skipped`, `onboarding.completed` + +Step properties when useful: `created_via` (`web` | `mcp`) for first post; MCP client if detectable later (v1 may omit client if unreliable). + +## Architecture notes + +- **WelcomeController** — ICP + subscribe + checkout only; redirects subscribed users to `/onboarding` or calendar per rules (subscribed + activation incomplete → can visit `/onboarding`; subscribed + complete/dismissed → calendar). +- **OnboardingController** — show checklist props (step statuses, platforms, accounts, MCP server URL, sample prompt), dismiss, complete. +- Prefer a small action/service e.g. `App\Actions\Onboarding\ResolveOnboardingStatus` that returns the three booleans + whether residual should show — shared by page, layout shared props, and tests. +- Frontend: one `resources/js/pages/onboarding/Index.vue`; poll step status while the page is open so MCP/OAuth and MCP-created drafts flip checkmarks without a full navigation. +- Wayfinder regenerate after route/controller rename. +- i18n: all new copy in `lang/*/welcome.php` and `lang/*/onboarding.php` (all locales that already have onboarding strings). + +## Testing + +- Welcome: renamed feature tests; persona → goals → referral → subscribe; checkout **without** social account succeeds; subscribed users don’t see Welcome ICP. +- Legacy route redirects. +- Billing processing redirects to `/onboarding`. +- Onboarding page renders step statuses from fixtures (MCP token, social, post). +- Dismiss sets `onboarding_dismissed_at`; residual hidden. +- Auto-complete when all three true sets `onboarding_completed_at`. +- `EnsureAccountReady` → `/welcome/persona` when unsubscribed. +- Self-hosted skips both flows. +- Residual shared prop / layout behavior covered at least once. + +## Open implementation details (resolved in plan, not product) + +- Exact MCP “connected” query against Passport tokens/clients. +- Claude/ChatGPT deep-link vs copy-paste URL UX copy. +- Residual component placement in `AppLayout`. +- Whether `onboarding.complete` is a dedicated POST or automatic on status resolve. + +## Success criteria + +- New paid users land on `/onboarding` after Stripe, not `/accounts`. +- They can connect Claude or ChatGPT, connect a network, and create a draft (via MCP or UI) on one page. +- They can skip and still use the product, with an optional residual nudge. +- Pre-Stripe flow lives under `/welcome/{step}` with aligned PHP/Vue naming. +- ICP collection no longer requires social connect before payment. From 5b8e6fffa3af8e71d8d28668cdee8a89aff01da0 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:05:00 -0300 Subject: [PATCH 02/17] Add post-subscription onboarding implementation plan. Co-authored-by: Cursor --- ...2026-07-24-post-subscription-onboarding.md | 552 ++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 .superpowers/plans/2026-07-24-post-subscription-onboarding.md diff --git a/.superpowers/plans/2026-07-24-post-subscription-onboarding.md b/.superpowers/plans/2026-07-24-post-subscription-onboarding.md new file mode 100644 index 00000000..0ffb0611 --- /dev/null +++ b/.superpowers/plans/2026-07-24-post-subscription-onboarding.md @@ -0,0 +1,552 @@ +# Post-Subscription Onboarding Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rename pre-Stripe ICP to Welcome (`/welcome/{step}`), remove social-before-pay, and ship a single-page post-Stripe activation checklist at `/onboarding` (MCP + social + first post) with skip + residual nudge. + +**Architecture:** `WelcomeController` owns ICP + Stripe checkout (no social gate). `OnboardingController` owns the activation page behind `EnsureAccountReady`. `ResolveOnboardingStatus` derives MCP/social/post completion and residual visibility; Account stores only `onboarding_completed_at` / `onboarding_dismissed_at`. Frontend: one `onboarding/Index.vue` with Inertia `usePoll`; residual banner via shared Inertia prop in `AppSidebarLayout`. + +**Tech Stack:** Laravel 13, Inertia v3 + Vue 3, Cashier, Passport (`AccessToken`), Pest 4, Wayfinder, PostHog enums/jobs, Tailwind v4, `@tabler/icons-vue`. + +**Spec:** `.superpowers/specs/2026-07-24-post-subscription-onboarding-design.md` + +## Global Constraints + +- Activation is one page only — no `/onboarding/step-*` wizard. +- AHA = MCP connected + first post exists (draft OK); publish optional. +- Skippable; no hard-gate after first land. +- v1 MCP clients: Claude + ChatGPT only. +- First post step: MCP prompt primary + UI fallback to `route('app.posts.create')`. +- Social connect removed from pre-Stripe; lives on activation page via existing `NetworkConnectGrid`. +- MCP “connected” = user has non-revoked `AccessToken` with `workspace_id IS NULL` (OAuth/MCP session; PATs bind `workspace_id`). +- Self-hosted skips Welcome and activation (redirect calendar), same as today. +- Never hardcode URLs in tests — use `route()`. +- FormRequests under `app/Http/Requests/App//`; no inline `$request->validate`. +- Arrow functions only in Vue/TS; Tabler icons only; Wayfinder for frontend routes. +- After PHP changes: `vendor/bin/pint --dirty --format agent`. +- After route/controller changes: `php artisan wayfinder:generate`. +- i18n: all 15 locales that have `lang/*/onboarding.php`. +- Branch: `feature/post-subscription-onboarding` (already created). +- Do not push or open PR unless asked. + +## File structure (target) + +| Path | Responsibility | +|------|----------------| +| `app/Actions/Onboarding/ResolveOnboardingStatus.php` | Derive step booleans + residual + maybe mark completed | +| `app/Http/Controllers/App/WelcomeController.php` | ICP + subscribe + checkout | +| `app/Http/Controllers/App/OnboardingController.php` | Activation show / dismiss / complete | +| `app/Http/Requests/App/Welcome/*` | Persona / goals / referral FormRequests | +| `app/Enums/PostHog/WelcomeEvent.php` | Welcome analytics event names | +| `app/Enums/PostHog/OnboardingEvent.php` | Onboarding analytics event names | +| `resources/js/pages/welcome/{Persona,Goals,ReferralSource,Subscribe}.vue` | ICP UI | +| `resources/js/pages/onboarding/Index.vue` | Single activation checklist | +| `resources/js/layouts/WelcomeLayout.vue` | Renamed from OnboardingLayout | +| `resources/js/components/onboarding/OnboardingResidualBanner.vue` | Residual nudge | +| `lang/*/welcome.php` | ICP copy (moved from onboarding) | +| `lang/*/onboarding.php` | Activation copy (replaced) | +| `tests/Feature/Welcome/WelcomeControllerTest.php` | Renamed + updated ICP tests | +| `tests/Feature/Onboarding/OnboardingControllerTest.php` | New activation tests | +| `tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php` | Status derivation | + +--- + +### Task 1: Account onboarding timestamps + +**Files:** +- Create: migration via `php artisan make:migration add_onboarding_timestamps_to_accounts_table --no-interaction` +- Modify: `app/Models/Account.php` (`$fillable`, `$casts`) +- Modify: `database/factories/AccountFactory.php` if needed for defaults +- Test: `tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php` + +**Interfaces:** +- Produces: `Account::$onboarding_completed_at`, `Account::$onboarding_dismissed_at` (`?CarbonInterface`) + +- [ ] **Step 1: Write the failing test** + +```php +create(); + $account = $user->account; + + $account->update([ + 'onboarding_completed_at' => now(), + 'onboarding_dismissed_at' => now()->subMinute(), + ]); + + $account->refresh(); + + expect($account->onboarding_completed_at)->not->toBeNull() + ->and($account->onboarding_dismissed_at)->not->toBeNull(); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `php artisan test --compact tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php` +Expected: FAIL (unknown columns / not fillable) + +- [ ] **Step 3: Create migration + update Account** + +Migration `up`: + +```php +Schema::table('accounts', function (Blueprint $table) { + $table->timestamp('onboarding_completed_at')->nullable()->after('trial_ends_at'); + $table->timestamp('onboarding_dismissed_at')->nullable()->after('onboarding_completed_at'); +}); + +// Backfill: existing subscribed accounts must not see residual/activation. +Account::query() + ->whereHas('subscriptions', function ($query) { + $query->where('type', Account::SUBSCRIPTION_NAME) + ->whereIn('stripe_status', ['active', 'trialing']); + }) + ->whereNull('onboarding_dismissed_at') + ->whereNull('onboarding_completed_at') + ->update(['onboarding_dismissed_at' => now()]); +``` + +On `Account`: add both fields to `$fillable` and cast to `datetime`. + +- [ ] **Step 4: Migrate and run test** + +Run: `php artisan migrate --no-interaction && php artisan test --compact tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add database/migrations/*onboarding_timestamps* app/Models/Account.php tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php +git commit -m "Add account onboarding completed and dismissed timestamps." +``` + +--- + +### Task 2: ResolveOnboardingStatus action + +**Files:** +- Create: `app/Actions/Onboarding/ResolveOnboardingStatus.php` +- Create: `tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php` + +**Interfaces:** +- Consumes: `User`, `Account`, `AccessToken` (workspace_id null = MCP), workspace socialAccounts, workspace posts +- Produces: + +```php +/** + * @return array{ + * mcp_connected: bool, + * social_connected: bool, + * first_post_created: bool, + * all_complete: bool, + * show_residual: bool, + * completed_at: ?string, + * dismissed_at: ?string + * } + */ +public function handle(User $user): array +``` + +When `all_complete` and `onboarding_completed_at` is null, set `onboarding_completed_at = now()` inside this action (idempotent). + +`show_residual` = not self-hosted AND account subscribed AND `completed_at` null AND `dismissed_at` null. + +MCP connected: + +```php +AccessToken::query() + ->where('user_id', $user->id) + ->whereNull('workspace_id') + ->where('revoked', false) + ->exists(); +``` + +- [ ] **Step 1: Write failing tests** covering: empty state; MCP-only (createToken + forceFill workspace_id null); social-only; post-only; all three auto-sets completed_at; dismissed hides residual; self-hosted show_residual false. + +- [ ] **Step 2: Run tests — expect FAIL** + +Run: `php artisan test --compact tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php` + +- [ ] **Step 3: Implement `ResolveOnboardingStatus`** + +- [ ] **Step 4: Run tests — expect PASS** + +- [ ] **Step 5: Pint + commit** + +```bash +vendor/bin/pint --dirty --format agent +git add app/Actions/Onboarding tests/Feature/Actions/Onboarding +git commit -m "Add ResolveOnboardingStatus for activation checklist state." +``` + +--- + +### Task 3: Rename ICP backend to Welcome + +**Files:** +- Create: `app/Http/Controllers/App/WelcomeController.php` (from `OnboardingController`, minus social-required checkout) +- Create: `app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php` (ex-StoreOnboardingRequest) +- Create: `app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php` +- Create: `app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php` +- Modify: `routes/app.php` — welcome routes; legacy redirects for goals/referral/connect; point `EnsureAccountReady` target +- Modify: `app/Http/Middleware/App/EnsureAccountReady.php` → `route('app.welcome.persona')` +- Delete: old `OnboardingController` ICP methods later (after Task 7 owns new controller) — for this task, rename file to WelcomeController and leave `/onboarding` route temporarily redirecting unsubscribed users via middleware only +- Update all PHP references: `BillingController::subscribe`, tests `EnsureAccountReadyTest`, `WorkspaceBillingTest`, `BillingControllerTest`, `TrialMiddlewareAccessTest`, `SocialControllerTest` onboarding mentions + +**Route map to register (auth group, not EnsureAccountReady):** + +```php +Route::get('welcome', fn () => redirect()->route('app.welcome.persona'))->name('app.welcome'); +Route::get('welcome/persona', [WelcomeController::class, 'persona'])->name('app.welcome.persona'); +Route::post('welcome/persona', [WelcomeController::class, 'storePersona'])->name('app.welcome.persona.store'); +Route::get('welcome/goals', [WelcomeController::class, 'goals'])->name('app.welcome.goals'); +Route::post('welcome/goals', [WelcomeController::class, 'storeGoals'])->name('app.welcome.goals.store'); +Route::get('welcome/referral-source', [WelcomeController::class, 'referralSource'])->name('app.welcome.referral-source'); +Route::post('welcome/referral-source', [WelcomeController::class, 'storeReferralSource'])->name('app.welcome.referral-source.store'); +Route::get('welcome/subscribe', [WelcomeController::class, 'subscribe'])->name('app.welcome.subscribe'); +Route::post('welcome/checkout', [WelcomeController::class, 'checkout'])->name('app.welcome.checkout'); + +// Legacy ICP URLs +Route::redirect('onboarding/goals', '/welcome/goals'); +Route::redirect('onboarding/referral-source', '/welcome/referral-source'); +Route::redirect('onboarding/connect', '/welcome/subscribe'); +``` + +Do **not** redirect `GET onboarding` — that path becomes activation in Task 7 (behind `EnsureAccountReady`). + +**WelcomeController behavior changes vs today:** +- Inertia pages: `welcome/Persona`, `welcome/Goals`, `welcome/ReferralSource`, `welcome/Subscribe` +- After referral store → `app.welcome.subscribe` (not connect) +- `subscribe()`: plan props only — **no** platforms/accounts required +- `checkout()`: remove social-account existence check; `cancel_url` = `route('app.welcome.subscribe')` +- Subscribed users hitting Welcome → redirect `app.onboarding` if residual should show, else `app.calendar` (until OnboardingController exists, temporary redirect to calendar is OK then tighten in Task 7) + +- [ ] **Step 1: Move/adapt tests** to `tests/Feature/Welcome/WelcomeControllerTest.php` with new route names; add test that checkout works **without** social account; remove must_connect assertions. + +- [ ] **Step 2: Run Welcome tests — expect FAIL** (routes missing) + +- [ ] **Step 3: Implement WelcomeController + requests + routes + middleware redirect** + +- [ ] **Step 4: Run Welcome + middleware tests — expect PASS** + +Run: `php artisan test --compact tests/Feature/Welcome tests/Feature/Middleware/EnsureAccountReadyTest.php tests/Feature/BillingControllerTest.php tests/Feature/WorkspaceBillingTest.php` + +- [ ] **Step 5: Commit** + +```bash +git commit -m "Rename pre-subscription ICP flow to Welcome routes." +``` + +--- + +### Task 4: Welcome frontend + i18n + +**Files:** +- Create: `resources/js/layouts/WelcomeLayout.vue` (copy/rename `OnboardingLayout.vue`) +- Create: `resources/js/pages/welcome/Persona.vue`, `Goals.vue`, `ReferralSource.vue`, `Subscribe.vue` +- Create: `lang/*/welcome.php` — move ICP keys from `onboarding.php` (title, personas, goals_*, referral_*, continue; subscribe title/description from old connect minus must_connect) +- Replace: `lang/*/onboarding.php` can stay temporarily until Task 8; or clear ICP keys now +- Delete: `resources/js/pages/onboarding/{Index,Goals,ReferralSource,Connect}.vue`, `OnboardingLayout.vue` after welcome pages work +- Update Vue imports to `@/routes/app/welcome/...` after wayfinder generate + +**Subscribe.vue:** plan CTA only — no `NetworkConnectGrid`, no `hasConnected` gate. On submit: `trackBeginCheckout` then POST `app.welcome.checkout`. + +- [ ] **Step 1: Move lang files** — for each of 15 locales, create `welcome.php` from ICP portion of `onboarding.php`; add `subscribe.title` / `subscribe.description` (reuse former connect copy without must_connect). + +- [ ] **Step 2: Create Vue pages** mirroring old onboarding pages with new routes/i18n keys/`WelcomeLayout`. + +- [ ] **Step 3: `php artisan wayfinder:generate`** + +- [ ] **Step 4: Smoke** — `php artisan test --compact tests/Feature/Welcome` still PASS; fix Inertia component names in asserts (`welcome/Persona`, etc.). + +- [ ] **Step 5: Commit** + +```bash +git commit -m "Add Welcome Vue pages and i18n; drop social from subscribe." +``` + +--- + +### Task 5: Onboarding activation backend + +**Files:** +- Create: `app/Http/Controllers/App/OnboardingController.php` +- Create: `app/Enums/PostHog/OnboardingEvent.php` +- Modify: `routes/app.php` — inside `EnsureAccountReady` + `EnsureHasWorkspace` group: + +```php +Route::get('onboarding', [OnboardingController::class, 'index'])->name('app.onboarding'); +Route::post('onboarding/dismiss', [OnboardingController::class, 'dismiss'])->name('app.onboarding.dismiss'); +Route::post('onboarding/complete', [OnboardingController::class, 'complete'])->name('app.onboarding.complete'); +``` + +- Create: `tests/Feature/Onboarding/OnboardingControllerTest.php` (activation — do not reuse old ICP file name confusion; old ICP file already moved to Welcome) + +**`index` props:** + +```php +$status = app(ResolveOnboardingStatus::class)->handle($request->user()); + +return Inertia::render('onboarding/Index', [ + 'status' => $status, + 'mcpUrl' => url('/mcp/trypost'), + 'mcpClients' => [ + ['id' => 'claude', 'label' => 'Claude'], + ['id' => 'chatgpt', 'label' => 'ChatGPT'], + ], + 'samplePrompt' => __('onboarding.first_post.sample_prompt'), + 'platforms' => /* same map as old connect */, + 'accounts' => SocialAccountResource::collection($workspace->socialAccounts()->orderBy('id')->get())->resolve(), + 'createPostUrl' => route('app.posts.create'), +]); +``` + +On first view, capture `OnboardingEvent::Viewed` via `PostHogService` (once per request is fine). + +`dismiss`: set `onboarding_dismissed_at`, capture skipped, redirect `app.calendar`. + +`complete`: if not `all_complete`, redirect back to onboarding; else ensure `onboarding_completed_at`, capture completed, redirect `app.calendar`. + +Self-hosted: index/dismiss/complete → calendar. + +Tighten `WelcomeController`: subscribed users → `app.onboarding` when `show_residual`, else calendar. + +- [ ] **Step 1: Write activation feature tests** (subscribed + workspace): renders statuses; dismiss sets timestamp; complete requires all steps; unsubscribed redirected by middleware to welcome; self-hosted → calendar. + +- [ ] **Step 2: Run — expect FAIL** + +- [ ] **Step 3: Implement controller + enum + routes** + +- [ ] **Step 4: Run activation + Welcome subscribed-redirect tests — PASS** + +- [ ] **Step 5: Commit** + +```bash +git commit -m "Add post-subscription onboarding activation endpoints." +``` + +--- + +### Task 6: Onboarding activation UI (single page) + +**Files:** +- Create: `resources/js/pages/onboarding/Index.vue` +- Replace: `lang/*/onboarding.php` with activation copy only (steps, skip, residual, sample prompt, MCP instructions) +- Keep using `NetworkConnectGrid` for social section + +**UI structure (one page):** +1. Header + Skip button (`form.post` dismiss) +2. Checklist item MCP — copy `mcpUrl`, Claude/ChatGPT short instructions, checkmark from `status.mcp_connected` +3. Checklist item Social — `NetworkConnectGrid` with props +4. Checklist item First post — show `samplePrompt` + copy button; link/button to `createPostUrl`; checkmark from `status.first_post_created` +5. When `status.all_complete` — primary “Continue to TryPost” → POST complete (or `router.visit` calendar after complete) + +**Polling:** + +```ts +import { usePoll } from '@inertiajs/vue3'; + +usePoll(2000, { + only: ['status', 'accounts'], +}); +``` + +- [ ] **Step 1: Write `lang/en/onboarding.php` activation strings**, then copy keys to other 14 locales (English fallback OK if translation lagging, but keys must exist in all locales that previously had the file). + +- [ ] **Step 2: Build `Index.vue`** (arrow functions, Tabler icons, Wayfinder routes for dismiss/complete). + +- [ ] **Step 3: `php artisan wayfinder:generate`** + +- [ ] **Step 4: Feature test assertInertia component `onboarding/Index` + status keys** + +- [ ] **Step 5: Commit** + +```bash +git commit -m "Add single-page post-subscription onboarding UI." +``` + +--- + +### Task 7: Redirect billing processing → onboarding + +**Files:** +- Modify: `resources/js/pages/billing/Processing.vue` — replace `accounts.url()` with onboarding route +- Modify/add tests if any assert post-checkout destination; add feature coverage if missing + +```ts +import { index as onboarding } from '@/routes/app/onboarding'; // or named export from wayfinder +const goToOnboarding = () => router.visit(onboarding.url()); +// use goToOnboarding in setTimeout instead of goToAccounts +``` + +- [ ] **Step 1: Change Processing.vue redirect target** + +- [ ] **Step 2: Manually verify no remaining `accounts` import used for redirect** + +- [ ] **Step 3: Commit** + +```bash +git commit -m "Send post-checkout users to onboarding instead of accounts." +``` + +--- + +### Task 8: Residual banner + shared prop + +**Files:** +- Modify: `app/Http/Middleware/App/HandleInertiaRequests.php` — share lazy `onboardingResidual`: + +```php +'onboardingResidual' => fn () => $user && $account + ? data_get(app(ResolveOnboardingStatus::class)->handle($user), 'show_residual', false) + : false, +``` + +- Create: `resources/js/components/onboarding/OnboardingResidualBanner.vue` +- Modify: `resources/js/layouts/app/AppSidebarLayout.vue` — render banner above `` when `page.props.onboardingResidual` +- Create: `tests/Feature/Onboarding/OnboardingResidualShareTest.php` — hit a subscribed app route (e.g. calendar) and assert shared prop true/false + +Banner: short copy + Link to `/onboarding` + dismiss button posting `app.onboarding.dismiss` (or small form). + +Skip rendering on the onboarding page itself (`usePage().url` includes `/onboarding`). + +- [ ] **Step 1: Write share/residual tests** + +- [ ] **Step 2: Implement share + banner** + +- [ ] **Step 3: Run tests — PASS** + +- [ ] **Step 4: Commit** + +```bash +git commit -m "Add dismissible residual onboarding banner in app shell." +``` + +--- + +### Task 9: Welcome + Onboarding PostHog events + +**Files:** +- Create: `app/Enums/PostHog/WelcomeEvent.php` + +```php +enum WelcomeEvent: string +{ + case PersonaSaved = 'welcome.persona_saved'; + case GoalsSaved = 'welcome.goals_saved'; + case ReferralSaved = 'welcome.referral_saved'; + case CheckoutStarted = 'welcome.checkout_started'; +} +``` + +- Create: `app/Enums/PostHog/OnboardingEvent.php` + +```php +enum OnboardingEvent: string +{ + case Viewed = 'onboarding.viewed'; + case StepCompleted = 'onboarding.step_completed'; + case Skipped = 'onboarding.skipped'; + case Completed = 'onboarding.completed'; +} +``` + +- Modify: `WelcomeController` — `capture` on store/checkout (keep existing `identify` for persona/goals/referral) +- Modify: `OnboardingController` — viewed / skipped / completed +- Optional: in `ResolveOnboardingStatus` or controller poll path, when a step flips true, capture `StepCompleted` with `step` property — avoid duplicate spam by only capturing when transitioning (session flash keys `onboarding_step_mcp` etc. OR compare previous request; v1 acceptable: capture from frontend once via optional endpoint — prefer server session flags on the Account JSON column only if already needed; simplest v1: capture StepCompleted inside ResolveOnboardingStatus only when the step is true AND a cache key `onboarding_step:{accountId}:{step}` missing, TTL 30 days) + +**YAGNI for v1:** capture Viewed, Skipped, Completed, and Welcome saves/checkout; defer per-step transition events if costly — but spec lists them, so implement cache-gated StepCompleted in `ResolveOnboardingStatus` when a step is true. + +- [ ] **Step 1: Tests with `Bus::fake` + `SendEvent` asserts** on welcome store and onboarding dismiss/complete/view + +- [ ] **Step 2: Implement enums + captures** + +- [ ] **Step 3: Run related tests — PASS** + +- [ ] **Step 4: Commit** + +```bash +git commit -m "Track welcome and onboarding activation events in PostHog." +``` + +--- + +### Task 10: Cleanup, Wayfinder, full regression + +**Files:** +- Remove dead old onboarding ICP code/files if any remain +- Ensure `Connect.vue` deleted +- Grep for `app.onboarding.goals`, `onboarding/Index` (persona), `OnboardingLayout`, `StoreOnboardingRequest`, `pages/onboarding/Goals` +- Run pint + wayfinder + +- [ ] **Step 1: Grep cleanup** + +```bash +rg -n "app\.onboarding\.(store|goals|referral|connect|checkout)|OnboardingLayout|StoreOnboarding|pages/onboarding/Goals|onboarding/Connect" --glob '!vendor/**' --glob '!node_modules/**' +``` + +Expected: no stale ICP references (activation `app.onboarding` / `app.onboarding.dismiss` OK). + +- [ ] **Step 2: Pint + wayfinder** + +```bash +vendor/bin/pint --dirty --format agent +php artisan wayfinder:generate +``` + +- [ ] **Step 3: Run full related suite** + +```bash +php artisan test --compact \ + tests/Feature/Welcome \ + tests/Feature/Onboarding \ + tests/Feature/Actions/Onboarding \ + tests/Feature/Middleware/EnsureAccountReadyTest.php \ + tests/Feature/BillingControllerTest.php \ + tests/Feature/WorkspaceBillingTest.php \ + tests/Feature/Social/SocialControllerTest.php +``` + +Fix failures. + +- [ ] **Step 4: Final commit if needed** + +```bash +git commit -m "Clean up legacy onboarding ICP references after Welcome rename." +``` + +--- + +## Self-review (spec coverage) + +| Spec requirement | Task | +|------------------|------| +| Welcome `/welcome/{step}` URLs | 3–4 | +| Remove social pre-pay | 3–4 | +| Checkout without social | 3 | +| `/onboarding` single page | 5–6 | +| MCP + social + first post checklist | 5–6 | +| Draft counts / dual create path | 6 (`createPostUrl` + prompt) | +| Skippable + residual | 5, 8 | +| Account timestamps + backfill | 1 | +| ResolveOnboardingStatus | 2 | +| Processing → onboarding | 7 | +| EnsureAccountReady → welcome | 3 | +| PostHog events | 9 | +| Self-hosted skip | 3, 5 | +| Legacy redirects (except GET `/onboarding`) | 3 | +| i18n all locales | 4, 6 | + +**Resolved open details:** +- MCP detection = `AccessToken` with `workspace_id` null, not revoked. +- Complete = dedicated POST `app.onboarding.complete` + auto-set timestamp in ResolveOnboardingStatus. +- Residual = shared `onboardingResidual` + banner in `AppSidebarLayout`. +- MCP UX v1 = copy `mcpUrl` + Claude/ChatGPT labels (no fragile deep links required). From 44fb9be5f9a84056c8b6027f66011fa07e39a39e Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:07:17 -0300 Subject: [PATCH 03/17] Add account onboarding completed and dismissed timestamps. Co-authored-by: Cursor --- app/Models/Account.php | 4 ++ ...nboarding_timestamps_to_accounts_table.php | 41 +++++++++++++++++++ .../AccountOnboardingTimestampsTest.php | 20 +++++++++ 3 files changed, 65 insertions(+) create mode 100644 database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php create mode 100644 tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php diff --git a/app/Models/Account.php b/app/Models/Account.php index b8e31b00..6f53f2c7 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -38,10 +38,14 @@ public static function postsCountCacheKey(string $accountId): string 'billing_email', 'plan_id', 'trial_ends_at', + 'onboarding_completed_at', + 'onboarding_dismissed_at', ]; protected $casts = [ 'trial_ends_at' => 'datetime', + 'onboarding_completed_at' => 'datetime', + 'onboarding_dismissed_at' => 'datetime', ]; public function owner(): BelongsTo diff --git a/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php b/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php new file mode 100644 index 00000000..3b33322f --- /dev/null +++ b/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php @@ -0,0 +1,41 @@ +timestamp('onboarding_completed_at')->nullable()->after('trial_ends_at'); + $table->timestamp('onboarding_dismissed_at')->nullable()->after('onboarding_completed_at'); + }); + + Account::query() + ->whereHas('subscriptions', function ($query) { + $query->where('type', Account::SUBSCRIPTION_NAME) + ->whereIn('stripe_status', ['active', 'trialing']); + }) + ->whereNull('onboarding_dismissed_at') + ->whereNull('onboarding_completed_at') + ->update(['onboarding_dismissed_at' => now()]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('accounts', function (Blueprint $table) { + $table->dropColumn(['onboarding_completed_at', 'onboarding_dismissed_at']); + }); + } +}; diff --git a/tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php b/tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php new file mode 100644 index 00000000..9943d79b --- /dev/null +++ b/tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php @@ -0,0 +1,20 @@ +create(); + $account = $user->account; + + $account->update([ + 'onboarding_completed_at' => now(), + 'onboarding_dismissed_at' => now()->subMinute(), + ]); + + $account->refresh(); + + expect($account->onboarding_completed_at)->not->toBeNull() + ->and($account->onboarding_dismissed_at)->not->toBeNull(); +}); From a69b799d66186e88b94f25368ce8d6a40ab8f445 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:11:11 -0300 Subject: [PATCH 04/17] Add ResolveOnboardingStatus for activation checklist state. Co-authored-by: Cursor --- .../Onboarding/ResolveOnboardingStatus.php | 61 ++++++++ .../ResolveOnboardingStatusTest.php | 145 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 app/Actions/Onboarding/ResolveOnboardingStatus.php create mode 100644 tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php diff --git a/app/Actions/Onboarding/ResolveOnboardingStatus.php b/app/Actions/Onboarding/ResolveOnboardingStatus.php new file mode 100644 index 00000000..012cff09 --- /dev/null +++ b/app/Actions/Onboarding/ResolveOnboardingStatus.php @@ -0,0 +1,61 @@ +where('user_id', $user->id) + ->whereNull('workspace_id') + ->where('revoked', false) + ->exists(); + $socialConnected = $workspace?->socialAccounts()->exists() ?? false; + $firstPostCreated = $workspace?->posts()->exists() ?? false; + $allComplete = $mcpConnected && $socialConnected && $firstPostCreated; + + if ($allComplete && $account?->onboarding_completed_at === null) { + $account->update(['onboarding_completed_at' => now()]); + } + + $showResidual = ! config('trypost.self_hosted') + && ($account?->subscribed(Account::SUBSCRIPTION_NAME) ?? false) + && $account->onboarding_completed_at === null + && $account->onboarding_dismissed_at === null; + + return [ + 'mcp_connected' => $mcpConnected, + 'social_connected' => $socialConnected, + 'first_post_created' => $firstPostCreated, + 'all_complete' => $allComplete, + 'show_residual' => $showResidual, + 'completed_at' => $account?->onboarding_completed_at?->toIso8601String(), + 'dismissed_at' => $account?->onboarding_dismissed_at?->toIso8601String(), + ]; + } +} diff --git a/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php b/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php new file mode 100644 index 00000000..12f591d9 --- /dev/null +++ b/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php @@ -0,0 +1,145 @@ + false]); + + $this->user = User::factory()->create(); + $this->workspace = Workspace::factory()->create([ + 'account_id' => $this->user->account_id, + 'user_id' => $this->user->id, + ]); + $this->user->update(['current_workspace_id' => $this->workspace->id]); + $this->user->refresh(); + + subscribeAccount($this->user->account); +}); + +test('resolves the empty onboarding state', function () { + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status)->toBe([ + 'mcp_connected' => false, + 'social_connected' => false, + 'first_post_created' => false, + 'all_complete' => false, + 'show_residual' => true, + 'completed_at' => null, + 'dismissed_at' => null, + ]); +}); + +test('resolves an OAuth token as MCP connected', function () { + $result = $this->user->createToken('OAuth Session'); + AccessToken::find($result->token->id) + ->forceFill(['workspace_id' => null]) + ->saveQuietly(); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status)->toMatchArray([ + 'mcp_connected' => true, + 'social_connected' => false, + 'first_post_created' => false, + 'all_complete' => false, + ]); +}); + +test('does not resolve a workspace-bound personal access token as MCP connected', function () { + $result = $this->user->createToken('Personal Access Token'); + AccessToken::find($result->token->id) + ->forceFill(['workspace_id' => $this->workspace->id]) + ->saveQuietly(); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status['mcp_connected'])->toBeFalse(); +}); + +test('resolves a social account in the current workspace as connected', function () { + SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status)->toMatchArray([ + 'mcp_connected' => false, + 'social_connected' => true, + 'first_post_created' => false, + 'all_complete' => false, + ]); +}); + +test('resolves any post in the current workspace as the first post', function () { + Post::factory()->failed()->create([ + 'workspace_id' => $this->workspace->id, + 'user_id' => $this->user->id, + ]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status)->toMatchArray([ + 'mcp_connected' => false, + 'social_connected' => false, + 'first_post_created' => true, + 'all_complete' => false, + ]); +}); + +test('marks onboarding completed once all three steps are complete', function () { + Carbon::setTestNow('2026-07-24 12:00:00'); + + $result = $this->user->createToken('OAuth Session'); + AccessToken::find($result->token->id) + ->forceFill(['workspace_id' => null]) + ->saveQuietly(); + SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]); + Post::factory()->create([ + 'workspace_id' => $this->workspace->id, + 'user_id' => $this->user->id, + ]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status)->toBe([ + 'mcp_connected' => true, + 'social_connected' => true, + 'first_post_created' => true, + 'all_complete' => true, + 'show_residual' => false, + 'completed_at' => now()->toIso8601String(), + 'dismissed_at' => null, + ])->and($this->user->account->fresh()->onboarding_completed_at?->equalTo(now()))->toBeTrue(); + + Carbon::setTestNow(now()->addHour()); + app(ResolveOnboardingStatus::class)->handle($this->user->fresh()); + + expect($this->user->account->fresh()->onboarding_completed_at?->toIso8601String()) + ->toBe('2026-07-24T12:00:00+00:00'); +}); + +test('dismissed onboarding does not show the residual checklist', function () { + Carbon::setTestNow('2026-07-24 12:00:00'); + $this->user->account->update(['onboarding_dismissed_at' => now()]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user->fresh()); + + expect($status['show_residual'])->toBeFalse() + ->and($status['dismissed_at'])->toBe(now()->toIso8601String()); +}); + +test('self-hosted onboarding does not show the residual checklist', function () { + config(['trypost.self_hosted' => true]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status['show_residual'])->toBeFalse(); +}); From abf247e30df38c574b2b521293029c55837ceadf Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:13:25 -0300 Subject: [PATCH 05/17] Fix ResolveOnboardingStatus null-account guard and workspace scoping tests. Co-authored-by: Cursor --- .../Onboarding/ResolveOnboardingStatus.php | 2 +- .../ResolveOnboardingStatusTest.php | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/Actions/Onboarding/ResolveOnboardingStatus.php b/app/Actions/Onboarding/ResolveOnboardingStatus.php index 012cff09..01a669cd 100644 --- a/app/Actions/Onboarding/ResolveOnboardingStatus.php +++ b/app/Actions/Onboarding/ResolveOnboardingStatus.php @@ -39,7 +39,7 @@ public function handle(User $user): array $firstPostCreated = $workspace?->posts()->exists() ?? false; $allComplete = $mcpConnected && $socialConnected && $firstPostCreated; - if ($allComplete && $account?->onboarding_completed_at === null) { + if ($allComplete && $account !== null && $account->onboarding_completed_at === null) { $account->update(['onboarding_completed_at' => now()]); } diff --git a/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php b/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php index 12f591d9..1ba46155 100644 --- a/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php +++ b/tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php @@ -65,6 +65,17 @@ expect($status['mcp_connected'])->toBeFalse(); }); +test('does not resolve a revoked token as MCP connected', function () { + $result = $this->user->createToken('OAuth Session'); + AccessToken::find($result->token->id) + ->forceFill(['workspace_id' => null, 'revoked' => true]) + ->saveQuietly(); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status['mcp_connected'])->toBeFalse(); +}); + test('resolves a social account in the current workspace as connected', function () { SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]); @@ -78,6 +89,18 @@ ]); }); +test('does not resolve a social account in another workspace as connected', function () { + $otherWorkspace = Workspace::factory()->create([ + 'account_id' => $this->user->account_id, + 'user_id' => $this->user->id, + ]); + SocialAccount::factory()->create(['workspace_id' => $otherWorkspace->id]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status['social_connected'])->toBeFalse(); +}); + test('resolves any post in the current workspace as the first post', function () { Post::factory()->failed()->create([ 'workspace_id' => $this->workspace->id, @@ -94,6 +117,21 @@ ]); }); +test('does not resolve a post in another workspace as the first post', function () { + $otherWorkspace = Workspace::factory()->create([ + 'account_id' => $this->user->account_id, + 'user_id' => $this->user->id, + ]); + Post::factory()->create([ + 'workspace_id' => $otherWorkspace->id, + 'user_id' => $this->user->id, + ]); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status['first_post_created'])->toBeFalse(); +}); + test('marks onboarding completed once all three steps are complete', function () { Carbon::setTestNow('2026-07-24 12:00:00'); @@ -143,3 +181,11 @@ expect($status['show_residual'])->toBeFalse(); }); + +test('unsubscribed account does not show the residual checklist', function () { + $this->user->account->subscriptions()->delete(); + + $status = app(ResolveOnboardingStatus::class)->handle($this->user); + + expect($status['show_residual'])->toBeFalse(); +}); From 7655c1fd3abd5a854e971dfec9744b879d2e93f6 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:22:56 -0300 Subject: [PATCH 06/17] Rename pre-subscription ICP flow to Welcome routes. Co-authored-by: Cursor --- .../Controllers/App/BillingController.php | 2 +- .../Controllers/App/OnboardingController.php | 257 -------- .../Controllers/App/WelcomeController.php | 203 ++++++ .../Middleware/App/EnsureAccountReady.php | 2 +- .../StoreWelcomeGoalsRequest.php} | 4 +- .../StoreWelcomePersonaRequest.php} | 4 +- .../StoreWelcomeReferralSourceRequest.php} | 4 +- routes/app.php | 23 +- tests/Feature/BillingControllerTest.php | 4 +- .../Middleware/EnsureAccountReadyTest.php | 4 +- .../Middleware/TrialMiddlewareAccessTest.php | 2 +- .../Onboarding/OnboardingControllerTest.php | 588 ------------------ .../Feature/Welcome/WelcomeControllerTest.php | 279 +++++++++ tests/Feature/WorkspaceBillingTest.php | 6 +- 14 files changed, 512 insertions(+), 870 deletions(-) delete mode 100644 app/Http/Controllers/App/OnboardingController.php create mode 100644 app/Http/Controllers/App/WelcomeController.php rename app/Http/Requests/App/{Onboarding/StoreOnboardingGoalsRequest.php => Welcome/StoreWelcomeGoalsRequest.php} (81%) rename app/Http/Requests/App/{Onboarding/StoreOnboardingRequest.php => Welcome/StoreWelcomePersonaRequest.php} (81%) rename app/Http/Requests/App/{Onboarding/StoreOnboardingReferralSourceRequest.php => Welcome/StoreWelcomeReferralSourceRequest.php} (80%) delete mode 100644 tests/Feature/Onboarding/OnboardingControllerTest.php create mode 100644 tests/Feature/Welcome/WelcomeControllerTest.php diff --git a/app/Http/Controllers/App/BillingController.php b/app/Http/Controllers/App/BillingController.php index e85d36c5..6773d693 100644 --- a/app/Http/Controllers/App/BillingController.php +++ b/app/Http/Controllers/App/BillingController.php @@ -18,7 +18,7 @@ class BillingController extends Controller { public function subscribe(): RedirectResponse { - return redirect()->route('app.onboarding'); + return redirect()->route('app.welcome.persona'); } public function processing(Request $request): Response|RedirectResponse diff --git a/app/Http/Controllers/App/OnboardingController.php b/app/Http/Controllers/App/OnboardingController.php deleted file mode 100644 index a9628a22..00000000 --- a/app/Http/Controllers/App/OnboardingController.php +++ /dev/null @@ -1,257 +0,0 @@ -route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - return Inertia::render('onboarding/Index', [ - 'personas' => array_map(fn (Persona $persona): string => $persona->value, Persona::cases()), - 'selected' => $user->persona?->value, - ]); - } - - public function store(StoreOnboardingRequest $request, PostHogService $postHog): RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - $persona = (string) $request->validated('persona'); - - $user->update(['persona' => $persona]); - - $postHog->identify($user->id, [ - 'persona' => $persona, - ]); - - return redirect()->route('app.onboarding.goals'); - } - - public function goals(Request $request): Response|RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - return Inertia::render('onboarding/Goals', [ - 'goals' => array_map(fn (Goal $goal): string => $goal->value, Goal::cases()), - 'selected' => $user->goals ?? [], - ]); - } - - public function storeGoals(StoreOnboardingGoalsRequest $request, PostHogService $postHog): RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - $goals = array_values($request->validated('goals')); - - $user->update(['goals' => $goals]); - - $postHog->identify($user->id, [ - 'goals' => $goals, - ]); - - return redirect()->route('app.onboarding.referral-source'); - } - - public function referralSource(Request $request): Response|RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - if (! $user->goals) { - return redirect()->route('app.onboarding.goals'); - } - - return Inertia::render('onboarding/ReferralSource', [ - 'sources' => array_map(fn (ReferralSource $source): string => $source->value, ReferralSource::cases()), - 'selected' => $user->referral_source?->value, - ]); - } - - public function storeReferralSource(StoreOnboardingReferralSourceRequest $request, PostHogService $postHog): RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - if (! $user->goals) { - return redirect()->route('app.onboarding.goals'); - } - - $referralSource = (string) $request->validated('referral_source'); - - $user->update(['referral_source' => $referralSource]); - - $postHog->identify($user->id, [ - 'referral_source' => $referralSource, - ]); - - return redirect()->route('app.onboarding.connect'); - } - - public function connect(Request $request): Response|RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - if (! $user->goals) { - return redirect()->route('app.onboarding.goals'); - } - - if (! $user->referral_source) { - return redirect()->route('app.onboarding.referral-source'); - } - - $workspace = $user->currentWorkspace; - - if (! $workspace) { - return redirect()->route('app.workspaces.create'); - } - - $accounts = $workspace->socialAccounts()->orderBy('id')->get(); - - $platforms = collect(SocialPlatform::cases()) - ->filter(fn (SocialPlatform $platform): bool => $platform->isConnectable()) - ->map(fn (SocialPlatform $platform): array => [ - 'value' => $platform->value, - 'label' => $platform->label(), - 'color' => $platform->color(), - 'network' => $platform->network(), - ])->values(); - - $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); - - return Inertia::render('onboarding/Connect', [ - 'platforms' => $platforms, - 'accounts' => SocialAccountResource::collection($accounts)->resolve(), - 'plan' => [ - 'name' => $plan->name, - 'interval' => 'monthly', - ], - ]); - } - - public function checkout(Request $request, StartSubscriptionCheckout $checkout): SymfonyResponse|RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - $account = $user->account; - - if ($account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - $workspace = $user->currentWorkspace; - - if (! $workspace || ! $workspace->socialAccounts()->exists()) { - return redirect()->route('app.onboarding.connect') - ->with('flash.banner', __('onboarding.connect.must_connect')) - ->with('flash.bannerStyle', 'danger'); - } - - $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); - - return $checkout->redirect( - $account, - (string) $plan->stripe_monthly_price_id, - route('app.onboarding.connect'), - ); - } -} diff --git a/app/Http/Controllers/App/WelcomeController.php b/app/Http/Controllers/App/WelcomeController.php new file mode 100644 index 00000000..024bd814 --- /dev/null +++ b/app/Http/Controllers/App/WelcomeController.php @@ -0,0 +1,203 @@ +redirectIfUnavailable($request)) { + return $redirect; + } + + return Inertia::render('welcome/Persona', [ + 'personas' => array_map(fn (Persona $persona): string => $persona->value, Persona::cases()), + 'selected' => $request->user()->persona?->value, + ]); + } + + public function storePersona(StoreWelcomePersonaRequest $request, PostHogService $postHog): RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + $persona = (string) $request->validated('persona'); + + $user->update(['persona' => $persona]); + + $postHog->identify($user->id, [ + 'persona' => $persona, + ]); + + return redirect()->route('app.welcome.goals'); + } + + public function goals(Request $request): Response|RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + return Inertia::render('welcome/Goals', [ + 'goals' => array_map(fn (Goal $goal): string => $goal->value, Goal::cases()), + 'selected' => $user->goals ?? [], + ]); + } + + public function storeGoals(StoreWelcomeGoalsRequest $request, PostHogService $postHog): RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + $goals = array_values($request->validated('goals')); + + $user->update(['goals' => $goals]); + + $postHog->identify($user->id, [ + 'goals' => $goals, + ]); + + return redirect()->route('app.welcome.referral-source'); + } + + public function referralSource(Request $request): Response|RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + if (! $user->goals) { + return redirect()->route('app.welcome.goals'); + } + + return Inertia::render('welcome/ReferralSource', [ + 'sources' => array_map(fn (ReferralSource $source): string => $source->value, ReferralSource::cases()), + 'selected' => $user->referral_source?->value, + ]); + } + + public function storeReferralSource(StoreWelcomeReferralSourceRequest $request, PostHogService $postHog): RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + if (! $user->goals) { + return redirect()->route('app.welcome.goals'); + } + + $referralSource = (string) $request->validated('referral_source'); + + $user->update(['referral_source' => $referralSource]); + + $postHog->identify($user->id, [ + 'referral_source' => $referralSource, + ]); + + return redirect()->route('app.welcome.subscribe'); + } + + public function subscribe(Request $request): Response|RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + if (! $user->goals) { + return redirect()->route('app.welcome.goals'); + } + + if (! $user->referral_source) { + return redirect()->route('app.welcome.referral-source'); + } + + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + + return Inertia::render('welcome/Subscribe', [ + 'plan' => [ + 'name' => $plan->name, + 'interval' => 'monthly', + ], + ]); + } + + public function checkout(Request $request, StartSubscriptionCheckout $checkout): SymfonyResponse|RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + + return $checkout->redirect( + $request->user()->account, + (string) $plan->stripe_monthly_price_id, + route('app.welcome.subscribe'), + ); + } + + private function redirectIfUnavailable(Request $request): ?RedirectResponse + { + if (config('trypost.self_hosted')) { + return redirect()->route('app.calendar'); + } + + if ($request->user()->account?->subscribed(Account::SUBSCRIPTION_NAME)) { + return redirect()->route('app.calendar'); + } + + return null; + } +} diff --git a/app/Http/Middleware/App/EnsureAccountReady.php b/app/Http/Middleware/App/EnsureAccountReady.php index 7e15bfe6..e6904f34 100644 --- a/app/Http/Middleware/App/EnsureAccountReady.php +++ b/app/Http/Middleware/App/EnsureAccountReady.php @@ -31,7 +31,7 @@ public function handle(Request $request, Closure $next): Response ); if (! $hasAccess) { - return redirect()->route('app.onboarding'); + return redirect()->route('app.welcome.persona'); } } diff --git a/app/Http/Requests/App/Onboarding/StoreOnboardingGoalsRequest.php b/app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php similarity index 81% rename from app/Http/Requests/App/Onboarding/StoreOnboardingGoalsRequest.php rename to app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php index e127eab3..987fa2c4 100644 --- a/app/Http/Requests/App/Onboarding/StoreOnboardingGoalsRequest.php +++ b/app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace App\Http\Requests\App\Onboarding; +namespace App\Http\Requests\App\Welcome; use App\Enums\User\Goal; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class StoreOnboardingGoalsRequest extends FormRequest +class StoreWelcomeGoalsRequest extends FormRequest { public function authorize(): bool { diff --git a/app/Http/Requests/App/Onboarding/StoreOnboardingRequest.php b/app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php similarity index 81% rename from app/Http/Requests/App/Onboarding/StoreOnboardingRequest.php rename to app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php index ee0d7428..c22f29fc 100644 --- a/app/Http/Requests/App/Onboarding/StoreOnboardingRequest.php +++ b/app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace App\Http\Requests\App\Onboarding; +namespace App\Http\Requests\App\Welcome; use App\Enums\User\Persona; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class StoreOnboardingRequest extends FormRequest +class StoreWelcomePersonaRequest extends FormRequest { public function authorize(): bool { diff --git a/app/Http/Requests/App/Onboarding/StoreOnboardingReferralSourceRequest.php b/app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php similarity index 80% rename from app/Http/Requests/App/Onboarding/StoreOnboardingReferralSourceRequest.php rename to app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php index 47c9a5ae..960089ec 100644 --- a/app/Http/Requests/App/Onboarding/StoreOnboardingReferralSourceRequest.php +++ b/app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace App\Http\Requests\App\Onboarding; +namespace App\Http\Requests\App\Welcome; use App\Enums\User\ReferralSource; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class StoreOnboardingReferralSourceRequest extends FormRequest +class StoreWelcomeReferralSourceRequest extends FormRequest { public function authorize(): bool { diff --git a/routes/app.php b/routes/app.php index 75969ac0..5892175f 100644 --- a/routes/app.php +++ b/routes/app.php @@ -11,7 +11,6 @@ use App\Http\Controllers\App\GiphyController; use App\Http\Controllers\App\LinkPreviewController; use App\Http\Controllers\App\NotificationController; -use App\Http\Controllers\App\OnboardingController; use App\Http\Controllers\App\PostAiCreateController; use App\Http\Controllers\App\PostAiGenerateController; use App\Http\Controllers\App\PostAiRegenerateMediaController; @@ -27,6 +26,7 @@ use App\Http\Controllers\App\Settings\SettingsController; use App\Http\Controllers\App\Settings\UsageController; use App\Http\Controllers\App\UnsplashController; +use App\Http\Controllers\App\WelcomeController; use App\Http\Controllers\App\WorkspaceController; use App\Http\Controllers\App\WorkspaceInviteController; use App\Http\Controllers\App\WorkspaceLabelController; @@ -57,14 +57,19 @@ })->name('app.home'); Route::get('subscribe', [BillingController::class, 'subscribe'])->name('app.subscribe'); - Route::get('onboarding', [OnboardingController::class, 'index'])->name('app.onboarding'); - Route::post('onboarding', [OnboardingController::class, 'store'])->name('app.onboarding.store'); - Route::get('onboarding/goals', [OnboardingController::class, 'goals'])->name('app.onboarding.goals'); - Route::post('onboarding/goals', [OnboardingController::class, 'storeGoals'])->name('app.onboarding.goals.store'); - Route::get('onboarding/referral-source', [OnboardingController::class, 'referralSource'])->name('app.onboarding.referral-source'); - Route::post('onboarding/referral-source', [OnboardingController::class, 'storeReferralSource'])->name('app.onboarding.referral-source.store'); - Route::get('onboarding/connect', [OnboardingController::class, 'connect'])->name('app.onboarding.connect'); - Route::post('onboarding/connect', [OnboardingController::class, 'checkout'])->name('app.onboarding.checkout'); + Route::get('welcome', fn () => redirect()->route('app.welcome.persona'))->name('app.welcome'); + Route::get('welcome/persona', [WelcomeController::class, 'persona'])->name('app.welcome.persona'); + Route::post('welcome/persona', [WelcomeController::class, 'storePersona'])->name('app.welcome.persona.store'); + Route::get('welcome/goals', [WelcomeController::class, 'goals'])->name('app.welcome.goals'); + Route::post('welcome/goals', [WelcomeController::class, 'storeGoals'])->name('app.welcome.goals.store'); + Route::get('welcome/referral-source', [WelcomeController::class, 'referralSource'])->name('app.welcome.referral-source'); + Route::post('welcome/referral-source', [WelcomeController::class, 'storeReferralSource'])->name('app.welcome.referral-source.store'); + Route::get('welcome/subscribe', [WelcomeController::class, 'subscribe'])->name('app.welcome.subscribe'); + Route::post('welcome/checkout', [WelcomeController::class, 'checkout'])->name('app.welcome.checkout'); + + Route::redirect('onboarding/goals', '/welcome/goals')->name('app.legacy-onboarding.goals'); + Route::redirect('onboarding/referral-source', '/welcome/referral-source')->name('app.legacy-onboarding.referral-source'); + Route::redirect('onboarding/connect', '/welcome/subscribe')->name('app.legacy-onboarding.connect'); Route::get('billing/processing', [BillingController::class, 'processing'])->name('app.billing.processing'); Route::get('workspaces/create', [WorkspaceController::class, 'create'])->name('app.workspaces.create'); diff --git a/tests/Feature/BillingControllerTest.php b/tests/Feature/BillingControllerTest.php index d3cea0ed..c15edd28 100644 --- a/tests/Feature/BillingControllerTest.php +++ b/tests/Feature/BillingControllerTest.php @@ -31,12 +31,12 @@ $response->assertRedirect(route('login')); }); -test('subscribe redirects to onboarding', function () { +test('subscribe redirects to welcome', function () { config(['trypost.self_hosted' => false]); $response = $this->actingAs($this->user)->get(route('app.subscribe')); - $response->assertRedirect(route('app.onboarding')); + $response->assertRedirect(route('app.welcome.persona')); }); test('swapToYearly redirects to calendar in self hosted mode', function () { diff --git a/tests/Feature/Middleware/EnsureAccountReadyTest.php b/tests/Feature/Middleware/EnsureAccountReadyTest.php index d48f22e4..60f12b6e 100644 --- a/tests/Feature/Middleware/EnsureAccountReadyTest.php +++ b/tests/Feature/Middleware/EnsureAccountReadyTest.php @@ -11,13 +11,13 @@ config()->set('trypost.self_hosted', false); }); -test('redirects to onboarding when account has no active subscription', function () { +test('redirects to welcome when account has no active subscription', function () { $account = Account::factory()->create(); $user = User::factory()->create(['account_id' => $account->id]); $this->actingAs($user) ->get(route('app.calendar')) - ->assertRedirect(route('app.onboarding')); + ->assertRedirect(route('app.welcome.persona')); }); test('redirects to workspace create when subscribed but no workspace', function () { diff --git a/tests/Feature/Middleware/TrialMiddlewareAccessTest.php b/tests/Feature/Middleware/TrialMiddlewareAccessTest.php index 5969a0ed..462bd4df 100644 --- a/tests/Feature/Middleware/TrialMiddlewareAccessTest.php +++ b/tests/Feature/Middleware/TrialMiddlewareAccessTest.php @@ -32,7 +32,7 @@ $response = $this->actingAs($user->fresh())->get(route('app.accounts')); - $response->assertRedirect(route('app.onboarding')); + $response->assertRedirect(route('app.welcome.persona')); }); test('user with active subscription can access the app', function () { diff --git a/tests/Feature/Onboarding/OnboardingControllerTest.php b/tests/Feature/Onboarding/OnboardingControllerTest.php deleted file mode 100644 index 92f1a0dc..00000000 --- a/tests/Feature/Onboarding/OnboardingControllerTest.php +++ /dev/null @@ -1,588 +0,0 @@ - false]); - $this->user = User::factory()->create(); -}); - -/** - * Give the acting user a current workspace under their account. - */ -function onboardingWorkspace(User $user): Workspace -{ - $workspace = Workspace::factory()->create([ - 'user_id' => $user->id, - 'account_id' => $user->account_id, - ]); - - $user->update(['current_workspace_id' => $workspace->id]); - - return $workspace; -} - -function subscribeOnboardingAccount(Account $account): void -{ - $account->subscriptions()->create([ - 'type' => Account::SUBSCRIPTION_NAME, - 'stripe_id' => 'sub_'.fake()->uuid(), - 'stripe_status' => 'active', - 'stripe_price' => 'price_123', - ]); -} - -test('onboarding renders the persona selection for an unsubscribed account', function () { - $response = $this->actingAs($this->user)->get(route('app.onboarding')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/Index') - ->has('personas', count(Persona::cases())) - ); -}); - -test('onboarding redirects to calendar in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - - $response = $this->actingAs($this->user)->get(route('app.onboarding')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('onboarding redirects to calendar when already subscribed', function () { - subscribeOnboardingAccount($this->user->account); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('onboarding store rejects an invalid persona', function () { - $response = $this->actingAs($this->user)->post(route('app.onboarding.store'), [ - 'persona' => 'not-a-persona', - ]); - - $response->assertSessionHasErrors('persona'); - expect($this->user->fresh()->persona)->toBeNull(); -}); - -test('onboarding store requires a persona', function () { - $response = $this->actingAs($this->user)->post(route('app.onboarding.store'), []); - - $response->assertSessionHasErrors('persona'); -}); - -test('onboarding store does nothing in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - - $response = $this->actingAs($this->user)->post(route('app.onboarding.store'), [ - 'persona' => Persona::Agency->value, - ]); - - $response->assertRedirect(route('app.calendar')); - expect($this->user->fresh()->persona)->toBeNull(); -}); - -test('onboarding store saves the persona, mirrors to PostHog and advances to the goals step', function () { - config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); - Bus::fake(); - - $response = $this->actingAs($this->user)->post(route('app.onboarding.store'), [ - 'persona' => Persona::Agency->value, - ]); - - $response->assertRedirect(route('app.onboarding.goals')); - expect($this->user->fresh()->persona)->toBe(Persona::Agency); - - Bus::assertDispatched(SendEvent::class); -}); - -test('onboarding store redirects an already-subscribed account to the calendar', function () { - subscribeOnboardingAccount($this->user->account); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.store'), [ - 'persona' => Persona::Agency->value, - ]); - - $response->assertRedirect(route('app.calendar')); - expect($this->user->fresh()->persona)->toBeNull(); -}); - -test('goals renders the goal selection for an account that picked a persona', function () { - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.goals')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/Goals') - ->has('goals', count(Goal::cases())) - ); -}); - -test('goals redirects to the persona step when no persona was chosen', function () { - $response = $this->actingAs($this->user)->get(route('app.onboarding.goals')); - - $response->assertRedirect(route('app.onboarding')); -}); - -test('goals redirects to calendar in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - - $response = $this->actingAs($this->user)->get(route('app.onboarding.goals')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('goals redirects to calendar when already subscribed', function () { - subscribeOnboardingAccount($this->user->account); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.goals')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('goals store requires at least one goal', function () { - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), ['goals' => []]); - - $response->assertSessionHasErrors('goals'); - expect($this->user->fresh()->goals)->toBeNull(); -}); - -test('goals store rejects an invalid goal', function () { - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), [ - 'goals' => ['not-a-goal'], - ]); - - $response->assertSessionHasErrors('goals.0'); -}); - -test('goals store accepts any combination of valid goals', function () { - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), [ - 'goals' => [Goal::JustExploring->value, Goal::SaveTime->value], - ]); - - $response->assertRedirect(route('app.onboarding.referral-source')); - expect($this->user->fresh()->goals)->toBe([Goal::JustExploring->value, Goal::SaveTime->value]); -}); - -test('goals store saves the goals, mirrors to PostHog and advances to the referral-source step', function () { - config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); - Bus::fake(); - - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), [ - 'goals' => [Goal::SaveTime->value, Goal::AiContent->value], - ]); - - $response->assertRedirect(route('app.onboarding.referral-source')); - expect($this->user->fresh()->goals)->toBe([Goal::SaveTime->value, Goal::AiContent->value]); - - Bus::assertDispatched(SendEvent::class); -}); - -test('goals store saves just exploring on its own as a real signal', function () { - config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); - Bus::fake(); - - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), [ - 'goals' => [Goal::JustExploring->value], - ]); - - $response->assertRedirect(route('app.onboarding.referral-source')); - expect($this->user->fresh()->goals)->toBe([Goal::JustExploring->value]); - - Bus::assertDispatched(SendEvent::class); -}); - -test('goals store redirects to the persona step when no persona was chosen', function () { - $response = $this->actingAs($this->user)->post(route('app.onboarding.goals.store'), [ - 'goals' => [Goal::SaveTime->value], - ]); - - $response->assertRedirect(route('app.onboarding')); - expect($this->user->fresh()->goals)->toBeNull(); -}); - -test('goals store does nothing in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), [ - 'goals' => [Goal::SaveTime->value], - ]); - - $response->assertRedirect(route('app.calendar')); - expect($this->user->fresh()->goals)->toBeNull(); -}); - -test('referral source renders the source selection for an account that picked persona and goals', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.referral-source')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/ReferralSource') - ->has('sources', count(ReferralSource::cases())) - ); -}); - -test('referral source redirects to the persona step when no persona was chosen', function () { - $response = $this->actingAs($this->user)->get(route('app.onboarding.referral-source')); - - $response->assertRedirect(route('app.onboarding')); -}); - -test('referral source redirects to the goals step when a persona was chosen but no goals', function () { - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.referral-source')); - - $response->assertRedirect(route('app.onboarding.goals')); -}); - -test('referral source redirects to calendar in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - - $response = $this->actingAs($this->user)->get(route('app.onboarding.referral-source')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('referral source redirects to calendar when already subscribed', function () { - subscribeOnboardingAccount($this->user->account); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.referral-source')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('referral source store requires a source', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), []); - - $response->assertSessionHasErrors('referral_source'); - expect($this->user->fresh()->referral_source)->toBeNull(); -}); - -test('referral source store rejects an invalid source', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => 'not-a-source', - ]); - - $response->assertSessionHasErrors('referral_source'); - expect($this->user->fresh()->referral_source)->toBeNull(); -}); - -test('referral source store saves the source, mirrors to PostHog and advances to the connect step', function () { - config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); - Bus::fake(); - - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => ReferralSource::ProductHunt->value, - ]); - - $response->assertRedirect(route('app.onboarding.connect')); - expect($this->user->fresh()->referral_source)->toBe(ReferralSource::ProductHunt); - - Bus::assertDispatched(SendEvent::class); -}); - -test('referral source store redirects to the persona step when no persona was chosen', function () { - $response = $this->actingAs($this->user)->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => ReferralSource::Google->value, - ]); - - $response->assertRedirect(route('app.onboarding')); - expect($this->user->fresh()->referral_source)->toBeNull(); -}); - -test('referral source store redirects to the goals step when no goals were chosen', function () { - $this->user->update(['persona' => Persona::Agency->value]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => ReferralSource::Google->value, - ]); - - $response->assertRedirect(route('app.onboarding.goals')); - expect($this->user->fresh()->referral_source)->toBeNull(); -}); - -test('referral source store does nothing in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => ReferralSource::Google->value, - ]); - - $response->assertRedirect(route('app.calendar')); - expect($this->user->fresh()->referral_source)->toBeNull(); -}); - -test('referral source store redirects an already-subscribed account to the calendar', function () { - subscribeOnboardingAccount($this->user->account); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => ReferralSource::Google->value, - ]); - - $response->assertRedirect(route('app.calendar')); - expect($this->user->fresh()->referral_source)->toBeNull(); -}); - -test('connect redirects to the referral-source step when persona and goals chosen but no source', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - onboardingWorkspace($this->user); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertRedirect(route('app.onboarding.referral-source')); -}); - -test('connect redirects to the goals step when a persona was chosen but no goals', function () { - $this->user->update(['persona' => Persona::Agency->value]); - onboardingWorkspace($this->user); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertRedirect(route('app.onboarding.goals')); -}); - -test('connect renders the network grid for an unsubscribed account that picked a persona', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value], 'referral_source' => ReferralSource::Google->value]); - onboardingWorkspace($this->user); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/Connect') - ->has('platforms') - ->has('platforms.0.network') - ->has('accounts') - ); -}); - -test('connect passes the workspace plan so the client can fire begin_checkout', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value], 'referral_source' => ReferralSource::Google->value]); - onboardingWorkspace($this->user); - - $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/Connect') - ->where('plan.name', $plan->name) - ->where('plan.interval', 'monthly') - ); -}); - -test('connect offers a single linkedin card and no standalone linkedin page card', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value], 'referral_source' => ReferralSource::Google->value]); - onboardingWorkspace($this->user); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/Connect') - ->where('platforms', fn ($platforms) => collect($platforms)->contains('value', Platform::LinkedIn->value) - && ! collect($platforms)->contains('value', Platform::LinkedInPage->value) - ) - ); -}); - -test('connect lists the workspace social accounts already connected', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value], 'referral_source' => ReferralSource::Google->value]); - $workspace = onboardingWorkspace($this->user); - SocialAccount::factory()->create(['workspace_id' => $workspace->id]); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertOk(); - $response->assertInertia(fn ($page) => $page - ->component('onboarding/Connect') - ->has('accounts', 1) - ); -}); - -test('connect redirects back to the persona step when no persona was chosen', function () { - onboardingWorkspace($this->user); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertRedirect(route('app.onboarding')); -}); - -test('connect redirects to calendar in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - - $response = $this->actingAs($this->user)->get(route('app.onboarding.connect')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('connect redirects to calendar when already subscribed', function () { - subscribeOnboardingAccount($this->user->account); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('checkout blocks and redirects back when no network is connected', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - onboardingWorkspace($this->user); - - $this->mock(StartSubscriptionCheckout::class) - ->shouldReceive('redirect') - ->never(); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.checkout')); - - $response->assertRedirect(route('app.onboarding.connect')); -}); - -test('checkout starts monthly checkout once at least one network is connected', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value]]); - $workspace = onboardingWorkspace($this->user); - SocialAccount::factory()->create(['workspace_id' => $workspace->id]); - - Plan::where('slug', Slug::Workspace)->firstOrFail()->update([ - 'stripe_monthly_price_id' => 'price_monthly_test', - 'stripe_yearly_price_id' => 'price_yearly_test', - ]); - - $this->mock(StartSubscriptionCheckout::class) - ->shouldReceive('redirect') - ->once() - ->withArgs(fn (Account $account, string $priceId, string $cancelUrl): bool => $priceId === 'price_monthly_test') - ->andReturn(redirect()->route('app.calendar')); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.checkout')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('checkout redirects an already-subscribed account to the calendar', function () { - subscribeOnboardingAccount($this->user->account); - - $this->mock(StartSubscriptionCheckout::class) - ->shouldReceive('redirect') - ->never(); - - $response = $this->actingAs($this->user->fresh())->post(route('app.onboarding.checkout')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('checkout does nothing in self-hosted mode', function () { - config(['trypost.self_hosted' => true]); - - $this->mock(StartSubscriptionCheckout::class) - ->shouldReceive('redirect') - ->never(); - - $response = $this->actingAs($this->user)->post(route('app.onboarding.checkout')); - - $response->assertRedirect(route('app.calendar')); -}); - -test('connect redirects to workspace creation when no workspace exists', function () { - $this->user->update(['persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value], 'referral_source' => ReferralSource::Google->value]); - - $response = $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect')); - - $response->assertRedirect(route('app.workspaces.create')); -}); - -test('a user walks the full onboarding flow from the account gate to stripe checkout', function () { - Plan::where('slug', Slug::Workspace)->firstOrFail()->update(['stripe_monthly_price_id' => 'price_monthly_test']); - $workspace = onboardingWorkspace($this->user); - - // The account gate sends an unsubscribed user into onboarding. - $this->actingAs($this->user->fresh())->get(route('app.calendar')) - ->assertRedirect(route('app.onboarding')); - - // Persona advances to goals. - $this->actingAs($this->user->fresh())->post(route('app.onboarding.store'), ['persona' => Persona::Creator->value]) - ->assertRedirect(route('app.onboarding.goals')); - - // Goals advances to the referral-source step. - $this->actingAs($this->user->fresh())->post(route('app.onboarding.goals.store'), [ - 'goals' => [Goal::AiContent->value, Goal::SaveTime->value], - ])->assertRedirect(route('app.onboarding.referral-source')); - - // Referral source advances to connect. - $this->actingAs($this->user->fresh())->post(route('app.onboarding.referral-source.store'), [ - 'referral_source' => ReferralSource::Friend->value, - ])->assertRedirect(route('app.onboarding.connect')); - - // Connect renders now that persona, goals, a source and a workspace are present. - $this->actingAs($this->user->fresh())->get(route('app.onboarding.connect'))->assertOk(); - - // Checkout blocks until a network is connected. - $this->actingAs($this->user->fresh())->post(route('app.onboarding.checkout')) - ->assertRedirect(route('app.onboarding.connect')); - - // Once a network is connected, checkout hands off to Stripe. - SocialAccount::factory()->create(['workspace_id' => $workspace->id]); - - $this->mock(StartSubscriptionCheckout::class) - ->shouldReceive('redirect') - ->once() - ->andReturn(redirect('https://checkout.stripe.test/session')); - - $this->actingAs($this->user->fresh())->post(route('app.onboarding.checkout')) - ->assertRedirect('https://checkout.stripe.test/session'); - - expect($this->user->fresh()->persona)->toBe(Persona::Creator); - expect($this->user->fresh()->goals)->toBe([Goal::AiContent->value, Goal::SaveTime->value]); - expect($this->user->fresh()->referral_source)->toBe(ReferralSource::Friend); -}); - -test('a self-hosted user never enters the onboarding flow', function () { - config(['trypost.self_hosted' => true]); - onboardingWorkspace($this->user); - - // The app is reachable directly, with no subscription. - $this->actingAs($this->user->fresh())->get(route('app.calendar'))->assertOk(); - - // Every onboarding step just bounces to the calendar. - foreach (['app.onboarding', 'app.onboarding.goals', 'app.onboarding.referral-source', 'app.onboarding.connect'] as $routeName) { - $this->actingAs($this->user->fresh())->get(route($routeName))->assertRedirect(route('app.calendar')); - } -}); diff --git a/tests/Feature/Welcome/WelcomeControllerTest.php b/tests/Feature/Welcome/WelcomeControllerTest.php new file mode 100644 index 00000000..d12307d8 --- /dev/null +++ b/tests/Feature/Welcome/WelcomeControllerTest.php @@ -0,0 +1,279 @@ + false]); + $this->user = User::factory()->create(); +}); + +function subscribeWelcomeAccount(Account $account): void +{ + $account->subscriptions()->create([ + 'type' => Account::SUBSCRIPTION_NAME, + 'stripe_id' => 'sub_'.fake()->uuid(), + 'stripe_status' => 'active', + 'stripe_price' => 'price_123', + ]); +} + +test('welcome redirects to the persona step', function () { + $this->actingAs($this->user) + ->get(route('app.welcome')) + ->assertRedirect(route('app.welcome.persona')); +}); + +test('persona renders for an unsubscribed account', function () { + $this->actingAs($this->user) + ->get(route('app.welcome.persona')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('welcome/Persona', false) + ->has('personas', count(Persona::cases())) + ); +}); + +test('persona requires a valid selection', function (array $payload) { + $this->actingAs($this->user) + ->post(route('app.welcome.persona.store'), $payload) + ->assertSessionHasErrors('persona'); + + expect($this->user->fresh()->persona)->toBeNull(); +})->with([ + 'missing' => [[]], + 'invalid' => [['persona' => 'not-a-persona']], +]); + +test('persona store saves the selection mirrors it to PostHog and advances to goals', function () { + config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); + Bus::fake(); + + $this->actingAs($this->user) + ->post(route('app.welcome.persona.store'), ['persona' => Persona::Agency->value]) + ->assertRedirect(route('app.welcome.goals')); + + expect($this->user->fresh()->persona)->toBe(Persona::Agency); + Bus::assertDispatched(SendEvent::class); +}); + +test('goals redirects to persona until a persona is selected', function () { + $this->actingAs($this->user) + ->get(route('app.welcome.goals')) + ->assertRedirect(route('app.welcome.persona')); +}); + +test('goals renders after a persona is selected', function () { + $this->user->update(['persona' => Persona::Agency->value]); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.goals')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('welcome/Goals', false) + ->has('goals', count(Goal::cases())) + ); +}); + +test('goals requires at least one valid goal', function (array $goals, string $error) { + $this->user->update(['persona' => Persona::Agency->value]); + + $this->actingAs($this->user->fresh()) + ->post(route('app.welcome.goals.store'), ['goals' => $goals]) + ->assertSessionHasErrors($error); + + expect($this->user->fresh()->goals)->toBeNull(); +})->with([ + 'empty' => [[], 'goals'], + 'invalid' => [['not-a-goal'], 'goals.0'], +]); + +test('goals store saves choices mirrors them to PostHog and advances to referral source', function () { + config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); + Bus::fake(); + $this->user->update(['persona' => Persona::Creator->value]); + + $goals = [Goal::AiContent->value, Goal::SaveTime->value]; + + $this->actingAs($this->user->fresh()) + ->post(route('app.welcome.goals.store'), ['goals' => $goals]) + ->assertRedirect(route('app.welcome.referral-source')); + + expect($this->user->fresh()->goals)->toBe($goals); + Bus::assertDispatched(SendEvent::class); +}); + +test('referral source redirects through incomplete prior steps', function (array $attributes, string $routeName) { + $this->user->update($attributes); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.referral-source')) + ->assertRedirect(route($routeName)); +})->with([ + 'missing persona' => [[], 'app.welcome.persona'], + 'missing goals' => [['persona' => Persona::Agency->value], 'app.welcome.goals'], +]); + +test('referral source renders after prior steps are complete', function () { + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.referral-source')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('welcome/ReferralSource', false) + ->has('sources', count(ReferralSource::cases())) + ); +}); + +test('referral source requires a valid selection', function (array $payload) { + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + + $this->actingAs($this->user->fresh()) + ->post(route('app.welcome.referral-source.store'), $payload) + ->assertSessionHasErrors('referral_source'); + + expect($this->user->fresh()->referral_source)->toBeNull(); +})->with([ + 'missing' => [[]], + 'invalid' => [['referral_source' => 'not-a-source']], +]); + +test('referral source store saves the source mirrors it to PostHog and advances to subscribe', function () { + config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); + Bus::fake(); + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + + $this->actingAs($this->user->fresh()) + ->post(route('app.welcome.referral-source.store'), [ + 'referral_source' => ReferralSource::ProductHunt->value, + ]) + ->assertRedirect(route('app.welcome.subscribe')); + + expect($this->user->fresh()->referral_source)->toBe(ReferralSource::ProductHunt); + Bus::assertDispatched(SendEvent::class); +}); + +test('subscribe redirects through incomplete welcome steps', function (array $attributes, string $routeName) { + $this->user->update($attributes); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.subscribe')) + ->assertRedirect(route($routeName)); +})->with([ + 'missing persona' => [[], 'app.welcome.persona'], + 'missing goals' => [['persona' => Persona::Agency->value], 'app.welcome.goals'], + 'missing referral source' => [[ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ], 'app.welcome.referral-source'], +]); + +test('subscribe renders plan props without social account props', function () { + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + 'referral_source' => ReferralSource::Google->value, + ]); + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.subscribe')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('welcome/Subscribe', false) + ->where('plan.name', $plan->name) + ->where('plan.interval', 'monthly') + ->missing('platforms') + ->missing('accounts') + ); +}); + +test('checkout starts without a connected social account and uses subscribe as its cancel url', function () { + Plan::where('slug', Slug::Workspace)->firstOrFail()->update([ + 'stripe_monthly_price_id' => 'price_monthly_test', + ]); + + $this->mock(StartSubscriptionCheckout::class) + ->shouldReceive('redirect') + ->once() + ->withArgs(fn (Account $account, string $priceId, string $cancelUrl): bool => $account->is($this->user->account) + && $priceId === 'price_monthly_test' + && $cancelUrl === route('app.welcome.subscribe')) + ->andReturn(redirect('https://checkout.stripe.test/session')); + + $this->actingAs($this->user) + ->post(route('app.welcome.checkout')) + ->assertRedirect('https://checkout.stripe.test/session'); +}); + +test('welcome steps redirect to calendar for subscribed accounts', function (string $routeName, string $method, array $payload = []) { + subscribeWelcomeAccount($this->user->account); + + $this->actingAs($this->user->fresh()); + + $response = $method === 'get' + ? $this->get(route($routeName)) + : $this->post(route($routeName), $payload); + + $response->assertRedirect(route('app.calendar')); +})->with([ + 'persona' => ['app.welcome.persona', 'get'], + 'persona store' => ['app.welcome.persona.store', 'post', ['persona' => Persona::Agency->value]], + 'goals' => ['app.welcome.goals', 'get'], + 'goals store' => ['app.welcome.goals.store', 'post', ['goals' => [Goal::SaveTime->value]]], + 'referral source' => ['app.welcome.referral-source', 'get'], + 'referral source store' => ['app.welcome.referral-source.store', 'post', ['referral_source' => ReferralSource::Google->value]], + 'subscribe' => ['app.welcome.subscribe', 'get'], + 'checkout' => ['app.welcome.checkout', 'post'], +]); + +test('welcome steps redirect to calendar in self hosted mode', function (string $routeName, string $method, array $payload = []) { + config(['trypost.self_hosted' => true]); + + $this->actingAs($this->user); + + $response = $method === 'get' + ? $this->get(route($routeName)) + : $this->post(route($routeName), $payload); + + $response->assertRedirect(route('app.calendar')); +})->with([ + 'persona' => ['app.welcome.persona', 'get'], + 'persona store' => ['app.welcome.persona.store', 'post', ['persona' => Persona::Agency->value]], + 'goals' => ['app.welcome.goals', 'get'], + 'goals store' => ['app.welcome.goals.store', 'post', ['goals' => [Goal::SaveTime->value]]], + 'referral source' => ['app.welcome.referral-source', 'get'], + 'referral source store' => ['app.welcome.referral-source.store', 'post', ['referral_source' => ReferralSource::Google->value]], + 'subscribe' => ['app.welcome.subscribe', 'get'], + 'checkout' => ['app.welcome.checkout', 'post'], +]); + +test('legacy welcome URLs redirect to their replacement routes', function (string $legacyRoute, string $routeName) { + $this->actingAs($this->user) + ->get(route($legacyRoute)) + ->assertRedirect(route($routeName)); +})->with([ + 'goals' => ['app.legacy-onboarding.goals', 'app.welcome.goals'], + 'referral source' => ['app.legacy-onboarding.referral-source', 'app.welcome.referral-source'], + 'connect' => ['app.legacy-onboarding.connect', 'app.welcome.subscribe'], +]); diff --git a/tests/Feature/WorkspaceBillingTest.php b/tests/Feature/WorkspaceBillingTest.php index 5ca5ab89..395e0f43 100644 --- a/tests/Feature/WorkspaceBillingTest.php +++ b/tests/Feature/WorkspaceBillingTest.php @@ -57,7 +57,7 @@ $response = $this->actingAs($this->user)->get(route('app.calendar')); - $response->assertRedirect(route('app.onboarding')); + $response->assertRedirect(route('app.welcome.persona')); }); test('billing page is accessible by account owner', function () { @@ -96,12 +96,12 @@ $response->assertForbidden(); }); -test('subscribe redirects to onboarding', function () { +test('subscribe redirects to welcome', function () { config(['trypost.self_hosted' => false]); $response = $this->actingAs($this->user)->get(route('app.subscribe')); - $response->assertRedirect(route('app.onboarding')); + $response->assertRedirect(route('app.welcome.persona')); }); test('stripe email returns account owner email', function () { From 778620476363358437c0f97994befdb0c8bf6feb Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:25:30 -0300 Subject: [PATCH 07/17] Fix Task 3 review: rename SocialController welcome test label. Co-authored-by: Cursor --- tests/Feature/SocialControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/SocialControllerTest.php b/tests/Feature/SocialControllerTest.php index 12b960f0..c5b49e8f 100644 --- a/tests/Feature/SocialControllerTest.php +++ b/tests/Feature/SocialControllerTest.php @@ -96,7 +96,7 @@ ); }); -test('an unsubscribed account can disconnect during onboarding (no active subscription required)', function () { +test('an unsubscribed account can disconnect during welcome (no active subscription required)', function () { config(['trypost.self_hosted' => false]); $account = SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]); From 982771410592a1eabf0488076dfaea11cc6540c7 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:30:42 -0300 Subject: [PATCH 08/17] Add Welcome Vue pages and i18n; drop social from subscribe. Co-authored-by: Cursor --- lang/ar/welcome.php | 56 +++++++ lang/de/welcome.php | 56 +++++++ lang/el/welcome.php | 56 +++++++ lang/en/welcome.php | 56 +++++++ lang/es/welcome.php | 56 +++++++ lang/fr/welcome.php | 56 +++++++ lang/it/welcome.php | 56 +++++++ lang/ja/welcome.php | 56 +++++++ lang/ko/welcome.php | 56 +++++++ lang/nl/welcome.php | 56 +++++++ lang/pl/welcome.php | 56 +++++++ lang/pt-BR/welcome.php | 56 +++++++ lang/ru/welcome.php | 56 +++++++ lang/tr/welcome.php | 56 +++++++ lang/zh/welcome.php | 56 +++++++ resources/js/layouts/OnboardingLayout.vue | 45 ------ resources/js/layouts/WelcomeLayout.vue | 68 ++++++++ resources/js/pages/onboarding/Connect.vue | 83 ---------- resources/js/pages/onboarding/Goals.vue | 147 ------------------ resources/js/pages/onboarding/Index.vue | 127 --------------- .../js/pages/onboarding/ReferralSource.vue | 135 ---------------- resources/js/pages/welcome/Goals.vue | 144 +++++++++++++++++ resources/js/pages/welcome/Persona.vue | 126 +++++++++++++++ resources/js/pages/welcome/ReferralSource.vue | 134 ++++++++++++++++ resources/js/pages/welcome/Subscribe.vue | 53 +++++++ 25 files changed, 1365 insertions(+), 537 deletions(-) create mode 100644 lang/ar/welcome.php create mode 100644 lang/de/welcome.php create mode 100644 lang/el/welcome.php create mode 100644 lang/en/welcome.php create mode 100644 lang/es/welcome.php create mode 100644 lang/fr/welcome.php create mode 100644 lang/it/welcome.php create mode 100644 lang/ja/welcome.php create mode 100644 lang/ko/welcome.php create mode 100644 lang/nl/welcome.php create mode 100644 lang/pl/welcome.php create mode 100644 lang/pt-BR/welcome.php create mode 100644 lang/ru/welcome.php create mode 100644 lang/tr/welcome.php create mode 100644 lang/zh/welcome.php delete mode 100644 resources/js/layouts/OnboardingLayout.vue create mode 100644 resources/js/layouts/WelcomeLayout.vue delete mode 100644 resources/js/pages/onboarding/Connect.vue delete mode 100644 resources/js/pages/onboarding/Goals.vue delete mode 100644 resources/js/pages/onboarding/Index.vue delete mode 100644 resources/js/pages/onboarding/ReferralSource.vue create mode 100644 resources/js/pages/welcome/Goals.vue create mode 100644 resources/js/pages/welcome/Persona.vue create mode 100644 resources/js/pages/welcome/ReferralSource.vue create mode 100644 resources/js/pages/welcome/Subscribe.vue diff --git a/lang/ar/welcome.php b/lang/ar/welcome.php new file mode 100644 index 00000000..32d28b39 --- /dev/null +++ b/lang/ar/welcome.php @@ -0,0 +1,56 @@ + 'مرحبًا بك في TryPost', + 'description' => 'أخبرنا بما يصفك أنت أو نشاطك التجاري على أفضل وجه حتى نتمكن من تخصيص تجربتك.', + 'continue' => 'متابعة', + 'personas' => [ + 'creator' => 'صانع محتوى', + 'freelancer' => 'مستقل', + 'developer' => 'مطوّر', + 'startup' => 'شركة ناشئة', + 'agency' => 'وكالة', + 'small_business' => 'نشاط تجاري صغير', + 'marketer' => 'مسوّق', + 'online_store' => 'متجر إلكتروني', + 'other' => 'أخرى', + ], + 'goals_title' => 'ما هدفك من استخدام TryPost؟', + 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', + 'goals' => [ + 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', + 'ai_content' => 'إنشاء منشورات أسرع بالذكاء الاصطناعي', + 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', + 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', + 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', + 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', + 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', + 'team_collaboration' => 'العمل مع فريقي', + 'automate_api' => 'أتمتة النشر عبر الواجهة البرمجية أو MCP أو الكود', + 'track_performance' => 'معرفة أداء منشوراتي', + 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', + 'other' => 'شيء آخر', + ], + 'referral_source_title' => 'كيف وجدتنا؟', + 'referral_source_description' => 'يساعدنا هذا على فهم كيفية اكتشاف الأشخاص لـ TryPost.', + 'referral_source' => [ + 'google' => 'Google أو البحث', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram أو Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'مساعد ذكاء اصطناعي (ChatGPT، Claude…)', + 'friend' => 'صديق أو زميل', + 'blog' => 'مدونة أو نشرة إخبارية أو مقال', + 'other' => 'شيء آخر', + ], + 'subscribe' => [ + 'title' => 'اربط شبكتك الأولى', + 'description' => 'اربط حسابًا اجتماعيًا واحدًا على الأقل لبدء الجدولة. يمكنك إضافة المزيد في أي وقت.', + ], +]; diff --git a/lang/de/welcome.php b/lang/de/welcome.php new file mode 100644 index 00000000..6a6deafa --- /dev/null +++ b/lang/de/welcome.php @@ -0,0 +1,56 @@ + 'Willkommen bei TryPost', + 'description' => 'Sag uns, was dich oder dein Unternehmen am besten beschreibt, damit wir dein Erlebnis anpassen können.', + 'continue' => 'Weiter', + 'personas' => [ + 'creator' => 'Content Creator', + 'freelancer' => 'Freelancer', + 'developer' => 'Entwickler', + 'startup' => 'Startup', + 'agency' => 'Agentur', + 'small_business' => 'Kleinunternehmen', + 'marketer' => 'Marketer', + 'online_store' => 'Onlineshop', + 'other' => 'Sonstiges', + ], + 'goals_title' => 'Was ist dein Ziel mit TryPost?', + 'goals_description' => 'Wähle alles aus, was passt, und wir richten TryPost für dich ein.', + 'goals' => [ + 'save_time' => 'Zeit sparen, indem ich überall gleichzeitig poste', + 'ai_content' => 'Beiträge schneller mit KI erstellen', + 'plan_calendar' => 'Meine Beiträge in einem Kalender planen', + 'stay_on_brand' => 'Jeden Beitrag markenkonform halten', + 'grow_audience' => 'Meine Reichweite und mein Engagement steigern', + 'drive_sales' => 'Mehr Traffic und Verkäufe erzielen', + 'manage_clients' => 'Mehrere Marken oder Kunden verwalten', + 'team_collaboration' => 'Mit meinem Team arbeiten', + 'automate_api' => 'Das Posten per API, MCP oder Code automatisieren', + 'track_performance' => 'Sehen, wie meine Beiträge performen', + 'just_exploring' => 'Ich schaue mich vorerst nur um', + 'other' => 'Etwas anderes', + ], + 'referral_source_title' => 'Wie hast du uns gefunden?', + 'referral_source_description' => 'Das hilft uns zu verstehen, wie Menschen TryPost entdecken.', + 'referral_source' => [ + 'google' => 'Google oder Suche', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram oder Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'KI-Assistent (ChatGPT, Claude…)', + 'friend' => 'Freund oder Kollege', + 'blog' => 'Blog, Newsletter oder Artikel', + 'other' => 'Etwas anderes', + ], + 'subscribe' => [ + 'title' => 'Verbinde dein erstes Netzwerk', + 'description' => 'Verknüpfe mindestens ein Social-Media-Konto, um mit der Planung zu beginnen. Du kannst jederzeit weitere hinzufügen.', + ], +]; diff --git a/lang/el/welcome.php b/lang/el/welcome.php new file mode 100644 index 00000000..5a8cdf7e --- /dev/null +++ b/lang/el/welcome.php @@ -0,0 +1,56 @@ + 'Καλώς ήρθατε στο TryPost', + 'description' => 'Πείτε μας τι σας περιγράφει καλύτερα, εσάς ή την επιχείρησή σας, ώστε να προσαρμόσουμε την εμπειρία σας.', + 'continue' => 'Συνέχεια', + 'personas' => [ + 'creator' => 'Δημιουργός περιεχομένου', + 'freelancer' => 'Ελεύθερος επαγγελματίας', + 'developer' => 'Προγραμματιστής', + 'startup' => 'Startup', + 'agency' => 'Πρακτορείο', + 'small_business' => 'Μικρή επιχείρηση', + 'marketer' => 'Marketer', + 'online_store' => 'Ηλεκτρονικό κατάστημα', + 'other' => 'Άλλο', + ], + 'goals_title' => 'Ποιος είναι ο στόχος σας με το TryPost;', + 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', + 'goals' => [ + 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', + 'ai_content' => 'Δημιουργία δημοσιεύσεων ταχύτερα με AI', + 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', + 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', + 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', + 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', + 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', + 'team_collaboration' => 'Συνεργασία με την ομάδα μου', + 'automate_api' => 'Αυτοματοποίηση δημοσιεύσεων με το API, το MCP ή κώδικα', + 'track_performance' => 'Παρακολούθηση της απόδοσης των δημοσιεύσεών μου', + 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', + 'other' => 'Κάτι άλλο', + ], + 'referral_source_title' => 'Πώς μας βρήκατε;', + 'referral_source_description' => 'Αυτό μας βοηθά να καταλάβουμε πώς οι άνθρωποι ανακαλύπτουν το TryPost.', + 'referral_source' => [ + 'google' => 'Google ή αναζήτηση', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram ή Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Βοηθός AI (ChatGPT, Claude…)', + 'friend' => 'Φίλος ή συνάδελφος', + 'blog' => 'Blog, newsletter ή άρθρο', + 'other' => 'Κάτι άλλο', + ], + 'subscribe' => [ + 'title' => 'Συνδέστε το πρώτο σας δίκτυο', + 'description' => 'Συνδέστε τουλάχιστον έναν λογαριασμό κοινωνικού δικτύου για να ξεκινήσετε τον προγραμματισμό. Μπορείτε να προσθέσετε κι άλλους ανά πάσα στιγμή.', + ], +]; diff --git a/lang/en/welcome.php b/lang/en/welcome.php new file mode 100644 index 00000000..f2bfb7b7 --- /dev/null +++ b/lang/en/welcome.php @@ -0,0 +1,56 @@ + 'Welcome to TryPost', + 'description' => 'Tell us what best describes you or your business so we can tailor your experience.', + 'continue' => 'Continue', + 'personas' => [ + 'creator' => 'Content creator', + 'freelancer' => 'Freelancer', + 'developer' => 'Developer', + 'startup' => 'Startup', + 'agency' => 'Agency', + 'small_business' => 'Small business', + 'marketer' => 'Marketer', + 'online_store' => 'Online store', + 'other' => 'Other', + ], + 'goals_title' => 'What\'s your goal with TryPost?', + 'goals_description' => 'Pick everything that fits and we\'ll set TryPost up for you.', + 'goals' => [ + 'save_time' => 'Save time by posting everywhere at once', + 'ai_content' => 'Create posts faster with AI', + 'plan_calendar' => 'Plan my posts on a calendar', + 'stay_on_brand' => 'Keep every post on brand', + 'grow_audience' => 'Grow my audience and engagement', + 'drive_sales' => 'Get more traffic and sales', + 'manage_clients' => 'Manage several brands or clients', + 'team_collaboration' => 'Work with my team', + 'automate_api' => 'Automate posting with the API, MCP or code', + 'track_performance' => 'See how my posts perform', + 'just_exploring' => 'Just exploring for now', + 'other' => 'Something else', + ], + 'referral_source_title' => 'How did you find us?', + 'referral_source_description' => 'This helps us understand how people discover TryPost.', + 'referral_source' => [ + 'google' => 'Google or search', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram or Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI assistant (ChatGPT, Claude…)', + 'friend' => 'Friend or colleague', + 'blog' => 'Blog, newsletter or article', + 'other' => 'Something else', + ], + 'subscribe' => [ + 'title' => 'Connect your first network', + 'description' => 'Link at least one social account to start scheduling. You can add more anytime.', + ], +]; diff --git a/lang/es/welcome.php b/lang/es/welcome.php new file mode 100644 index 00000000..7558dc24 --- /dev/null +++ b/lang/es/welcome.php @@ -0,0 +1,56 @@ + 'Bienvenido a TryPost', + 'description' => 'Cuéntanos qué te describe mejor a ti o a tu negocio para personalizar tu experiencia.', + 'continue' => 'Continuar', + 'personas' => [ + 'creator' => 'Creador de contenido', + 'freelancer' => 'Freelancer', + 'developer' => 'Desarrollador', + 'startup' => 'Startup', + 'agency' => 'Agencia', + 'small_business' => 'Pequeña empresa', + 'marketer' => 'Profesional de marketing', + 'online_store' => 'Tienda online', + 'other' => 'Otro', + ], + 'goals_title' => '¿Cuál es tu objetivo con TryPost?', + 'goals_description' => 'Marca todo lo que encaje y adaptamos TryPost a ti.', + 'goals' => [ + 'save_time' => 'Ahorrar tiempo publicando en todas mis redes a la vez', + 'ai_content' => 'Crear publicaciones más rápido con IA', + 'plan_calendar' => 'Planificar mis publicaciones en un calendario', + 'stay_on_brand' => 'Mantener la coherencia de mi marca', + 'grow_audience' => 'Hacer crecer mi audiencia y engagement', + 'drive_sales' => 'Conseguir más tráfico y ventas', + 'manage_clients' => 'Gestionar varias marcas o clientes', + 'team_collaboration' => 'Trabajar con mi equipo', + 'automate_api' => 'Automatizar publicaciones con la API, MCP o código', + 'track_performance' => 'Ver cómo rinden mis publicaciones', + 'just_exploring' => 'Solo estoy explorando por ahora', + 'other' => 'Otra cosa', + ], + 'referral_source_title' => '¿Cómo nos encontraste?', + 'referral_source_description' => 'Esto nos ayuda a entender cómo la gente descubre TryPost.', + 'referral_source' => [ + 'google' => 'Google o búsqueda', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram o Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Asistente de IA (ChatGPT, Claude…)', + 'friend' => 'Amigo o colega', + 'blog' => 'Blog, newsletter o artículo', + 'other' => 'Otra cosa', + ], + 'subscribe' => [ + 'title' => 'Conecta tu primera red', + 'description' => 'Vincula al menos una cuenta social para empezar a programar. Puedes añadir más cuando quieras.', + ], +]; diff --git a/lang/fr/welcome.php b/lang/fr/welcome.php new file mode 100644 index 00000000..70a5b5af --- /dev/null +++ b/lang/fr/welcome.php @@ -0,0 +1,56 @@ + 'Bienvenue sur TryPost', + 'description' => 'Dites-nous ce qui vous décrit le mieux, vous ou votre entreprise, afin que nous puissions personnaliser votre expérience.', + 'continue' => 'Continuer', + 'personas' => [ + 'creator' => 'Créateur de contenu', + 'freelancer' => 'Freelance', + 'developer' => 'Développeur', + 'startup' => 'Startup', + 'agency' => 'Agence', + 'small_business' => 'Petite entreprise', + 'marketer' => 'Marketeur', + 'online_store' => 'Boutique en ligne', + 'other' => 'Autre', + ], + 'goals_title' => 'Quel est votre objectif avec TryPost ?', + 'goals_description' => 'Choisissez tout ce qui vous correspond et nous configurerons TryPost pour vous.', + 'goals' => [ + 'save_time' => 'Gagner du temps en publiant partout à la fois', + 'ai_content' => 'Créer des publications plus vite avec l\'IA', + 'plan_calendar' => 'Planifier mes publications sur un calendrier', + 'stay_on_brand' => 'Garder chaque publication fidèle à ma marque', + 'grow_audience' => 'Développer mon audience et mon engagement', + 'drive_sales' => 'Obtenir plus de trafic et de ventes', + 'manage_clients' => 'Gérer plusieurs marques ou clients', + 'team_collaboration' => 'Travailler avec mon équipe', + 'automate_api' => 'Automatiser la publication avec l\'API, le MCP ou du code', + 'track_performance' => 'Voir les performances de mes publications', + 'just_exploring' => 'Je découvre pour l\'instant', + 'other' => 'Autre chose', + ], + 'referral_source_title' => 'Comment nous avez-vous connus ?', + 'referral_source_description' => 'Cela nous aide à comprendre comment les gens découvrent TryPost.', + 'referral_source' => [ + 'google' => 'Google ou recherche', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram ou Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Assistant IA (ChatGPT, Claude…)', + 'friend' => 'Ami ou collègue', + 'blog' => 'Blog, newsletter ou article', + 'other' => 'Autre chose', + ], + 'subscribe' => [ + 'title' => 'Connectez votre premier réseau', + 'description' => 'Associez au moins un compte social pour commencer à programmer. Vous pourrez en ajouter d\'autres à tout moment.', + ], +]; diff --git a/lang/it/welcome.php b/lang/it/welcome.php new file mode 100644 index 00000000..b61b08f5 --- /dev/null +++ b/lang/it/welcome.php @@ -0,0 +1,56 @@ + 'Benvenuto su TryPost', + 'description' => 'Dicci cosa descrive meglio te o la tua attività così possiamo personalizzare la tua esperienza.', + 'continue' => 'Continua', + 'personas' => [ + 'creator' => 'Creatore di contenuti', + 'freelancer' => 'Freelance', + 'developer' => 'Sviluppatore', + 'startup' => 'Startup', + 'agency' => 'Agenzia', + 'small_business' => 'Piccola impresa', + 'marketer' => 'Marketer', + 'online_store' => 'Negozio online', + 'other' => 'Altro', + ], + 'goals_title' => 'Qual è il tuo obiettivo con TryPost?', + 'goals_description' => 'Scegli tutto ciò che fa per te e configureremo TryPost per te.', + 'goals' => [ + 'save_time' => 'Risparmiare tempo pubblicando ovunque in una volta', + 'ai_content' => 'Creare post più velocemente con l\'IA', + 'plan_calendar' => 'Pianificare i miei post su un calendario', + 'stay_on_brand' => 'Mantenere ogni post in linea con il brand', + 'grow_audience' => 'Far crescere il mio pubblico e il coinvolgimento', + 'drive_sales' => 'Ottenere più traffico e vendite', + 'manage_clients' => 'Gestire più brand o clienti', + 'team_collaboration' => 'Lavorare con il mio team', + 'automate_api' => 'Automatizzare la pubblicazione con API, MCP o codice', + 'track_performance' => 'Vedere come vanno i miei post', + 'just_exploring' => 'Sto solo dando un\'occhiata', + 'other' => 'Qualcos\'altro', + ], + 'referral_source_title' => 'Come ci hai trovato?', + 'referral_source_description' => 'Questo ci aiuta a capire come le persone scoprono TryPost.', + 'referral_source' => [ + 'google' => 'Google o ricerca', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram o Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Assistente IA (ChatGPT, Claude…)', + 'friend' => 'Amico o collega', + 'blog' => 'Blog, newsletter o articolo', + 'other' => 'Qualcos\'altro', + ], + 'subscribe' => [ + 'title' => 'Collega la tua prima rete', + 'description' => 'Collega almeno un account social per iniziare a programmare. Puoi aggiungerne altri in qualsiasi momento.', + ], +]; diff --git a/lang/ja/welcome.php b/lang/ja/welcome.php new file mode 100644 index 00000000..461423a7 --- /dev/null +++ b/lang/ja/welcome.php @@ -0,0 +1,56 @@ + 'TryPost へようこそ', + 'description' => 'あなたやあなたのビジネスに最も当てはまるものを教えてください。体験を最適化します。', + 'continue' => '続ける', + 'personas' => [ + 'creator' => 'コンテンツクリエイター', + 'freelancer' => 'フリーランス', + 'developer' => '開発者', + 'startup' => 'スタートアップ', + 'agency' => '代理店', + 'small_business' => '中小企業', + 'marketer' => 'マーケター', + 'online_store' => 'オンラインストア', + 'other' => 'その他', + ], + 'goals_title' => 'TryPost での目標は何ですか?', + 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', + 'goals' => [ + 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', + 'ai_content' => 'AI でより速く投稿を作成する', + 'plan_calendar' => 'カレンダーで投稿を計画する', + 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', + 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', + 'drive_sales' => 'トラフィックと売上を増やす', + 'manage_clients' => '複数のブランドやクライアントを管理する', + 'team_collaboration' => 'チームで作業する', + 'automate_api' => 'API、MCP、コードで投稿を自動化する', + 'track_performance' => '投稿のパフォーマンスを確認する', + 'just_exploring' => '今はまだ様子を見ている', + 'other' => 'その他', + ], + 'referral_source_title' => 'どこで私たちを知りましたか?', + 'referral_source_description' => 'これは、人々がどのように TryPost を見つけるかを理解するのに役立ちます。', + 'referral_source' => [ + 'google' => 'Google または検索', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram または Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI アシスタント(ChatGPT、Claude など)', + 'friend' => '友人または同僚', + 'blog' => 'ブログ、ニュースレター、記事', + 'other' => 'その他', + ], + 'subscribe' => [ + 'title' => '最初のネットワークを接続', + 'description' => 'スケジュールを始めるには、少なくとも 1 つのソーシャルアカウントを連携してください。後からいつでも追加できます。', + ], +]; diff --git a/lang/ko/welcome.php b/lang/ko/welcome.php new file mode 100644 index 00000000..144fd114 --- /dev/null +++ b/lang/ko/welcome.php @@ -0,0 +1,56 @@ + 'TryPost에 오신 것을 환영합니다', + 'description' => '회원님이나 비즈니스를 가장 잘 설명하는 항목을 알려주시면 맞춤형 경험을 제공해 드립니다.', + 'continue' => '계속', + 'personas' => [ + 'creator' => '콘텐츠 크리에이터', + 'freelancer' => '프리랜서', + 'developer' => '개발자', + 'startup' => '스타트업', + 'agency' => '에이전시', + 'small_business' => '소상공인', + 'marketer' => '마케터', + 'online_store' => '온라인 스토어', + 'other' => '기타', + ], + 'goals_title' => 'TryPost로 이루려는 목표는 무엇인가요?', + 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', + 'goals' => [ + 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', + 'ai_content' => 'AI로 더 빠르게 게시물 작성', + 'plan_calendar' => '캘린더에서 게시물 계획', + 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', + 'grow_audience' => '팔로워와 참여 늘리기', + 'drive_sales' => '더 많은 트래픽과 판매 유도', + 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', + 'team_collaboration' => '팀과 협업', + 'automate_api' => 'API, MCP 또는 코드로 게시 자동화', + 'track_performance' => '게시물 성과 확인', + 'just_exploring' => '지금은 둘러보는 중', + 'other' => '다른 것', + ], + 'referral_source_title' => '저희를 어떻게 알게 되셨나요?', + 'referral_source_description' => '사람들이 TryPost를 어떻게 발견하는지 파악하는 데 도움이 됩니다.', + 'referral_source' => [ + 'google' => 'Google 또는 검색', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram 또는 Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI 어시스턴트 (ChatGPT, Claude 등)', + 'friend' => '친구 또는 동료', + 'blog' => '블로그, 뉴스레터 또는 기사', + 'other' => '기타', + ], + 'subscribe' => [ + 'title' => '첫 네트워크 연결', + 'description' => '예약을 시작하려면 소셜 계정을 하나 이상 연결하세요. 언제든지 추가할 수 있습니다.', + ], +]; diff --git a/lang/nl/welcome.php b/lang/nl/welcome.php new file mode 100644 index 00000000..edcfbc42 --- /dev/null +++ b/lang/nl/welcome.php @@ -0,0 +1,56 @@ + 'Welkom bij TryPost', + 'description' => 'Vertel ons wat jou of je bedrijf het beste omschrijft, zodat we je ervaring kunnen afstemmen.', + 'continue' => 'Doorgaan', + 'personas' => [ + 'creator' => 'Contentmaker', + 'freelancer' => 'Freelancer', + 'developer' => 'Ontwikkelaar', + 'startup' => 'Startup', + 'agency' => 'Bureau', + 'small_business' => 'Klein bedrijf', + 'marketer' => 'Marketeer', + 'online_store' => 'Webshop', + 'other' => 'Anders', + ], + 'goals_title' => 'Wat is je doel met TryPost?', + 'goals_description' => 'Kies alles wat past en we stellen TryPost voor je in.', + 'goals' => [ + 'save_time' => 'Tijd besparen door overal tegelijk te posten', + 'ai_content' => 'Sneller posts maken met AI', + 'plan_calendar' => 'Mijn posts plannen op een kalender', + 'stay_on_brand' => 'Elke post in lijn met mijn merk houden', + 'grow_audience' => 'Mijn publiek en betrokkenheid laten groeien', + 'drive_sales' => 'Meer verkeer en verkopen krijgen', + 'manage_clients' => 'Meerdere merken of klanten beheren', + 'team_collaboration' => 'Samenwerken met mijn team', + 'automate_api' => 'Posten automatiseren met de API, MCP of code', + 'track_performance' => 'Zien hoe mijn posts presteren', + 'just_exploring' => 'Voorlopig gewoon aan het verkennen', + 'other' => 'Iets anders', + ], + 'referral_source_title' => 'Hoe heb je ons gevonden?', + 'referral_source_description' => 'Dit helpt ons te begrijpen hoe mensen TryPost ontdekken.', + 'referral_source' => [ + 'google' => 'Google of zoekmachine', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram of Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI-assistent (ChatGPT, Claude…)', + 'friend' => 'Vriend of collega', + 'blog' => 'Blog, nieuwsbrief of artikel', + 'other' => 'Iets anders', + ], + 'subscribe' => [ + 'title' => 'Koppel je eerste netwerk', + 'description' => 'Koppel ten minste één social account om te beginnen met plannen. Je kunt er altijd meer toevoegen.', + ], +]; diff --git a/lang/pl/welcome.php b/lang/pl/welcome.php new file mode 100644 index 00000000..39503cdc --- /dev/null +++ b/lang/pl/welcome.php @@ -0,0 +1,56 @@ + 'Witamy w TryPost', + 'description' => 'Powiedz nam, co najlepiej opisuje Ciebie lub Twoją firmę, abyśmy mogli dopasować Twoje doświadczenie.', + 'continue' => 'Kontynuuj', + 'personas' => [ + 'creator' => 'Twórca treści', + 'freelancer' => 'Freelancer', + 'developer' => 'Programista', + 'startup' => 'Startup', + 'agency' => 'Agencja', + 'small_business' => 'Mała firma', + 'marketer' => 'Marketingowiec', + 'online_store' => 'Sklep internetowy', + 'other' => 'Inne', + ], + 'goals_title' => 'Jaki jest Twój cel z TryPost?', + 'goals_description' => 'Wybierz wszystko, co pasuje, a my skonfigurujemy TryPost dla Ciebie.', + 'goals' => [ + 'save_time' => 'Oszczędzaj czas, publikując wszędzie naraz', + 'ai_content' => 'Twórz posty szybciej dzięki AI', + 'plan_calendar' => 'Planuj posty w kalendarzu', + 'stay_on_brand' => 'Utrzymuj każdy post spójny z marką', + 'grow_audience' => 'Powiększaj grono odbiorców i zaangażowanie', + 'drive_sales' => 'Zdobywaj więcej ruchu i sprzedaży', + 'manage_clients' => 'Zarządzaj wieloma markami lub klientami', + 'team_collaboration' => 'Pracuj z moim zespołem', + 'automate_api' => 'Automatyzuj publikowanie za pomocą API, MCP lub kodu', + 'track_performance' => 'Sprawdzaj, jak radzą sobie moje posty', + 'just_exploring' => 'Na razie tylko się rozglądam', + 'other' => 'Coś innego', + ], + 'referral_source_title' => 'Jak nas znalazłeś?', + 'referral_source_description' => 'To pomaga nam zrozumieć, jak ludzie odkrywają TryPost.', + 'referral_source' => [ + 'google' => 'Google lub wyszukiwarka', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram lub Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Asystent AI (ChatGPT, Claude…)', + 'friend' => 'Znajomy lub współpracownik', + 'blog' => 'Blog, newsletter lub artykuł', + 'other' => 'Coś innego', + ], + 'subscribe' => [ + 'title' => 'Połącz swoją pierwszą sieć', + 'description' => 'Połącz co najmniej jedno konto społecznościowe, aby zacząć planować. Więcej możesz dodać w dowolnym momencie.', + ], +]; diff --git a/lang/pt-BR/welcome.php b/lang/pt-BR/welcome.php new file mode 100644 index 00000000..9168741e --- /dev/null +++ b/lang/pt-BR/welcome.php @@ -0,0 +1,56 @@ + 'Bem-vindo ao TryPost', + 'description' => 'Conte o que melhor descreve você ou seu negócio para personalizarmos sua experiência.', + 'continue' => 'Continuar', + 'personas' => [ + 'creator' => 'Criador de conteúdo', + 'freelancer' => 'Freelancer', + 'developer' => 'Desenvolvedor', + 'startup' => 'Startup', + 'agency' => 'Agência', + 'small_business' => 'Pequena empresa', + 'marketer' => 'Profissional de marketing', + 'online_store' => 'Loja online', + 'other' => 'Outro', + ], + 'goals_title' => 'Qual o seu objetivo com o TryPost?', + 'goals_description' => 'Marque tudo que faz sentido e a gente ajusta o TryPost pra você.', + 'goals' => [ + 'save_time' => 'Economizar tempo postando em todas as redes de uma vez', + 'ai_content' => 'Criar posts mais rápido com IA', + 'plan_calendar' => 'Planejar meus posts num calendário', + 'stay_on_brand' => 'Manter a consistência da minha marca', + 'grow_audience' => 'Crescer minha audiência e engajamento', + 'drive_sales' => 'Conseguir mais tráfego e vendas', + 'manage_clients' => 'Gerenciar várias marcas ou clientes', + 'team_collaboration' => 'Trabalhar com meu time', + 'automate_api' => 'Automatizar publicações com a API, MCP ou código', + 'track_performance' => 'Ver o desempenho dos meus posts', + 'just_exploring' => 'Só dando uma olhada por enquanto', + 'other' => 'Outra coisa', + ], + 'referral_source_title' => 'Como você nos encontrou?', + 'referral_source_description' => 'Isso nos ajuda a entender como as pessoas descobrem o TryPost.', + 'referral_source' => [ + 'google' => 'Google ou busca', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram ou Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Assistente de IA (ChatGPT, Claude…)', + 'friend' => 'Amigo ou colega', + 'blog' => 'Blog, newsletter ou artigo', + 'other' => 'Outra coisa', + ], + 'subscribe' => [ + 'title' => 'Conecte sua primeira rede', + 'description' => 'Vincule pelo menos uma conta social para começar a agendar. Você pode adicionar mais quando quiser.', + ], +]; diff --git a/lang/ru/welcome.php b/lang/ru/welcome.php new file mode 100644 index 00000000..9411e542 --- /dev/null +++ b/lang/ru/welcome.php @@ -0,0 +1,56 @@ + 'Добро пожаловать в TryPost', + 'description' => 'Расскажите, что лучше всего описывает вас или ваш бизнес, чтобы мы могли настроить работу под вас.', + 'continue' => 'Продолжить', + 'personas' => [ + 'creator' => 'Автор контента', + 'freelancer' => 'Фрилансер', + 'developer' => 'Разработчик', + 'startup' => 'Стартап', + 'agency' => 'Агентство', + 'small_business' => 'Малый бизнес', + 'marketer' => 'Маркетолог', + 'online_store' => 'Интернет-магазин', + 'other' => 'Другое', + ], + 'goals_title' => 'Какова ваша цель с TryPost?', + 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', + 'goals' => [ + 'save_time' => 'Экономить время, публикуя всюду сразу', + 'ai_content' => 'Создавать посты быстрее с помощью ИИ', + 'plan_calendar' => 'Планировать посты в календаре', + 'stay_on_brand' => 'Держать каждый пост в стиле бренда', + 'grow_audience' => 'Наращивать аудиторию и вовлечённость', + 'drive_sales' => 'Получать больше трафика и продаж', + 'manage_clients' => 'Управлять несколькими брендами или клиентами', + 'team_collaboration' => 'Работать с командой', + 'automate_api' => 'Автоматизировать публикацию с помощью API, MCP или кода', + 'track_performance' => 'Отслеживать эффективность постов', + 'just_exploring' => 'Пока просто знакомлюсь', + 'other' => 'Что-то ещё', + ], + 'referral_source_title' => 'Как вы нас нашли?', + 'referral_source_description' => 'Это помогает нам понять, как люди узнают о TryPost.', + 'referral_source' => [ + 'google' => 'Google или поиск', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram или Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'ИИ-ассистент (ChatGPT, Claude…)', + 'friend' => 'Друг или коллега', + 'blog' => 'Блог, рассылка или статья', + 'other' => 'Что-то другое', + ], + 'subscribe' => [ + 'title' => 'Подключите первую сеть', + 'description' => 'Привяжите хотя бы один социальный аккаунт, чтобы начать планировать. Вы можете добавить другие в любой момент.', + ], +]; diff --git a/lang/tr/welcome.php b/lang/tr/welcome.php new file mode 100644 index 00000000..a8da22b2 --- /dev/null +++ b/lang/tr/welcome.php @@ -0,0 +1,56 @@ + 'TryPost\'a hoş geldiniz', + 'description' => 'Deneyiminizi kişiselleştirebilmemiz için sizi veya işinizi en iyi tanımlayan seçeneği belirtin.', + 'continue' => 'Devam et', + 'personas' => [ + 'creator' => 'İçerik üreticisi', + 'freelancer' => 'Serbest çalışan', + 'developer' => 'Geliştirici', + 'startup' => 'Girişim', + 'agency' => 'Ajans', + 'small_business' => 'Küçük işletme', + 'marketer' => 'Pazarlamacı', + 'online_store' => 'Çevrimiçi mağaza', + 'other' => 'Diğer', + ], + 'goals_title' => 'TryPost ile hedefiniz nedir?', + 'goals_description' => 'Size uyan her şeyi seçin, biz de TryPost\'u sizin için ayarlayalım.', + 'goals' => [ + 'save_time' => 'Her yere aynı anda paylaşarak zaman kazanmak', + 'ai_content' => 'AI ile daha hızlı gönderi oluşturmak', + 'plan_calendar' => 'Gönderilerimi bir takvimde planlamak', + 'stay_on_brand' => 'Her gönderiyi marka çizgisinde tutmak', + 'grow_audience' => 'Kitlemi ve etkileşimimi büyütmek', + 'drive_sales' => 'Daha fazla trafik ve satış elde etmek', + 'manage_clients' => 'Birden fazla marka veya müşteri yönetmek', + 'team_collaboration' => 'Ekibimle çalışmak', + 'automate_api' => 'API, MCP veya kodla paylaşımı otomatikleştirmek', + 'track_performance' => 'Gönderilerimin performansını görmek', + 'just_exploring' => 'Şimdilik sadece keşfetmek', + 'other' => 'Başka bir şey', + ], + 'referral_source_title' => 'Bizi nasıl buldunuz?', + 'referral_source_description' => 'İnsanların TryPost\'u nasıl keşfettiğini anlamamıza yardımcı olur.', + 'referral_source' => [ + 'google' => 'Google veya arama', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram veya Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Yapay zeka asistanı (ChatGPT, Claude…)', + 'friend' => 'Arkadaş veya meslektaş', + 'blog' => 'Blog, bülten veya makale', + 'other' => 'Başka bir şey', + ], + 'subscribe' => [ + 'title' => 'İlk ağınızı bağlayın', + 'description' => 'Zamanlamaya başlamak için en az bir sosyal hesap bağlayın. İstediğiniz zaman daha fazlasını ekleyebilirsiniz.', + ], +]; diff --git a/lang/zh/welcome.php b/lang/zh/welcome.php new file mode 100644 index 00000000..1e9ea403 --- /dev/null +++ b/lang/zh/welcome.php @@ -0,0 +1,56 @@ + '欢迎使用 TryPost', + 'description' => '告诉我们最能描述你或你业务的选项,以便我们为你定制体验。', + 'continue' => '继续', + 'personas' => [ + 'creator' => '内容创作者', + 'freelancer' => '自由职业者', + 'developer' => '开发者', + 'startup' => '初创公司', + 'agency' => '代理机构', + 'small_business' => '小型企业', + 'marketer' => '营销人员', + 'online_store' => '网店', + 'other' => '其他', + ], + 'goals_title' => '你使用 TryPost 的目标是什么?', + 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', + 'goals' => [ + 'save_time' => '一次发布到所有平台,节省时间', + 'ai_content' => '借助 AI 更快地创建帖子', + 'plan_calendar' => '在日历上规划我的帖子', + 'stay_on_brand' => '让每一条帖子都符合品牌调性', + 'grow_audience' => '增长我的受众和互动', + 'drive_sales' => '获得更多流量和销量', + 'manage_clients' => '管理多个品牌或客户', + 'team_collaboration' => '与我的团队协作', + 'automate_api' => '通过 API、MCP 或代码自动发帖', + 'track_performance' => '查看我的帖子表现', + 'just_exploring' => '目前只是随便看看', + 'other' => '其他需求', + ], + 'referral_source_title' => '您是如何找到我们的?', + 'referral_source_description' => '这有助于我们了解人们是如何发现 TryPost 的。', + 'referral_source' => [ + 'google' => 'Google 或搜索', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram 或 Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI 助手(ChatGPT、Claude 等)', + 'friend' => '朋友或同事', + 'blog' => '博客、新闻通讯或文章', + 'other' => '其他', + ], + 'subscribe' => [ + 'title' => '连接你的第一个平台', + 'description' => '至少关联一个社交账号即可开始排期。你可以随时添加更多。', + ], +]; diff --git a/resources/js/layouts/OnboardingLayout.vue b/resources/js/layouts/OnboardingLayout.vue deleted file mode 100644 index 2f859c55..00000000 --- a/resources/js/layouts/OnboardingLayout.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/resources/js/layouts/WelcomeLayout.vue b/resources/js/layouts/WelcomeLayout.vue new file mode 100644 index 00000000..a111aa76 --- /dev/null +++ b/resources/js/layouts/WelcomeLayout.vue @@ -0,0 +1,68 @@ + + + diff --git a/resources/js/pages/onboarding/Connect.vue b/resources/js/pages/onboarding/Connect.vue deleted file mode 100644 index 80ae728a..00000000 --- a/resources/js/pages/onboarding/Connect.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - diff --git a/resources/js/pages/onboarding/Goals.vue b/resources/js/pages/onboarding/Goals.vue deleted file mode 100644 index a5e7db0d..00000000 --- a/resources/js/pages/onboarding/Goals.vue +++ /dev/null @@ -1,147 +0,0 @@ - - - diff --git a/resources/js/pages/onboarding/Index.vue b/resources/js/pages/onboarding/Index.vue deleted file mode 100644 index 376c9d09..00000000 --- a/resources/js/pages/onboarding/Index.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - diff --git a/resources/js/pages/onboarding/ReferralSource.vue b/resources/js/pages/onboarding/ReferralSource.vue deleted file mode 100644 index 76afe58b..00000000 --- a/resources/js/pages/onboarding/ReferralSource.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - diff --git a/resources/js/pages/welcome/Goals.vue b/resources/js/pages/welcome/Goals.vue new file mode 100644 index 00000000..e9c5d777 --- /dev/null +++ b/resources/js/pages/welcome/Goals.vue @@ -0,0 +1,144 @@ + + + diff --git a/resources/js/pages/welcome/Persona.vue b/resources/js/pages/welcome/Persona.vue new file mode 100644 index 00000000..cd8b9955 --- /dev/null +++ b/resources/js/pages/welcome/Persona.vue @@ -0,0 +1,126 @@ + + + diff --git a/resources/js/pages/welcome/ReferralSource.vue b/resources/js/pages/welcome/ReferralSource.vue new file mode 100644 index 00000000..647e97e9 --- /dev/null +++ b/resources/js/pages/welcome/ReferralSource.vue @@ -0,0 +1,134 @@ + + + diff --git a/resources/js/pages/welcome/Subscribe.vue b/resources/js/pages/welcome/Subscribe.vue new file mode 100644 index 00000000..611b2111 --- /dev/null +++ b/resources/js/pages/welcome/Subscribe.vue @@ -0,0 +1,53 @@ + + + From 1c13007ee9d6213a6625827a9356f7061d0b0111 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:39:17 -0300 Subject: [PATCH 09/17] Add post-subscription onboarding activation endpoints. Co-authored-by: Cursor --- app/Enums/PostHog/OnboardingEvent.php | 13 ++ .../Controllers/App/OnboardingController.php | 108 ++++++++++++++ .../Controllers/App/WelcomeController.php | 13 +- routes/app.php | 7 +- .../Onboarding/OnboardingControllerTest.php | 139 ++++++++++++++++++ .../Feature/Welcome/WelcomeControllerTest.php | 16 +- 6 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 app/Enums/PostHog/OnboardingEvent.php create mode 100644 app/Http/Controllers/App/OnboardingController.php create mode 100644 tests/Feature/Onboarding/OnboardingControllerTest.php diff --git a/app/Enums/PostHog/OnboardingEvent.php b/app/Enums/PostHog/OnboardingEvent.php new file mode 100644 index 00000000..ff72b671 --- /dev/null +++ b/app/Enums/PostHog/OnboardingEvent.php @@ -0,0 +1,13 @@ +route('app.calendar'); + } + + $user = $request->user(); + $workspace = $user->currentWorkspace; + $status = $this->resolveOnboardingStatus->handle($user); + + $platforms = collect(SocialPlatform::cases()) + ->filter(fn (SocialPlatform $platform): bool => $platform->isConnectable()) + ->map(fn (SocialPlatform $platform): array => [ + 'value' => $platform->value, + 'label' => $platform->label(), + 'color' => $platform->color(), + 'network' => $platform->network(), + ])->values(); + + $this->postHog->capture( + $user->id, + OnboardingEvent::Viewed->value, + account: $user->account, + ); + + return Inertia::render('onboarding/Index', [ + 'status' => $status, + 'mcpUrl' => url('/mcp/trypost'), + 'mcpClients' => [ + ['id' => 'claude', 'label' => 'Claude'], + ['id' => 'chatgpt', 'label' => 'ChatGPT'], + ], + 'samplePrompt' => __('onboarding.first_post.sample_prompt'), + 'platforms' => $platforms, + 'accounts' => SocialAccountResource::collection( + $workspace->socialAccounts()->orderBy('id')->get(), + )->resolve(), + 'createPostUrl' => route('app.posts.create'), + ]); + } + + public function dismiss(Request $request): RedirectResponse + { + if (config('trypost.self_hosted')) { + return redirect()->route('app.calendar'); + } + + $user = $request->user(); + $user->account->update(['onboarding_dismissed_at' => now()]); + + $this->postHog->capture( + $user->id, + OnboardingEvent::Skipped->value, + account: $user->account, + ); + + return redirect()->route('app.calendar'); + } + + public function complete(Request $request): RedirectResponse + { + if (config('trypost.self_hosted')) { + return redirect()->route('app.calendar'); + } + + $user = $request->user(); + $status = $this->resolveOnboardingStatus->handle($user); + + if (! $status['all_complete']) { + return redirect()->route('app.onboarding'); + } + + if ($user->account->onboarding_completed_at === null) { + $user->account->update(['onboarding_completed_at' => now()]); + } + + $this->postHog->capture( + $user->id, + OnboardingEvent::Completed->value, + account: $user->account, + ); + + return redirect()->route('app.calendar'); + } +} diff --git a/app/Http/Controllers/App/WelcomeController.php b/app/Http/Controllers/App/WelcomeController.php index 024bd814..d2b85af1 100644 --- a/app/Http/Controllers/App/WelcomeController.php +++ b/app/Http/Controllers/App/WelcomeController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\App; use App\Actions\Billing\StartSubscriptionCheckout; +use App\Actions\Onboarding\ResolveOnboardingStatus; use App\Enums\Plan\Slug; use App\Enums\User\Goal; use App\Enums\User\Persona; @@ -23,6 +24,10 @@ class WelcomeController extends Controller { + public function __construct( + private readonly ResolveOnboardingStatus $resolveOnboardingStatus, + ) {} + public function persona(Request $request): Response|RedirectResponse { if ($redirect = $this->redirectIfUnavailable($request)) { @@ -194,8 +199,12 @@ private function redirectIfUnavailable(Request $request): ?RedirectResponse return redirect()->route('app.calendar'); } - if ($request->user()->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); + $user = $request->user(); + + if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { + $status = $this->resolveOnboardingStatus->handle($user); + + return redirect()->route($status['show_residual'] ? 'app.onboarding' : 'app.calendar'); } return null; diff --git a/routes/app.php b/routes/app.php index 5892175f..50b5b5dc 100644 --- a/routes/app.php +++ b/routes/app.php @@ -11,6 +11,7 @@ use App\Http\Controllers\App\GiphyController; use App\Http\Controllers\App\LinkPreviewController; use App\Http\Controllers\App\NotificationController; +use App\Http\Controllers\App\OnboardingController; use App\Http\Controllers\App\PostAiCreateController; use App\Http\Controllers\App\PostAiGenerateController; use App\Http\Controllers\App\PostAiRegenerateMediaController; @@ -149,8 +150,12 @@ Route::get('accounts/discord/callback', [DiscordController::class, 'callback'])->name('app.social.discord.callback'); }); -// Routes that require active subscription and completed onboarding +// Routes that require account access and a current workspace Route::middleware(['auth', EnsureAccountReady::class, EnsureHasWorkspace::class])->group(function () { + Route::get('onboarding', [OnboardingController::class, 'index'])->name('app.onboarding'); + Route::post('onboarding/dismiss', [OnboardingController::class, 'dismiss'])->name('app.onboarding.dismiss'); + Route::post('onboarding/complete', [OnboardingController::class, 'complete'])->name('app.onboarding.complete'); + // Discord — live lookups for the composer (channel picker + mention autocomplete). // Throttled because they proxy the shared bot's (rate-limited) Discord API. Route::get('discord/accounts/{account}/channels', [AppDiscordController::class, 'channels']) diff --git a/tests/Feature/Onboarding/OnboardingControllerTest.php b/tests/Feature/Onboarding/OnboardingControllerTest.php new file mode 100644 index 00000000..9f6458d5 --- /dev/null +++ b/tests/Feature/Onboarding/OnboardingControllerTest.php @@ -0,0 +1,139 @@ + false, + 'services.posthog.enabled' => true, + 'services.posthog.api_key' => 'phc_test', + ]); + + Bus::fake(); + + $this->user = User::factory()->create(); + $this->workspace = Workspace::factory()->create([ + 'account_id' => $this->user->account_id, + 'user_id' => $this->user->id, + ]); + $this->user->update(['current_workspace_id' => $this->workspace->id]); + $this->user->refresh(); + + subscribeAccount($this->user->account); +}); + +test('onboarding renders activation status and connection props', function () { + $socialAccount = SocialAccount::factory()->create([ + 'workspace_id' => $this->workspace->id, + ]); + + $this->actingAs($this->user) + ->get(route('app.onboarding')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('onboarding/Index', false) + ->where('status.mcp_connected', false) + ->where('status.social_connected', true) + ->where('status.first_post_created', false) + ->where('status.all_complete', false) + ->where('status.show_residual', true) + ->where('mcpUrl', url('/mcp/trypost')) + ->where('mcpClients', [ + ['id' => 'claude', 'label' => 'Claude'], + ['id' => 'chatgpt', 'label' => 'ChatGPT'], + ]) + ->where('samplePrompt', __('onboarding.first_post.sample_prompt')) + ->has('platforms', collect(Platform::cases())->filter->isConnectable()->count()) + ->where('accounts.0.id', $socialAccount->id) + ->where('createPostUrl', route('app.posts.create')) + ); + + Bus::assertDispatched(SendEvent::class, fn (SendEvent $event): bool => $event->method === 'capture' + && data_get($event->payload, 'distinctId') === $this->user->id + && data_get($event->payload, 'event') === OnboardingEvent::Viewed->value); +}); + +test('onboarding can be dismissed', function () { + Carbon::setTestNow('2026-07-24 12:00:00'); + + $this->actingAs($this->user) + ->post(route('app.onboarding.dismiss')) + ->assertRedirect(route('app.calendar')); + + expect($this->user->account->fresh()->onboarding_dismissed_at?->equalTo(now()))->toBeTrue(); + + Bus::assertDispatched(SendEvent::class, fn (SendEvent $event): bool => data_get($event->payload, 'event') === OnboardingEvent::Skipped->value); +}); + +test('onboarding cannot be completed before every activation step', function () { + $this->actingAs($this->user) + ->post(route('app.onboarding.complete')) + ->assertRedirect(route('app.onboarding')); + + expect($this->user->account->fresh()->onboarding_completed_at)->toBeNull(); + + Bus::assertNotDispatched(SendEvent::class, fn (SendEvent $event): bool => data_get($event->payload, 'event') === OnboardingEvent::Completed->value); +}); + +test('onboarding completes after every activation step', function () { + Carbon::setTestNow('2026-07-24 12:00:00'); + + $token = $this->user->createToken('OAuth Session'); + AccessToken::findOrFail($token->token->id) + ->forceFill(['workspace_id' => null]) + ->saveQuietly(); + SocialAccount::factory()->create(['workspace_id' => $this->workspace->id]); + Post::factory()->create([ + 'workspace_id' => $this->workspace->id, + 'user_id' => $this->user->id, + ]); + + $this->actingAs($this->user->fresh()) + ->post(route('app.onboarding.complete')) + ->assertRedirect(route('app.calendar')); + + expect($this->user->account->fresh()->onboarding_completed_at?->equalTo(now()))->toBeTrue(); + + Bus::assertDispatched(SendEvent::class, fn (SendEvent $event): bool => data_get($event->payload, 'event') === OnboardingEvent::Completed->value); +}); + +test('unsubscribed accounts are redirected to welcome by middleware', function (string $routeName, string $method) { + $this->user->account->subscriptions()->delete(); + $this->actingAs($this->user->fresh()); + + $response = $method === 'get' + ? $this->get(route($routeName)) + : $this->post(route($routeName)); + + $response->assertRedirect(route('app.welcome.persona')); +})->with([ + 'index' => ['app.onboarding', 'get'], + 'dismiss' => ['app.onboarding.dismiss', 'post'], + 'complete' => ['app.onboarding.complete', 'post'], +]); + +test('self hosted activation endpoints redirect to calendar', function (string $routeName, string $method) { + config(['trypost.self_hosted' => true]); + $this->actingAs($this->user); + + $response = $method === 'get' + ? $this->get(route($routeName)) + : $this->post(route($routeName)); + + $response->assertRedirect(route('app.calendar')); +})->with([ + 'index' => ['app.onboarding', 'get'], + 'dismiss' => ['app.onboarding.dismiss', 'post'], + 'complete' => ['app.onboarding.complete', 'post'], +]); diff --git a/tests/Feature/Welcome/WelcomeControllerTest.php b/tests/Feature/Welcome/WelcomeControllerTest.php index d12307d8..cb79bd4c 100644 --- a/tests/Feature/Welcome/WelcomeControllerTest.php +++ b/tests/Feature/Welcome/WelcomeControllerTest.php @@ -226,7 +226,7 @@ function subscribeWelcomeAccount(Account $account): void ->assertRedirect('https://checkout.stripe.test/session'); }); -test('welcome steps redirect to calendar for subscribed accounts', function (string $routeName, string $method, array $payload = []) { +test('welcome steps redirect to activation for subscribed accounts with residual onboarding', function (string $routeName, string $method, array $payload = []) { subscribeWelcomeAccount($this->user->account); $this->actingAs($this->user->fresh()); @@ -235,7 +235,7 @@ function subscribeWelcomeAccount(Account $account): void ? $this->get(route($routeName)) : $this->post(route($routeName), $payload); - $response->assertRedirect(route('app.calendar')); + $response->assertRedirect(route('app.onboarding')); })->with([ 'persona' => ['app.welcome.persona', 'get'], 'persona store' => ['app.welcome.persona.store', 'post', ['persona' => Persona::Agency->value]], @@ -247,6 +247,18 @@ function subscribeWelcomeAccount(Account $account): void 'checkout' => ['app.welcome.checkout', 'post'], ]); +test('welcome redirects subscribed accounts without residual onboarding to calendar', function (array $attributes) { + subscribeWelcomeAccount($this->user->account); + $this->user->account->update($attributes); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.persona')) + ->assertRedirect(route('app.calendar')); +})->with([ + 'dismissed' => [['onboarding_dismissed_at' => now()]], + 'completed' => [['onboarding_completed_at' => now()]], +]); + test('welcome steps redirect to calendar in self hosted mode', function (string $routeName, string $method, array $payload = []) { config(['trypost.self_hosted' => true]); From ea615a61e8ec0dcad3131eb3ff2813ef49d54d5a Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:45:05 -0300 Subject: [PATCH 10/17] Add single-page post-subscription onboarding UI. Co-authored-by: Cursor --- lang/ar/onboarding.php | 78 ++--- lang/de/onboarding.php | 78 ++--- lang/el/onboarding.php | 78 ++--- lang/en/onboarding.php | 78 ++--- lang/es/onboarding.php | 78 ++--- lang/fr/onboarding.php | 78 ++--- lang/it/onboarding.php | 78 ++--- lang/ja/onboarding.php | 78 ++--- lang/ko/onboarding.php | 78 ++--- lang/nl/onboarding.php | 78 ++--- lang/pl/onboarding.php | 78 ++--- lang/pt-BR/onboarding.php | 78 ++--- lang/ru/onboarding.php | 78 ++--- lang/tr/onboarding.php | 78 ++--- lang/zh/onboarding.php | 78 ++--- resources/js/pages/onboarding/Index.vue | 366 ++++++++++++++++++++++++ 16 files changed, 831 insertions(+), 705 deletions(-) create mode 100644 resources/js/pages/onboarding/Index.vue diff --git a/lang/ar/onboarding.php b/lang/ar/onboarding.php index 5103f1d3..ddfc9174 100644 --- a/lang/ar/onboarding.php +++ b/lang/ar/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'مرحبًا بك في TryPost', - 'description' => 'أخبرنا بما يصفك أنت أو نشاطك التجاري على أفضل وجه حتى نتمكن من تخصيص تجربتك.', - 'continue' => 'متابعة', - 'personas' => [ - 'creator' => 'صانع محتوى', - 'freelancer' => 'مستقل', - 'developer' => 'مطوّر', - 'startup' => 'شركة ناشئة', - 'agency' => 'وكالة', - 'small_business' => 'نشاط تجاري صغير', - 'marketer' => 'مسوّق', - 'online_store' => 'متجر إلكتروني', - 'other' => 'أخرى', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'ما هدفك من استخدام TryPost؟', - 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', - 'goals' => [ - 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', - 'ai_content' => 'إنشاء منشورات أسرع بالذكاء الاصطناعي', - 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', - 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', - 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', - 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', - 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', - 'team_collaboration' => 'العمل مع فريقي', - 'automate_api' => 'أتمتة النشر عبر الواجهة البرمجية أو MCP أو الكود', - 'track_performance' => 'معرفة أداء منشوراتي', - 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', - 'other' => 'شيء آخر', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'كيف وجدتنا؟', - 'referral_source_description' => 'يساعدنا هذا على فهم كيفية اكتشاف الأشخاص لـ TryPost.', - 'referral_source' => [ - 'google' => 'Google أو البحث', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram أو Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'مساعد ذكاء اصطناعي (ChatGPT، Claude…)', - 'friend' => 'صديق أو زميل', - 'blog' => 'مدونة أو نشرة إخبارية أو مقال', - 'other' => 'شيء آخر', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'اربط شبكتك الأولى', - 'description' => 'اربط حسابًا اجتماعيًا واحدًا على الأقل لبدء الجدولة. يمكنك إضافة المزيد في أي وقت.', - 'must_connect' => 'اربط شبكة واحدة على الأقل للمتابعة.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/de/onboarding.php b/lang/de/onboarding.php index 9ccf8ad7..ddfc9174 100644 --- a/lang/de/onboarding.php +++ b/lang/de/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Willkommen bei TryPost', - 'description' => 'Sag uns, was dich oder dein Unternehmen am besten beschreibt, damit wir dein Erlebnis anpassen können.', - 'continue' => 'Weiter', - 'personas' => [ - 'creator' => 'Content Creator', - 'freelancer' => 'Freelancer', - 'developer' => 'Entwickler', - 'startup' => 'Startup', - 'agency' => 'Agentur', - 'small_business' => 'Kleinunternehmen', - 'marketer' => 'Marketer', - 'online_store' => 'Onlineshop', - 'other' => 'Sonstiges', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Was ist dein Ziel mit TryPost?', - 'goals_description' => 'Wähle alles aus, was passt, und wir richten TryPost für dich ein.', - 'goals' => [ - 'save_time' => 'Zeit sparen, indem ich überall gleichzeitig poste', - 'ai_content' => 'Beiträge schneller mit KI erstellen', - 'plan_calendar' => 'Meine Beiträge in einem Kalender planen', - 'stay_on_brand' => 'Jeden Beitrag markenkonform halten', - 'grow_audience' => 'Meine Reichweite und mein Engagement steigern', - 'drive_sales' => 'Mehr Traffic und Verkäufe erzielen', - 'manage_clients' => 'Mehrere Marken oder Kunden verwalten', - 'team_collaboration' => 'Mit meinem Team arbeiten', - 'automate_api' => 'Das Posten per API, MCP oder Code automatisieren', - 'track_performance' => 'Sehen, wie meine Beiträge performen', - 'just_exploring' => 'Ich schaue mich vorerst nur um', - 'other' => 'Etwas anderes', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Wie hast du uns gefunden?', - 'referral_source_description' => 'Das hilft uns zu verstehen, wie Menschen TryPost entdecken.', - 'referral_source' => [ - 'google' => 'Google oder Suche', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram oder Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'KI-Assistent (ChatGPT, Claude…)', - 'friend' => 'Freund oder Kollege', - 'blog' => 'Blog, Newsletter oder Artikel', - 'other' => 'Etwas anderes', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Verbinde dein erstes Netzwerk', - 'description' => 'Verknüpfe mindestens ein Social-Media-Konto, um mit der Planung zu beginnen. Du kannst jederzeit weitere hinzufügen.', - 'must_connect' => 'Verbinde mindestens ein Netzwerk, um fortzufahren.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/el/onboarding.php b/lang/el/onboarding.php index d0eccc36..ddfc9174 100644 --- a/lang/el/onboarding.php +++ b/lang/el/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Καλώς ήρθατε στο TryPost', - 'description' => 'Πείτε μας τι σας περιγράφει καλύτερα, εσάς ή την επιχείρησή σας, ώστε να προσαρμόσουμε την εμπειρία σας.', - 'continue' => 'Συνέχεια', - 'personas' => [ - 'creator' => 'Δημιουργός περιεχομένου', - 'freelancer' => 'Ελεύθερος επαγγελματίας', - 'developer' => 'Προγραμματιστής', - 'startup' => 'Startup', - 'agency' => 'Πρακτορείο', - 'small_business' => 'Μικρή επιχείρηση', - 'marketer' => 'Marketer', - 'online_store' => 'Ηλεκτρονικό κατάστημα', - 'other' => 'Άλλο', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Ποιος είναι ο στόχος σας με το TryPost;', - 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', - 'goals' => [ - 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', - 'ai_content' => 'Δημιουργία δημοσιεύσεων ταχύτερα με AI', - 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', - 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', - 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', - 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', - 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', - 'team_collaboration' => 'Συνεργασία με την ομάδα μου', - 'automate_api' => 'Αυτοματοποίηση δημοσιεύσεων με το API, το MCP ή κώδικα', - 'track_performance' => 'Παρακολούθηση της απόδοσης των δημοσιεύσεών μου', - 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', - 'other' => 'Κάτι άλλο', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Πώς μας βρήκατε;', - 'referral_source_description' => 'Αυτό μας βοηθά να καταλάβουμε πώς οι άνθρωποι ανακαλύπτουν το TryPost.', - 'referral_source' => [ - 'google' => 'Google ή αναζήτηση', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ή Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Βοηθός AI (ChatGPT, Claude…)', - 'friend' => 'Φίλος ή συνάδελφος', - 'blog' => 'Blog, newsletter ή άρθρο', - 'other' => 'Κάτι άλλο', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Συνδέστε το πρώτο σας δίκτυο', - 'description' => 'Συνδέστε τουλάχιστον έναν λογαριασμό κοινωνικού δικτύου για να ξεκινήσετε τον προγραμματισμό. Μπορείτε να προσθέσετε κι άλλους ανά πάσα στιγμή.', - 'must_connect' => 'Συνδέστε τουλάχιστον ένα δίκτυο για να συνεχίσετε.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/en/onboarding.php b/lang/en/onboarding.php index e76cf671..ddfc9174 100644 --- a/lang/en/onboarding.php +++ b/lang/en/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Welcome to TryPost', - 'description' => 'Tell us what best describes you or your business so we can tailor your experience.', - 'continue' => 'Continue', - 'personas' => [ - 'creator' => 'Content creator', - 'freelancer' => 'Freelancer', - 'developer' => 'Developer', - 'startup' => 'Startup', - 'agency' => 'Agency', - 'small_business' => 'Small business', - 'marketer' => 'Marketer', - 'online_store' => 'Online store', - 'other' => 'Other', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'What\'s your goal with TryPost?', - 'goals_description' => 'Pick everything that fits and we\'ll set TryPost up for you.', - 'goals' => [ - 'save_time' => 'Save time by posting everywhere at once', - 'ai_content' => 'Create posts faster with AI', - 'plan_calendar' => 'Plan my posts on a calendar', - 'stay_on_brand' => 'Keep every post on brand', - 'grow_audience' => 'Grow my audience and engagement', - 'drive_sales' => 'Get more traffic and sales', - 'manage_clients' => 'Manage several brands or clients', - 'team_collaboration' => 'Work with my team', - 'automate_api' => 'Automate posting with the API, MCP or code', - 'track_performance' => 'See how my posts perform', - 'just_exploring' => 'Just exploring for now', - 'other' => 'Something else', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'How did you find us?', - 'referral_source_description' => 'This helps us understand how people discover TryPost.', - 'referral_source' => [ - 'google' => 'Google or search', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram or Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI assistant (ChatGPT, Claude…)', - 'friend' => 'Friend or colleague', - 'blog' => 'Blog, newsletter or article', - 'other' => 'Something else', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Connect your first network', - 'description' => 'Link at least one social account to start scheduling. You can add more anytime.', - 'must_connect' => 'Connect at least one network to continue.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/es/onboarding.php b/lang/es/onboarding.php index 4fb16d04..ddfc9174 100644 --- a/lang/es/onboarding.php +++ b/lang/es/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Bienvenido a TryPost', - 'description' => 'Cuéntanos qué te describe mejor a ti o a tu negocio para personalizar tu experiencia.', - 'continue' => 'Continuar', - 'personas' => [ - 'creator' => 'Creador de contenido', - 'freelancer' => 'Freelancer', - 'developer' => 'Desarrollador', - 'startup' => 'Startup', - 'agency' => 'Agencia', - 'small_business' => 'Pequeña empresa', - 'marketer' => 'Profesional de marketing', - 'online_store' => 'Tienda online', - 'other' => 'Otro', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => '¿Cuál es tu objetivo con TryPost?', - 'goals_description' => 'Marca todo lo que encaje y adaptamos TryPost a ti.', - 'goals' => [ - 'save_time' => 'Ahorrar tiempo publicando en todas mis redes a la vez', - 'ai_content' => 'Crear publicaciones más rápido con IA', - 'plan_calendar' => 'Planificar mis publicaciones en un calendario', - 'stay_on_brand' => 'Mantener la coherencia de mi marca', - 'grow_audience' => 'Hacer crecer mi audiencia y engagement', - 'drive_sales' => 'Conseguir más tráfico y ventas', - 'manage_clients' => 'Gestionar varias marcas o clientes', - 'team_collaboration' => 'Trabajar con mi equipo', - 'automate_api' => 'Automatizar publicaciones con la API, MCP o código', - 'track_performance' => 'Ver cómo rinden mis publicaciones', - 'just_exploring' => 'Solo estoy explorando por ahora', - 'other' => 'Otra cosa', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => '¿Cómo nos encontraste?', - 'referral_source_description' => 'Esto nos ayuda a entender cómo la gente descubre TryPost.', - 'referral_source' => [ - 'google' => 'Google o búsqueda', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram o Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Asistente de IA (ChatGPT, Claude…)', - 'friend' => 'Amigo o colega', - 'blog' => 'Blog, newsletter o artículo', - 'other' => 'Otra cosa', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Conecta tu primera red', - 'description' => 'Vincula al menos una cuenta social para empezar a programar. Puedes añadir más cuando quieras.', - 'must_connect' => 'Conecta al menos una red para continuar.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/fr/onboarding.php b/lang/fr/onboarding.php index 7d103c10..ddfc9174 100644 --- a/lang/fr/onboarding.php +++ b/lang/fr/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Bienvenue sur TryPost', - 'description' => 'Dites-nous ce qui vous décrit le mieux, vous ou votre entreprise, afin que nous puissions personnaliser votre expérience.', - 'continue' => 'Continuer', - 'personas' => [ - 'creator' => 'Créateur de contenu', - 'freelancer' => 'Freelance', - 'developer' => 'Développeur', - 'startup' => 'Startup', - 'agency' => 'Agence', - 'small_business' => 'Petite entreprise', - 'marketer' => 'Marketeur', - 'online_store' => 'Boutique en ligne', - 'other' => 'Autre', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Quel est votre objectif avec TryPost ?', - 'goals_description' => 'Choisissez tout ce qui vous correspond et nous configurerons TryPost pour vous.', - 'goals' => [ - 'save_time' => 'Gagner du temps en publiant partout à la fois', - 'ai_content' => 'Créer des publications plus vite avec l\'IA', - 'plan_calendar' => 'Planifier mes publications sur un calendrier', - 'stay_on_brand' => 'Garder chaque publication fidèle à ma marque', - 'grow_audience' => 'Développer mon audience et mon engagement', - 'drive_sales' => 'Obtenir plus de trafic et de ventes', - 'manage_clients' => 'Gérer plusieurs marques ou clients', - 'team_collaboration' => 'Travailler avec mon équipe', - 'automate_api' => 'Automatiser la publication avec l\'API, le MCP ou du code', - 'track_performance' => 'Voir les performances de mes publications', - 'just_exploring' => 'Je découvre pour l\'instant', - 'other' => 'Autre chose', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Comment nous avez-vous connus ?', - 'referral_source_description' => 'Cela nous aide à comprendre comment les gens découvrent TryPost.', - 'referral_source' => [ - 'google' => 'Google ou recherche', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ou Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistant IA (ChatGPT, Claude…)', - 'friend' => 'Ami ou collègue', - 'blog' => 'Blog, newsletter ou article', - 'other' => 'Autre chose', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Connectez votre premier réseau', - 'description' => 'Associez au moins un compte social pour commencer à programmer. Vous pourrez en ajouter d\'autres à tout moment.', - 'must_connect' => 'Connectez au moins un réseau pour continuer.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/it/onboarding.php b/lang/it/onboarding.php index 0beba033..ddfc9174 100644 --- a/lang/it/onboarding.php +++ b/lang/it/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Benvenuto su TryPost', - 'description' => 'Dicci cosa descrive meglio te o la tua attività così possiamo personalizzare la tua esperienza.', - 'continue' => 'Continua', - 'personas' => [ - 'creator' => 'Creatore di contenuti', - 'freelancer' => 'Freelance', - 'developer' => 'Sviluppatore', - 'startup' => 'Startup', - 'agency' => 'Agenzia', - 'small_business' => 'Piccola impresa', - 'marketer' => 'Marketer', - 'online_store' => 'Negozio online', - 'other' => 'Altro', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Qual è il tuo obiettivo con TryPost?', - 'goals_description' => 'Scegli tutto ciò che fa per te e configureremo TryPost per te.', - 'goals' => [ - 'save_time' => 'Risparmiare tempo pubblicando ovunque in una volta', - 'ai_content' => 'Creare post più velocemente con l\'IA', - 'plan_calendar' => 'Pianificare i miei post su un calendario', - 'stay_on_brand' => 'Mantenere ogni post in linea con il brand', - 'grow_audience' => 'Far crescere il mio pubblico e il coinvolgimento', - 'drive_sales' => 'Ottenere più traffico e vendite', - 'manage_clients' => 'Gestire più brand o clienti', - 'team_collaboration' => 'Lavorare con il mio team', - 'automate_api' => 'Automatizzare la pubblicazione con API, MCP o codice', - 'track_performance' => 'Vedere come vanno i miei post', - 'just_exploring' => 'Sto solo dando un\'occhiata', - 'other' => 'Qualcos\'altro', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Come ci hai trovato?', - 'referral_source_description' => 'Questo ci aiuta a capire come le persone scoprono TryPost.', - 'referral_source' => [ - 'google' => 'Google o ricerca', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram o Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistente IA (ChatGPT, Claude…)', - 'friend' => 'Amico o collega', - 'blog' => 'Blog, newsletter o articolo', - 'other' => 'Qualcos\'altro', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Collega la tua prima rete', - 'description' => 'Collega almeno un account social per iniziare a programmare. Puoi aggiungerne altri in qualsiasi momento.', - 'must_connect' => 'Collega almeno una rete per continuare.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/ja/onboarding.php b/lang/ja/onboarding.php index db92adcf..ddfc9174 100644 --- a/lang/ja/onboarding.php +++ b/lang/ja/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'TryPost へようこそ', - 'description' => 'あなたやあなたのビジネスに最も当てはまるものを教えてください。体験を最適化します。', - 'continue' => '続ける', - 'personas' => [ - 'creator' => 'コンテンツクリエイター', - 'freelancer' => 'フリーランス', - 'developer' => '開発者', - 'startup' => 'スタートアップ', - 'agency' => '代理店', - 'small_business' => '中小企業', - 'marketer' => 'マーケター', - 'online_store' => 'オンラインストア', - 'other' => 'その他', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'TryPost での目標は何ですか?', - 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', - 'goals' => [ - 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', - 'ai_content' => 'AI でより速く投稿を作成する', - 'plan_calendar' => 'カレンダーで投稿を計画する', - 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', - 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', - 'drive_sales' => 'トラフィックと売上を増やす', - 'manage_clients' => '複数のブランドやクライアントを管理する', - 'team_collaboration' => 'チームで作業する', - 'automate_api' => 'API、MCP、コードで投稿を自動化する', - 'track_performance' => '投稿のパフォーマンスを確認する', - 'just_exploring' => '今はまだ様子を見ている', - 'other' => 'その他', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'どこで私たちを知りましたか?', - 'referral_source_description' => 'これは、人々がどのように TryPost を見つけるかを理解するのに役立ちます。', - 'referral_source' => [ - 'google' => 'Google または検索', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram または Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI アシスタント(ChatGPT、Claude など)', - 'friend' => '友人または同僚', - 'blog' => 'ブログ、ニュースレター、記事', - 'other' => 'その他', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => '最初のネットワークを接続', - 'description' => 'スケジュールを始めるには、少なくとも 1 つのソーシャルアカウントを連携してください。後からいつでも追加できます。', - 'must_connect' => '続けるには、少なくとも 1 つのネットワークを接続してください。', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/ko/onboarding.php b/lang/ko/onboarding.php index fc954799..ddfc9174 100644 --- a/lang/ko/onboarding.php +++ b/lang/ko/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'TryPost에 오신 것을 환영합니다', - 'description' => '회원님이나 비즈니스를 가장 잘 설명하는 항목을 알려주시면 맞춤형 경험을 제공해 드립니다.', - 'continue' => '계속', - 'personas' => [ - 'creator' => '콘텐츠 크리에이터', - 'freelancer' => '프리랜서', - 'developer' => '개발자', - 'startup' => '스타트업', - 'agency' => '에이전시', - 'small_business' => '소상공인', - 'marketer' => '마케터', - 'online_store' => '온라인 스토어', - 'other' => '기타', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'TryPost로 이루려는 목표는 무엇인가요?', - 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', - 'goals' => [ - 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', - 'ai_content' => 'AI로 더 빠르게 게시물 작성', - 'plan_calendar' => '캘린더에서 게시물 계획', - 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', - 'grow_audience' => '팔로워와 참여 늘리기', - 'drive_sales' => '더 많은 트래픽과 판매 유도', - 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', - 'team_collaboration' => '팀과 협업', - 'automate_api' => 'API, MCP 또는 코드로 게시 자동화', - 'track_performance' => '게시물 성과 확인', - 'just_exploring' => '지금은 둘러보는 중', - 'other' => '다른 것', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => '저희를 어떻게 알게 되셨나요?', - 'referral_source_description' => '사람들이 TryPost를 어떻게 발견하는지 파악하는 데 도움이 됩니다.', - 'referral_source' => [ - 'google' => 'Google 또는 검색', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram 또는 Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI 어시스턴트 (ChatGPT, Claude 등)', - 'friend' => '친구 또는 동료', - 'blog' => '블로그, 뉴스레터 또는 기사', - 'other' => '기타', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => '첫 네트워크 연결', - 'description' => '예약을 시작하려면 소셜 계정을 하나 이상 연결하세요. 언제든지 추가할 수 있습니다.', - 'must_connect' => '계속하려면 네트워크를 하나 이상 연결하세요.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/nl/onboarding.php b/lang/nl/onboarding.php index 424cfe42..ddfc9174 100644 --- a/lang/nl/onboarding.php +++ b/lang/nl/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Welkom bij TryPost', - 'description' => 'Vertel ons wat jou of je bedrijf het beste omschrijft, zodat we je ervaring kunnen afstemmen.', - 'continue' => 'Doorgaan', - 'personas' => [ - 'creator' => 'Contentmaker', - 'freelancer' => 'Freelancer', - 'developer' => 'Ontwikkelaar', - 'startup' => 'Startup', - 'agency' => 'Bureau', - 'small_business' => 'Klein bedrijf', - 'marketer' => 'Marketeer', - 'online_store' => 'Webshop', - 'other' => 'Anders', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Wat is je doel met TryPost?', - 'goals_description' => 'Kies alles wat past en we stellen TryPost voor je in.', - 'goals' => [ - 'save_time' => 'Tijd besparen door overal tegelijk te posten', - 'ai_content' => 'Sneller posts maken met AI', - 'plan_calendar' => 'Mijn posts plannen op een kalender', - 'stay_on_brand' => 'Elke post in lijn met mijn merk houden', - 'grow_audience' => 'Mijn publiek en betrokkenheid laten groeien', - 'drive_sales' => 'Meer verkeer en verkopen krijgen', - 'manage_clients' => 'Meerdere merken of klanten beheren', - 'team_collaboration' => 'Samenwerken met mijn team', - 'automate_api' => 'Posten automatiseren met de API, MCP of code', - 'track_performance' => 'Zien hoe mijn posts presteren', - 'just_exploring' => 'Voorlopig gewoon aan het verkennen', - 'other' => 'Iets anders', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Hoe heb je ons gevonden?', - 'referral_source_description' => 'Dit helpt ons te begrijpen hoe mensen TryPost ontdekken.', - 'referral_source' => [ - 'google' => 'Google of zoekmachine', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram of Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI-assistent (ChatGPT, Claude…)', - 'friend' => 'Vriend of collega', - 'blog' => 'Blog, nieuwsbrief of artikel', - 'other' => 'Iets anders', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Koppel je eerste netwerk', - 'description' => 'Koppel ten minste één social account om te beginnen met plannen. Je kunt er altijd meer toevoegen.', - 'must_connect' => 'Koppel ten minste één netwerk om door te gaan.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/pl/onboarding.php b/lang/pl/onboarding.php index 91092398..ddfc9174 100644 --- a/lang/pl/onboarding.php +++ b/lang/pl/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Witamy w TryPost', - 'description' => 'Powiedz nam, co najlepiej opisuje Ciebie lub Twoją firmę, abyśmy mogli dopasować Twoje doświadczenie.', - 'continue' => 'Kontynuuj', - 'personas' => [ - 'creator' => 'Twórca treści', - 'freelancer' => 'Freelancer', - 'developer' => 'Programista', - 'startup' => 'Startup', - 'agency' => 'Agencja', - 'small_business' => 'Mała firma', - 'marketer' => 'Marketingowiec', - 'online_store' => 'Sklep internetowy', - 'other' => 'Inne', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Jaki jest Twój cel z TryPost?', - 'goals_description' => 'Wybierz wszystko, co pasuje, a my skonfigurujemy TryPost dla Ciebie.', - 'goals' => [ - 'save_time' => 'Oszczędzaj czas, publikując wszędzie naraz', - 'ai_content' => 'Twórz posty szybciej dzięki AI', - 'plan_calendar' => 'Planuj posty w kalendarzu', - 'stay_on_brand' => 'Utrzymuj każdy post spójny z marką', - 'grow_audience' => 'Powiększaj grono odbiorców i zaangażowanie', - 'drive_sales' => 'Zdobywaj więcej ruchu i sprzedaży', - 'manage_clients' => 'Zarządzaj wieloma markami lub klientami', - 'team_collaboration' => 'Pracuj z moim zespołem', - 'automate_api' => 'Automatyzuj publikowanie za pomocą API, MCP lub kodu', - 'track_performance' => 'Sprawdzaj, jak radzą sobie moje posty', - 'just_exploring' => 'Na razie tylko się rozglądam', - 'other' => 'Coś innego', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Jak nas znalazłeś?', - 'referral_source_description' => 'To pomaga nam zrozumieć, jak ludzie odkrywają TryPost.', - 'referral_source' => [ - 'google' => 'Google lub wyszukiwarka', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram lub Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Asystent AI (ChatGPT, Claude…)', - 'friend' => 'Znajomy lub współpracownik', - 'blog' => 'Blog, newsletter lub artykuł', - 'other' => 'Coś innego', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Połącz swoją pierwszą sieć', - 'description' => 'Połącz co najmniej jedno konto społecznościowe, aby zacząć planować. Więcej możesz dodać w dowolnym momencie.', - 'must_connect' => 'Połącz co najmniej jedną sieć, aby kontynuować.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/pt-BR/onboarding.php b/lang/pt-BR/onboarding.php index 12ad7962..ddfc9174 100644 --- a/lang/pt-BR/onboarding.php +++ b/lang/pt-BR/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Bem-vindo ao TryPost', - 'description' => 'Conte o que melhor descreve você ou seu negócio para personalizarmos sua experiência.', - 'continue' => 'Continuar', - 'personas' => [ - 'creator' => 'Criador de conteúdo', - 'freelancer' => 'Freelancer', - 'developer' => 'Desenvolvedor', - 'startup' => 'Startup', - 'agency' => 'Agência', - 'small_business' => 'Pequena empresa', - 'marketer' => 'Profissional de marketing', - 'online_store' => 'Loja online', - 'other' => 'Outro', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Qual o seu objetivo com o TryPost?', - 'goals_description' => 'Marque tudo que faz sentido e a gente ajusta o TryPost pra você.', - 'goals' => [ - 'save_time' => 'Economizar tempo postando em todas as redes de uma vez', - 'ai_content' => 'Criar posts mais rápido com IA', - 'plan_calendar' => 'Planejar meus posts num calendário', - 'stay_on_brand' => 'Manter a consistência da minha marca', - 'grow_audience' => 'Crescer minha audiência e engajamento', - 'drive_sales' => 'Conseguir mais tráfego e vendas', - 'manage_clients' => 'Gerenciar várias marcas ou clientes', - 'team_collaboration' => 'Trabalhar com meu time', - 'automate_api' => 'Automatizar publicações com a API, MCP ou código', - 'track_performance' => 'Ver o desempenho dos meus posts', - 'just_exploring' => 'Só dando uma olhada por enquanto', - 'other' => 'Outra coisa', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Como você nos encontrou?', - 'referral_source_description' => 'Isso nos ajuda a entender como as pessoas descobrem o TryPost.', - 'referral_source' => [ - 'google' => 'Google ou busca', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ou Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistente de IA (ChatGPT, Claude…)', - 'friend' => 'Amigo ou colega', - 'blog' => 'Blog, newsletter ou artigo', - 'other' => 'Outra coisa', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Conecte sua primeira rede', - 'description' => 'Vincule pelo menos uma conta social para começar a agendar. Você pode adicionar mais quando quiser.', - 'must_connect' => 'Conecte pelo menos uma rede para continuar.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/ru/onboarding.php b/lang/ru/onboarding.php index d7b64add..ddfc9174 100644 --- a/lang/ru/onboarding.php +++ b/lang/ru/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'Добро пожаловать в TryPost', - 'description' => 'Расскажите, что лучше всего описывает вас или ваш бизнес, чтобы мы могли настроить работу под вас.', - 'continue' => 'Продолжить', - 'personas' => [ - 'creator' => 'Автор контента', - 'freelancer' => 'Фрилансер', - 'developer' => 'Разработчик', - 'startup' => 'Стартап', - 'agency' => 'Агентство', - 'small_business' => 'Малый бизнес', - 'marketer' => 'Маркетолог', - 'online_store' => 'Интернет-магазин', - 'other' => 'Другое', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'Какова ваша цель с TryPost?', - 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', - 'goals' => [ - 'save_time' => 'Экономить время, публикуя всюду сразу', - 'ai_content' => 'Создавать посты быстрее с помощью ИИ', - 'plan_calendar' => 'Планировать посты в календаре', - 'stay_on_brand' => 'Держать каждый пост в стиле бренда', - 'grow_audience' => 'Наращивать аудиторию и вовлечённость', - 'drive_sales' => 'Получать больше трафика и продаж', - 'manage_clients' => 'Управлять несколькими брендами или клиентами', - 'team_collaboration' => 'Работать с командой', - 'automate_api' => 'Автоматизировать публикацию с помощью API, MCP или кода', - 'track_performance' => 'Отслеживать эффективность постов', - 'just_exploring' => 'Пока просто знакомлюсь', - 'other' => 'Что-то ещё', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Как вы нас нашли?', - 'referral_source_description' => 'Это помогает нам понять, как люди узнают о TryPost.', - 'referral_source' => [ - 'google' => 'Google или поиск', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram или Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'ИИ-ассистент (ChatGPT, Claude…)', - 'friend' => 'Друг или коллега', - 'blog' => 'Блог, рассылка или статья', - 'other' => 'Что-то другое', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'Подключите первую сеть', - 'description' => 'Привяжите хотя бы один социальный аккаунт, чтобы начать планировать. Вы можете добавить другие в любой момент.', - 'must_connect' => 'Подключите хотя бы одну сеть, чтобы продолжить.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/tr/onboarding.php b/lang/tr/onboarding.php index b54630a4..ddfc9174 100644 --- a/lang/tr/onboarding.php +++ b/lang/tr/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => 'TryPost\'a hoş geldiniz', - 'description' => 'Deneyiminizi kişiselleştirebilmemiz için sizi veya işinizi en iyi tanımlayan seçeneği belirtin.', - 'continue' => 'Devam et', - 'personas' => [ - 'creator' => 'İçerik üreticisi', - 'freelancer' => 'Serbest çalışan', - 'developer' => 'Geliştirici', - 'startup' => 'Girişim', - 'agency' => 'Ajans', - 'small_business' => 'Küçük işletme', - 'marketer' => 'Pazarlamacı', - 'online_store' => 'Çevrimiçi mağaza', - 'other' => 'Diğer', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => 'TryPost ile hedefiniz nedir?', - 'goals_description' => 'Size uyan her şeyi seçin, biz de TryPost\'u sizin için ayarlayalım.', - 'goals' => [ - 'save_time' => 'Her yere aynı anda paylaşarak zaman kazanmak', - 'ai_content' => 'AI ile daha hızlı gönderi oluşturmak', - 'plan_calendar' => 'Gönderilerimi bir takvimde planlamak', - 'stay_on_brand' => 'Her gönderiyi marka çizgisinde tutmak', - 'grow_audience' => 'Kitlemi ve etkileşimimi büyütmek', - 'drive_sales' => 'Daha fazla trafik ve satış elde etmek', - 'manage_clients' => 'Birden fazla marka veya müşteri yönetmek', - 'team_collaboration' => 'Ekibimle çalışmak', - 'automate_api' => 'API, MCP veya kodla paylaşımı otomatikleştirmek', - 'track_performance' => 'Gönderilerimin performansını görmek', - 'just_exploring' => 'Şimdilik sadece keşfetmek', - 'other' => 'Başka bir şey', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => 'Bizi nasıl buldunuz?', - 'referral_source_description' => 'İnsanların TryPost\'u nasıl keşfettiğini anlamamıza yardımcı olur.', - 'referral_source' => [ - 'google' => 'Google veya arama', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram veya Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Yapay zeka asistanı (ChatGPT, Claude…)', - 'friend' => 'Arkadaş veya meslektaş', - 'blog' => 'Blog, bülten veya makale', - 'other' => 'Başka bir şey', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => 'İlk ağınızı bağlayın', - 'description' => 'Zamanlamaya başlamak için en az bir sosyal hesap bağlayın. İstediğiniz zaman daha fazlasını ekleyebilirsiniz.', - 'must_connect' => 'Devam etmek için en az bir ağ bağlayın.', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/lang/zh/onboarding.php b/lang/zh/onboarding.php index 9761ce99..ddfc9174 100644 --- a/lang/zh/onboarding.php +++ b/lang/zh/onboarding.php @@ -3,55 +3,39 @@ declare(strict_types=1); return [ - 'title' => '欢迎使用 TryPost', - 'description' => '告诉我们最能描述你或你业务的选项,以便我们为你定制体验。', - 'continue' => '继续', - 'personas' => [ - 'creator' => '内容创作者', - 'freelancer' => '自由职业者', - 'developer' => '开发者', - 'startup' => '初创公司', - 'agency' => '代理机构', - 'small_business' => '小型企业', - 'marketer' => '营销人员', - 'online_store' => '网店', - 'other' => '其他', + 'title' => 'Activate your TryPost workspace', + 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - 'goals_title' => '你使用 TryPost 的目标是什么?', - 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', - 'goals' => [ - 'save_time' => '一次发布到所有平台,节省时间', - 'ai_content' => '借助 AI 更快地创建帖子', - 'plan_calendar' => '在日历上规划我的帖子', - 'stay_on_brand' => '让每一条帖子都符合品牌调性', - 'grow_audience' => '增长我的受众和互动', - 'drive_sales' => '获得更多流量和销量', - 'manage_clients' => '管理多个品牌或客户', - 'team_collaboration' => '与我的团队协作', - 'automate_api' => '通过 API、MCP 或代码自动发帖', - 'track_performance' => '查看我的帖子表现', - 'just_exploring' => '目前只是随便看看', - 'other' => '其他需求', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', ], - 'referral_source_title' => '您是如何找到我们的?', - 'referral_source_description' => '这有助于我们了解人们是如何发现 TryPost 的。', - 'referral_source' => [ - 'google' => 'Google 或搜索', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram 或 Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI 助手(ChatGPT、Claude 等)', - 'friend' => '朋友或同事', - 'blog' => '博客、新闻通讯或文章', - 'other' => '其他', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', ], - 'connect' => [ - 'title' => '连接你的第一个平台', - 'description' => '至少关联一个社交账号即可开始排期。你可以随时添加更多。', - 'must_connect' => '请至少连接一个平台以继续。', + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', + ], + 'residual' => [ + 'title' => 'Finish whenever you are ready', + 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', ], ]; diff --git a/resources/js/pages/onboarding/Index.vue b/resources/js/pages/onboarding/Index.vue new file mode 100644 index 00000000..adda4b96 --- /dev/null +++ b/resources/js/pages/onboarding/Index.vue @@ -0,0 +1,366 @@ + + + From 47262044f3df6add6eca04e8008078581600c31f Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:47:56 -0300 Subject: [PATCH 11/17] Send post-checkout users to onboarding instead of accounts. Co-authored-by: Cursor --- resources/js/pages/billing/Processing.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/js/pages/billing/Processing.vue b/resources/js/pages/billing/Processing.vue index f16f026f..1045f1b7 100644 --- a/resources/js/pages/billing/Processing.vue +++ b/resources/js/pages/billing/Processing.vue @@ -4,7 +4,7 @@ import { IconLoader2 } from '@tabler/icons-vue'; import { onMounted, onUnmounted, ref, watch } from 'vue'; import { useTracking } from '@/composables/useTracking'; -import { accounts } from '@/routes/app'; +import { onboarding } from '@/routes/app'; import type { Auth } from '@/types'; const props = defineProps<{ @@ -34,7 +34,7 @@ const { trackPurchase } = useTracking(); const finishing = ref(false); let redirectTimer: ReturnType | null = null; -const goToAccounts = () => router.visit(accounts.url()); +const goToOnboarding = () => router.visit(onboarding.url()); // Fires `checkout.completed` exactly once for a real checkout. A trial-with-card // subscription is already `subscribed()` (status `trialing`) by the time the @@ -61,7 +61,7 @@ const completePurchase = () => { // Always hold for the same window before navigating, so PostHog and the ad // pixels (Google/Meta via dataLayer → GTM) reliably flush. - redirectTimer = setTimeout(goToAccounts, REDIRECT_DELAY_MS); + redirectTimer = setTimeout(goToOnboarding, REDIRECT_DELAY_MS); }; watch( From ec64768f7ca04e83651dcd382e44b90c5295077b Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 13:52:49 -0300 Subject: [PATCH 12/17] Add dismissible residual onboarding banner in app shell. Co-authored-by: Cursor --- .../Middleware/App/HandleInertiaRequests.php | 4 ++ lang/ar/onboarding.php | 2 + lang/de/onboarding.php | 2 + lang/el/onboarding.php | 2 + lang/en/onboarding.php | 2 + lang/es/onboarding.php | 2 + lang/fr/onboarding.php | 2 + lang/it/onboarding.php | 2 + lang/ja/onboarding.php | 2 + lang/ko/onboarding.php | 2 + lang/nl/onboarding.php | 2 + lang/pl/onboarding.php | 2 + lang/pt-BR/onboarding.php | 2 + lang/ru/onboarding.php | 2 + lang/tr/onboarding.php | 2 + lang/zh/onboarding.php | 2 + .../onboarding/OnboardingResidualBanner.vue | 51 +++++++++++++++++++ resources/js/layouts/app/AppSidebarLayout.vue | 21 ++++++-- resources/js/types/index.d.ts | 2 +- .../OnboardingResidualShareTest.php | 36 +++++++++++++ 20 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 resources/js/components/onboarding/OnboardingResidualBanner.vue create mode 100644 tests/Feature/Onboarding/OnboardingResidualShareTest.php diff --git a/app/Http/Middleware/App/HandleInertiaRequests.php b/app/Http/Middleware/App/HandleInertiaRequests.php index 5e51d6d8..86fdd869 100644 --- a/app/Http/Middleware/App/HandleInertiaRequests.php +++ b/app/Http/Middleware/App/HandleInertiaRequests.php @@ -4,6 +4,7 @@ namespace App\Http\Middleware\App; +use App\Actions\Onboarding\ResolveOnboardingStatus; use App\Http\Resources\App\HandleInertiaRequests\AuthAccountResource; use App\Http\Resources\App\HandleInertiaRequests\AuthPlanResource; use App\Http\Resources\App\HandleInertiaRequests\AuthUserResource; @@ -47,6 +48,9 @@ public function share(Request $request): array ], 'usage' => $account && ! $isSelfHosted ? $account->usage() : null, 'features' => $account && ! $isSelfHosted ? $account->featureLimits() : null, + 'onboardingResidual' => fn (): bool => $user && $account + ? (bool) data_get(app(ResolveOnboardingStatus::class)->handle($user), 'show_residual', false) + : false, 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'flash' => $request->session()->get('flash', []), 'applicationUrl' => config('app.url'), diff --git a/lang/ar/onboarding.php b/lang/ar/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/ar/onboarding.php +++ b/lang/ar/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/de/onboarding.php b/lang/de/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/de/onboarding.php +++ b/lang/de/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/el/onboarding.php b/lang/el/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/el/onboarding.php +++ b/lang/el/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/en/onboarding.php b/lang/en/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/en/onboarding.php +++ b/lang/en/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/es/onboarding.php b/lang/es/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/es/onboarding.php +++ b/lang/es/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/fr/onboarding.php b/lang/fr/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/fr/onboarding.php +++ b/lang/fr/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/it/onboarding.php b/lang/it/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/it/onboarding.php +++ b/lang/it/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/ja/onboarding.php b/lang/ja/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/ja/onboarding.php +++ b/lang/ja/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/ko/onboarding.php b/lang/ko/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/ko/onboarding.php +++ b/lang/ko/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/nl/onboarding.php b/lang/nl/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/nl/onboarding.php +++ b/lang/nl/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/pl/onboarding.php b/lang/pl/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/pl/onboarding.php +++ b/lang/pl/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/pt-BR/onboarding.php b/lang/pt-BR/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/pt-BR/onboarding.php +++ b/lang/pt-BR/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/ru/onboarding.php b/lang/ru/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/ru/onboarding.php +++ b/lang/ru/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/tr/onboarding.php b/lang/tr/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/tr/onboarding.php +++ b/lang/tr/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/lang/zh/onboarding.php b/lang/zh/onboarding.php index ddfc9174..c9d5060f 100644 --- a/lang/zh/onboarding.php +++ b/lang/zh/onboarding.php @@ -37,5 +37,7 @@ 'residual' => [ 'title' => 'Finish whenever you are ready', 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', + 'cta' => 'Finish setup', + 'dismiss' => 'Dismiss onboarding reminder', ], ]; diff --git a/resources/js/components/onboarding/OnboardingResidualBanner.vue b/resources/js/components/onboarding/OnboardingResidualBanner.vue new file mode 100644 index 00000000..b33299cf --- /dev/null +++ b/resources/js/components/onboarding/OnboardingResidualBanner.vue @@ -0,0 +1,51 @@ + + + diff --git a/resources/js/layouts/app/AppSidebarLayout.vue b/resources/js/layouts/app/AppSidebarLayout.vue index f0ac4b82..a29232b3 100644 --- a/resources/js/layouts/app/AppSidebarLayout.vue +++ b/resources/js/layouts/app/AppSidebarLayout.vue @@ -1,15 +1,23 @@ diff --git a/resources/js/pages/welcome/Subscribe.vue b/resources/js/pages/welcome/Subscribe.vue deleted file mode 100644 index 611b2111..00000000 --- a/resources/js/pages/welcome/Subscribe.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - diff --git a/routes/app.php b/routes/app.php index 50b5b5dc..fa9d1d47 100644 --- a/routes/app.php +++ b/routes/app.php @@ -65,12 +65,9 @@ Route::post('welcome/goals', [WelcomeController::class, 'storeGoals'])->name('app.welcome.goals.store'); Route::get('welcome/referral-source', [WelcomeController::class, 'referralSource'])->name('app.welcome.referral-source'); Route::post('welcome/referral-source', [WelcomeController::class, 'storeReferralSource'])->name('app.welcome.referral-source.store'); - Route::get('welcome/subscribe', [WelcomeController::class, 'subscribe'])->name('app.welcome.subscribe'); - Route::post('welcome/checkout', [WelcomeController::class, 'checkout'])->name('app.welcome.checkout'); - Route::redirect('onboarding/goals', '/welcome/goals')->name('app.legacy-onboarding.goals'); Route::redirect('onboarding/referral-source', '/welcome/referral-source')->name('app.legacy-onboarding.referral-source'); - Route::redirect('onboarding/connect', '/welcome/subscribe')->name('app.legacy-onboarding.connect'); + Route::redirect('onboarding/connect', '/welcome/referral-source')->name('app.legacy-onboarding.connect'); Route::get('billing/processing', [BillingController::class, 'processing'])->name('app.billing.processing'); Route::get('workspaces/create', [WorkspaceController::class, 'create'])->name('app.workspaces.create'); diff --git a/tests/Feature/Welcome/WelcomeControllerTest.php b/tests/Feature/Welcome/WelcomeControllerTest.php index d5d3f1b1..c35b3211 100644 --- a/tests/Feature/Welcome/WelcomeControllerTest.php +++ b/tests/Feature/Welcome/WelcomeControllerTest.php @@ -135,6 +135,7 @@ function subscribeWelcomeAccount(Account $account): void 'persona' => Persona::Agency->value, 'goals' => [Goal::SaveTime->value], ]); + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); $this->actingAs($this->user->fresh()) ->get(route('app.welcome.referral-source')) @@ -142,6 +143,8 @@ function subscribeWelcomeAccount(Account $account): void ->assertInertia(fn ($page) => $page ->component('welcome/ReferralSource', false) ->has('sources', count(ReferralSource::cases())) + ->where('plan.name', $plan->name) + ->where('plan.interval', 'monthly') ); }); @@ -161,7 +164,7 @@ function subscribeWelcomeAccount(Account $account): void 'invalid' => [['referral_source' => 'not-a-source']], ]); -test('referral source store saves the source mirrors it to PostHog and advances to subscribe', function () { +test('referral source store saves the source and starts Stripe checkout without a social account', function () { config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); Bus::fake(); $this->user->update([ @@ -169,68 +172,6 @@ function subscribeWelcomeAccount(Account $account): void 'goals' => [Goal::SaveTime->value], ]); - $this->actingAs($this->user->fresh()) - ->post(route('app.welcome.referral-source.store'), [ - 'referral_source' => ReferralSource::ProductHunt->value, - ]) - ->assertRedirect(route('app.welcome.subscribe')); - - expect($this->user->fresh()->referral_source)->toBe(ReferralSource::ProductHunt); - Bus::assertDispatched(SendEvent::class, fn (SendEvent $event): bool => $event->method === 'capture' - && data_get($event->payload, 'event') === WelcomeEvent::ReferralSaved->value - && data_get($event->payload, 'properties.referral_source') === ReferralSource::ProductHunt->value); -}); - -test('subscribe redirects through incomplete welcome steps', function (array $attributes, string $routeName) { - $this->user->update($attributes); - - $this->actingAs($this->user->fresh()) - ->get(route('app.welcome.subscribe')) - ->assertRedirect(route($routeName)); -})->with([ - 'missing persona' => [[], 'app.welcome.persona'], - 'missing goals' => [['persona' => Persona::Agency->value], 'app.welcome.goals'], - 'missing referral source' => [[ - 'persona' => Persona::Agency->value, - 'goals' => [Goal::SaveTime->value], - ], 'app.welcome.referral-source'], -]); - -test('subscribe renders plan props without social account props', function () { - $this->user->update([ - 'persona' => Persona::Agency->value, - 'goals' => [Goal::SaveTime->value], - 'referral_source' => ReferralSource::Google->value, - ]); - $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); - - $this->actingAs($this->user->fresh()) - ->get(route('app.welcome.subscribe')) - ->assertOk() - ->assertInertia(fn ($page) => $page - ->component('welcome/Subscribe', false) - ->where('plan.name', $plan->name) - ->where('plan.interval', 'monthly') - ->missing('platforms') - ->missing('accounts') - ); -}); - -test('subscribe copy describes starting a subscription instead of connecting a network', function () { - $copy = strtolower(__('welcome.subscribe.title').' '.__('welcome.subscribe.description', locale: 'en')); - - expect($copy) - ->toContain('subscription') - ->not->toContain('connect') - ->not->toContain('network') - ->not->toContain('social account') - ->not->toContain('link'); -}); - -test('checkout starts without a connected social account and uses subscribe as its cancel url', function () { - config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test']); - Bus::fake(); - Plan::where('slug', Slug::Workspace)->firstOrFail()->update([ 'stripe_monthly_price_id' => 'price_monthly_test', ]); @@ -240,15 +181,20 @@ function subscribeWelcomeAccount(Account $account): void ->once() ->withArgs(fn (Account $account, string $priceId, string $cancelUrl): bool => $account->is($this->user->account) && $priceId === 'price_monthly_test' - && $cancelUrl === route('app.welcome.subscribe')) + && $cancelUrl === route('app.welcome.referral-source')) ->andReturn(redirect('https://checkout.stripe.test/session')); - $this->actingAs($this->user) - ->post(route('app.welcome.checkout')) + $this->actingAs($this->user->fresh()) + ->post(route('app.welcome.referral-source.store'), [ + 'referral_source' => ReferralSource::ProductHunt->value, + ]) ->assertRedirect('https://checkout.stripe.test/session'); + expect($this->user->fresh()->referral_source)->toBe(ReferralSource::ProductHunt); + Bus::assertDispatched(SendEvent::class, fn (SendEvent $event): bool => $event->method === 'capture' + && data_get($event->payload, 'event') === WelcomeEvent::ReferralSaved->value + && data_get($event->payload, 'properties.referral_source') === ReferralSource::ProductHunt->value); Bus::assertDispatched(SendEvent::class, fn (SendEvent $event): bool => $event->method === 'capture' - && data_get($event->payload, 'distinctId') === $this->user->id && data_get($event->payload, 'event') === WelcomeEvent::CheckoutStarted->value); }); @@ -269,8 +215,6 @@ function subscribeWelcomeAccount(Account $account): void 'goals store' => ['app.welcome.goals.store', 'post', ['goals' => [Goal::SaveTime->value]]], 'referral source' => ['app.welcome.referral-source', 'get'], 'referral source store' => ['app.welcome.referral-source.store', 'post', ['referral_source' => ReferralSource::Google->value]], - 'subscribe' => ['app.welcome.subscribe', 'get'], - 'checkout' => ['app.welcome.checkout', 'post'], ]); test('welcome redirects subscribed accounts without residual onboarding to calendar', function (array $attributes) { @@ -302,8 +246,6 @@ function subscribeWelcomeAccount(Account $account): void 'goals store' => ['app.welcome.goals.store', 'post', ['goals' => [Goal::SaveTime->value]]], 'referral source' => ['app.welcome.referral-source', 'get'], 'referral source store' => ['app.welcome.referral-source.store', 'post', ['referral_source' => ReferralSource::Google->value]], - 'subscribe' => ['app.welcome.subscribe', 'get'], - 'checkout' => ['app.welcome.checkout', 'post'], ]); test('legacy welcome URLs redirect to their replacement routes', function (string $legacyRoute, string $routeName) { @@ -313,5 +255,5 @@ function subscribeWelcomeAccount(Account $account): void })->with([ 'goals' => ['app.legacy-onboarding.goals', 'app.welcome.goals'], 'referral source' => ['app.legacy-onboarding.referral-source', 'app.welcome.referral-source'], - 'connect' => ['app.legacy-onboarding.connect', 'app.welcome.subscribe'], + 'connect' => ['app.legacy-onboarding.connect', 'app.welcome.referral-source'], ]); From 5dd498301967cae4c00212c63b4e895ca2319538 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 15:27:24 -0300 Subject: [PATCH 16/17] Use AppLayout with sidebar on the onboarding activation page. Co-authored-by: Cursor --- resources/js/pages/onboarding/Index.vue | 58 +++++++------------------ 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/resources/js/pages/onboarding/Index.vue b/resources/js/pages/onboarding/Index.vue index adda4b96..ea651884 100644 --- a/resources/js/pages/onboarding/Index.vue +++ b/resources/js/pages/onboarding/Index.vue @@ -16,7 +16,9 @@ import NetworkConnectGrid, { type AvailablePlatform, type ConnectedAccount, } from '@/components/accounts/NetworkConnectGrid.vue'; +import PageHeader from '@/components/PageHeader.vue'; import { Button } from '@/components/ui/button'; +import AppLayout from '@/layouts/AppLayout.vue'; import { copyToClipboard } from '@/lib/utils'; import { complete, dismiss } from '@/routes/app/onboarding'; @@ -74,46 +76,26 @@ const continueToTryPost = (): void => { From 8659da85d476a45da65125be9d673cdc29fa91cc Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 24 Jul 2026 17:04:28 -0300 Subject: [PATCH 17/17] Polish welcome and onboarding UX and unify the sidebar account menu. Tighten welcome persona/goals/referral screens, slim goal options, redesign residual onboarding in the sidebar footer, and merge workspace/user menus into one header dropdown with notifications at the top. Co-authored-by: Cursor --- app/Enums/User/Goal.php | 3 - .../Controllers/App/OnboardingController.php | 14 +- .../Middleware/App/HandleInertiaRequests.php | 25 +- lang/ar/onboarding.php | 32 +- lang/ar/sidebar.php | 2 + lang/ar/welcome.php | 11 +- lang/de/onboarding.php | 32 +- lang/de/sidebar.php | 2 + lang/de/welcome.php | 11 +- lang/el/onboarding.php | 32 +- lang/el/sidebar.php | 2 + lang/el/welcome.php | 11 +- lang/en/onboarding.php | 32 +- lang/en/sidebar.php | 2 + lang/en/welcome.php | 11 +- lang/es/onboarding.php | 32 +- lang/es/sidebar.php | 2 + lang/es/welcome.php | 11 +- lang/fr/onboarding.php | 32 +- lang/fr/sidebar.php | 2 + lang/fr/welcome.php | 11 +- lang/it/onboarding.php | 32 +- lang/it/sidebar.php | 2 + lang/it/welcome.php | 11 +- lang/ja/onboarding.php | 32 +- lang/ja/sidebar.php | 2 + lang/ja/welcome.php | 11 +- lang/ko/onboarding.php | 32 +- lang/ko/sidebar.php | 2 + lang/ko/welcome.php | 11 +- lang/nl/onboarding.php | 32 +- lang/nl/sidebar.php | 2 + lang/nl/welcome.php | 11 +- lang/pl/onboarding.php | 32 +- lang/pl/sidebar.php | 2 + lang/pl/welcome.php | 11 +- lang/pt-BR/onboarding.php | 68 ++- lang/pt-BR/sidebar.php | 2 + lang/pt-BR/welcome.php | 11 +- lang/ru/onboarding.php | 32 +- lang/ru/sidebar.php | 2 + lang/ru/welcome.php | 11 +- lang/tr/onboarding.php | 32 +- lang/tr/sidebar.php | 2 + lang/tr/welcome.php | 11 +- lang/zh/onboarding.php | 32 +- lang/zh/sidebar.php | 2 + lang/zh/welcome.php | 11 +- public/images/ai/chatgpt-black.svg | 1 + public/images/ai/chatgpt-white.svg | 1 + public/images/ai/claude.svg | 1 + resources/js/components/AppSidebar.vue | 254 +++++++--- resources/js/components/NavUser.vue | 56 -- resources/js/components/NotificationBell.vue | 10 +- resources/js/components/UserMenuContent.vue | 115 ----- .../js/components/WorkspaceMenuContent.vue | 200 ++++++++ .../onboarding/OnboardingResidualBanner.vue | 51 -- resources/js/layouts/WelcomeLayout.vue | 57 ++- resources/js/layouts/app/AppSidebarLayout.vue | 9 +- resources/js/pages/onboarding/Index.vue | 477 +++++++++++------- resources/js/pages/welcome/Goals.vue | 97 ++-- resources/js/pages/welcome/Persona.vue | 88 +++- resources/js/pages/welcome/ReferralSource.vue | 128 +++-- resources/js/types/index.d.ts | 7 +- .../Onboarding/OnboardingControllerTest.php | 14 +- .../OnboardingResidualShareTest.php | 7 +- .../Feature/Welcome/WelcomeControllerTest.php | 17 + 67 files changed, 1509 insertions(+), 834 deletions(-) create mode 100644 public/images/ai/chatgpt-black.svg create mode 100644 public/images/ai/chatgpt-white.svg create mode 100644 public/images/ai/claude.svg delete mode 100644 resources/js/components/NavUser.vue delete mode 100644 resources/js/components/UserMenuContent.vue create mode 100644 resources/js/components/WorkspaceMenuContent.vue delete mode 100644 resources/js/components/onboarding/OnboardingResidualBanner.vue diff --git a/app/Enums/User/Goal.php b/app/Enums/User/Goal.php index ced8c382..836e4bd1 100644 --- a/app/Enums/User/Goal.php +++ b/app/Enums/User/Goal.php @@ -13,9 +13,6 @@ enum Goal: string case GrowAudience = 'grow_audience'; case DriveSales = 'drive_sales'; case ManageClients = 'manage_clients'; - case TeamCollaboration = 'team_collaboration'; - case AutomateApi = 'automate_api'; - case TrackPerformance = 'track_performance'; case JustExploring = 'just_exploring'; case Other = 'other'; } diff --git a/app/Http/Controllers/App/OnboardingController.php b/app/Http/Controllers/App/OnboardingController.php index 1168d691..71c73e44 100644 --- a/app/Http/Controllers/App/OnboardingController.php +++ b/app/Http/Controllers/App/OnboardingController.php @@ -52,8 +52,18 @@ public function index(Request $request): Response|RedirectResponse 'status' => $status, 'mcpUrl' => url('/mcp/trypost'), 'mcpClients' => [ - ['id' => 'claude', 'label' => 'Claude'], - ['id' => 'chatgpt', 'label' => 'ChatGPT'], + [ + 'id' => 'claude', + 'label' => 'Claude', + 'logo' => '/images/ai/claude.svg', + 'settings_url' => 'https://claude.ai/customize/connectors', + ], + [ + 'id' => 'chatgpt', + 'label' => 'ChatGPT', + 'logo' => '/images/ai/chatgpt-white.svg', + 'settings_url' => 'https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins', + ], ], 'samplePrompt' => __('onboarding.first_post.sample_prompt'), 'platforms' => $platforms, diff --git a/app/Http/Middleware/App/HandleInertiaRequests.php b/app/Http/Middleware/App/HandleInertiaRequests.php index 86fdd869..b1c6c129 100644 --- a/app/Http/Middleware/App/HandleInertiaRequests.php +++ b/app/Http/Middleware/App/HandleInertiaRequests.php @@ -48,9 +48,28 @@ public function share(Request $request): array ], 'usage' => $account && ! $isSelfHosted ? $account->usage() : null, 'features' => $account && ! $isSelfHosted ? $account->featureLimits() : null, - 'onboardingResidual' => fn (): bool => $user && $account - ? (bool) data_get(app(ResolveOnboardingStatus::class)->handle($user), 'show_residual', false) - : false, + 'onboardingResidual' => function () use ($user, $account): array|false { + if (! $user || ! $account) { + return false; + } + + $status = app(ResolveOnboardingStatus::class)->handle($user); + + if (! data_get($status, 'show_residual', false)) { + return false; + } + + $completed = collect([ + data_get($status, 'social_connected'), + data_get($status, 'mcp_connected'), + data_get($status, 'first_post_created'), + ])->filter()->count(); + + return [ + 'completed' => $completed, + 'total' => 3, + ]; + }, 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'flash' => $request->session()->get('flash', []), 'applicationUrl' => config('app.url'), diff --git a/lang/ar/onboarding.php b/lang/ar/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/ar/onboarding.php +++ b/lang/ar/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/ar/sidebar.php b/lang/ar/sidebar.php index a9587e13..3919d330 100644 --- a/lang/ar/sidebar.php +++ b/lang/ar/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'التحليلات', 'automations' => 'الأتمتة', 'settings' => 'الإعدادات', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'أكمل الإعداد', 'posts' => [ 'calendar' => 'التقويم', diff --git a/lang/ar/welcome.php b/lang/ar/welcome.php index 987ebac3..b1f2179b 100644 --- a/lang/ar/welcome.php +++ b/lang/ar/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'مرحبًا بك في TryPost', - 'description' => 'أخبرنا بما يصفك أنت أو نشاطك التجاري على أفضل وجه حتى نتمكن من تخصيص تجربتك.', + 'title' => 'ما الذي يصفك بشكل أفضل؟', + 'description' => 'اختر الخيار الأقرب وسنخصص تجربتك.', 'continue' => 'متابعة', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'صانع محتوى', 'freelancer' => 'مستقل', @@ -17,7 +19,7 @@ 'online_store' => 'متجر إلكتروني', 'other' => 'أخرى', ], - 'goals_title' => 'ما هدفك من استخدام TryPost؟', + 'goals_title' => 'ما هدفك؟', 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', 'goals' => [ 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', @@ -27,9 +29,6 @@ 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', - 'team_collaboration' => 'العمل مع فريقي', - 'automate_api' => 'أتمتة النشر عبر الواجهة البرمجية أو MCP أو الكود', - 'track_performance' => 'معرفة أداء منشوراتي', 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', 'other' => 'شيء آخر', ], diff --git a/lang/de/onboarding.php b/lang/de/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/de/onboarding.php +++ b/lang/de/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/de/sidebar.php b/lang/de/sidebar.php index c9a593f9..799612bd 100644 --- a/lang/de/sidebar.php +++ b/lang/de/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automatisierungen', 'settings' => 'Einstellungen', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Einrichtung abschließen', 'posts' => [ 'calendar' => 'Kalender', diff --git a/lang/de/welcome.php b/lang/de/welcome.php index 4e03b9c7..a9470d85 100644 --- a/lang/de/welcome.php +++ b/lang/de/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Willkommen bei TryPost', - 'description' => 'Sag uns, was dich oder dein Unternehmen am besten beschreibt, damit wir dein Erlebnis anpassen können.', + 'title' => 'Was beschreibt dich am besten?', + 'description' => 'Wähle die passende Option, und wir passen dein Erlebnis an.', 'continue' => 'Weiter', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Content Creator', 'freelancer' => 'Freelancer', @@ -17,7 +19,7 @@ 'online_store' => 'Onlineshop', 'other' => 'Sonstiges', ], - 'goals_title' => 'Was ist dein Ziel mit TryPost?', + 'goals_title' => 'Was ist dein Ziel?', 'goals_description' => 'Wähle alles aus, was passt, und wir richten TryPost für dich ein.', 'goals' => [ 'save_time' => 'Zeit sparen, indem ich überall gleichzeitig poste', @@ -27,9 +29,6 @@ 'grow_audience' => 'Meine Reichweite und mein Engagement steigern', 'drive_sales' => 'Mehr Traffic und Verkäufe erzielen', 'manage_clients' => 'Mehrere Marken oder Kunden verwalten', - 'team_collaboration' => 'Mit meinem Team arbeiten', - 'automate_api' => 'Das Posten per API, MCP oder Code automatisieren', - 'track_performance' => 'Sehen, wie meine Beiträge performen', 'just_exploring' => 'Ich schaue mich vorerst nur um', 'other' => 'Etwas anderes', ], diff --git a/lang/el/onboarding.php b/lang/el/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/el/onboarding.php +++ b/lang/el/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/el/sidebar.php b/lang/el/sidebar.php index a19adb71..3da750fa 100644 --- a/lang/el/sidebar.php +++ b/lang/el/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Στατιστικά', 'automations' => 'Αυτοματισμοί', 'settings' => 'Ρυθμίσεις', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Ολοκλήρωση ρύθμισης', 'posts' => [ 'calendar' => 'Ημερολόγιο', diff --git a/lang/el/welcome.php b/lang/el/welcome.php index f729056e..878af48e 100644 --- a/lang/el/welcome.php +++ b/lang/el/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Καλώς ήρθατε στο TryPost', - 'description' => 'Πείτε μας τι σας περιγράφει καλύτερα, εσάς ή την επιχείρησή σας, ώστε να προσαρμόσουμε την εμπειρία σας.', + 'title' => 'Τι σας περιγράφει καλύτερα;', + 'description' => 'Επιλέξτε την πιο κοντινή επιλογή και θα προσαρμόσουμε την εμπειρία σας.', 'continue' => 'Συνέχεια', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Δημιουργός περιεχομένου', 'freelancer' => 'Ελεύθερος επαγγελματίας', @@ -17,7 +19,7 @@ 'online_store' => 'Ηλεκτρονικό κατάστημα', 'other' => 'Άλλο', ], - 'goals_title' => 'Ποιος είναι ο στόχος σας με το TryPost;', + 'goals_title' => 'Ποιος είναι ο στόχος σας;', 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', 'goals' => [ 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', @@ -27,9 +29,6 @@ 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', - 'team_collaboration' => 'Συνεργασία με την ομάδα μου', - 'automate_api' => 'Αυτοματοποίηση δημοσιεύσεων με το API, το MCP ή κώδικα', - 'track_performance' => 'Παρακολούθηση της απόδοσης των δημοσιεύσεών μου', 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', 'other' => 'Κάτι άλλο', ], diff --git a/lang/en/onboarding.php b/lang/en/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/en/onboarding.php +++ b/lang/en/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/en/sidebar.php b/lang/en/sidebar.php index f9cddbd6..0df286fa 100644 --- a/lang/en/sidebar.php +++ b/lang/en/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automations', 'settings' => 'Settings', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Finish setup', 'posts' => [ 'calendar' => 'Calendar', diff --git a/lang/en/welcome.php b/lang/en/welcome.php index b2f02455..760e2116 100644 --- a/lang/en/welcome.php +++ b/lang/en/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Welcome to TryPost', - 'description' => 'Tell us what best describes you or your business so we can tailor your experience.', + 'title' => 'What best describes you?', + 'description' => 'Choose the closest match and we\'ll tailor your experience.', 'continue' => 'Continue', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Content creator', 'freelancer' => 'Freelancer', @@ -17,7 +19,7 @@ 'online_store' => 'Online store', 'other' => 'Other', ], - 'goals_title' => 'What\'s your goal with TryPost?', + 'goals_title' => 'What\'s your goal?', 'goals_description' => 'Pick everything that fits and we\'ll set TryPost up for you.', 'goals' => [ 'save_time' => 'Save time by posting everywhere at once', @@ -27,9 +29,6 @@ 'grow_audience' => 'Grow my audience and engagement', 'drive_sales' => 'Get more traffic and sales', 'manage_clients' => 'Manage several brands or clients', - 'team_collaboration' => 'Work with my team', - 'automate_api' => 'Automate posting with the API, MCP or code', - 'track_performance' => 'See how my posts perform', 'just_exploring' => 'Just exploring for now', 'other' => 'Something else', ], diff --git a/lang/es/onboarding.php b/lang/es/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/es/onboarding.php +++ b/lang/es/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/es/sidebar.php b/lang/es/sidebar.php index c6f768d4..0c9789e5 100644 --- a/lang/es/sidebar.php +++ b/lang/es/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automatizaciones', 'settings' => 'Configuración', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Termina la configuración', 'posts' => [ 'calendar' => 'Calendario', diff --git a/lang/es/welcome.php b/lang/es/welcome.php index 45539ac7..bd514bc9 100644 --- a/lang/es/welcome.php +++ b/lang/es/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Bienvenido a TryPost', - 'description' => 'Cuéntanos qué te describe mejor a ti o a tu negocio para personalizar tu experiencia.', + 'title' => '¿Qué te describe mejor?', + 'description' => 'Elige la opción más cercana y personalizaremos tu experiencia.', 'continue' => 'Continuar', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Creador de contenido', 'freelancer' => 'Freelancer', @@ -17,7 +19,7 @@ 'online_store' => 'Tienda online', 'other' => 'Otro', ], - 'goals_title' => '¿Cuál es tu objetivo con TryPost?', + 'goals_title' => '¿Cuál es tu objetivo?', 'goals_description' => 'Marca todo lo que encaje y adaptamos TryPost a ti.', 'goals' => [ 'save_time' => 'Ahorrar tiempo publicando en todas mis redes a la vez', @@ -27,9 +29,6 @@ 'grow_audience' => 'Hacer crecer mi audiencia y engagement', 'drive_sales' => 'Conseguir más tráfico y ventas', 'manage_clients' => 'Gestionar varias marcas o clientes', - 'team_collaboration' => 'Trabajar con mi equipo', - 'automate_api' => 'Automatizar publicaciones con la API, MCP o código', - 'track_performance' => 'Ver cómo rinden mis publicaciones', 'just_exploring' => 'Solo estoy explorando por ahora', 'other' => 'Otra cosa', ], diff --git a/lang/fr/onboarding.php b/lang/fr/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/fr/onboarding.php +++ b/lang/fr/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/fr/sidebar.php b/lang/fr/sidebar.php index a6325025..3586a524 100644 --- a/lang/fr/sidebar.php +++ b/lang/fr/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Statistiques', 'automations' => 'Automatisations', 'settings' => 'Paramètres', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Terminer la configuration', 'posts' => [ 'calendar' => 'Calendrier', diff --git a/lang/fr/welcome.php b/lang/fr/welcome.php index 6ff697e8..5512f7b1 100644 --- a/lang/fr/welcome.php +++ b/lang/fr/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Bienvenue sur TryPost', - 'description' => 'Dites-nous ce qui vous décrit le mieux, vous ou votre entreprise, afin que nous puissions personnaliser votre expérience.', + 'title' => 'Qu\'est-ce qui vous décrit le mieux ?', + 'description' => 'Choisissez l\'option la plus proche et nous personnaliserons votre expérience.', 'continue' => 'Continuer', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Créateur de contenu', 'freelancer' => 'Freelance', @@ -17,7 +19,7 @@ 'online_store' => 'Boutique en ligne', 'other' => 'Autre', ], - 'goals_title' => 'Quel est votre objectif avec TryPost ?', + 'goals_title' => 'Quel est votre objectif ?', 'goals_description' => 'Choisissez tout ce qui vous correspond et nous configurerons TryPost pour vous.', 'goals' => [ 'save_time' => 'Gagner du temps en publiant partout à la fois', @@ -27,9 +29,6 @@ 'grow_audience' => 'Développer mon audience et mon engagement', 'drive_sales' => 'Obtenir plus de trafic et de ventes', 'manage_clients' => 'Gérer plusieurs marques ou clients', - 'team_collaboration' => 'Travailler avec mon équipe', - 'automate_api' => 'Automatiser la publication avec l\'API, le MCP ou du code', - 'track_performance' => 'Voir les performances de mes publications', 'just_exploring' => 'Je découvre pour l\'instant', 'other' => 'Autre chose', ], diff --git a/lang/it/onboarding.php b/lang/it/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/it/onboarding.php +++ b/lang/it/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/it/sidebar.php b/lang/it/sidebar.php index fa517924..48f24136 100644 --- a/lang/it/sidebar.php +++ b/lang/it/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Statistiche', 'automations' => 'Automazioni', 'settings' => 'Impostazioni', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Completa la configurazione', 'posts' => [ 'calendar' => 'Calendario', diff --git a/lang/it/welcome.php b/lang/it/welcome.php index 61684544..ffe12459 100644 --- a/lang/it/welcome.php +++ b/lang/it/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Benvenuto su TryPost', - 'description' => 'Dicci cosa descrive meglio te o la tua attività così possiamo personalizzare la tua esperienza.', + 'title' => 'Cosa ti descrive meglio?', + 'description' => 'Scegli l\'opzione più vicina e personalizzeremo la tua esperienza.', 'continue' => 'Continua', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Creatore di contenuti', 'freelancer' => 'Freelance', @@ -17,7 +19,7 @@ 'online_store' => 'Negozio online', 'other' => 'Altro', ], - 'goals_title' => 'Qual è il tuo obiettivo con TryPost?', + 'goals_title' => 'Qual è il tuo obiettivo?', 'goals_description' => 'Scegli tutto ciò che fa per te e configureremo TryPost per te.', 'goals' => [ 'save_time' => 'Risparmiare tempo pubblicando ovunque in una volta', @@ -27,9 +29,6 @@ 'grow_audience' => 'Far crescere il mio pubblico e il coinvolgimento', 'drive_sales' => 'Ottenere più traffico e vendite', 'manage_clients' => 'Gestire più brand o clienti', - 'team_collaboration' => 'Lavorare con il mio team', - 'automate_api' => 'Automatizzare la pubblicazione con API, MCP o codice', - 'track_performance' => 'Vedere come vanno i miei post', 'just_exploring' => 'Sto solo dando un\'occhiata', 'other' => 'Qualcos\'altro', ], diff --git a/lang/ja/onboarding.php b/lang/ja/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/ja/onboarding.php +++ b/lang/ja/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/ja/sidebar.php b/lang/ja/sidebar.php index 09ff7c59..c52e0222 100644 --- a/lang/ja/sidebar.php +++ b/lang/ja/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'アナリティクス', 'automations' => 'オートメーション', 'settings' => '設定', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'セットアップを完了', 'posts' => [ 'calendar' => 'カレンダー', diff --git a/lang/ja/welcome.php b/lang/ja/welcome.php index 0daa4b06..fc38eec9 100644 --- a/lang/ja/welcome.php +++ b/lang/ja/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'TryPost へようこそ', - 'description' => 'あなたやあなたのビジネスに最も当てはまるものを教えてください。体験を最適化します。', + 'title' => 'あなたに一番近いのは?', + 'description' => '近いものを選ぶと、体験を最適化します。', 'continue' => '続ける', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'コンテンツクリエイター', 'freelancer' => 'フリーランス', @@ -17,7 +19,7 @@ 'online_store' => 'オンラインストア', 'other' => 'その他', ], - 'goals_title' => 'TryPost での目標は何ですか?', + 'goals_title' => '目標は何ですか?', 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', 'goals' => [ 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', @@ -27,9 +29,6 @@ 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', 'drive_sales' => 'トラフィックと売上を増やす', 'manage_clients' => '複数のブランドやクライアントを管理する', - 'team_collaboration' => 'チームで作業する', - 'automate_api' => 'API、MCP、コードで投稿を自動化する', - 'track_performance' => '投稿のパフォーマンスを確認する', 'just_exploring' => '今はまだ様子を見ている', 'other' => 'その他', ], diff --git a/lang/ko/onboarding.php b/lang/ko/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/ko/onboarding.php +++ b/lang/ko/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/ko/sidebar.php b/lang/ko/sidebar.php index 3d47a2bf..2ba1faca 100644 --- a/lang/ko/sidebar.php +++ b/lang/ko/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => '분석', 'automations' => '자동화', 'settings' => '설정', + 'onboarding' => 'Getting started', + 'onboarding_hint' => '설정 마치기', 'posts' => [ 'calendar' => '캘린더', diff --git a/lang/ko/welcome.php b/lang/ko/welcome.php index 03c91d04..c3910d41 100644 --- a/lang/ko/welcome.php +++ b/lang/ko/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'TryPost에 오신 것을 환영합니다', - 'description' => '회원님이나 비즈니스를 가장 잘 설명하는 항목을 알려주시면 맞춤형 경험을 제공해 드립니다.', + 'title' => '무엇을 가장 잘 설명하나요?', + 'description' => '가장 가까운 항목을 선택하면 맞춤 경험을 제공해 드립니다.', 'continue' => '계속', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => '콘텐츠 크리에이터', 'freelancer' => '프리랜서', @@ -17,7 +19,7 @@ 'online_store' => '온라인 스토어', 'other' => '기타', ], - 'goals_title' => 'TryPost로 이루려는 목표는 무엇인가요?', + 'goals_title' => '목표가 무엇인가요?', 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', 'goals' => [ 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', @@ -27,9 +29,6 @@ 'grow_audience' => '팔로워와 참여 늘리기', 'drive_sales' => '더 많은 트래픽과 판매 유도', 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', - 'team_collaboration' => '팀과 협업', - 'automate_api' => 'API, MCP 또는 코드로 게시 자동화', - 'track_performance' => '게시물 성과 확인', 'just_exploring' => '지금은 둘러보는 중', 'other' => '다른 것', ], diff --git a/lang/nl/onboarding.php b/lang/nl/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/nl/onboarding.php +++ b/lang/nl/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/nl/sidebar.php b/lang/nl/sidebar.php index daca60f2..6f94cd61 100644 --- a/lang/nl/sidebar.php +++ b/lang/nl/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Statistieken', 'automations' => 'Automatiseringen', 'settings' => 'Instellingen', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Setup afronden', 'posts' => [ 'calendar' => 'Kalender', diff --git a/lang/nl/welcome.php b/lang/nl/welcome.php index a7371a14..dcd994d4 100644 --- a/lang/nl/welcome.php +++ b/lang/nl/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Welkom bij TryPost', - 'description' => 'Vertel ons wat jou of je bedrijf het beste omschrijft, zodat we je ervaring kunnen afstemmen.', + 'title' => 'Wat omschrijft jou het beste?', + 'description' => 'Kies de dichtstbijzijnde optie, dan stemmen we je ervaring af.', 'continue' => 'Doorgaan', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Contentmaker', 'freelancer' => 'Freelancer', @@ -17,7 +19,7 @@ 'online_store' => 'Webshop', 'other' => 'Anders', ], - 'goals_title' => 'Wat is je doel met TryPost?', + 'goals_title' => 'Wat is je doel?', 'goals_description' => 'Kies alles wat past en we stellen TryPost voor je in.', 'goals' => [ 'save_time' => 'Tijd besparen door overal tegelijk te posten', @@ -27,9 +29,6 @@ 'grow_audience' => 'Mijn publiek en betrokkenheid laten groeien', 'drive_sales' => 'Meer verkeer en verkopen krijgen', 'manage_clients' => 'Meerdere merken of klanten beheren', - 'team_collaboration' => 'Samenwerken met mijn team', - 'automate_api' => 'Posten automatiseren met de API, MCP of code', - 'track_performance' => 'Zien hoe mijn posts presteren', 'just_exploring' => 'Voorlopig gewoon aan het verkennen', 'other' => 'Iets anders', ], diff --git a/lang/pl/onboarding.php b/lang/pl/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/pl/onboarding.php +++ b/lang/pl/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/pl/sidebar.php b/lang/pl/sidebar.php index 9e83547d..f3c47c79 100644 --- a/lang/pl/sidebar.php +++ b/lang/pl/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Analityka', 'automations' => 'Automatyzacje', 'settings' => 'Ustawienia', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Dokończ konfigurację', 'posts' => [ 'calendar' => 'Kalendarz', diff --git a/lang/pl/welcome.php b/lang/pl/welcome.php index eb63df1d..a5a31a31 100644 --- a/lang/pl/welcome.php +++ b/lang/pl/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Witamy w TryPost', - 'description' => 'Powiedz nam, co najlepiej opisuje Ciebie lub Twoją firmę, abyśmy mogli dopasować Twoje doświadczenie.', + 'title' => 'Co najlepiej Cię opisuje?', + 'description' => 'Wybierz najbliższą opcję, a my dopasujemy Twoje doświadczenie.', 'continue' => 'Kontynuuj', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Twórca treści', 'freelancer' => 'Freelancer', @@ -17,7 +19,7 @@ 'online_store' => 'Sklep internetowy', 'other' => 'Inne', ], - 'goals_title' => 'Jaki jest Twój cel z TryPost?', + 'goals_title' => 'Jaki jest Twój cel?', 'goals_description' => 'Wybierz wszystko, co pasuje, a my skonfigurujemy TryPost dla Ciebie.', 'goals' => [ 'save_time' => 'Oszczędzaj czas, publikując wszędzie naraz', @@ -27,9 +29,6 @@ 'grow_audience' => 'Powiększaj grono odbiorców i zaangażowanie', 'drive_sales' => 'Zdobywaj więcej ruchu i sprzedaży', 'manage_clients' => 'Zarządzaj wieloma markami lub klientami', - 'team_collaboration' => 'Pracuj z moim zespołem', - 'automate_api' => 'Automatyzuj publikowanie za pomocą API, MCP lub kodu', - 'track_performance' => 'Sprawdzaj, jak radzą sobie moje posty', 'just_exploring' => 'Na razie tylko się rozglądam', 'other' => 'Coś innego', ], diff --git a/lang/pt-BR/onboarding.php b/lang/pt-BR/onboarding.php index c9d5060f..35dea7e7 100644 --- a/lang/pt-BR/onboarding.php +++ b/lang/pt-BR/onboarding.php @@ -3,41 +3,55 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', - 'skip' => 'Skip for now', - 'continue' => 'Continue to TryPost', + 'title' => 'Primeiros passos', + 'welcome' => 'Boas-vindas ao TryPost, :name', + 'description' => 'Siga os passos abaixo pra ver como o TryPost funciona e publicar seu primeiro post.', + 'skip' => 'Pular por agora', + 'continue' => 'Continuar no TryPost', + 'progress' => ':done de :total concluídos', + 'progress_label' => 'Progresso', + 'next_step' => 'Próximo', + 'step_label' => 'Passo :number', + 'status' => [ + 'complete' => 'Concluído', + 'todo' => 'Pendente', + ], 'mcp' => [ - 'title' => 'Connect your AI assistant', - 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', - 'copy' => 'Copy URL', - 'copied' => 'MCP URL copied.', + 'title' => 'Conecte seu assistente de IA', + 'description' => 'Adicione o TryPost como servidor MCP para o assistente criar e gerenciar posts por você.', + 'copy_step' => 'Copie a URL do servidor TryPost', + 'open_step' => 'Abra seu assistente de IA', + 'open_hint' => 'Escolha um. A página de configurações abre em uma nova aba.', + 'url_label' => 'URL do servidor MCP', + 'copy' => 'Copiar URL', + 'copied' => 'URL do MCP copiada.', + 'guide' => 'Abrir configurações', + 'connect' => 'Conectar com :client', 'clients' => [ - 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', - 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + 'claude' => 'Abra Settings → Connectors, adicione um connector customizado e cole a URL acima.', + 'chatgpt' => 'Abra Settings → Apps & Connectors, crie um connector customizado e cole a URL acima.', ], ], 'social' => [ - 'title' => 'Connect a social account', - 'description' => 'Choose at least one network where TryPost can publish your content.', + 'title' => 'Conecte uma rede social', + 'description' => 'Escolha pelo menos uma rede onde o TryPost possa publicar seu conteúdo.', + 'action' => 'Escolha onde você quer publicar', + 'action_hint' => 'Conecte pelo menos uma conta. Você pode adicionar mais quando quiser.', ], 'first_post' => [ - 'title' => 'Create your first post', - 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', - 'prompt_label' => 'Sample prompt', - 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', - 'copy_prompt' => 'Copy prompt', - 'copied' => 'Sample prompt copied.', - 'create_button' => 'Create your first post', + 'title' => 'Crie seu primeiro post', + 'description' => 'Use este prompt no seu assistente, ou crie o post direto no TryPost.', + 'action' => 'Crie um post e veja o TryPost em ação', + 'action_hint' => 'Envie este prompt pro assistente, ou abra o editor do TryPost.', + 'prompt_label' => 'Prompt de exemplo', + 'sample_prompt' => 'Crie um post social amigável apresentando minha marca e adapte para cada rede conectada.', + 'copy_prompt' => 'Copiar prompt', + 'copied' => 'Prompt de exemplo copiado.', + 'create_button' => 'Criar seu primeiro post', + 'or' => 'ou', ], 'ready' => [ - 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'title' => 'Tudo pronto pra publicar', + 'description' => 'Você já pode seguir. Continue no TryPost e comece a planejar seu conteúdo.', ], ]; diff --git a/lang/pt-BR/sidebar.php b/lang/pt-BR/sidebar.php index 0717d621..b5532cc3 100644 --- a/lang/pt-BR/sidebar.php +++ b/lang/pt-BR/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automações', 'settings' => 'Configurações', + 'onboarding' => 'Primeiros passos', + 'onboarding_hint' => 'Complete a configuração', 'posts' => [ 'calendar' => 'Calendário', diff --git a/lang/pt-BR/welcome.php b/lang/pt-BR/welcome.php index a4296b4d..7018e232 100644 --- a/lang/pt-BR/welcome.php +++ b/lang/pt-BR/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Bem-vindo ao TryPost', - 'description' => 'Conte o que melhor descreve você ou seu negócio para personalizarmos sua experiência.', + 'title' => 'O que melhor descreve você?', + 'description' => 'Escolha a opção mais próxima e a gente personaliza sua experiência.', 'continue' => 'Continuar', + 'progress' => 'Progresso do onboarding', + 'go_to_step' => 'Ir para a etapa :step', 'personas' => [ 'creator' => 'Criador de conteúdo', 'freelancer' => 'Freelancer', @@ -17,7 +19,7 @@ 'online_store' => 'Loja online', 'other' => 'Outro', ], - 'goals_title' => 'Qual o seu objetivo com o TryPost?', + 'goals_title' => 'Qual o seu objetivo?', 'goals_description' => 'Marque tudo que faz sentido e a gente ajusta o TryPost pra você.', 'goals' => [ 'save_time' => 'Economizar tempo postando em todas as redes de uma vez', @@ -27,9 +29,6 @@ 'grow_audience' => 'Crescer minha audiência e engajamento', 'drive_sales' => 'Conseguir mais tráfego e vendas', 'manage_clients' => 'Gerenciar várias marcas ou clientes', - 'team_collaboration' => 'Trabalhar com meu time', - 'automate_api' => 'Automatizar publicações com a API, MCP ou código', - 'track_performance' => 'Ver o desempenho dos meus posts', 'just_exploring' => 'Só dando uma olhada por enquanto', 'other' => 'Outra coisa', ], diff --git a/lang/ru/onboarding.php b/lang/ru/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/ru/onboarding.php +++ b/lang/ru/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/ru/sidebar.php b/lang/ru/sidebar.php index 4ebffe4e..592d3f7c 100644 --- a/lang/ru/sidebar.php +++ b/lang/ru/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Аналитика', 'automations' => 'Автоматизации', 'settings' => 'Настройки', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Завершите настройку', 'posts' => [ 'calendar' => 'Календарь', diff --git a/lang/ru/welcome.php b/lang/ru/welcome.php index 63afebd6..cb66161f 100644 --- a/lang/ru/welcome.php +++ b/lang/ru/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'Добро пожаловать в TryPost', - 'description' => 'Расскажите, что лучше всего описывает вас или ваш бизнес, чтобы мы могли настроить работу под вас.', + 'title' => 'Что лучше всего вас описывает?', + 'description' => 'Выберите ближайший вариант — мы настроим опыт под вас.', 'continue' => 'Продолжить', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'Автор контента', 'freelancer' => 'Фрилансер', @@ -17,7 +19,7 @@ 'online_store' => 'Интернет-магазин', 'other' => 'Другое', ], - 'goals_title' => 'Какова ваша цель с TryPost?', + 'goals_title' => 'Какова ваша цель?', 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', 'goals' => [ 'save_time' => 'Экономить время, публикуя всюду сразу', @@ -27,9 +29,6 @@ 'grow_audience' => 'Наращивать аудиторию и вовлечённость', 'drive_sales' => 'Получать больше трафика и продаж', 'manage_clients' => 'Управлять несколькими брендами или клиентами', - 'team_collaboration' => 'Работать с командой', - 'automate_api' => 'Автоматизировать публикацию с помощью API, MCP или кода', - 'track_performance' => 'Отслеживать эффективность постов', 'just_exploring' => 'Пока просто знакомлюсь', 'other' => 'Что-то ещё', ], diff --git a/lang/tr/onboarding.php b/lang/tr/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/tr/onboarding.php +++ b/lang/tr/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/tr/sidebar.php b/lang/tr/sidebar.php index c7936fc9..57744069 100644 --- a/lang/tr/sidebar.php +++ b/lang/tr/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => 'Analitik', 'automations' => 'Otomasyonlar', 'settings' => 'Ayarlar', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Kurulumu bitir', 'posts' => [ 'calendar' => 'Takvim', diff --git a/lang/tr/welcome.php b/lang/tr/welcome.php index 6f628e6a..748acfb9 100644 --- a/lang/tr/welcome.php +++ b/lang/tr/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => 'TryPost\'a hoş geldiniz', - 'description' => 'Deneyiminizi kişiselleştirebilmemiz için sizi veya işinizi en iyi tanımlayan seçeneği belirtin.', + 'title' => 'Sizi en iyi ne tanımlar?', + 'description' => 'En yakın seçeneği seçin, deneyiminizi kişiselleştirelim.', 'continue' => 'Devam et', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => 'İçerik üreticisi', 'freelancer' => 'Serbest çalışan', @@ -17,7 +19,7 @@ 'online_store' => 'Çevrimiçi mağaza', 'other' => 'Diğer', ], - 'goals_title' => 'TryPost ile hedefiniz nedir?', + 'goals_title' => 'Hedefiniz nedir?', 'goals_description' => 'Size uyan her şeyi seçin, biz de TryPost\'u sizin için ayarlayalım.', 'goals' => [ 'save_time' => 'Her yere aynı anda paylaşarak zaman kazanmak', @@ -27,9 +29,6 @@ 'grow_audience' => 'Kitlemi ve etkileşimimi büyütmek', 'drive_sales' => 'Daha fazla trafik ve satış elde etmek', 'manage_clients' => 'Birden fazla marka veya müşteri yönetmek', - 'team_collaboration' => 'Ekibimle çalışmak', - 'automate_api' => 'API, MCP veya kodla paylaşımı otomatikleştirmek', - 'track_performance' => 'Gönderilerimin performansını görmek', 'just_exploring' => 'Şimdilik sadece keşfetmek', 'other' => 'Başka bir şey', ], diff --git a/lang/zh/onboarding.php b/lang/zh/onboarding.php index c9d5060f..87034ba7 100644 --- a/lang/zh/onboarding.php +++ b/lang/zh/onboarding.php @@ -3,15 +3,30 @@ declare(strict_types=1); return [ - 'title' => 'Activate your TryPost workspace', - 'description' => 'Complete these three essentials to connect your tools, publish everywhere, and start creating.', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', 'skip' => 'Skip for now', 'continue' => 'Continue to TryPost', + 'progress' => ':done of :total complete', + 'progress_label' => 'Setup progress', + 'next_step' => 'Up next', + 'step_label' => 'Step :number', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + ], 'mcp' => [ 'title' => 'Connect your AI assistant', 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'open_hint' => 'Choose one. Its settings page opens in a new tab.', + 'url_label' => 'MCP server URL', 'copy' => 'Copy URL', 'copied' => 'MCP URL copied.', + 'guide' => 'Open settings', + 'connect' => 'Connect with :client', 'clients' => [ 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', @@ -20,24 +35,23 @@ 'social' => [ 'title' => 'Connect a social account', 'description' => 'Choose at least one network where TryPost can publish your content.', + 'action' => 'Choose where you want to publish', + 'action_hint' => 'Connect at least one account. You can add more whenever you want.', ], 'first_post' => [ 'title' => 'Create your first post', 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'action' => 'Create a post and see TryPost in action', + 'action_hint' => 'Send this prompt to your assistant, or open the TryPost editor.', 'prompt_label' => 'Sample prompt', 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', 'copy_prompt' => 'Copy prompt', 'copied' => 'Sample prompt copied.', 'create_button' => 'Create your first post', + 'or' => 'or', ], 'ready' => [ 'title' => 'You are ready to publish', - 'description' => 'Your workspace is activated. Continue to TryPost and start planning your content.', - ], - 'residual' => [ - 'title' => 'Finish whenever you are ready', - 'description' => 'TryPost checks your progress automatically. Complete all three items to continue.', - 'cta' => 'Finish setup', - 'dismiss' => 'Dismiss onboarding reminder', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/zh/sidebar.php b/lang/zh/sidebar.php index 46a3eda6..eb38fcb6 100644 --- a/lang/zh/sidebar.php +++ b/lang/zh/sidebar.php @@ -30,6 +30,8 @@ 'analytics' => '分析', 'automations' => '自动化', 'settings' => '设置', + 'onboarding' => 'Getting started', + 'onboarding_hint' => '完成设置', 'posts' => [ 'calendar' => '日历', diff --git a/lang/zh/welcome.php b/lang/zh/welcome.php index ce9d394d..b24ec949 100644 --- a/lang/zh/welcome.php +++ b/lang/zh/welcome.php @@ -3,9 +3,11 @@ declare(strict_types=1); return [ - 'title' => '欢迎使用 TryPost', - 'description' => '告诉我们最能描述你或你业务的选项,以便我们为你定制体验。', + 'title' => '哪项最能描述你?', + 'description' => '选择最接近的一项,我们会为你定制体验。', 'continue' => '继续', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', 'personas' => [ 'creator' => '内容创作者', 'freelancer' => '自由职业者', @@ -17,7 +19,7 @@ 'online_store' => '网店', 'other' => '其他', ], - 'goals_title' => '你使用 TryPost 的目标是什么?', + 'goals_title' => '你的目标是什么?', 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', 'goals' => [ 'save_time' => '一次发布到所有平台,节省时间', @@ -27,9 +29,6 @@ 'grow_audience' => '增长我的受众和互动', 'drive_sales' => '获得更多流量和销量', 'manage_clients' => '管理多个品牌或客户', - 'team_collaboration' => '与我的团队协作', - 'automate_api' => '通过 API、MCP 或代码自动发帖', - 'track_performance' => '查看我的帖子表现', 'just_exploring' => '目前只是随便看看', 'other' => '其他需求', ], diff --git a/public/images/ai/chatgpt-black.svg b/public/images/ai/chatgpt-black.svg new file mode 100644 index 00000000..9c80705e --- /dev/null +++ b/public/images/ai/chatgpt-black.svg @@ -0,0 +1 @@ +ChatGPT \ No newline at end of file diff --git a/public/images/ai/chatgpt-white.svg b/public/images/ai/chatgpt-white.svg new file mode 100644 index 00000000..fb4d0ac4 --- /dev/null +++ b/public/images/ai/chatgpt-white.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/public/images/ai/claude.svg b/public/images/ai/claude.svg new file mode 100644 index 00000000..5d8d7461 --- /dev/null +++ b/public/images/ai/claude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 7aea78e4..3662cfdf 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -1,5 +1,5 @@ \ No newline at end of file + diff --git a/resources/js/components/NavUser.vue b/resources/js/components/NavUser.vue deleted file mode 100644 index 8253dbf4..00000000 --- a/resources/js/components/NavUser.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/resources/js/components/NotificationBell.vue b/resources/js/components/NotificationBell.vue index 7fcd5aab..d6db6e5d 100644 --- a/resources/js/components/NotificationBell.vue +++ b/resources/js/components/NotificationBell.vue @@ -205,16 +205,16 @@ onBeforeUnmount(() => {
diff --git a/resources/js/components/UserMenuContent.vue b/resources/js/components/UserMenuContent.vue deleted file mode 100644 index 2fc9c54d..00000000 --- a/resources/js/components/UserMenuContent.vue +++ /dev/null @@ -1,115 +0,0 @@ - - - diff --git a/resources/js/components/WorkspaceMenuContent.vue b/resources/js/components/WorkspaceMenuContent.vue new file mode 100644 index 00000000..d47015b0 --- /dev/null +++ b/resources/js/components/WorkspaceMenuContent.vue @@ -0,0 +1,200 @@ + + + diff --git a/resources/js/components/onboarding/OnboardingResidualBanner.vue b/resources/js/components/onboarding/OnboardingResidualBanner.vue deleted file mode 100644 index b33299cf..00000000 --- a/resources/js/components/onboarding/OnboardingResidualBanner.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - diff --git a/resources/js/layouts/WelcomeLayout.vue b/resources/js/layouts/WelcomeLayout.vue index 48a2dae6..b51414f7 100644 --- a/resources/js/layouts/WelcomeLayout.vue +++ b/resources/js/layouts/WelcomeLayout.vue @@ -1,9 +1,15 @@