fix: stripe
add: custom error pages fix: invititation feat: new quick login for first users (UX++) feat: more internal notifications
This commit is contained in:
parent
0dbb8b4420
commit
e7c0c26b32
@ -6,6 +6,7 @@
|
|||||||
USERID=
|
USERID=
|
||||||
GROUPID=
|
GROUPID=
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
|
APP_NAME=Coolify-localhost
|
||||||
APP_ID=development
|
APP_ID=development
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
@ -6,20 +6,20 @@
|
|||||||
use App\Models\Waitlist;
|
use App\Models\Waitlist;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class WaitlistInvite extends Command
|
class WaitlistInvite extends Command
|
||||||
{
|
{
|
||||||
public Waitlist|null $next_patient = null;
|
public Waitlist|User|null $next_patient = null;
|
||||||
public User|null $new_user = null;
|
|
||||||
public string|null $password = null;
|
public string|null $password = null;
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'waitlist:invite {email?}';
|
protected $signature = 'waitlist:invite {email?} {--only-email}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@ -34,7 +34,16 @@ class WaitlistInvite extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if ($this->argument('email')) {
|
if ($this->argument('email')) {
|
||||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
if ($this->option('only-email')) {
|
||||||
|
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||||
|
$this->password = Str::password();
|
||||||
|
$this->next_patient->update([
|
||||||
|
'password' => Hash::make($this->password),
|
||||||
|
'force_password_reset' => true,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||||
|
}
|
||||||
if (!$this->next_patient) {
|
if (!$this->next_patient) {
|
||||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||||
return;
|
return;
|
||||||
@ -43,6 +52,10 @@ public function handle()
|
|||||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||||
}
|
}
|
||||||
if ($this->next_patient) {
|
if ($this->next_patient) {
|
||||||
|
if ($this->option('only-email')) {
|
||||||
|
$this->send_email();
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->register_user();
|
$this->register_user();
|
||||||
$this->remove_from_waitlist();
|
$this->remove_from_waitlist();
|
||||||
$this->send_email();
|
$this->send_email();
|
||||||
@ -55,7 +68,7 @@ private function register_user()
|
|||||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||||
if (!$already_registered) {
|
if (!$already_registered) {
|
||||||
$this->password = Str::password();
|
$this->password = Str::password();
|
||||||
$this->new_user = User::create([
|
User::create([
|
||||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
@ -73,10 +86,14 @@ private function remove_from_waitlist()
|
|||||||
}
|
}
|
||||||
private function send_email()
|
private function send_email()
|
||||||
{
|
{
|
||||||
|
ray($this->next_patient->email, $this->password);
|
||||||
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => $this->password,
|
'password' => $this->password,
|
||||||
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||||
send_user_an_email($mail, $this->next_patient->email);
|
send_user_an_email($mail, $this->next_patient->email);
|
||||||
|
@ -8,15 +8,41 @@
|
|||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Auth;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
|
||||||
|
public function link()
|
||||||
|
{
|
||||||
|
$token = request()->get('token');
|
||||||
|
if ($token) {
|
||||||
|
$decrypted = Crypt::decryptString($token);
|
||||||
|
$email = Str::of($decrypted)->before('@@@');
|
||||||
|
$password = Str::of($decrypted)->after('@@@');
|
||||||
|
$user = User::whereEmail($email)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
if (Hash::check($password, $user->password)) {
|
||||||
|
Auth::login($user);
|
||||||
|
$team = $user->teams()->first();
|
||||||
|
session(['currentTeam' => $team]);
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||||
|
}
|
||||||
public function subscription()
|
public function subscription()
|
||||||
{
|
{
|
||||||
if (!isCloud()) {
|
if (!isCloud()) {
|
||||||
@ -37,10 +63,12 @@ public function license()
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function force_passoword_reset() {
|
public function force_passoword_reset()
|
||||||
|
{
|
||||||
return view('auth.force-password-reset');
|
return view('auth.force-password-reset');
|
||||||
}
|
}
|
||||||
public function boarding() {
|
public function boarding()
|
||||||
|
{
|
||||||
if (currentTeam()->boarding || isDev()) {
|
if (currentTeam()->boarding || isDev()) {
|
||||||
return view('boarding');
|
return view('boarding');
|
||||||
} else {
|
} else {
|
||||||
|
@ -18,22 +18,26 @@ class ForcePasswordReset extends Component
|
|||||||
'password' => 'required|min:8',
|
'password' => 'required|min:8',
|
||||||
'password_confirmation' => 'required|same:password',
|
'password_confirmation' => 'required|same:password',
|
||||||
];
|
];
|
||||||
public function mount() {
|
public function mount()
|
||||||
|
{
|
||||||
$this->email = auth()->user()->email;
|
$this->email = auth()->user()->email;
|
||||||
}
|
}
|
||||||
public function submit() {
|
public function submit()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->rateLimit(10);
|
$this->rateLimit(10);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
$firstLogin = auth()->user()->created_at == auth()->user()->updated_at;
|
||||||
auth()->user()->forceFill([
|
auth()->user()->forceFill([
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
'force_password_reset' => false,
|
'force_password_reset' => false,
|
||||||
])->save();
|
])->save();
|
||||||
auth()->logout();
|
if ($firstLogin) {
|
||||||
return redirect()->route('login')->with('status', 'Your initial password has been set.');
|
send_internal_notification('First login for ' . auth()->user()->email);
|
||||||
} catch(\Exception $e) {
|
}
|
||||||
return general_error_handler(err:$e, that:$this);
|
return redirect()->route('dashboard');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,12 @@ class CheckForcePasswordReset
|
|||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (auth()->user()) {
|
if (auth()->user()) {
|
||||||
|
if ($request->path() === 'auth/link') {
|
||||||
|
auth()->logout();
|
||||||
|
request()->session()->invalidate();
|
||||||
|
request()->session()->regenerateToken();
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
$force_password_reset = auth()->user()->force_password_reset;
|
$force_password_reset = auth()->user()->force_password_reset;
|
||||||
if ($force_password_reset) {
|
if ($force_password_reset) {
|
||||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
||||||
|
@ -12,12 +12,12 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public function __construct(public string $message)
|
public function __construct(public string $message)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels[] = DiscordChannel::class;
|
return [DiscordChannel::class];
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
|
@ -50,10 +50,6 @@ public function toMail($notifiable)
|
|||||||
protected function buildMailMessage($url)
|
protected function buildMailMessage($url)
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->from(
|
|
||||||
data_get($this->settings, 'smtp_from_address'),
|
|
||||||
data_get($this->settings, 'smtp_from_name'),
|
|
||||||
);
|
|
||||||
$mail->subject('Reset Password');
|
$mail->subject('Reset Password');
|
||||||
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
||||||
return $mail;
|
return $mail;
|
||||||
|
@ -57,6 +57,7 @@ public function boot(): void
|
|||||||
});
|
});
|
||||||
|
|
||||||
Fortify::loginView(function () {
|
Fortify::loginView(function () {
|
||||||
|
abort(503,'Login is disabled');
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
$users = User::count();
|
$users = User::count();
|
||||||
if ($users == 0) {
|
if ($users == 0) {
|
||||||
|
@ -242,7 +242,8 @@ function validate_cron_expression($expression_to_validate): bool
|
|||||||
function send_internal_notification(string $message): void
|
function send_internal_notification(string $message): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$baseUrl = base_url(false);
|
ray('Sending internal notification... 📬 ' . $message);
|
||||||
|
$baseUrl = config('app.name');
|
||||||
$team = Team::find(0);
|
$team = Team::find(0);
|
||||||
$team->notify(new GeneralNotification("👀 Internal notifications from {$baseUrl}: " . $message));
|
$team->notify(new GeneralNotification("👀 Internal notifications from {$baseUrl}: " . $message));
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
|
32
database/migrations/2023_08_22_071054_add_stripe_reasons.php
Normal file
32
database/migrations/2023_08_22_071054_add_stripe_reasons.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->string('stripe_feedback')->nullable()->after('stripe_cancel_at_period_end');
|
||||||
|
$table->string('stripe_comment')->nullable()->after('stripe_feedback');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('stripe_feedback');
|
||||||
|
$table->dropColumn('stripe_comment');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -51,6 +51,11 @@ class="text-xs normal-case hover:no-underline btn btn-sm bg-coollabs-gradient">
|
|||||||
{{ session('status') }}
|
{{ session('status') }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="mb-4 font-medium text-red-600">
|
||||||
|
{{ session('error') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{{ Illuminate\Mail\Markdown::parse('---') }}
|
{{ Illuminate\Mail\Markdown::parse('---') }}
|
||||||
|
|
||||||
Thank you.<br>
|
Thank you,<br>
|
||||||
{{ config('app.name') ?? 'Coolify' }}
|
{{ config('app.name') ?? 'Coolify' }}
|
||||||
|
|
||||||
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io)') }}
|
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io)') }}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<x-emails.layout>
|
<x-emails.layout>
|
||||||
A password reset has been requested for this email address on [{{ config('app.name') }}]({{ config('app.url') }}).
|
A password reset has been requested for this email address.
|
||||||
|
|
||||||
Click [here]({{ $url }}) to reset your password.
|
Click [here]({{ $url }}) to reset your password.
|
||||||
|
|
||||||
|
@ -1,19 +1,4 @@
|
|||||||
<x-emails.layout>
|
<x-emails.layout>
|
||||||
You have been invited to join the Coolify Cloud.
|
You have been invited to join the Coolify Cloud: [Get Started]({{$loginLink}})
|
||||||
|
|
||||||
[Login here]({{base_url()}}/login)
|
|
||||||
|
|
||||||
Here is your initial login information.
|
|
||||||
|
|
||||||
Email:
|
|
||||||
|
|
||||||
**{{ $email }}**
|
|
||||||
|
|
||||||
Initial Password:
|
|
||||||
|
|
||||||
**{{ $password }}**
|
|
||||||
|
|
||||||
(You will forced to change it on first login.)
|
|
||||||
|
|
||||||
</x-emails.layout>
|
</x-emails.layout>
|
||||||
|
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero">
|
||||||
@section('title', __('Unauthorized'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '401')
|
<div class="">
|
||||||
@section('message', __('Unauthorized'))
|
<p class="font-mono text-6xl font-semibold text-warning">401</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">You shall not pass!</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">You don't have permission to access this page.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
||||||
|
support
|
||||||
|
<x-external-link />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero">
|
||||||
@section('title', __('Forbidden'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '403')
|
<div class="">
|
||||||
@section('message', __($exception->getMessage() ?: 'Forbidden'))
|
<p class="font-mono text-6xl font-semibold text-warning">403</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">You shall not pass!</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">You don't have permission to access this page.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
||||||
|
support
|
||||||
|
<x-external-link />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
<x-layout>
|
@extends('layouts.base')
|
||||||
<div class="min-h-screen hero">
|
<div class="min-h-screen hero">
|
||||||
<div class="text-center hero-content">
|
<div class="text-center hero-content">
|
||||||
<div class="">
|
<div class="">
|
||||||
<p class="font-mono text-6xl font-semibold text-warning">404</p>
|
<p class="font-mono text-6xl font-semibold text-warning">404</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight text-white">How did you got here?</h1>
|
<h1 class="mt-4 font-bold tracking-tight text-white">How did you got here?</h1>
|
||||||
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
||||||
for.
|
for.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center justify-center mt-10 gap-x-6">
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<x-forms.button isHighlighted>Go back home</x-forms.button>
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
<a target="_blank" class="text-xs" href="https://docs.coollabs.io/contact.html">Contact
|
||||||
support
|
support
|
||||||
<x-external-link />
|
<x-external-link />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-layout>
|
</div>
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
<x-layout>
|
@extends('layouts.base')
|
||||||
<div class="min-h-screen hero">
|
<div class="min-h-screen hero">
|
||||||
<div class="text-center hero-content">
|
<div class="text-center hero-content">
|
||||||
<div class="">
|
<div class="">
|
||||||
<p class="font-mono text-6xl font-semibold text-warning">419</p>
|
<p class="font-mono text-6xl font-semibold text-warning">419</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight text-white">This page is definitely old</h1>
|
<h1 class="mt-4 font-bold tracking-tight text-white">This page is definitely old, not like you!</h1>
|
||||||
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
<p class="mt-6 text-base leading-7 text-neutral-300">Sorry, we couldn’t find the page you’re looking
|
||||||
for.
|
for.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center justify-center mt-10 gap-x-6">
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
<a href="/"
|
<a href="/">
|
||||||
class="rounded-md bg-coollabs px-3.5 py-2.5 font-semibold text-white shadow-sm hover:bg-coollabs-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 hover:no-underline">Go
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
back home</a>
|
</a>
|
||||||
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
||||||
support
|
support
|
||||||
<span aria-hidden="true">→</span></a>
|
<span aria-hidden="true">→</span></a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-layout>
|
</div>
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero">
|
||||||
@section('title', __('Too Many Requests'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '429')
|
<div class="">
|
||||||
@section('message', __('Too Many Requests'))
|
<p class="font-mono text-6xl font-semibold text-warning">429</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">Woah, slow down there!</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">You're making too many requests. Please wait a few seconds before trying again.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
||||||
|
support
|
||||||
|
<span aria-hidden="true">→</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,5 +1,23 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero ">
|
||||||
@section('title', __('Server Error'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '500')
|
<div>
|
||||||
@section('message', __('Server Error'))
|
<p class="font-mono text-6xl font-semibold text-warning">500</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">Something is not okay, are you okay?</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">There has been an error, we are working on it.
|
||||||
|
</p>
|
||||||
|
@if ($exception->getMessage() !== '')
|
||||||
|
<p class="mt-6 text-base leading-7 text-red-500">Error: {{ $exception->getMessage() }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="/">
|
||||||
|
<x-forms.button>Go back home</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
||||||
|
support
|
||||||
|
<span aria-hidden="true">→</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
@extends('errors::minimal')
|
@extends('layouts.base')
|
||||||
|
<div class="min-h-screen hero ">
|
||||||
@section('title', __('Service Unavailable'))
|
<div class="text-center hero-content">
|
||||||
@section('code', '503')
|
<div>
|
||||||
@section('message', __('Service Unavailable'))
|
<p class="font-mono text-6xl font-semibold text-warning">503</p>
|
||||||
|
<h1 class="mt-4 font-bold tracking-tight text-white">We are working on serious things.</h1>
|
||||||
|
<p class="mt-6 text-base leading-7 text-neutral-300">Service Unavailable. Be right back. Thanks for your
|
||||||
|
patience.
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
<a href="https://docs.coollabs.io/contact.html" class="font-semibold text-white ">Contact
|
||||||
|
support
|
||||||
|
<span aria-hidden="true">→</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<title>@yield('title')</title>
|
|
||||||
|
|
||||||
<!-- Styles -->
|
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #636b6f;
|
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
font-weight: 100;
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-height {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-center {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.position-ref {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 36px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="flex-center position-ref full-height">
|
|
||||||
<div class="content">
|
|
||||||
<div class="title">
|
|
||||||
@yield('message')
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,552 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<title>@yield('title')</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
|
||||||
html {
|
|
||||||
line-height: 1.15;
|
|
||||||
-webkit-text-size-adjust: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color: transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: monospace, monospace;
|
|
||||||
font-size: 1em
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
|
||||||
line-height: 1.5
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
:after,
|
|
||||||
:before {
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 0 solid #e2e8f0
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace
|
|
||||||
}
|
|
||||||
|
|
||||||
svg,
|
|
||||||
video {
|
|
||||||
display: block;
|
|
||||||
vertical-align: middle
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-white {
|
|
||||||
--bg-opacity: 1;
|
|
||||||
background-color: #fff;
|
|
||||||
background-color: rgba(255, 255, 255, var(--bg-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-100 {
|
|
||||||
--bg-opacity: 1;
|
|
||||||
background-color: #f7fafc;
|
|
||||||
background-color: rgba(247, 250, 252, var(--bg-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-200 {
|
|
||||||
--border-opacity: 1;
|
|
||||||
border-color: #edf2f7;
|
|
||||||
border-color: rgba(237, 242, 247, var(--border-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-400 {
|
|
||||||
--border-opacity: 1;
|
|
||||||
border-color: #cbd5e0;
|
|
||||||
border-color: rgba(203, 213, 224, var(--border-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-t {
|
|
||||||
border-top-width: 1px
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-r {
|
|
||||||
border-right-width: 1px
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
|
||||||
align-items: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-semibold {
|
|
||||||
font-weight: 600
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-5 {
|
|
||||||
height: 1.25rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-8 {
|
|
||||||
height: 2rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-16 {
|
|
||||||
height: 4rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-sm {
|
|
||||||
font-size: .875rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-lg {
|
|
||||||
font-size: 1.125rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.leading-7 {
|
|
||||||
line-height: 1.75rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-auto {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-1 {
|
|
||||||
margin-left: .25rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-2 {
|
|
||||||
margin-top: .5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-2 {
|
|
||||||
margin-right: .5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-2 {
|
|
||||||
margin-left: .5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-4 {
|
|
||||||
margin-top: 1rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-4 {
|
|
||||||
margin-left: 1rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-8 {
|
|
||||||
margin-top: 2rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-12 {
|
|
||||||
margin-left: 3rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.-mt-px {
|
|
||||||
margin-top: -1px
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-xl {
|
|
||||||
max-width: 36rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-6xl {
|
|
||||||
max-width: 72rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.min-h-screen {
|
|
||||||
min-height: 100vh
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-hidden {
|
|
||||||
overflow: hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-6 {
|
|
||||||
padding: 1.5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-4 {
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-4 {
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-6 {
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
padding-right: 1.5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.pt-8 {
|
|
||||||
padding-top: 2rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed {
|
|
||||||
position: fixed
|
|
||||||
}
|
|
||||||
|
|
||||||
.relative {
|
|
||||||
position: relative
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-0 {
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-0 {
|
|
||||||
right: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow {
|
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06)
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-200 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #edf2f7;
|
|
||||||
color: rgba(237, 242, 247, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-300 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #e2e8f0;
|
|
||||||
color: rgba(226, 232, 240, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-400 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #cbd5e0;
|
|
||||||
color: rgba(203, 213, 224, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-500 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #a0aec0;
|
|
||||||
color: rgba(160, 174, 192, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-600 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #718096;
|
|
||||||
color: rgba(113, 128, 150, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-700 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #4a5568;
|
|
||||||
color: rgba(74, 85, 104, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-900 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #1a202c;
|
|
||||||
color: rgba(26, 32, 44, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.uppercase {
|
|
||||||
text-transform: uppercase
|
|
||||||
}
|
|
||||||
|
|
||||||
.underline {
|
|
||||||
text-decoration: underline
|
|
||||||
}
|
|
||||||
|
|
||||||
.antialiased {
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracking-wider {
|
|
||||||
letter-spacing: .05em
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-5 {
|
|
||||||
width: 1.25rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-8 {
|
|
||||||
width: 2rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-auto {
|
|
||||||
width: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-cols-1 {
|
|
||||||
grid-template-columns: repeat(1, minmax(0, 1fr))
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg)
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: rotate(1turn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg)
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: rotate(1turn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes ping {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
75%,
|
|
||||||
to {
|
|
||||||
transform: scale(2);
|
|
||||||
opacity: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ping {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
75%,
|
|
||||||
to {
|
|
||||||
transform: scale(2);
|
|
||||||
opacity: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes pulse {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
to {
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
opacity: .5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
to {
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
opacity: .5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes bounce {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
to {
|
|
||||||
transform: translateY(-25%);
|
|
||||||
-webkit-animation-timing-function: cubic-bezier(.8, 0, 1, 1);
|
|
||||||
animation-timing-function: cubic-bezier(.8, 0, 1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: translateY(0);
|
|
||||||
-webkit-animation-timing-function: cubic-bezier(0, 0, .2, 1);
|
|
||||||
animation-timing-function: cubic-bezier(0, 0, .2, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
to {
|
|
||||||
transform: translateY(-25%);
|
|
||||||
-webkit-animation-timing-function: cubic-bezier(.8, 0, 1, 1);
|
|
||||||
animation-timing-function: cubic-bezier(.8, 0, 1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: translateY(0);
|
|
||||||
-webkit-animation-timing-function: cubic-bezier(0, 0, .2, 1);
|
|
||||||
animation-timing-function: cubic-bezier(0, 0, .2, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.sm\:rounded-lg {
|
|
||||||
border-radius: .5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:block {
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:items-center {
|
|
||||||
align-items: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:justify-start {
|
|
||||||
justify-content: flex-start
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:justify-between {
|
|
||||||
justify-content: space-between
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:h-20 {
|
|
||||||
height: 5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:ml-0 {
|
|
||||||
margin-left: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:px-6 {
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
padding-right: 1.5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:pt-0 {
|
|
||||||
padding-top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:text-left {
|
|
||||||
text-align: left
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:text-right {
|
|
||||||
text-align: right
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.md\:border-t-0 {
|
|
||||||
border-top-width: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.md\:border-l {
|
|
||||||
border-left-width: 1px
|
|
||||||
}
|
|
||||||
|
|
||||||
.md\:grid-cols-2 {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.lg\:px-8 {
|
|
||||||
padding-left: 2rem;
|
|
||||||
padding-right: 2rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.dark\:bg-gray-800 {
|
|
||||||
--bg-opacity: 1;
|
|
||||||
background-color: #2d3748;
|
|
||||||
background-color: rgba(45, 55, 72, var(--bg-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-gray-900 {
|
|
||||||
--bg-opacity: 1;
|
|
||||||
background-color: #1a202c;
|
|
||||||
background-color: rgba(26, 32, 44, var(--bg-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:border-gray-700 {
|
|
||||||
--border-opacity: 1;
|
|
||||||
border-color: #4a5568;
|
|
||||||
border-color: rgba(74, 85, 104, var(--border-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:text-white {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #fff;
|
|
||||||
color: rgba(255, 255, 255, var(--text-opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:text-gray-400 {
|
|
||||||
--text-opacity: 1;
|
|
||||||
color: #cbd5e0;
|
|
||||||
color: rgba(203, 213, 224, var(--text-opacity))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="antialiased">
|
|
||||||
<div
|
|
||||||
class="relative flex justify-center min-h-screen bg-gray-100 items-top dark:bg-gray-900 sm:items-center sm:pt-0">
|
|
||||||
<div class="max-w-xl mx-auto sm:px-6 lg:px-8">
|
|
||||||
<div class="flex items-center pt-8 sm:justify-start sm:pt-0">
|
|
||||||
<div class="px-4 text-lg tracking-wider text-gray-500 border-r border-gray-400">
|
|
||||||
@yield('code')
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ml-4 text-lg tracking-wider text-gray-500 uppercase">
|
|
||||||
@yield('message')
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -49,7 +49,9 @@
|
|||||||
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||||
})->name('password.forgot');
|
})->name('password.forgot');
|
||||||
Route::get('/waitlist', WaitlistIndex::class)->name('waitlist.index');
|
Route::get('/waitlist', WaitlistIndex::class)->name('waitlist.index');
|
||||||
|
Route::middleware(['throttle:login'])->group(function() {
|
||||||
|
Route::get('/auth/link', [Controller::class, 'link'])->name('auth.link');
|
||||||
|
});
|
||||||
Route::prefix('magic')->middleware(['auth'])->group(function () {
|
Route::prefix('magic')->middleware(['auth'])->group(function () {
|
||||||
Route::get('/servers', [MagicController::class, 'servers']);
|
Route::get('/servers', [MagicController::class, 'servers']);
|
||||||
Route::get('/destinations', [MagicController::class, 'destinations']);
|
Route::get('/destinations', [MagicController::class, 'destinations']);
|
||||||
|
@ -225,6 +225,7 @@
|
|||||||
]);
|
]);
|
||||||
$type = data_get($event, 'type');
|
$type = data_get($event, 'type');
|
||||||
$data = data_get($event, 'data.object');
|
$data = data_get($event, 'data.object');
|
||||||
|
ray('Event: '. $type);
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'checkout.session.completed':
|
case 'checkout.session.completed':
|
||||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||||
@ -239,12 +240,14 @@
|
|||||||
}
|
}
|
||||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||||
if ($subscription) {
|
if ($subscription) {
|
||||||
|
send_internal_notification('Old subscription activated for team: ' . $teamId);
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
'stripe_customer_id' => $customerId,
|
'stripe_customer_id' => $customerId,
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
send_internal_notification('New subscription for team: ' . $teamId);
|
||||||
Subscription::create([
|
Subscription::create([
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
@ -254,46 +257,67 @@
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'invoice.paid':
|
case 'invoice.paid':
|
||||||
$subscriptionId = data_get($data, 'lines.data.0.subscription');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_subscription_id', $subscriptionId)->firstOrFail();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case 'invoice.payment_failed':
|
// case 'invoice.payment_failed':
|
||||||
$customerId = data_get($data, 'customer');
|
// $customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
// $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
if (!$subscription) {
|
// if ($subscription) {
|
||||||
return;
|
// SubscriptionInvoiceFailedJob::dispatch($subscription->team);
|
||||||
}
|
// }
|
||||||
SubscriptionInvoiceFailedJob::dispatch($subscription->team);
|
// break;
|
||||||
break;
|
|
||||||
case 'customer.subscription.updated':
|
case 'customer.subscription.updated':
|
||||||
|
$customerId = data_get($data, 'customer');
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
||||||
$planId = data_get($data, 'items.data.0.plan.id');
|
$planId = data_get($data, 'items.data.0.plan.id');
|
||||||
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
||||||
$subscription = Subscription::where('stripe_subscription_id', $subscriptionId)->firstOrFail();
|
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
|
||||||
|
$feedback = data_get($data, 'cancellation_details.feedback');
|
||||||
|
$comment = data_get($data, 'cancellation_details.comment');
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
|
'stripe_feedback' => $feedback,
|
||||||
|
'stripe_comment' => $comment,
|
||||||
'stripe_plan_id' => $planId,
|
'stripe_plan_id' => $planId,
|
||||||
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
|
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
|
||||||
]);
|
]);
|
||||||
|
ray($feedback, $comment, $alreadyCancelAtPeriodEnd, $cancelAtPeriodEnd);
|
||||||
|
if ($feedback) {
|
||||||
|
$reason = "Cancellation feedback for {$subscription->team->id}: '" . $feedback ."'";
|
||||||
|
if ($comment) {
|
||||||
|
$reason .= ' with comment: \'' . $comment ."'";
|
||||||
|
}
|
||||||
|
send_internal_notification($reason);
|
||||||
|
}
|
||||||
|
ray($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd);
|
||||||
|
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
|
||||||
|
if ($cancelAtPeriodEnd) {
|
||||||
|
send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||||
|
} else {
|
||||||
|
send_internal_notification('Subscription resumed for team: ' . $subscription->team->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'customer.subscription.deleted':
|
case 'customer.subscription.deleted':
|
||||||
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_subscription_id', $subscriptionId)->firstOrFail();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_subscription_id' => null,
|
'stripe_subscription_id' => null,
|
||||||
'stripe_plan_id'=> null,
|
'stripe_plan_id'=> null,
|
||||||
'stripe_cancel_at_period_end' => false,
|
'stripe_cancel_at_period_end' => false,
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
]);
|
]);
|
||||||
|
send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Unhandled event type
|
// Unhandled event type
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
ray($e->getMessage());
|
send_internal_notification("Subscription webhook ($type) failed: " . $e->getMessage());
|
||||||
send_internal_notification('Subscription webhook failed: ' . $e->getMessage());
|
|
||||||
$webhook->update([
|
$webhook->update([
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
'failure_reason' => $e->getMessage(),
|
'failure_reason' => $e->getMessage(),
|
||||||
|
Loading…
Reference in New Issue
Block a user