- Sidebar reorganized: Workspace group (connections, hashtags, labels, API keys, settings) and Account group (settings, usage, billing) - Account group only visible to owner and hidden in self-hosted mode - Onboarding simplified: role -> account (connect socials) -> completed -> redirect to /subscribe. Removed Subscription setup step. - Subscribe page redesigned with 4 plan cards, monthly/yearly toggle, trial info, and per-plan features list - Billing page redesigned following Sendkit layout (sections with sidebar labels) - Processing page uses usePoll with immediate watch for subscription activation - Cancel URL redirects directly to /subscribe - Account settings page with name and billing_email (syncs with Stripe) - Usage page with ring meters for all plan limits - Settings layout tabs only for user pages (profile, password, notifications). Workspace/API keys/billing are standalone pages. - GoogleAuthButton extracted as reusable component - WorkspaceRole TypeScript enum for type-safe role checks in frontend - Trial period changed to 7 days - Fixed onboarding loop when user confirms email - All 1101 tests passing
105 lines
3.1 KiB
PHP
105 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\App;
|
|
|
|
use App\Enums\SocialAccount\Platform as SocialPlatform;
|
|
use App\Enums\User\Persona;
|
|
use App\Enums\User\Setup;
|
|
use App\Models\User;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\Rule;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
|
|
class OnboardingController extends Controller
|
|
{
|
|
public function role(Request $request): Response|RedirectResponse
|
|
{
|
|
$redirect = $this->enforceStep($request->user(), Setup::Role);
|
|
if ($redirect) {
|
|
return $redirect;
|
|
}
|
|
|
|
return Inertia::render('onboarding/Role', [
|
|
'personas' => Persona::toSelectArray(),
|
|
]);
|
|
}
|
|
|
|
public function storeRole(Request $request): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'persona' => ['required', Rule::enum(Persona::class)],
|
|
]);
|
|
|
|
$request->user()->update([
|
|
'persona' => data_get($validated, 'persona'),
|
|
'setup' => Setup::Connections,
|
|
]);
|
|
|
|
return redirect()->route('app.onboarding.account');
|
|
}
|
|
|
|
public function account(Request $request): Response|RedirectResponse
|
|
{
|
|
$redirect = $this->enforceStep($request->user(), Setup::Connections);
|
|
if ($redirect) {
|
|
return $redirect;
|
|
}
|
|
|
|
$user = $request->user();
|
|
$workspace = $user->currentWorkspace;
|
|
|
|
$platforms = collect();
|
|
|
|
if ($workspace) {
|
|
$connectedAccounts = $workspace->socialAccounts;
|
|
|
|
$platforms = collect(SocialPlatform::enabled())->map(fn ($platform) => [
|
|
'value' => $platform->value,
|
|
'label' => $platform->label(),
|
|
'color' => $platform->color(),
|
|
'connected' => $connectedAccounts->firstWhere('platform', $platform) !== null,
|
|
'account' => $connectedAccounts->firstWhere('platform', $platform),
|
|
])->values();
|
|
}
|
|
|
|
return Inertia::render('onboarding/Account', [
|
|
'platforms' => $platforms,
|
|
'hasWorkspace' => $workspace !== null,
|
|
]);
|
|
}
|
|
|
|
public function storeAccount(Request $request): RedirectResponse
|
|
{
|
|
$request->user()->update(['setup' => Setup::Completed]);
|
|
|
|
if (config('trypost.self_hosted')) {
|
|
session()->flash('flash.banner', __('auth.flash.welcome'));
|
|
session()->flash('flash.bannerStyle', 'success');
|
|
|
|
return redirect()->route('app.calendar');
|
|
}
|
|
|
|
return redirect()->route('app.subscribe');
|
|
}
|
|
|
|
private function enforceStep(User $user, Setup $expectedStep): ?RedirectResponse
|
|
{
|
|
if ($user->setup === $expectedStep) {
|
|
return null;
|
|
}
|
|
|
|
if ($user->setup === Setup::Completed) {
|
|
return redirect()->route('app.calendar');
|
|
}
|
|
|
|
return match ($user->setup) {
|
|
Setup::Role => redirect()->route('app.onboarding.role'),
|
|
Setup::Connections => redirect()->route('app.onboarding.account'),
|
|
default => redirect()->route('app.onboarding.role'),
|
|
};
|
|
}
|
|
}
|