diff --git a/app/Http/Controllers/Auth/FacebookController.php b/app/Http/Controllers/Auth/FacebookController.php
index f0fb65fb..e6dcf895 100644
--- a/app/Http/Controllers/Auth/FacebookController.php
+++ b/app/Http/Controllers/Auth/FacebookController.php
@@ -45,7 +45,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
session([
@@ -174,15 +177,19 @@ public function selectPage(Request $request)
$workspaceId = session('social_connect_workspace');
if (! $oauthData || ! $workspaceId) {
- return redirect()->route('dashboard')
- ->with('error', 'Session expired. Please try again.');
+ session()->flash('flash.banner', __('accounts.flash.session_expired'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route('accounts');
}
$workspace = Workspace::find($workspaceId);
if (! $workspace) {
- return redirect()->route('dashboard')
- ->with('error', 'Workspace not found.');
+ session()->flash('flash.banner', __('accounts.flash.workspace_not_found'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route('accounts');
}
return Inertia::render('accounts/FacebookPageSelect', [
diff --git a/app/Http/Controllers/Auth/InstagramController.php b/app/Http/Controllers/Auth/InstagramController.php
index e761b332..d26473ca 100644
--- a/app/Http/Controllers/Auth/InstagramController.php
+++ b/app/Http/Controllers/Auth/InstagramController.php
@@ -41,7 +41,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
session([
diff --git a/app/Http/Controllers/Auth/LinkedInController.php b/app/Http/Controllers/Auth/LinkedInController.php
index 9a8e2c99..89413d4b 100644
--- a/app/Http/Controllers/Auth/LinkedInController.php
+++ b/app/Http/Controllers/Auth/LinkedInController.php
@@ -44,7 +44,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
return $this->redirectToProvider($request, $this->driver, $this->scopes);
diff --git a/app/Http/Controllers/Auth/LinkedInPageController.php b/app/Http/Controllers/Auth/LinkedInPageController.php
index c49d911d..6b151cea 100644
--- a/app/Http/Controllers/Auth/LinkedInPageController.php
+++ b/app/Http/Controllers/Auth/LinkedInPageController.php
@@ -49,7 +49,10 @@ public function connect(Request $request): SymfonyResponse|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
session([
@@ -128,15 +131,19 @@ public function selectPage(Request $request): Response|RedirectResponse
$pendingData = session('linkedin_page_pending');
if (! $pendingData) {
- return redirect()->route('dashboard')
- ->with('error', 'Session expired. Please try again.');
+ session()->flash('flash.banner', __('accounts.flash.session_expired'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route('accounts');
}
$workspace = Workspace::find($pendingData['workspace_id']);
if (! $workspace || ! $request->user()->can('manageAccounts', $workspace)) {
- return redirect()->route('dashboard')
- ->with('error', 'Workspace not found.');
+ session()->flash('flash.banner', __('accounts.flash.workspace_not_found'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route('accounts');
}
return Inertia::render('accounts/LinkedInPageSelect', [
diff --git a/app/Http/Controllers/Auth/PinterestController.php b/app/Http/Controllers/Auth/PinterestController.php
index e5b66036..4f335947 100644
--- a/app/Http/Controllers/Auth/PinterestController.php
+++ b/app/Http/Controllers/Auth/PinterestController.php
@@ -43,7 +43,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
return $this->redirectToProvider($request, $this->driver, $this->scopes);
diff --git a/app/Http/Controllers/Auth/ThreadsController.php b/app/Http/Controllers/Auth/ThreadsController.php
index bccdab11..90ea35b5 100644
--- a/app/Http/Controllers/Auth/ThreadsController.php
+++ b/app/Http/Controllers/Auth/ThreadsController.php
@@ -41,7 +41,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
session([
diff --git a/app/Http/Controllers/Auth/TikTokController.php b/app/Http/Controllers/Auth/TikTokController.php
index 4487ec8d..597a6cf5 100644
--- a/app/Http/Controllers/Auth/TikTokController.php
+++ b/app/Http/Controllers/Auth/TikTokController.php
@@ -41,7 +41,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
session(['social_reconnect_id' => $existingAccount?->id]);
diff --git a/app/Http/Controllers/Auth/XController.php b/app/Http/Controllers/Auth/XController.php
index 2b57cc6b..481dc998 100644
--- a/app/Http/Controllers/Auth/XController.php
+++ b/app/Http/Controllers/Auth/XController.php
@@ -39,7 +39,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
return $this->redirectToProvider($request, $this->driver, $this->scopes);
diff --git a/app/Http/Controllers/Auth/YouTubeController.php b/app/Http/Controllers/Auth/YouTubeController.php
index fd2c47e7..27790385 100644
--- a/app/Http/Controllers/Auth/YouTubeController.php
+++ b/app/Http/Controllers/Auth/YouTubeController.php
@@ -41,7 +41,10 @@ public function connect(Request $request): Response|RedirectResponse
->first();
if ($existingAccount && ! $existingAccount->isDisconnected()) {
- return back()->with('error', 'This platform is already connected.');
+ session()->flash('flash.banner', __('accounts.flash.already_connected'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return back();
}
session([
@@ -164,15 +167,19 @@ public function selectChannel(Request $request)
$workspaceId = session('social_connect_workspace');
if (! $oauthData || ! $workspaceId) {
- return redirect()->route('dashboard')
- ->with('error', 'Session expired. Please try again.');
+ session()->flash('flash.banner', __('accounts.flash.session_expired'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route('accounts');
}
$workspace = Workspace::find($workspaceId);
if (! $workspace) {
- return redirect()->route('dashboard')
- ->with('error', 'Workspace not found.');
+ session()->flash('flash.banner', __('accounts.flash.workspace_not_found'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route('accounts');
}
// Fetch YouTube channels
@@ -183,8 +190,10 @@ public function selectChannel(Request $request)
$this->forgetSocialConnectSession();
session()->forget('youtube_oauth');
- return redirect()->route($redirectRoute)
- ->with('error', 'No YouTube channels found. Please create a channel first.');
+ session()->flash('flash.banner', __('accounts.flash.no_youtube_channels'));
+ session()->flash('flash.bannerStyle', 'danger');
+
+ return redirect()->route($redirectRoute);
}
return inertia('accounts/YouTubeChannelSelect', [
diff --git a/app/Http/Controllers/BillingController.php b/app/Http/Controllers/BillingController.php
index 6ecdffb5..9cd5e470 100644
--- a/app/Http/Controllers/BillingController.php
+++ b/app/Http/Controllers/BillingController.php
@@ -89,9 +89,9 @@ public function processing(Request $request): Response|RedirectResponse
$user = $request->user();
$status = $request->query('status', 'processing');
- // If already subscribed, redirect to dashboard
+ // If already subscribed, redirect to calendar
if ($user->subscribed('default')) {
- return redirect()->route('dashboard');
+ return redirect()->route('calendar');
}
// Validate status
diff --git a/app/Models/Traits/HasWorkspace.php b/app/Models/Traits/HasWorkspace.php
index d48c194c..8998b117 100644
--- a/app/Models/Traits/HasWorkspace.php
+++ b/app/Models/Traits/HasWorkspace.php
@@ -52,9 +52,14 @@ public function ownedWorkspacesCount(): int
/**
* Check if user can create more workspaces based on subscription.
+ * In self-hosted mode, users can create unlimited workspaces.
*/
public function canCreateWorkspace(): bool
{
+ if (config('trypost.self_hosted')) {
+ return true;
+ }
+
if (! $this->hasActiveSubscription()) {
return $this->ownedWorkspacesCount() === 0;
}
@@ -66,9 +71,14 @@ public function canCreateWorkspace(): bool
/**
* Increment workspace quantity on subscription.
+ * Skipped in self-hosted mode (no Stripe subscription).
*/
public function incrementWorkspaceQuantity(): void
{
+ if (config('trypost.self_hosted')) {
+ return;
+ }
+
if ($this->hasActiveSubscription()) {
$this->subscription('default')->incrementQuantity();
}
@@ -76,9 +86,14 @@ public function incrementWorkspaceQuantity(): void
/**
* Decrement workspace quantity on subscription.
+ * Skipped in self-hosted mode (no Stripe subscription).
*/
public function decrementWorkspaceQuantity(): void
{
+ if (config('trypost.self_hosted')) {
+ return;
+ }
+
if ($this->hasActiveSubscription()) {
$subscription = $this->subscription('default');
@@ -90,9 +105,14 @@ public function decrementWorkspaceQuantity(): void
/**
* Sync subscription quantity with actual workspace count.
+ * Skipped in self-hosted mode (no Stripe subscription).
*/
public function syncWorkspaceQuantity(): void
{
+ if (config('trypost.self_hosted')) {
+ return;
+ }
+
if ($this->hasActiveSubscription()) {
$count = $this->ownedWorkspacesCount();
diff --git a/app/Models/User.php b/app/Models/User.php
index 3d4bcf09..0fbd5e72 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -88,9 +88,14 @@ public function language(): BelongsTo
/**
* Check if user has an active subscription.
+ * In self-hosted mode, always returns true (no subscription required).
*/
public function hasActiveSubscription(): bool
{
+ if (config('trypost.self_hosted')) {
+ return true;
+ }
+
return $this->subscribed('default');
}
diff --git a/lang/en/accounts.php b/lang/en/accounts.php
index 6a3ea502..f7c3e30d 100644
--- a/lang/en/accounts.php
+++ b/lang/en/accounts.php
@@ -59,5 +59,10 @@
'flash' => [
'disconnected' => 'Account disconnected successfully!',
+ 'connected' => 'Account connected successfully!',
+ 'session_expired' => 'Session expired. Please try again.',
+ 'workspace_not_found' => 'Workspace not found.',
+ 'already_connected' => 'This platform is already connected.',
+ 'no_youtube_channels' => 'No YouTube channels found. Please create a channel first.',
],
];
diff --git a/lang/en/billing.php b/lang/en/billing.php
new file mode 100644
index 00000000..33448842
--- /dev/null
+++ b/lang/en/billing.php
@@ -0,0 +1,37 @@
+ 'Subscription',
+ 'description' => 'Manage your subscription and payment method',
+
+ 'trial' => [
+ 'title' => 'Trial period active',
+ 'description' => 'Your trial ends on :date. After that, your subscription will be charged automatically.',
+ ],
+
+ 'subscription' => [
+ 'title' => 'Your Subscription',
+ 'workspaces' => 'Workspaces',
+ 'quantity' => 'Subscription quantity',
+ 'expires' => 'Expires :date',
+ 'canceled_on' => 'Your subscription will be canceled on :date',
+ 'manage' => 'Manage on Stripe',
+ ],
+
+ 'invoices' => [
+ 'title' => 'Invoices',
+ 'description' => 'Payment history',
+ 'empty' => 'No invoices found',
+ 'paid' => 'Paid',
+ ],
+
+ 'status' => [
+ 'active' => 'Active',
+ 'canceled' => 'Canceled',
+ 'incomplete' => 'Incomplete',
+ 'incomplete_expired' => 'Expired',
+ 'past_due' => 'Past due',
+ 'trialing' => 'Trial',
+ 'unpaid' => 'Unpaid',
+ ],
+];
diff --git a/lang/pt-br/accounts.php b/lang/pt-br/accounts.php
index 7a49bece..65667854 100644
--- a/lang/pt-br/accounts.php
+++ b/lang/pt-br/accounts.php
@@ -59,5 +59,10 @@
'flash' => [
'disconnected' => 'Conta desconectada com sucesso!',
+ 'connected' => 'Conta conectada com sucesso!',
+ 'session_expired' => 'Sessão expirada. Por favor, tente novamente.',
+ 'workspace_not_found' => 'Workspace não encontrado.',
+ 'already_connected' => 'Esta plataforma já está conectada.',
+ 'no_youtube_channels' => 'Nenhum canal do YouTube encontrado. Por favor, crie um canal primeiro.',
],
];
diff --git a/lang/pt-br/billing.php b/lang/pt-br/billing.php
new file mode 100644
index 00000000..943b13f9
--- /dev/null
+++ b/lang/pt-br/billing.php
@@ -0,0 +1,37 @@
+ 'Assinatura',
+ 'description' => 'Gerencie sua assinatura e método de pagamento',
+
+ 'trial' => [
+ 'title' => 'Período de teste ativo',
+ 'description' => 'Seu período de teste termina em :date. Após isso, sua assinatura será cobrada automaticamente.',
+ ],
+
+ 'subscription' => [
+ 'title' => 'Sua Assinatura',
+ 'workspaces' => 'Workspaces',
+ 'quantity' => 'Quantidade da assinatura',
+ 'expires' => 'Expira em :date',
+ 'canceled_on' => 'Sua assinatura será cancelada em :date',
+ 'manage' => 'Gerenciar no Stripe',
+ ],
+
+ 'invoices' => [
+ 'title' => 'Faturas',
+ 'description' => 'Histórico de pagamentos',
+ 'empty' => 'Nenhuma fatura encontrada',
+ 'paid' => 'Pago',
+ ],
+
+ 'status' => [
+ 'active' => 'Ativo',
+ 'canceled' => 'Cancelado',
+ 'incomplete' => 'Incompleto',
+ 'incomplete_expired' => 'Expirado',
+ 'past_due' => 'Vencido',
+ 'trialing' => 'Teste',
+ 'unpaid' => 'Não pago',
+ ],
+];
diff --git a/resources/js/pages/billing/Index.vue b/resources/js/pages/billing/Index.vue
index 6e5ce76a..4441a1bc 100644
--- a/resources/js/pages/billing/Index.vue
+++ b/resources/js/pages/billing/Index.vue
@@ -1,5 +1,6 @@
-
+