Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions app/Livewire/MobilePricing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace App\Livewire;

use App\Enums\Subscription;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Livewire\Component;

class MobilePricing extends Component
{
protected $listeners = [
'purchase-request-submitted' => 'handlePurchaseRequest',
];

public function handlePurchaseRequest(array $data)
{
$user = $this->findOrCreateUser($data['email']);

return $this->createCheckoutSession($data['plan'], $user);
}

public function createCheckoutSession(string $plan, ?User $user = null)
{
if (! ($user ??= Auth::user())) {
return;
}

if (! ($subscription = Subscription::tryFrom($plan))) {
return;
}

$user->createOrGetStripeCustomer();

$checkout = $user
->newSubscription('default', $subscription->stripePriceId())
->allowPromotionCodes()
->checkout([
'success_url' => $this->successUrl(),
'cancel_url' => route('early-adopter'),
]);

return redirect($checkout->url);
}

private function findOrCreateUser(string $email): User
{
return User::firstOrCreate([
'email' => $email,
], [
'password' => Hash::make(Str::random(72)),
]);
}

private function successUrl(): string
{
// This is a hack to get the route() function to work. If you try
// to pass {CHECKOUT_SESSION_ID} to the route function, it will
// throw an error.
return Str::replace(
'abc',
'{CHECKOUT_SESSION_ID}',
route('order.success', ['checkoutSessionId' => 'abc'])
);
}

public function render()
{
return view('livewire.mobile-pricing');
}
}
51 changes: 51 additions & 0 deletions app/Livewire/PurchaseModal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Livewire;

use Livewire\Attributes\Renderless;
use Livewire\Attributes\Validate;
use Livewire\Component;

class PurchaseModal extends Component
{
public bool $showModal = false;

#[Validate]
public string $email = '';

public ?string $selectedPlan = null;

protected $rules = [
'email' => 'required|email',
];

#[Renderless]
public function setPlan(string $plan): void
{
$this->selectedPlan = $plan;
}

public function closeModal(): void
{
$this->showModal = false;
$this->reset('email', 'selectedPlan');
$this->resetValidation();
}

public function submit(): void
{
$this->validate();

$this->dispatch('purchase-request-submitted', [
'email' => $this->email,
'plan' => $this->selectedPlan,
]);

$this->closeModal();
}

public function render()
{
return view('livewire.purchase-modal');
}
}
22 changes: 12 additions & 10 deletions resources/views/early-adopter.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ class="absolute inset-0 -z-10 h-full w-full object-cover"
</section>

{{-- Pricing Section --}}
<x-mobile-pricing />
<livewire:mobile-pricing />

{{-- Testimonials Section --}}
{{-- <x-testimonials /> --}}
Expand Down Expand Up @@ -676,11 +676,14 @@ class="mx-auto flex w-full max-w-2xl flex-col items-center gap-4 pt-10"
</p>
</x-faq-card>

<x-faq-card question="Will there ever be a free & open source version?">
<x-faq-card
question="Will there ever be a free & open source version?"
>
<p>
Yes! Once we've hit sustainability and can afford to continue
investing in this project indirectly, then a version of it will
be fully open source and made available for free.
Yes! Once we've hit sustainability and can afford to
continue investing in this project indirectly, then a
version of it will be fully open source and made available
for free.
</p>
</x-faq-card>

Expand All @@ -694,9 +697,7 @@ class="mx-auto flex w-full max-w-2xl flex-col items-center gap-4 pt-10"
</x-faq-card>

<x-faq-card question="When will Android support be ready?">
<p>
It's READY! Sign up and build apps for Android today!
</p>
<p>It's READY! Sign up and build apps for Android today!</p>
</x-faq-card>

<x-faq-card question="When will the EAP end?">
Expand Down Expand Up @@ -744,14 +745,15 @@ class="mx-auto flex w-full max-w-2xl flex-col items-center gap-4 pt-10"
</x-faq-card>
<x-faq-card question="Can I get an invoice?">
<p>
If you purchased after May 6, 2025, you should get an invoice with your receipt via email.
If you purchased after May 6, 2025, you should get an
invoice with your receipt via email.
</p>
<p class="mt-4">
For purchases made before this, you simply need to
<a
href="https://zenvoice.io/p/67a61665e7a3400c73fb75af"
onclick="event.stopPropagation()"
class="underline inline-block"
class="inline-block underline"
>
follow the instructions here
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,25 @@ class="size-5 shrink-0"
</p>
</div>

{{-- Button --}}
<a
href="{{ \App\Enums\Subscription::Mini->stripePaymentLink() }}"
class="my-5 block w-full rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
aria-label="Get started with Mini plan"
>
Get started
</a>
@auth
<button
type="button"
wire:click="createCheckoutSession('mini')"
class="my-5 block w-full rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
aria-label="Get started with Mini plan"
>
Get started
</button>
@else
<button
type="button"
@click="$dispatch('open-purchase-modal', { plan: 'mini' })"
class="my-5 block w-full rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
aria-label="Get started with Mini plan"
>
Get started
</button>
@endauth

{{-- Features --}}
<div
Expand Down Expand Up @@ -281,14 +292,25 @@ class="size-5 shrink-0"
</p>
</div>

{{-- Button --}}
<a
href="{{ \App\Enums\Subscription::Pro->stripePaymentLink() }}"
class="my-5 block w-full rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
aria-label="Get started with Pro plan"
>
Get started
</a>
@auth
<button
type="button"
wire:click="createCheckoutSession('pro')"
class="my-5 block w-full rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
aria-label="Get started with Pro plan"
>
Get started
</button>
@else
<button
type="button"
@click="$dispatch('open-purchase-modal', { plan: 'pro' })"
class="my-5 block w-full rounded-2xl bg-zinc-200 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
aria-label="Get started with Pro plan"
>
Get started
</button>
@endauth

{{-- Features --}}
<div
Expand Down Expand Up @@ -452,14 +474,25 @@ class="size-5 shrink-0"
</p>
</div>

{{-- Button --}}
<a
href="{{ \App\Enums\Subscription::Max->stripePaymentLink() }}"
class="my-5 block w-full rounded-2xl bg-zinc-800 py-4 text-center text-sm font-medium text-white transition duration-200 ease-in-out hover:bg-zinc-900 dark:bg-[#d68ffe] dark:text-black dark:hover:bg-[#e1acff]"
aria-label="Get started with Max plan"
>
Get started
</a>
@auth
<button
type="button"
wire:click="createCheckoutSession('max')"
class="my-5 block w-full rounded-2xl bg-zinc-800 py-4 text-center text-sm font-medium text-white transition duration-200 ease-in-out hover:bg-zinc-900 dark:bg-[#d68ffe] dark:text-black dark:hover:bg-[#e1acff]"
aria-label="Get started with Max plan"
>
Get started
</button>
@else
<button
type="button"
@click="$dispatch('open-purchase-modal', { plan: 'max' })"
class="my-5 block w-full rounded-2xl bg-zinc-800 py-4 text-center text-sm font-medium text-white transition duration-200 ease-in-out hover:bg-zinc-900 dark:bg-[#d68ffe] dark:text-black dark:hover:bg-[#e1acff]"
aria-label="Get started with Max plan"
>
Get started
</button>
@endauth

{{-- Features --}}
<div
Expand Down Expand Up @@ -569,4 +602,7 @@ class="grid size-7 shrink-0 place-items-center rounded-xl bg-[#D4FD7D] dark:bg-[
</div>
</div>
</div>
@guest
<livewire:purchase-modal />
@endguest
</section>
94 changes: 94 additions & 0 deletions resources/views/livewire/purchase-modal.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<div>
<div
x-data="{
open: @entangle('showModal'),
}"
@open-purchase-modal.window="
open = true;
$wire.setPlan($event.detail.plan);
"
>
<!-- Modal Backdrop -->
<div
x-show="open"
x-transition:enter="transition duration-300 ease-out"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition duration-200 ease-in"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
x-cloak
></div>

<div
x-show="open"
x-transition:enter="transition duration-300 ease-out"
x-transition:enter-start="scale-95 opacity-0"
x-transition:enter-end="scale-100 opacity-100"
x-transition:leave="transition duration-200 ease-in"
x-transition:leave-start="scale-100 opacity-100"
x-transition:leave-end="scale-95 opacity-0"
class="fixed inset-0 z-50 flex items-center justify-center p-4"
x-cloak
>
<div
@click.away="open = false"
class="w-full max-w-md rounded-2xl bg-white p-8 shadow-xl dark:bg-mirage"
>
<div class="mb-6 text-center">
<h3 class="text-xl font-semibold dark:text-white">
Get Started with NativePHP
</h3>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
Enter your email to continue to checkout
</p>
</div>

<form
wire:submit.prevent="submit"
class="space-y-8"
>
<div>
<label
for="email"
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Email Address
</label>
<input
type="email"
id="email"
wire:model.blur="email"
class="w-full rounded-lg border border-gray-300 px-4 py-2.5 focus:border-purple-400 focus:outline-none dark:border-gray-600 dark:bg-gray-800 dark:text-white"
placeholder="[email protected]"
required
x-effect="if (open) $nextTick(() => $el.focus())"
/>
@error('email')
<p class="mt-1 text-sm text-red-600">
{{ $message }}
</p>
@enderror
</div>

<div class="flex items-center justify-between gap-3">
<button
type="button"
wire:click="closeModal"
class="rounded-xl bg-zinc-200 px-8 py-4 text-center text-sm font-medium transition duration-200 ease-in-out hover:bg-zinc-800 hover:text-white dark:bg-slate-700/30 dark:hover:bg-slate-700/40"
>
Cancel
</button>
<button
type="submit"
class="rounded-xl bg-zinc-800 px-8 py-4 text-center text-sm font-medium text-white transition duration-200 ease-in-out hover:bg-zinc-900 dark:bg-[#d68ffe] dark:text-black dark:hover:bg-[#e1acff]"
>
Next
</button>
</div>
</form>
</div>
</div>
</div>
</div>
Loading