diff --git a/app/Enums/PostHog/WelcomeEvent.php b/app/Enums/PostHog/WelcomeEvent.php new file mode 100644 index 00000000..934937de --- /dev/null +++ b/app/Enums/PostHog/WelcomeEvent.php @@ -0,0 +1,12 @@ +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 5f906a17..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 = $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 = $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..1db42bc4 --- /dev/null +++ b/app/Http/Controllers/App/WelcomeController.php @@ -0,0 +1,240 @@ +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, + ]); + $postHog->capture( + $user->id, + WelcomeEvent::Persona->value, + ['persona' => $persona], + $user->account, + ); + + return redirect()->route('app.welcome.goals'); + } + + public function goals(Request $request): InertiaResponse|RedirectResponse + { + if ($redirect = $this->redirectIfStepIncomplete($request)) { + return $redirect; + } + + $user = $request->user(); + + 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->redirectIfStepIncomplete($request)) { + return $redirect; + } + + $user = $request->user(); + $goals = array_values($request->validated('goals')); + + $user->update(['goals' => $goals]); + + $postHog->identify($user->id, [ + 'goals' => $goals, + ]); + $postHog->capture( + $user->id, + WelcomeEvent::Goals->value, + ['goals' => $goals], + $user->account, + ); + + return redirect()->route('app.welcome.referral-source'); + } + + public function referralSource(Request $request): InertiaResponse|RedirectResponse + { + if ($redirect = $this->redirectIfStepIncomplete($request, requireGoals: true)) { + return $redirect; + } + + $user = $request->user(); + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + + return Inertia::render('welcome/ReferralSource', [ + 'sources' => array_map(fn (ReferralSource $source): string => $source->value, ReferralSource::cases()), + 'selected' => $user->referral_source?->value, + 'plan' => [ + 'name' => $plan->name, + 'interval' => 'monthly', + ], + ]); + } + + public function storeReferralSource( + StoreWelcomeReferralSourceRequest $request, + StartSubscriptionCheckout $checkout, + PostHogService $postHog, + ): Response|RedirectResponse { + if ($redirect = $this->redirectIfStepIncomplete($request, requireGoals: true)) { + return $redirect; + } + + $user = $request->user(); + + abort_unless($user->isAccountOwner(), Response::HTTP_FORBIDDEN); + + $referralSource = (string) $request->validated('referral_source'); + + $user->update(['referral_source' => $referralSource]); + + $postHog->identify($user->id, [ + 'referral_source' => $referralSource, + ]); + $postHog->capture( + $user->id, + WelcomeEvent::Referral->value, + ['referral_source' => $referralSource], + $user->account, + ); + + return $this->startCheckout($request, $checkout); + } + + private function startCheckout( + Request $request, + StartSubscriptionCheckout $checkout, + ): Response|RedirectResponse { + $user = $request->user(); + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + $priceId = $plan->stripe_monthly_price_id; + + abort_if($priceId === null, Response::HTTP_INTERNAL_SERVER_ERROR, 'Monthly price is not configured.'); + + return $checkout->redirect( + $user->account, + $priceId, + route('app.welcome.referral-source'), + ); + } + + public function subscriptionRequired(Request $request): InertiaResponse|RedirectResponse + { + $user = $request->user(); + + if ($user->account?->hasAppAccess()) { + return redirect()->route('app.calendar'); + } + + if ($user->isAccountOwner()) { + return redirect()->route('app.welcome.persona'); + } + + return Inertia::render('welcome/SubscriptionRequired', [ + 'ownerName' => $user->account?->owner?->name, + ]); + } + + private function redirectIfStepIncomplete(Request $request, bool $requireGoals = false): ?RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + if ($requireGoals && ! $this->hasCurrentGoals($user)) { + return redirect()->route('app.welcome.goals'); + } + + return null; + } + + /** + * True when the user has at least one goal that still exists in Goal. + * Dropped enum values must not satisfy the gate or users mid-funnel can + * skip re-selecting after we slim the list. + */ + private function hasCurrentGoals(User $user): bool + { + $goals = $user->goals; + + if (! is_array($goals) || $goals === []) { + return false; + } + + $allowed = array_map(fn (Goal $goal): string => $goal->value, Goal::cases()); + + return array_intersect($goals, $allowed) !== []; + } + + private function redirectIfUnavailable(Request $request): ?RedirectResponse + { + $user = $request->user(); + + // Match EnsureAccountReady — generic-trial (no-card) users already have + // app access and must not be sent through Stripe checkout again. + // Self-hosted always has app access, so welcome/checkout is skipped too. + if ($user->account?->hasAppAccess()) { + return redirect()->route('app.calendar'); + } + + // Members can't check out — hold them on a dedicated screen instead of + // walking an ICP flow they can never finish. + if (! $user->isAccountOwner()) { + return redirect()->route('app.welcome.subscription-required'); + } + + return null; + } +} diff --git a/app/Http/Middleware/App/EnsureAccountReady.php b/app/Http/Middleware/App/EnsureAccountReady.php index b0f1011c..9e41604b 100644 --- a/app/Http/Middleware/App/EnsureAccountReady.php +++ b/app/Http/Middleware/App/EnsureAccountReady.php @@ -25,7 +25,13 @@ public function handle(Request $request, Closure $next): Response $account = $user->account; if (! $account?->hasAppAccess()) { - return redirect()->route('app.onboarding'); + // Members can't finish Welcome checkout — send them straight to + // the hold screen instead of hopping through persona first. + if (! $user->isAccountOwner()) { + return redirect()->route('app.welcome.subscription-required'); + } + + 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/lang/ar/auth.php b/lang/ar/auth.php index c40107c9..ac5f6858 100644 --- a/lang/ar/auth.php +++ b/lang/ar/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'تسجيل الدخول', 'email' => 'البريد الإلكتروني', 'password' => 'كلمة المرور', + 'show_password' => 'إظهار كلمة المرور', + 'hide_password' => 'إخفاء كلمة المرور', 'forgot_password' => 'نسيت كلمة المرور؟', 'remember_me' => 'تذكّرني', 'submit' => 'تسجيل الدخول', diff --git a/lang/ar/onboarding.php b/lang/ar/onboarding.php index 5103f1d3..3c69fa2a 100644 --- a/lang/ar/onboarding.php +++ b/lang/ar/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', 'goals' => [ 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', - 'ai_content' => 'إنشاء منشورات أسرع بالذكاء الاصطناعي', + 'ai_content' => 'إنشاء منشورات بذكاء TryPost الاصطناعي', + 'use_mcp' => 'إنشاء منشورات عبر Claude أو ChatGPT أو Cursor', 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', - 'team_collaboration' => 'العمل مع فريقي', - 'automate_api' => 'أتمتة النشر عبر الواجهة البرمجية أو MCP أو الكود', - 'track_performance' => 'معرفة أداء منشوراتي', 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', 'other' => 'شيء آخر', ], diff --git a/lang/ar/sidebar.php b/lang/ar/sidebar.php index 08691c29..0e468b80 100644 --- a/lang/ar/sidebar.php +++ b/lang/ar/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'التوثيق', 'referral' => 'اربح عمولة إحالة 30%', - 'stay_updated' => 'ابقَ على اطلاع', + 'discord' => 'مجتمع Discord', ], ]; diff --git a/lang/ar/welcome.php b/lang/ar/welcome.php new file mode 100644 index 00000000..04932d72 --- /dev/null +++ b/lang/ar/welcome.php @@ -0,0 +1,57 @@ + 'ما الذي يصفك بشكل أفضل؟', + 'description' => 'اختر الخيار الأقرب وسنخصص تجربتك.', + 'continue' => 'متابعة', + 'subscription_required_title' => 'في انتظار مالك الحساب', + 'subscription_required_description' => 'هذا الحساب لا يملك اشتراكًا نشطًا بعد. اطلب من مالك الحساب إكمال الدفع — ستحصل على وصول كامل فور تفعيل الاشتراك.', + 'subscription_required_owner' => 'مالك حسابك هو :name.', + 'subscription_required_auto' => 'يتم تحديث هذه الصفحة تلقائيًا — لا حاجة لإعادة التحميل.', + 'progress' => 'تقدم الترحيب', + 'go_to_step' => 'الانتقال إلى الخطوة :step', + 'step_current' => 'الخطوة :step (الحالية)', + 'personas' => [ + 'creator' => 'صانع محتوى', + 'freelancer' => 'مستقل', + 'developer' => 'مطوّر', + 'startup' => 'شركة ناشئة', + 'agency' => 'وكالة', + 'small_business' => 'نشاط تجاري صغير', + 'marketer' => 'مسوّق', + 'online_store' => 'متجر إلكتروني', + 'other' => 'أخرى', + ], + 'goals_title' => 'ما هدفك؟', + 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', + 'goals' => [ + 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', + 'ai_content' => 'إنشاء منشورات بذكاء TryPost الاصطناعي', + 'use_mcp' => 'إنشاء منشورات عبر Claude أو ChatGPT أو Cursor', + 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', + 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', + 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', + 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', + 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', + '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' => 'شيء آخر', + ], +]; diff --git a/lang/de/auth.php b/lang/de/auth.php index f9536dbc..75721ef6 100644 --- a/lang/de/auth.php +++ b/lang/de/auth.php @@ -73,6 +73,8 @@ 'page_title' => 'Anmelden', 'email' => 'E-Mail-Adresse', 'password' => 'Passwort', + 'show_password' => 'Passwort anzeigen', + 'hide_password' => 'Passwort verbergen', 'forgot_password' => 'Passwort vergessen?', 'remember_me' => 'Angemeldet bleiben', 'submit' => 'Anmelden', diff --git a/lang/de/onboarding.php b/lang/de/onboarding.php index 9ccf8ad7..aad34eb0 100644 --- a/lang/de/onboarding.php +++ b/lang/de/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Beiträge mit TryPost-KI erstellen', + 'use_mcp' => 'Beiträge über Claude, ChatGPT oder Cursor 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', ], diff --git a/lang/de/sidebar.php b/lang/de/sidebar.php index 35872c7d..0e6129f0 100644 --- a/lang/de/sidebar.php +++ b/lang/de/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Dokumentation', 'referral' => '30% Provision verdienen', - 'stay_updated' => 'Auf dem Laufenden bleiben', + 'discord' => 'Discord-Community', ], ]; diff --git a/lang/de/welcome.php b/lang/de/welcome.php new file mode 100644 index 00000000..510408bd --- /dev/null +++ b/lang/de/welcome.php @@ -0,0 +1,57 @@ + 'Was beschreibt dich am besten?', + 'description' => 'Wähle die passende Option, und wir passen dein Erlebnis an.', + 'continue' => 'Weiter', + 'subscription_required_title' => 'Warten auf den Kontoinhaber', + 'subscription_required_description' => 'Dieses Konto hat noch kein aktives Abo. Bitte den Kontoinhaber, den Checkout abzuschließen — du erhältst vollen Zugriff, sobald es aktiv ist.', + 'subscription_required_owner' => 'Der Kontoinhaber ist :name.', + 'subscription_required_auto' => 'Diese Seite aktualisiert sich automatisch — kein Neuladen nötig.', + 'progress' => 'Willkommensfortschritt', + 'go_to_step' => 'Zu Schritt :step gehen', + 'step_current' => 'Schritt :step (aktuell)', + '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?', + '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 mit TryPost-KI erstellen', + 'use_mcp' => 'Beiträge über Claude, ChatGPT oder Cursor 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', + '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', + ], +]; diff --git a/lang/el/auth.php b/lang/el/auth.php index 1e78eae9..f128cc62 100644 --- a/lang/el/auth.php +++ b/lang/el/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Σύνδεση', 'email' => 'Διεύθυνση email', 'password' => 'Κωδικός πρόσβασης', + 'show_password' => 'Εμφάνιση κωδικού', + 'hide_password' => 'Απόκρυψη κωδικού', 'forgot_password' => 'Ξεχάσατε τον κωδικό;', 'remember_me' => 'Να με θυμάσαι', 'submit' => 'Σύνδεση', diff --git a/lang/el/onboarding.php b/lang/el/onboarding.php index d0eccc36..3d0799a0 100644 --- a/lang/el/onboarding.php +++ b/lang/el/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', 'goals' => [ 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', - 'ai_content' => 'Δημιουργία δημοσιεύσεων ταχύτερα με AI', + 'ai_content' => 'Δημιουργία δημοσιεύσεων με το AI του TryPost', + 'use_mcp' => 'Δημιουργία δημοσιεύσεων από Claude, ChatGPT ή Cursor', 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', - 'team_collaboration' => 'Συνεργασία με την ομάδα μου', - 'automate_api' => 'Αυτοματοποίηση δημοσιεύσεων με το API, το MCP ή κώδικα', - 'track_performance' => 'Παρακολούθηση της απόδοσης των δημοσιεύσεών μου', 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', 'other' => 'Κάτι άλλο', ], diff --git a/lang/el/sidebar.php b/lang/el/sidebar.php index de0a14c7..8ce651f4 100644 --- a/lang/el/sidebar.php +++ b/lang/el/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Τεκμηρίωση', 'referral' => 'Κερδίστε 30% από συστάσεις', - 'stay_updated' => 'Μείνετε ενημερωμένοι', + 'discord' => 'Κοινότητα Discord', ], ]; diff --git a/lang/el/welcome.php b/lang/el/welcome.php new file mode 100644 index 00000000..ae97a50c --- /dev/null +++ b/lang/el/welcome.php @@ -0,0 +1,57 @@ + 'Τι σας περιγράφει καλύτερα;', + 'description' => 'Επιλέξτε την πιο κοντινή επιλογή και θα προσαρμόσουμε την εμπειρία σας.', + 'continue' => 'Συνέχεια', + 'subscription_required_title' => 'Αναμονή για τον κάτοχο του λογαριασμού', + 'subscription_required_description' => 'Αυτός ο λογαριασμός δεν έχει ακόμη ενεργή συνδρομή. Ζητήστε από τον κάτοχο να ολοκληρώσει την πληρωμή — θα έχετε πλήρη πρόσβαση μόλις ενεργοποιηθεί.', + 'subscription_required_owner' => 'Ο κάτοχος του λογαριασμού σας είναι ο/η :name.', + 'subscription_required_auto' => 'Αυτή η σελίδα ενημερώνεται αυτόματα — δεν χρειάζεται ανανέωση.', + 'progress' => 'Πρόοδος καλωσορίσματος', + 'go_to_step' => 'Μετάβαση στο βήμα :step', + 'step_current' => 'Βήμα :step (τρέχον)', + 'personas' => [ + 'creator' => 'Δημιουργός περιεχομένου', + 'freelancer' => 'Ελεύθερος επαγγελματίας', + 'developer' => 'Προγραμματιστής', + 'startup' => 'Startup', + 'agency' => 'Πρακτορείο', + 'small_business' => 'Μικρή επιχείρηση', + 'marketer' => 'Marketer', + 'online_store' => 'Ηλεκτρονικό κατάστημα', + 'other' => 'Άλλο', + ], + 'goals_title' => 'Ποιος είναι ο στόχος σας;', + 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', + 'goals' => [ + 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', + 'ai_content' => 'Δημιουργία δημοσιεύσεων με το AI του TryPost', + 'use_mcp' => 'Δημιουργία δημοσιεύσεων από Claude, ChatGPT ή Cursor', + 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', + 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', + 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', + 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', + 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', + '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' => 'Κάτι άλλο', + ], +]; diff --git a/lang/en/auth.php b/lang/en/auth.php index c4027a4f..71fc5f38 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Log in', 'email' => 'Email address', 'password' => 'Password', + 'show_password' => 'Show password', + 'hide_password' => 'Hide password', 'forgot_password' => 'Forgot password?', 'remember_me' => 'Remember me', 'submit' => 'Log in', diff --git a/lang/en/onboarding.php b/lang/en/onboarding.php index e76cf671..1c96007c 100644 --- a/lang/en/onboarding.php +++ b/lang/en/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Generate posts with TryPost AI', + 'use_mcp' => 'Create posts from Claude, ChatGPT, or Cursor', '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', ], diff --git a/lang/en/sidebar.php b/lang/en/sidebar.php index 0e2b2a04..d8d21536 100644 --- a/lang/en/sidebar.php +++ b/lang/en/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Documentation', 'referral' => 'Earn 30% referral', - 'stay_updated' => 'Stay updated', + 'discord' => 'Discord community', ], ]; diff --git a/lang/en/welcome.php b/lang/en/welcome.php new file mode 100644 index 00000000..262db0ce --- /dev/null +++ b/lang/en/welcome.php @@ -0,0 +1,57 @@ + 'What best describes you?', + 'description' => 'Choose the closest match and we\'ll tailor your experience.', + 'continue' => 'Continue', + 'subscription_required_title' => 'Waiting for the account owner', + 'subscription_required_description' => 'This account doesn\'t have an active subscription yet. Ask the account owner to finish checkout — you\'ll get full access as soon as it is active.', + 'subscription_required_owner' => 'Your account owner is :name.', + 'subscription_required_auto' => 'This page updates automatically — no need to refresh.', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', + 'step_current' => 'Step :step (current)', + '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?', + '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' => 'Generate posts with TryPost AI', + 'use_mcp' => 'Create posts from Claude, ChatGPT, or Cursor', + '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', + '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', + ], +]; diff --git a/lang/es/auth.php b/lang/es/auth.php index 2675b6ca..520f83f7 100644 --- a/lang/es/auth.php +++ b/lang/es/auth.php @@ -59,6 +59,8 @@ 'page_title' => 'Iniciar sesión', 'email' => 'Correo electrónico', 'password' => 'Contraseña', + 'show_password' => 'Mostrar contraseña', + 'hide_password' => 'Ocultar contraseña', 'forgot_password' => '¿Olvidaste tu contraseña?', 'remember_me' => 'Recuérdame', 'submit' => 'Iniciar sesión', diff --git a/lang/es/onboarding.php b/lang/es/onboarding.php index 4fb16d04..91e12023 100644 --- a/lang/es/onboarding.php +++ b/lang/es/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Generar publicaciones con la IA de TryPost', + 'use_mcp' => 'Crear publicaciones desde Claude, ChatGPT o Cursor', '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', ], diff --git a/lang/es/sidebar.php b/lang/es/sidebar.php index b5804ae6..63b454b6 100644 --- a/lang/es/sidebar.php +++ b/lang/es/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Documentación', 'referral' => 'Gana 30% de comisión', - 'stay_updated' => 'Mantente al día', + 'discord' => 'Comunidad de Discord', ], ]; diff --git a/lang/es/welcome.php b/lang/es/welcome.php new file mode 100644 index 00000000..17c5bf55 --- /dev/null +++ b/lang/es/welcome.php @@ -0,0 +1,57 @@ + '¿Qué te describe mejor?', + 'description' => 'Elige la opción más cercana y personalizaremos tu experiencia.', + 'continue' => 'Continuar', + 'subscription_required_title' => 'Esperando al propietario de la cuenta', + 'subscription_required_description' => 'Esta cuenta aún no tiene una suscripción activa. Pide al propietario que complete el checkout: tendrás acceso total en cuanto esté activa.', + 'subscription_required_owner' => 'El propietario de tu cuenta es :name.', + 'subscription_required_auto' => 'Esta página se actualiza automáticamente, no hace falta recargar.', + 'progress' => 'Progreso de bienvenida', + 'go_to_step' => 'Ir al paso :step', + 'step_current' => 'Paso :step (actual)', + '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?', + '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' => 'Generar publicaciones con la IA de TryPost', + 'use_mcp' => 'Crear publicaciones desde Claude, ChatGPT o Cursor', + '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', + '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', + ], +]; diff --git a/lang/fr/auth.php b/lang/fr/auth.php index 6a71cdb7..cd85c421 100644 --- a/lang/fr/auth.php +++ b/lang/fr/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Connexion', 'email' => 'Adresse e-mail', 'password' => 'Mot de passe', + 'show_password' => 'Afficher le mot de passe', + 'hide_password' => 'Masquer le mot de passe', 'forgot_password' => 'Mot de passe oublié ?', 'remember_me' => 'Se souvenir de moi', 'submit' => 'Se connecter', diff --git a/lang/fr/onboarding.php b/lang/fr/onboarding.php index 7d103c10..4c532e19 100644 --- a/lang/fr/onboarding.php +++ b/lang/fr/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Générer des publications avec l\'IA TryPost', + 'use_mcp' => 'Créer des publications depuis Claude, ChatGPT ou Cursor', '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', ], diff --git a/lang/fr/sidebar.php b/lang/fr/sidebar.php index fa9b76d6..b5f4d9b5 100644 --- a/lang/fr/sidebar.php +++ b/lang/fr/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Documentation', 'referral' => 'Gagnez 30 % de parrainage', - 'stay_updated' => 'Rester informé', + 'discord' => 'Communauté Discord', ], ]; diff --git a/lang/fr/welcome.php b/lang/fr/welcome.php new file mode 100644 index 00000000..9eef5802 --- /dev/null +++ b/lang/fr/welcome.php @@ -0,0 +1,57 @@ + 'Qu\'est-ce qui vous décrit le mieux ?', + 'description' => 'Choisissez l\'option la plus proche et nous personnaliserons votre expérience.', + 'continue' => 'Continuer', + 'subscription_required_title' => 'En attente du propriétaire du compte', + 'subscription_required_description' => 'Ce compte n\'a pas encore d\'abonnement actif. Demandez au propriétaire de finaliser le paiement — vous aurez un accès complet dès qu\'il sera actif.', + 'subscription_required_owner' => 'Le propriétaire de votre compte est :name.', + 'subscription_required_auto' => 'Cette page se met à jour automatiquement — inutile de la recharger.', + 'progress' => 'Progression d’accueil', + 'go_to_step' => 'Aller à l’étape :step', + 'step_current' => 'Étape :step (actuelle)', + '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 ?', + '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' => 'Générer des publications avec l\'IA TryPost', + 'use_mcp' => 'Créer des publications depuis Claude, ChatGPT ou Cursor', + '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', + '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', + ], +]; diff --git a/lang/it/auth.php b/lang/it/auth.php index 67ac95b3..2a1674db 100644 --- a/lang/it/auth.php +++ b/lang/it/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Accedi', 'email' => 'Indirizzo email', 'password' => 'Password', + 'show_password' => 'Mostra password', + 'hide_password' => 'Nascondi password', 'forgot_password' => 'Password dimenticata?', 'remember_me' => 'Ricordami', 'submit' => 'Accedi', diff --git a/lang/it/onboarding.php b/lang/it/onboarding.php index 0beba033..a425efd5 100644 --- a/lang/it/onboarding.php +++ b/lang/it/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Generare post con l\'IA di TryPost', + 'use_mcp' => 'Creare post da Claude, ChatGPT o Cursor', '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', ], diff --git a/lang/it/sidebar.php b/lang/it/sidebar.php index 3bf70c49..9b7c43e4 100644 --- a/lang/it/sidebar.php +++ b/lang/it/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Documentazione', 'referral' => 'Guadagna il 30% di referral', - 'stay_updated' => 'Resta aggiornato', + 'discord' => 'Community Discord', ], ]; diff --git a/lang/it/welcome.php b/lang/it/welcome.php new file mode 100644 index 00000000..9e32b27a --- /dev/null +++ b/lang/it/welcome.php @@ -0,0 +1,57 @@ + 'Cosa ti descrive meglio?', + 'description' => 'Scegli l\'opzione più vicina e personalizzeremo la tua esperienza.', + 'continue' => 'Continua', + 'subscription_required_title' => 'In attesa del proprietario dell\'account', + 'subscription_required_description' => 'Questo account non ha ancora un abbonamento attivo. Chiedi al proprietario di completare il checkout: avrai accesso completo non appena sarà attivo.', + 'subscription_required_owner' => 'Il proprietario del tuo account è :name.', + 'subscription_required_auto' => 'Questa pagina si aggiorna automaticamente: non serve ricaricarla.', + 'progress' => 'Progresso di benvenuto', + 'go_to_step' => 'Vai al passaggio :step', + 'step_current' => 'Passaggio :step (attuale)', + '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?', + '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' => 'Generare post con l\'IA di TryPost', + 'use_mcp' => 'Creare post da Claude, ChatGPT o Cursor', + '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', + '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', + ], +]; diff --git a/lang/ja/auth.php b/lang/ja/auth.php index 252e4955..8740a5ca 100644 --- a/lang/ja/auth.php +++ b/lang/ja/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'ログイン', 'email' => 'メールアドレス', 'password' => 'パスワード', + 'show_password' => 'パスワードを表示', + 'hide_password' => 'パスワードを隠す', 'forgot_password' => 'パスワードをお忘れですか?', 'remember_me' => 'ログイン状態を保持', 'submit' => 'ログイン', diff --git a/lang/ja/onboarding.php b/lang/ja/onboarding.php index db92adcf..d84992ce 100644 --- a/lang/ja/onboarding.php +++ b/lang/ja/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', 'goals' => [ 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', - 'ai_content' => 'AI でより速く投稿を作成する', + 'ai_content' => 'TryPost AI で投稿を生成する', + 'use_mcp' => 'Claude・ChatGPT・Cursor から投稿を作成する', 'plan_calendar' => 'カレンダーで投稿を計画する', 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', 'drive_sales' => 'トラフィックと売上を増やす', 'manage_clients' => '複数のブランドやクライアントを管理する', - 'team_collaboration' => 'チームで作業する', - 'automate_api' => 'API、MCP、コードで投稿を自動化する', - 'track_performance' => '投稿のパフォーマンスを確認する', 'just_exploring' => '今はまだ様子を見ている', 'other' => 'その他', ], diff --git a/lang/ja/sidebar.php b/lang/ja/sidebar.php index 4c55a799..95f28854 100644 --- a/lang/ja/sidebar.php +++ b/lang/ja/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'ドキュメント', 'referral' => '30% の紹介報酬を獲得', - 'stay_updated' => '最新情報を受け取る', + 'discord' => 'Discord コミュニティ', ], ]; diff --git a/lang/ja/welcome.php b/lang/ja/welcome.php new file mode 100644 index 00000000..dcc87b1f --- /dev/null +++ b/lang/ja/welcome.php @@ -0,0 +1,57 @@ + 'あなたに一番近いのは?', + 'description' => '近いものを選ぶと、体験を最適化します。', + 'continue' => '続ける', + 'subscription_required_title' => 'アカウントのオーナーを待っています', + 'subscription_required_description' => 'このアカウントにはまだ有効なサブスクリプションがありません。オーナーにチェックアウトの完了を依頼してください — 有効になり次第、フルアクセスできます。', + 'subscription_required_owner' => 'アカウントのオーナーは :name です。', + 'subscription_required_auto' => 'このページは自動で更新されます — 再読み込みは不要です。', + 'progress' => 'ようこそ進捗', + 'go_to_step' => 'ステップ :step へ', + 'step_current' => 'ステップ :step(現在)', + 'personas' => [ + 'creator' => 'コンテンツクリエイター', + 'freelancer' => 'フリーランス', + 'developer' => '開発者', + 'startup' => 'スタートアップ', + 'agency' => '代理店', + 'small_business' => '中小企業', + 'marketer' => 'マーケター', + 'online_store' => 'オンラインストア', + 'other' => 'その他', + ], + 'goals_title' => '目標は何ですか?', + 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', + 'goals' => [ + 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', + 'ai_content' => 'TryPost AI で投稿を生成する', + 'use_mcp' => 'Claude・ChatGPT・Cursor から投稿を作成する', + 'plan_calendar' => 'カレンダーで投稿を計画する', + 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', + 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', + 'drive_sales' => 'トラフィックと売上を増やす', + 'manage_clients' => '複数のブランドやクライアントを管理する', + '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' => 'その他', + ], +]; diff --git a/lang/ko/auth.php b/lang/ko/auth.php index a86708da..251c8eec 100644 --- a/lang/ko/auth.php +++ b/lang/ko/auth.php @@ -71,6 +71,8 @@ 'page_title' => '로그인', 'email' => '이메일 주소', 'password' => '비밀번호', + 'show_password' => '비밀번호 표시', + 'hide_password' => '비밀번호 숨기기', 'forgot_password' => '비밀번호를 잊으셨나요?', 'remember_me' => '로그인 상태 유지', 'submit' => '로그인', diff --git a/lang/ko/onboarding.php b/lang/ko/onboarding.php index fc954799..8a02c90b 100644 --- a/lang/ko/onboarding.php +++ b/lang/ko/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', 'goals' => [ 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', - 'ai_content' => 'AI로 더 빠르게 게시물 작성', + 'ai_content' => 'TryPost AI로 게시물 생성', + 'use_mcp' => 'Claude, ChatGPT 또는 Cursor에서 게시물 작성', 'plan_calendar' => '캘린더에서 게시물 계획', 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', 'grow_audience' => '팔로워와 참여 늘리기', 'drive_sales' => '더 많은 트래픽과 판매 유도', 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', - 'team_collaboration' => '팀과 협업', - 'automate_api' => 'API, MCP 또는 코드로 게시 자동화', - 'track_performance' => '게시물 성과 확인', 'just_exploring' => '지금은 둘러보는 중', 'other' => '다른 것', ], diff --git a/lang/ko/sidebar.php b/lang/ko/sidebar.php index 4c935cd1..305badd9 100644 --- a/lang/ko/sidebar.php +++ b/lang/ko/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => '문서', 'referral' => '30% 추천 수익 받기', - 'stay_updated' => '최신 소식 받기', + 'discord' => 'Discord 커뮤니티', ], ]; diff --git a/lang/ko/welcome.php b/lang/ko/welcome.php new file mode 100644 index 00000000..215f96f4 --- /dev/null +++ b/lang/ko/welcome.php @@ -0,0 +1,57 @@ + '무엇을 가장 잘 설명하나요?', + 'description' => '가장 가까운 항목을 선택하면 맞춤 경험을 제공해 드립니다.', + 'continue' => '계속', + 'subscription_required_title' => '계정 소유자를 기다리는 중', + 'subscription_required_description' => '이 계정에는 아직 활성 구독이 없습니다. 소유자에게 결제 완료를 요청하세요 — 활성화되는 즉시 모든 기능을 사용할 수 있습니다.', + 'subscription_required_owner' => '계정 소유자는 :name 님입니다.', + 'subscription_required_auto' => '이 페이지는 자동으로 업데이트됩니다 — 새로고침할 필요가 없습니다.', + 'progress' => '환영 진행률', + 'go_to_step' => ':step단계로 이동', + 'step_current' => ':step단계 (현재)', + 'personas' => [ + 'creator' => '콘텐츠 크리에이터', + 'freelancer' => '프리랜서', + 'developer' => '개발자', + 'startup' => '스타트업', + 'agency' => '에이전시', + 'small_business' => '소상공인', + 'marketer' => '마케터', + 'online_store' => '온라인 스토어', + 'other' => '기타', + ], + 'goals_title' => '목표가 무엇인가요?', + 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', + 'goals' => [ + 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', + 'ai_content' => 'TryPost AI로 게시물 생성', + 'use_mcp' => 'Claude, ChatGPT 또는 Cursor에서 게시물 작성', + 'plan_calendar' => '캘린더에서 게시물 계획', + 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', + 'grow_audience' => '팔로워와 참여 늘리기', + 'drive_sales' => '더 많은 트래픽과 판매 유도', + 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', + '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' => '기타', + ], +]; diff --git a/lang/nl/auth.php b/lang/nl/auth.php index a12ae71c..93ee64b8 100644 --- a/lang/nl/auth.php +++ b/lang/nl/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Inloggen', 'email' => 'E-mailadres', 'password' => 'Wachtwoord', + 'show_password' => 'Wachtwoord tonen', + 'hide_password' => 'Wachtwoord verbergen', 'forgot_password' => 'Wachtwoord vergeten?', 'remember_me' => 'Ingelogd blijven', 'submit' => 'Inloggen', diff --git a/lang/nl/onboarding.php b/lang/nl/onboarding.php index 424cfe42..88367ef4 100644 --- a/lang/nl/onboarding.php +++ b/lang/nl/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Posts genereren met TryPost AI', + 'use_mcp' => 'Posts maken via Claude, ChatGPT of Cursor', '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', ], diff --git a/lang/nl/sidebar.php b/lang/nl/sidebar.php index 86505bc0..308e0ddf 100644 --- a/lang/nl/sidebar.php +++ b/lang/nl/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Documentatie', 'referral' => 'Verdien 30% referral', - 'stay_updated' => 'Blijf op de hoogte', + 'discord' => 'Discord-community', ], ]; diff --git a/lang/nl/welcome.php b/lang/nl/welcome.php new file mode 100644 index 00000000..46cbfe98 --- /dev/null +++ b/lang/nl/welcome.php @@ -0,0 +1,57 @@ + 'Wat omschrijft jou het beste?', + 'description' => 'Kies de dichtstbijzijnde optie, dan stemmen we je ervaring af.', + 'continue' => 'Doorgaan', + 'subscription_required_title' => 'Wachten op de accounteigenaar', + 'subscription_required_description' => 'Dit account heeft nog geen actief abonnement. Vraag de eigenaar om de checkout af te ronden — je krijgt volledige toegang zodra het actief is.', + 'subscription_required_owner' => 'De accounteigenaar is :name.', + 'subscription_required_auto' => 'Deze pagina vernieuwt automatisch — verversen is niet nodig.', + 'progress' => 'Welkomstvoortgang', + 'go_to_step' => 'Ga naar stap :step', + 'step_current' => 'Stap :step (huidig)', + '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?', + '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' => 'Posts genereren met TryPost AI', + 'use_mcp' => 'Posts maken via Claude, ChatGPT of Cursor', + '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', + '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', + ], +]; diff --git a/lang/pl/auth.php b/lang/pl/auth.php index 056b6667..13b6a9d3 100644 --- a/lang/pl/auth.php +++ b/lang/pl/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Zaloguj się', 'email' => 'Adres e-mail', 'password' => 'Hasło', + 'show_password' => 'Pokaż hasło', + 'hide_password' => 'Ukryj hasło', 'forgot_password' => 'Nie pamiętasz hasła?', 'remember_me' => 'Zapamiętaj mnie', 'submit' => 'Zaloguj się', diff --git a/lang/pl/onboarding.php b/lang/pl/onboarding.php index 91092398..acc0e002 100644 --- a/lang/pl/onboarding.php +++ b/lang/pl/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Generuj posty z AI TryPost', + 'use_mcp' => 'Twórz posty w Claude, ChatGPT lub Cursor', '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', ], diff --git a/lang/pl/sidebar.php b/lang/pl/sidebar.php index 5c3041d1..0771bc2e 100644 --- a/lang/pl/sidebar.php +++ b/lang/pl/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Dokumentacja', 'referral' => 'Zarabiaj 30% z poleceń', - 'stay_updated' => 'Bądź na bieżąco', + 'discord' => 'Społeczność Discord', ], ]; diff --git a/lang/pl/welcome.php b/lang/pl/welcome.php new file mode 100644 index 00000000..6b4ff4ab --- /dev/null +++ b/lang/pl/welcome.php @@ -0,0 +1,57 @@ + 'Co najlepiej Cię opisuje?', + 'description' => 'Wybierz najbliższą opcję, a my dopasujemy Twoje doświadczenie.', + 'continue' => 'Kontynuuj', + 'subscription_required_title' => 'Oczekiwanie na właściciela konta', + 'subscription_required_description' => 'To konto nie ma jeszcze aktywnej subskrypcji. Poproś właściciela o dokończenie płatności — uzyskasz pełny dostęp, gdy tylko będzie aktywna.', + 'subscription_required_owner' => 'Właścicielem Twojego konta jest :name.', + 'subscription_required_auto' => 'Ta strona odświeża się automatycznie — nie musisz jej przeładowywać.', + 'progress' => 'Postęp powitalny', + 'go_to_step' => 'Przejdź do kroku :step', + 'step_current' => 'Krok :step (bieżący)', + '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?', + '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' => 'Generuj posty z AI TryPost', + 'use_mcp' => 'Twórz posty w Claude, ChatGPT lub Cursor', + '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', + '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', + ], +]; diff --git a/lang/pt-BR/auth.php b/lang/pt-BR/auth.php index 70725a65..6122b630 100644 --- a/lang/pt-BR/auth.php +++ b/lang/pt-BR/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Entrar', 'email' => 'Endereço de email', 'password' => 'Senha', + 'show_password' => 'Mostrar senha', + 'hide_password' => 'Esconder senha', 'forgot_password' => 'Esqueceu a senha?', 'remember_me' => 'Lembrar de mim', 'submit' => 'Entrar', diff --git a/lang/pt-BR/onboarding.php b/lang/pt-BR/onboarding.php index 12ad7962..84a91560 100644 --- a/lang/pt-BR/onboarding.php +++ b/lang/pt-BR/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'Gerar posts com a IA do TryPost', + 'use_mcp' => 'Criar posts pelo Claude, ChatGPT ou Cursor', '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', ], diff --git a/lang/pt-BR/sidebar.php b/lang/pt-BR/sidebar.php index 2edbfe59..6b30fe47 100644 --- a/lang/pt-BR/sidebar.php +++ b/lang/pt-BR/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Documentação', 'referral' => 'Ganhe 30% de indicação', - 'stay_updated' => 'Fique por dentro', + 'discord' => 'Comunidade Discord', ], ]; diff --git a/lang/pt-BR/welcome.php b/lang/pt-BR/welcome.php new file mode 100644 index 00000000..a1a61d90 --- /dev/null +++ b/lang/pt-BR/welcome.php @@ -0,0 +1,57 @@ + 'O que melhor descreve você?', + 'description' => 'Escolha a opção mais próxima e a gente personaliza sua experiência.', + 'continue' => 'Continuar', + 'subscription_required_title' => 'Aguardando o dono da conta', + 'subscription_required_description' => 'Esta conta ainda não tem uma assinatura ativa. Peça ao dono da conta para concluir o checkout — você terá acesso total assim que ela estiver ativa.', + 'subscription_required_owner' => 'O dono da sua conta é :name.', + 'subscription_required_auto' => 'Esta página atualiza automaticamente — não precisa recarregar.', + 'progress' => 'Progresso das boas-vindas', + 'go_to_step' => 'Ir para a etapa :step', + 'step_current' => 'Etapa :step (atual)', + '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?', + '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' => 'Gerar posts com a IA do TryPost', + 'use_mcp' => 'Criar posts pelo Claude, ChatGPT ou Cursor', + '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', + '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', + ], +]; diff --git a/lang/ru/auth.php b/lang/ru/auth.php index b538e791..3ba01801 100644 --- a/lang/ru/auth.php +++ b/lang/ru/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Вход', 'email' => 'Адрес email', 'password' => 'Пароль', + 'show_password' => 'Показать пароль', + 'hide_password' => 'Скрыть пароль', 'forgot_password' => 'Забыли пароль?', 'remember_me' => 'Запомнить меня', 'submit' => 'Войти', diff --git a/lang/ru/onboarding.php b/lang/ru/onboarding.php index d7b64add..93103c85 100644 --- a/lang/ru/onboarding.php +++ b/lang/ru/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', 'goals' => [ 'save_time' => 'Экономить время, публикуя всюду сразу', - 'ai_content' => 'Создавать посты быстрее с помощью ИИ', + 'ai_content' => 'Генерировать посты с ИИ TryPost', + 'use_mcp' => 'Создавать посты через Claude, ChatGPT или Cursor', 'plan_calendar' => 'Планировать посты в календаре', 'stay_on_brand' => 'Держать каждый пост в стиле бренда', 'grow_audience' => 'Наращивать аудиторию и вовлечённость', 'drive_sales' => 'Получать больше трафика и продаж', 'manage_clients' => 'Управлять несколькими брендами или клиентами', - 'team_collaboration' => 'Работать с командой', - 'automate_api' => 'Автоматизировать публикацию с помощью API, MCP или кода', - 'track_performance' => 'Отслеживать эффективность постов', 'just_exploring' => 'Пока просто знакомлюсь', 'other' => 'Что-то ещё', ], diff --git a/lang/ru/sidebar.php b/lang/ru/sidebar.php index b6c0638b..be9c935d 100644 --- a/lang/ru/sidebar.php +++ b/lang/ru/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Документация', 'referral' => 'Зарабатывайте 30% по реферальной программе', - 'stay_updated' => 'Следите за обновлениями', + 'discord' => 'Сообщество Discord', ], ]; diff --git a/lang/ru/welcome.php b/lang/ru/welcome.php new file mode 100644 index 00000000..ab9ec23b --- /dev/null +++ b/lang/ru/welcome.php @@ -0,0 +1,57 @@ + 'Что лучше всего вас описывает?', + 'description' => 'Выберите ближайший вариант — мы настроим опыт под вас.', + 'continue' => 'Продолжить', + 'subscription_required_title' => 'Ожидание владельца аккаунта', + 'subscription_required_description' => 'У этого аккаунта пока нет активной подписки. Попросите владельца завершить оплату — вы получите полный доступ сразу после её активации.', + 'subscription_required_owner' => 'Владелец вашего аккаунта — :name.', + 'subscription_required_auto' => 'Эта страница обновляется автоматически — перезагружать не нужно.', + 'progress' => 'Прогресс приветствия', + 'go_to_step' => 'Перейти к шагу :step', + 'step_current' => 'Шаг :step (текущий)', + 'personas' => [ + 'creator' => 'Автор контента', + 'freelancer' => 'Фрилансер', + 'developer' => 'Разработчик', + 'startup' => 'Стартап', + 'agency' => 'Агентство', + 'small_business' => 'Малый бизнес', + 'marketer' => 'Маркетолог', + 'online_store' => 'Интернет-магазин', + 'other' => 'Другое', + ], + 'goals_title' => 'Какова ваша цель?', + 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', + 'goals' => [ + 'save_time' => 'Экономить время, публикуя всюду сразу', + 'ai_content' => 'Генерировать посты с ИИ TryPost', + 'use_mcp' => 'Создавать посты через Claude, ChatGPT или Cursor', + 'plan_calendar' => 'Планировать посты в календаре', + 'stay_on_brand' => 'Держать каждый пост в стиле бренда', + 'grow_audience' => 'Наращивать аудиторию и вовлечённость', + 'drive_sales' => 'Получать больше трафика и продаж', + 'manage_clients' => 'Управлять несколькими брендами или клиентами', + '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' => 'Что-то другое', + ], +]; diff --git a/lang/tr/auth.php b/lang/tr/auth.php index 6e6633e8..e19acf68 100644 --- a/lang/tr/auth.php +++ b/lang/tr/auth.php @@ -73,6 +73,8 @@ 'page_title' => 'Giriş yap', 'email' => 'E-posta adresi', 'password' => 'Parola', + 'show_password' => 'Parolayı göster', + 'hide_password' => 'Parolayı gizle', 'forgot_password' => 'Parolanızı mı unuttunuz?', 'remember_me' => 'Beni hatırla', 'submit' => 'Giriş yap', diff --git a/lang/tr/onboarding.php b/lang/tr/onboarding.php index b54630a4..96efe708 100644 --- a/lang/tr/onboarding.php +++ b/lang/tr/onboarding.php @@ -21,15 +21,13 @@ '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', + 'ai_content' => 'TryPost AI ile gönderi oluşturmak', + 'use_mcp' => 'Claude, ChatGPT veya Cursor ile 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', ], diff --git a/lang/tr/sidebar.php b/lang/tr/sidebar.php index 6a175752..46e5d573 100644 --- a/lang/tr/sidebar.php +++ b/lang/tr/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Dokümantasyon', 'referral' => '%30 referans kazanın', - 'stay_updated' => 'Güncel kalın', + 'discord' => 'Discord topluluğu', ], ]; diff --git a/lang/tr/welcome.php b/lang/tr/welcome.php new file mode 100644 index 00000000..40306fd3 --- /dev/null +++ b/lang/tr/welcome.php @@ -0,0 +1,57 @@ + 'Sizi en iyi ne tanımlar?', + 'description' => 'En yakın seçeneği seçin, deneyiminizi kişiselleştirelim.', + 'continue' => 'Devam et', + 'subscription_required_title' => 'Hesap sahibi bekleniyor', + 'subscription_required_description' => 'Bu hesabın henüz etkin bir aboneliği yok. Hesap sahibinden ödemeyi tamamlamasını isteyin — abonelik etkinleşir etkinleşmez tam erişiminiz olur.', + 'subscription_required_owner' => 'Hesap sahibiniz :name.', + 'subscription_required_auto' => 'Bu sayfa otomatik olarak güncellenir — yenilemenize gerek yok.', + 'progress' => 'Karşılama ilerlemesi', + 'go_to_step' => ':step. adıma git', + 'step_current' => 'Adım :step (şu anki)', + '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' => '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' => 'TryPost AI ile gönderi oluşturmak', + 'use_mcp' => 'Claude, ChatGPT veya Cursor ile 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', + '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', + ], +]; diff --git a/lang/uk/auth.php b/lang/uk/auth.php index 549cd961..d470f499 100644 --- a/lang/uk/auth.php +++ b/lang/uk/auth.php @@ -71,6 +71,8 @@ 'page_title' => 'Вхід', 'email' => 'Адреса email', 'password' => 'Пароль', + 'show_password' => 'Показати пароль', + 'hide_password' => 'Приховати пароль', 'forgot_password' => 'Забули пароль?', 'remember_me' => 'Запам’ятати мене', 'submit' => 'Увійти', diff --git a/lang/uk/onboarding.php b/lang/uk/onboarding.php index 6c8663b1..b0daabb4 100644 --- a/lang/uk/onboarding.php +++ b/lang/uk/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => 'Виберіть усе, що підходить, і ми налаштуємо TryPost для вас.', 'goals' => [ 'save_time' => 'Економити час, публікуючи всюди одразу', - 'ai_content' => 'Створювати пости швидше з AI', + 'ai_content' => 'Генерувати пости з AI TryPost', + 'use_mcp' => 'Створювати пости через Claude, ChatGPT або Cursor', 'plan_calendar' => 'Планувати пости в календарі', 'stay_on_brand' => 'Тримати кожен пост у стилі бренду', 'grow_audience' => 'Збільшувати аудиторію та залучення', 'drive_sales' => 'Отримувати більше трафіку та продажів', 'manage_clients' => 'Керувати кількома брендами або клієнтами', - 'team_collaboration' => 'Працювати з командою', - 'automate_api' => 'Автоматизувати публікацію через API, MCP або код', - 'track_performance' => 'Бачити, як працюють мої пости', 'just_exploring' => 'Поки що просто досліджую', 'other' => 'Щось інше', ], diff --git a/lang/uk/sidebar.php b/lang/uk/sidebar.php index 8f0d567c..ad4194ca 100644 --- a/lang/uk/sidebar.php +++ b/lang/uk/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => 'Документація', 'referral' => 'Отримуйте 30% за рефералами', - 'stay_updated' => 'Слідкуйте за оновленнями', + 'discord' => 'Спільнота Discord', ], ]; diff --git a/lang/uk/welcome.php b/lang/uk/welcome.php new file mode 100644 index 00000000..fd44a0c9 --- /dev/null +++ b/lang/uk/welcome.php @@ -0,0 +1,57 @@ + 'Що найкраще вас описує?', + 'description' => 'Оберіть найближчий варіант — ми адаптуємо ваш досвід.', + 'continue' => 'Продовжити', + 'subscription_required_title' => 'Очікуємо власника акаунта', + 'subscription_required_description' => 'У цього акаунта ще немає активної підписки. Попросіть власника завершити оплату — повний доступ з’явиться одразу після активації.', + 'subscription_required_owner' => 'Власник вашого акаунта — :name.', + 'subscription_required_auto' => 'Ця сторінка оновлюється автоматично — оновлювати вручну не потрібно.', + 'progress' => 'Прогрес привітання', + 'go_to_step' => 'Перейти до кроку :step', + 'step_current' => 'Крок :step (поточний)', + 'personas' => [ + 'creator' => 'Автор контенту', + 'freelancer' => 'Фрілансер', + 'developer' => 'Розробник', + 'startup' => 'Стартап', + 'agency' => 'Агенція', + 'small_business' => 'Малий бізнес', + 'marketer' => 'Маркетолог', + 'online_store' => 'Інтернет-магазин', + 'other' => 'Інше', + ], + 'goals_title' => 'Яка ваша мета?', + 'goals_description' => 'Виберіть усе, що підходить, і ми налаштуємо TryPost для вас.', + 'goals' => [ + 'save_time' => 'Економити час, публікуючи всюди одразу', + 'ai_content' => 'Генерувати пости з AI TryPost', + 'use_mcp' => 'Створювати пости через Claude, ChatGPT або Cursor', + 'plan_calendar' => 'Планувати пости в календарі', + 'stay_on_brand' => 'Тримати кожен пост у стилі бренду', + 'grow_audience' => 'Збільшувати аудиторію та залучення', + 'drive_sales' => 'Отримувати більше трафіку та продажів', + 'manage_clients' => 'Керувати кількома брендами або клієнтами', + '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' => 'Щось інше', + ], +]; diff --git a/lang/zh/auth.php b/lang/zh/auth.php index 060399e6..96b6bafc 100644 --- a/lang/zh/auth.php +++ b/lang/zh/auth.php @@ -71,6 +71,8 @@ 'page_title' => '登录', 'email' => '邮箱地址', 'password' => '密码', + 'show_password' => '显示密码', + 'hide_password' => '隐藏密码', 'forgot_password' => '忘记密码?', 'remember_me' => '记住我', 'submit' => '登录', diff --git a/lang/zh/onboarding.php b/lang/zh/onboarding.php index 9761ce99..294a652c 100644 --- a/lang/zh/onboarding.php +++ b/lang/zh/onboarding.php @@ -21,15 +21,13 @@ 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', 'goals' => [ 'save_time' => '一次发布到所有平台,节省时间', - 'ai_content' => '借助 AI 更快地创建帖子', + 'ai_content' => '用 TryPost AI 生成帖子', + 'use_mcp' => '通过 Claude、ChatGPT 或 Cursor 创建帖子', 'plan_calendar' => '在日历上规划我的帖子', 'stay_on_brand' => '让每一条帖子都符合品牌调性', 'grow_audience' => '增长我的受众和互动', 'drive_sales' => '获得更多流量和销量', 'manage_clients' => '管理多个品牌或客户', - 'team_collaboration' => '与我的团队协作', - 'automate_api' => '通过 API、MCP 或代码自动发帖', - 'track_performance' => '查看我的帖子表现', 'just_exploring' => '目前只是随便看看', 'other' => '其他需求', ], diff --git a/lang/zh/sidebar.php b/lang/zh/sidebar.php index 61c22ae7..d9e406c4 100644 --- a/lang/zh/sidebar.php +++ b/lang/zh/sidebar.php @@ -60,6 +60,6 @@ 'support' => [ 'docs' => '文档', 'referral' => '赚取 30% 推荐奖励', - 'stay_updated' => '获取最新动态', + 'discord' => 'Discord 社区', ], ]; diff --git a/lang/zh/welcome.php b/lang/zh/welcome.php new file mode 100644 index 00000000..d6e769a1 --- /dev/null +++ b/lang/zh/welcome.php @@ -0,0 +1,57 @@ + '哪项最能描述你?', + 'description' => '选择最接近的一项,我们会为你定制体验。', + 'continue' => '继续', + 'subscription_required_title' => '等待账户所有者', + 'subscription_required_description' => '此账户还没有有效订阅。请让账户所有者完成结账 — 订阅生效后您即可获得完整访问权限。', + 'subscription_required_owner' => '您的账户所有者是 :name。', + 'subscription_required_auto' => '此页面会自动更新 — 无需刷新。', + 'progress' => '欢迎进度', + 'go_to_step' => '前往第 :step 步', + 'step_current' => '第 :step 步(当前)', + 'personas' => [ + 'creator' => '内容创作者', + 'freelancer' => '自由职业者', + 'developer' => '开发者', + 'startup' => '初创公司', + 'agency' => '代理机构', + 'small_business' => '小型企业', + 'marketer' => '营销人员', + 'online_store' => '网店', + 'other' => '其他', + ], + 'goals_title' => '你的目标是什么?', + 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', + 'goals' => [ + 'save_time' => '一次发布到所有平台,节省时间', + 'ai_content' => '用 TryPost AI 生成帖子', + 'use_mcp' => '通过 Claude、ChatGPT 或 Cursor 创建帖子', + 'plan_calendar' => '在日历上规划我的帖子', + 'stay_on_brand' => '让每一条帖子都符合品牌调性', + 'grow_audience' => '增长我的受众和互动', + 'drive_sales' => '获得更多流量和销量', + 'manage_clients' => '管理多个品牌或客户', + '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' => '其他', + ], +]; diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 458c46a7..218625ff 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -4,7 +4,7 @@ import { IconAffiliate, IconAlertTriangle, IconBolt, - IconBrandX, + IconBrandDiscord, IconCalendar, IconChartBar, IconChevronRight, @@ -178,9 +178,9 @@ const bottomNavItems = computed(() => [ icon: IconGift, }, { - title: trans('sidebar.support.stay_updated'), - href: 'https://x.com/trypostit', - icon: IconBrandX, + title: trans('sidebar.support.discord'), + href: 'https://trypost.it/discord', + icon: IconBrandDiscord, }, { title: trans('sidebar.support.docs'), 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 @@ - - - - - - - - - - - - - - - - - - - - {{ title }} - - {{ description }} - - - - - - - - diff --git a/resources/js/layouts/WelcomeLayout.vue b/resources/js/layouts/WelcomeLayout.vue new file mode 100644 index 00000000..375d2bb4 --- /dev/null +++ b/resources/js/layouts/WelcomeLayout.vue @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + {{ title }} + + {{ description }} + + + + + + + + diff --git a/resources/js/pages/auth/Login.vue b/resources/js/pages/auth/Login.vue index 3b25e1d9..8a5a510a 100644 --- a/resources/js/pages/auth/Login.vue +++ b/resources/js/pages/auth/Login.vue @@ -1,6 +1,7 @@ - - + - + {{ status }} - - + + {{ $t('auth.login.email') }} - + - {{ $t('auth.login.password') }} - + {{ + $t('auth.login.password') + }} + {{ $t('auth.login.forgot_password') }} - + + + + + + + + + + + + + + {{ + showPassword + ? $t( + 'auth.login.hide_password', + ) + : $t( + 'auth.login.show_password', + ) + }} + + + + + + - - + + {{ $t('auth.login.remember_me') }} - + {{ $t('auth.login.submit') }} - + {{ $t('auth.login.no_account') }} - {{ $t('auth.login.sign_up') }} + {{ + $t('auth.login.sign_up') + }} 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 @@ - - - - - - - - - - - - - {{ $t('onboarding.connect.title') }} - - - {{ $t('onboarding.connect.description') }} - - - - - - - - {{ $t('onboarding.continue') }} - - - - {{ $t('onboarding.connect.must_connect') }} - - - - - 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 @@ - - - - - - - - - - - - - {{ $t('onboarding.goals_title') }} - - - {{ $t('onboarding.goals_description') }} - - - - - - - - - - {{ goalLabel(goal) }} - - - - - - - - - - {{ $t('onboarding.continue') }} - - - - - - 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 @@ - - - - - - - - - - - - - {{ $t('onboarding.title') }} - - - {{ $t('onboarding.description') }} - - - - - - - - - - {{ personaLabel(persona) }} - - - - - - - - - - {{ $t('onboarding.continue') }} - - - - - - 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 @@ - - - - - - - - - - - - - {{ $t('onboarding.referral_source_title') }} - - - {{ $t('onboarding.referral_source_description') }} - - - - - - - - - - {{ sourceLabel(source) }} - - - - - - - - - - {{ $t('onboarding.continue') }} - - - - - - diff --git a/resources/js/pages/welcome/Goals.vue b/resources/js/pages/welcome/Goals.vue new file mode 100644 index 00000000..cae52391 --- /dev/null +++ b/resources/js/pages/welcome/Goals.vue @@ -0,0 +1,194 @@ + + + + + + + + + + + + + {{ goalLabel(goal) }} + + + + + + + + + + + {{ $t('welcome.continue') }} + + + + diff --git a/resources/js/pages/welcome/Persona.vue b/resources/js/pages/welcome/Persona.vue new file mode 100644 index 00000000..4df59a66 --- /dev/null +++ b/resources/js/pages/welcome/Persona.vue @@ -0,0 +1,169 @@ + + + + + + + + + + + + + {{ personaLabel(persona) }} + + + + + + + + + + + {{ $t('welcome.continue') }} + + + + diff --git a/resources/js/pages/welcome/ReferralSource.vue b/resources/js/pages/welcome/ReferralSource.vue new file mode 100644 index 00000000..6758f035 --- /dev/null +++ b/resources/js/pages/welcome/ReferralSource.vue @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + {{ sourceLabel(source) }} + + + + + + + + + + + {{ $t('welcome.continue') }} + + + + diff --git a/resources/js/pages/welcome/SubscriptionRequired.vue b/resources/js/pages/welcome/SubscriptionRequired.vue new file mode 100644 index 00000000..03d5d39d --- /dev/null +++ b/resources/js/pages/welcome/SubscriptionRequired.vue @@ -0,0 +1,49 @@ + + + + + + + + + + + + {{ + $t('welcome.subscription_required_owner', { + name: ownerName, + }) + }} + + + {{ $t('welcome.subscription_required_auto') }} + + + + diff --git a/routes/app.php b/routes/app.php index ebe00059..186539b7 100644 --- a/routes/app.php +++ b/routes/app.php @@ -12,7 +12,6 @@ use App\Http\Controllers\App\LinkPreviewController; use App\Http\Controllers\App\McpSettingsController; 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; @@ -28,6 +27,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; @@ -58,14 +58,16 @@ })->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::get('welcome/subscription-required', [WelcomeController::class, 'subscriptionRequired'])->name('app.welcome.subscription-required'); + Route::post('welcome/referral-source', [WelcomeController::class, 'storeReferralSource']) + ->middleware('throttle:6,1') + ->name('app.welcome.referral-source.store'); 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/McpSettingsControllerTest.php b/tests/Feature/McpSettingsControllerTest.php index 1c404b26..76fd35f8 100644 --- a/tests/Feature/McpSettingsControllerTest.php +++ b/tests/Feature/McpSettingsControllerTest.php @@ -280,10 +280,10 @@ ->assertForbidden(); }); -it('redirects to onboarding when the account has no app access', function (): void { +it('redirects to welcome when the account has no app access', function (): void { $this->user->account->subscriptions()->delete(); $this->actingAs($this->user->fresh()) ->get(route('app.mcp.index')) - ->assertRedirect(route('app.onboarding')); + ->assertRedirect(route('app.welcome.persona')); }); diff --git a/tests/Feature/Middleware/EnsureAccountReadyTest.php b/tests/Feature/Middleware/EnsureAccountReadyTest.php index d48f22e4..af80bcbf 100644 --- a/tests/Feature/Middleware/EnsureAccountReadyTest.php +++ b/tests/Feature/Middleware/EnsureAccountReadyTest.php @@ -11,13 +11,28 @@ config()->set('trypost.self_hosted', false); }); -test('redirects to onboarding when account has no active subscription', function () { +test('redirects owners 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 members without app access straight to subscription required', function () { + $owner = User::factory()->create(); + $member = User::factory()->create(['account_id' => $owner->account_id]); + $workspace = Workspace::factory()->create([ + 'account_id' => $owner->account_id, + 'user_id' => $owner->id, + ]); + $workspace->members()->attach($member->id, ['role' => Role::Member->value]); + $member->update(['current_workspace_id' => $workspace->id]); + + $this->actingAs($member->fresh()) + ->get(route('app.calendar')) + ->assertRedirect(route('app.welcome.subscription-required')); }); 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..226ac69c --- /dev/null +++ b/tests/Feature/Welcome/WelcomeControllerTest.php @@ -0,0 +1,403 @@ + false]); + $this->user = User::factory()->create(); +}); + +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, fn (SendEvent $event): bool => $event->method === 'capture' + && data_get($event->payload, 'distinctId') === $this->user->id + && data_get($event->payload, 'event') === WelcomeEvent::Persona->value + && data_get($event->payload, 'properties.persona') === Persona::Agency->value); +}); + +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, fn (SendEvent $event): bool => $event->method === 'capture' + && data_get($event->payload, 'event') === WelcomeEvent::Goals->value + && data_get($event->payload, 'properties.goals') === $goals); +}); + +test('completed welcome steps remain reachable when going back', function () { + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.persona')) + ->assertOk() + ->assertInertia(fn ($page) => $page->component('welcome/Persona', false)); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.goals')) + ->assertOk() + ->assertInertia(fn ($page) => $page->component('welcome/Goals', false)); +}); + +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'], + 'only removed goals' => [ + [ + 'persona' => Persona::Agency->value, + 'goals' => ['team_collaboration', 'automate_api', 'track_performance'], + ], + 'app.welcome.goals', + ], +]); + +test('referral source allows users who still have at least one current goal', function () { + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value, 'team_collaboration'], + ]); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.referral-source')) + ->assertOk() + ->assertInertia(fn ($page) => $page->component('welcome/ReferralSource', false)); +}); + +test('referral source renders after prior steps are complete', function () { + $this->user->update([ + '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')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('welcome/ReferralSource', false) + ->has('sources', count(ReferralSource::cases())) + ->where('plan.name', $plan->name) + ->where('plan.interval', 'monthly') + ); +}); + +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 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([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + + 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.referral-source')) + ->andReturn(redirect('https://checkout.stripe.test/session')); + + $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::Referral->value + && data_get($event->payload, 'properties.referral_source') === ReferralSource::ProductHunt->value); +}); + +test('welcome steps redirect to calendar for subscribed accounts', function (string $routeName, string $method, array $payload = []) { + subscribeAccount($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]], +]); + +test('welcome redirects generic-trial accounts with app access to calendar', function () { + config(['trypost.billing.require_card_for_trial' => false]); + + $this->user->account->forceFill([ + 'trial_ends_at' => now()->addDays(8), + ])->save(); + + expect($this->user->account->fresh()->hasAppAccess())->toBeTrue() + ->and($this->user->account->fresh()->subscribed(Account::SUBSCRIPTION_NAME))->toBeFalse(); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.persona')) + ->assertRedirect(route('app.calendar')); +}); + +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]], +]); + +test('old onboarding icp routes are not registered', function (string $routeName) { + expect(Route::has($routeName))->toBeFalse(); +})->with([ + 'root' => 'app.onboarding', + 'store' => 'app.onboarding.store', + 'goals' => 'app.onboarding.goals', + 'goals store' => 'app.onboarding.goals.store', + 'referral source' => 'app.onboarding.referral-source', + 'referral source store' => 'app.onboarding.referral-source.store', + 'connect' => 'app.onboarding.connect', + 'checkout' => 'app.onboarding.checkout', +]); + +test('members cannot start Stripe checkout from welcome', function () { + $member = User::factory()->create(['account_id' => $this->user->account_id]); + $member->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + + $this->mock(StartSubscriptionCheckout::class)->shouldNotReceive('redirect'); + + // Members never reach the referral step — they are held on the + // subscription-required screen before any checkout attempt. + $this->actingAs($member->fresh()) + ->get(route('app.welcome.referral-source')) + ->assertRedirect(route('app.welcome.subscription-required')); + + $this->actingAs($member->fresh()) + ->post(route('app.welcome.referral-source.store'), [ + 'referral_source' => ReferralSource::Google->value, + ]) + ->assertRedirect(route('app.welcome.subscription-required')); + + expect($member->fresh()->referral_source)->toBeNull(); +}); + +test('members without app access are held on the subscription required screen', function (string $routeName, string $method, array $payload = []) { + $member = User::factory()->create(['account_id' => $this->user->account_id]); + + $this->actingAs($member->fresh()); + + $response = $method === 'get' + ? $this->get(route($routeName)) + : $this->post(route($routeName), $payload); + + $response->assertRedirect(route('app.welcome.subscription-required')); +})->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]], +]); + +test('subscription required screen renders for members without app access', function () { + $member = User::factory()->create(['account_id' => $this->user->account_id]); + + $this->actingAs($member->fresh()) + ->get(route('app.welcome.subscription-required')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('welcome/SubscriptionRequired', false) + ->where('ownerName', $this->user->name) + ); +}); + +test('subscription required screen sends owners back to the welcome flow', function () { + $this->actingAs($this->user) + ->get(route('app.welcome.subscription-required')) + ->assertRedirect(route('app.welcome.persona')); +}); + +test('subscription required screen sends subscribed users to the calendar', function () { + subscribeAccount($this->user->account); + + $this->actingAs($this->user->fresh()) + ->get(route('app.welcome.subscription-required')) + ->assertRedirect(route('app.calendar')); +}); + +test('subscription required screen sends members with app access to the calendar', function () { + ['owner' => $owner, 'member' => $member] = strandedMemberOnSharedAccount(); + subscribeAccount($owner->account); + + $this->actingAs($member) + ->get(route('app.welcome.subscription-required')) + ->assertRedirect(route('app.calendar')); +}); + +test('subscription required screen redirects to calendar in self hosted mode', function () { + config(['trypost.self_hosted' => true]); + + $member = User::factory()->create(['account_id' => $this->user->account_id]); + + $this->actingAs($member->fresh()) + ->get(route('app.welcome.subscription-required')) + ->assertRedirect(route('app.calendar')); +}); + +test('welcome sends members with app access to the calendar', function () { + ['owner' => $owner, 'member' => $member] = strandedMemberOnSharedAccount(); + subscribeAccount($owner->account); + + $this->actingAs($member) + ->get(route('app.welcome.persona')) + ->assertRedirect(route('app.calendar')); +}); + +test('referral source store fails loudly when the monthly price is not configured', function () { + $this->user->update([ + 'persona' => Persona::Agency->value, + 'goals' => [Goal::SaveTime->value], + ]); + Plan::where('slug', Slug::Workspace)->update(['stripe_monthly_price_id' => null]); + + $this->mock(StartSubscriptionCheckout::class)->shouldNotReceive('redirect'); + + $this->actingAs($this->user->fresh()) + ->post(route('app.welcome.referral-source.store'), [ + 'referral_source' => ReferralSource::Google->value, + ]) + ->assertServerError(); +}); 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 () {
- {{ description }} -
+ {{ description }} +
+ {{ + showPassword + ? $t( + 'auth.login.hide_password', + ) + : $t( + 'auth.login.show_password', + ) + }} +
- {{ $t('onboarding.connect.description') }} -
- {{ $t('onboarding.connect.must_connect') }} -
- {{ $t('onboarding.goals_description') }} -
- {{ $t('onboarding.description') }} -
- {{ $t('onboarding.referral_source_description') }} -
+ {{ + $t('welcome.subscription_required_owner', { + name: ownerName, + }) + }} +
+ {{ $t('welcome.subscription_required_auto') }} +