diff --git a/.cursor/rules/laravel-boost.mdc b/.cursor/rules/laravel-boost.mdc index da9e12e7..c9d997a9 100644 --- a/.cursor/rules/laravel-boost.mdc +++ b/.cursor/rules/laravel-boost.mdc @@ -11,7 +11,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.4.19 +- php - 8.4.20 - filament/filament (FILAMENT) - v5 - laravel/cashier (CASHIER) - v15 - laravel/framework (LARAVEL) - v12 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d5a3f494..8265115b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.4.19 +- php - 8.4.20 - filament/filament (FILAMENT) - v5 - laravel/cashier (CASHIER) - v15 - laravel/framework (LARAVEL) - v12 diff --git a/.junie/guidelines.md b/.junie/guidelines.md index d5a3f494..8265115b 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.4.19 +- php - 8.4.20 - filament/filament (FILAMENT) - v5 - laravel/cashier (CASHIER) - v15 - laravel/framework (LARAVEL) - v12 diff --git a/AGENTS.md b/AGENTS.md index d5a3f494..8265115b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.4.19 +- php - 8.4.20 - filament/filament (FILAMENT) - v5 - laravel/cashier (CASHIER) - v15 - laravel/framework (LARAVEL) - v12 diff --git a/CLAUDE.md b/CLAUDE.md index d5a3f494..8265115b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.4.19 +- php - 8.4.20 - filament/filament (FILAMENT) - v5 - laravel/cashier (CASHIER) - v15 - laravel/framework (LARAVEL) - v12 diff --git a/app/Livewire/LeadSubmissionForm.php b/app/Livewire/LeadSubmissionForm.php index 2a9d6564..dfd391a2 100644 --- a/app/Livewire/LeadSubmissionForm.php +++ b/app/Livewire/LeadSubmissionForm.php @@ -21,6 +21,8 @@ class LeadSubmissionForm extends Component public string $description = ''; + public string $budget = ''; + public string $turnstileToken = ''; #[Locked] @@ -33,6 +35,7 @@ protected function rules(): array 'email' => ['required', 'email', 'max:255'], 'company' => ['required', 'string', 'max:255'], 'description' => ['required', 'string', 'max:5000'], + 'budget' => ['required', 'string', 'in:'.implode(',', array_keys(Lead::BUDGETS))], ]; if (config('services.turnstile.secret_key')) { @@ -45,6 +48,7 @@ protected function rules(): array public function messages(): array { return [ + 'budget.in' => 'Please select a budget range.', 'turnstileToken.required' => 'Please complete the security check.', ]; } @@ -69,6 +73,7 @@ public function submit(): void 'email' => $this->email, 'company' => $this->company, 'description' => $this->description, + 'budget' => $this->budget, 'ip_address' => request()->ip(), ]); @@ -82,6 +87,8 @@ public function submit(): void public function render() { - return view('livewire.lead-submission-form'); + return view('livewire.lead-submission-form', [ + 'budgets' => Lead::BUDGETS, + ]); } } diff --git a/app/Notifications/NewLeadSubmitted.php b/app/Notifications/NewLeadSubmitted.php index b057e579..64eff3f3 100644 --- a/app/Notifications/NewLeadSubmitted.php +++ b/app/Notifications/NewLeadSubmitted.php @@ -24,7 +24,7 @@ public function via(object $notifiable): array public function toMail(object $notifiable): MailMessage { return (new MailMessage) - ->subject('New Consulting Enquiry: '.$this->lead->company) + ->subject('New App Build Enquiry: '.$this->lead->company) ->replyTo($this->lead->email, $this->lead->name) ->greeting('New lead received!') ->line("**Name:** {$this->lead->name}") diff --git a/package-lock.json b/package-lock.json index bdd48d5c..33750f8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "opal-parrot", + "name": "jovial-camel", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/views/build-my-app.blade.php b/resources/views/build-my-app.blade.php new file mode 100644 index 00000000..a84c4cd2 --- /dev/null +++ b/resources/views/build-my-app.blade.php @@ -0,0 +1,161 @@ + + @push('head') + + @if (config('services.turnstile.site_key')) + + @endif + @endpush + +
+ {{-- Hero --}} +
+
+

+ { + Build My App + } +

+ +

+ Got an app idea? Let's build it together. The NativePHP core team partners with founders and businesses + to design, build, and ship cross-platform apps with PHP and Laravel. +

+
+
+ + {{-- What We Build --}} +
+
+
+
+ +
+

Mobile Apps

+

+ iOS and Android apps built with NativePHP for Mobile, ready for the App Store and Play Store. +

+
+ +
+
+ +
+

Desktop Apps

+

+ Native desktop apps for macOS, Windows, and Linux using NativePHP for Desktop. +

+
+ +
+
+ +
+

End-to-End Delivery

+

+ From idea and design through to launch, marketing, and ongoing iteration. We sweat the details. +

+
+
+
+ + {{-- Form --}} +
+

+ Tell Us About Your App +

+

+ Share your idea and rough budget. We'll be in touch to plan the next steps. +

+ +
+ +
+ +

+ Just need a quick technical session? + + Book a consulting slot + + instead. +

+
+
+
diff --git a/resources/views/components/footer.blade.php b/resources/views/components/footer.blade.php index 44adb8d8..6635cafd 100644 --- a/resources/views/components/footer.blade.php +++ b/resources/views/components/footer.blade.php @@ -253,6 +253,17 @@ class="inline-block px-px py-1.5 transition duration-300 will-change-transform h +
  • + + + Build + New + + +
  • routeIs('blog*'); $isPartnersActive = request()->routeIs('partners*'); $isServicesActive = request()->routeIs('consulting'); + $isBuildActive = request()->routeIs('build-my-app'); $isCourseActive = request()->routeIs('course'); $isSupportActive = request()->routeIs('support.*'); $isSponsorActive = request()->routeIs('sponsoring*'); @@ -257,6 +258,32 @@ class="size-4 shrink-0" + {{-- Build Link (mobile only, shown in navbar on desktop) --}} +
    + $isBuildActive, + 'opacity-70 hover:translate-x-1 hover:opacity-100 dark:opacity-50' => ! $isBuildActive, + ]) + aria-current="{{ $isBuildActive ? 'page' : 'false' }}" + > + @if ($isBuildActive) + +
    + {{-- Course Link (mobile only, shown in navbar on desktop) --}}
    Ultra @@ -102,6 +102,14 @@ class="hidden items-center gap-1.5 rounded-full bg-blue-500/10 px-3 py-1.5 text- Consulting + {{-- Build link (desktop only) --}} + + {{-- Bifrost button (visible on large screens) --}} @@ -343,12 +344,12 @@ class="grid gap-6 md:grid-cols-3" > {{-- Book a Consultation --}} -

    Book a Consultation

    +

    Book a Session

    - Tell us about your project and we'll arrange a discovery call. + Pick a time and jump straight into a working session with us.

    Get started @@ -388,8 +389,8 @@ class="group rounded-2xl bg-gray-100 p-8 transition duration-200 hover:bg-gray-2
    - {{-- Enquiry Form --}} -
    + {{-- Book a Session --}} +

    - Request a Consultation + Book a Session

    - Tell us about your project and we'll be in touch to arrange a discovery call. + Grab a slot on our calendar — available as soon as two hours from now — and we'll get straight to work. + Sessions are billed at $250/hour and paid up front. + Ultra subscribers + get a discounted rate, and + partners + get an even better one.

    -
    diff --git a/resources/views/livewire/customer/support/index.blade.php b/resources/views/livewire/customer/support/index.blade.php index 626fa96c..172a0d4b 100644 --- a/resources/views/livewire/customer/support/index.blade.php +++ b/resources/views/livewire/customer/support/index.blade.php @@ -13,6 +13,18 @@ @endif + + Need help even more quickly? + + Book a session with the NativePHP core team — available as soon as two hours from now. Discounted for Ultra subscribers. + + + + Book a session + + + + @if($this->supportTickets->count() > 0) diff --git a/resources/views/livewire/lead-submission-form.blade.php b/resources/views/livewire/lead-submission-form.blade.php index 3be5ed7b..9909070b 100644 --- a/resources/views/livewire/lead-submission-form.blade.php +++ b/resources/views/livewire/lead-submission-form.blade.php @@ -56,18 +56,35 @@ class="mt-1 block w-full rounded-md border-gray-300 focus:border-gray-500 focus:
    @error('description')

    {{ $message }}

    @enderror
    +
    + + + @error('budget')

    {{ $message }}

    @enderror +
    +
    @if (config('services.turnstile.site_key'))
    - Request Consultation + Send Enquiry Submitting...
    diff --git a/resources/views/livewire/mobile-pricing.blade.php b/resources/views/livewire/mobile-pricing.blade.php index 8bf40afc..410633fd 100644 --- a/resources/views/livewire/mobile-pricing.blade.php +++ b/resources/views/livewire/mobile-pricing.blade.php @@ -266,6 +266,15 @@ class="grid size-7 shrink-0 place-items-center rounded-xl bg-[#D4FD7D] dark:bg-[
    Premium support — private channels, expedited turnaround
    +
    + +
    Discounted consult hourly rate
    +
    +
    + + + + + + +
    +
    +

    Need help even more quickly?

    +

    + Book a working session with the NativePHP core team — available as soon as two hours from now. + Discounted rate for Ultra subscribers. +

    +
    + + Book a session + +
    diff --git a/routes/web.php b/routes/web.php index 17bfe820..fb44dc0a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -164,7 +164,7 @@ Route::view('terms-of-service', 'terms-of-service')->name('terms-of-service'); Route::view('developer-terms', 'developer-terms')->name('developer-terms'); Route::view('partners', 'partners')->name('partners'); -Route::redirect('build-my-app', '/consulting', 301); +Route::view('build-my-app', 'build-my-app')->name('build-my-app'); Route::view('consulting', 'consulting')->name('consulting'); Route::view('the-vibes', 'the-vibes')->name('the-vibes'); Route::view('the-vibes-prospectus', 'the-vibes-prospectus')->name('the-vibes-prospectus'); diff --git a/tests/Feature/LeadSubmissionTest.php b/tests/Feature/LeadSubmissionTest.php index cc08edf0..a678397e 100644 --- a/tests/Feature/LeadSubmissionTest.php +++ b/tests/Feature/LeadSubmissionTest.php @@ -26,19 +26,19 @@ protected function setUp(): void } #[Test] - public function consulting_page_is_accessible(): void + public function build_my_app_page_is_accessible(): void { - $this->get(route('consulting')) + $this->get(route('build-my-app')) ->assertOk() ->assertSeeLivewire(LeadSubmissionForm::class); } #[Test] - public function build_my_app_redirects_to_consulting(): void + public function consulting_page_is_accessible(): void { - $this->get('/build-my-app') - ->assertRedirect('/consulting') - ->assertStatus(301); + $this->get(route('consulting')) + ->assertOk() + ->assertSee('cal.com/team/nativephp/consult', false); } #[Test] @@ -51,6 +51,7 @@ public function lead_can_be_submitted_successfully(): void ->set('email', 'john@example.com') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app for my business.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'test-token') ->call('submit') ->assertSet('submitted', true) @@ -61,6 +62,7 @@ public function lead_can_be_submitted_successfully(): void 'email' => 'john@example.com', 'company' => 'Acme Corp', 'description' => 'I need a mobile app for my business.', + 'budget' => '10k_to_25k', ]); Notification::assertSentTo( @@ -84,9 +86,10 @@ public function all_fields_are_required(): void ->set('email', '') ->set('company', '') ->set('description', '') + ->set('budget', '') ->set('turnstileToken', 'test-token') ->call('submit') - ->assertHasErrors(['name', 'email', 'company', 'description']); + ->assertHasErrors(['name', 'email', 'company', 'description', 'budget']); } #[Test] @@ -97,11 +100,26 @@ public function email_must_be_valid(): void ->set('email', 'not-an-email') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'test-token') ->call('submit') ->assertHasErrors(['email']); } + #[Test] + public function budget_must_be_a_valid_option(): void + { + Livewire::test(LeadSubmissionForm::class) + ->set('name', 'John Doe') + ->set('email', 'john@example.com') + ->set('company', 'Acme Corp') + ->set('description', 'I need a mobile app.') + ->set('budget', 'not-a-real-budget') + ->set('turnstileToken', 'test-token') + ->call('submit') + ->assertHasErrors(['budget']); + } + #[Test] public function rate_limiting_is_enforced(): void { @@ -113,6 +131,7 @@ public function rate_limiting_is_enforced(): void ->set('email', "john{$i}@example.com") ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'test-token') ->call('submit') ->assertSet('submitted', true); @@ -125,6 +144,7 @@ public function rate_limiting_is_enforced(): void ->set('email', 'limited@example.com') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'test-token') ->call('submit') ->assertHasErrors(['form']); @@ -144,6 +164,7 @@ public function turnstile_validation_passes_when_secret_key_is_not_configured(): ->set('email', 'john@example.com') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', '') ->call('submit') ->assertSet('submitted', true); @@ -167,6 +188,7 @@ public function turnstile_validation_fails_with_invalid_token(): void ->set('email', 'john@example.com') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'invalid-token') ->call('submit') ->assertHasErrors(['turnstileToken']); @@ -192,6 +214,7 @@ public function turnstile_validation_passes_with_valid_token(): void ->set('email', 'john@example.com') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'valid-token') ->call('submit') ->assertSet('submitted', true); @@ -209,6 +232,7 @@ public function ip_address_is_recorded_with_submission(): void ->set('email', 'john@example.com') ->set('company', 'Acme Corp') ->set('description', 'I need a mobile app.') + ->set('budget', '10k_to_25k') ->set('turnstileToken', 'test-token') ->call('submit');