@extends('layouts.public') @section('content') @php /** * Restaurant sign-up: four steps SIDE BY SIDE on one page, one form, one submit. * 1 Your plan - arrives chosen from the pricing page; the other tiers are one click away, the * Monthly/Annual switch shows the annual price against the struck monthly x 12 * 2 Your details - "Continue with Google" (a pop-up; the page stays) fills and locks identity - no * password to invent - or type * 3 Your restaurant - "Find your restaurant on Google" (Places) fills the venue in place; where the * install has Business Profile, "Connect" (a pop-up) lists the owner's listings and * the one picked fills the form; or type. The venue then reads as three sections * that open and close: Restaurant details, Opening hours (the weekly editor), Menu * (the listing's dishes, to tick through) * 4 Payment - the gateway for a paid tier, the agreement, the button * Every Google path is an OFFER beside the fields; the manual form is complete on its own and nothing * Google supplies is saved unreviewed - it lands in the fields, editable, and is validated like typed text. * Every step head carries a check that turns green once the step is complete; the button is live only * when all four are (the server re-validates everything regardless). * * @var array $cuisines Ordered cuisine rows (id, name, slug). * @var array|null $selectedPlan The chosen plan (name, slug, ...), or null when none exist. * @var array $plans Every active plan, for step 1. * @var array $payMethods Gateways that can take this tier's money; empty when it is free. * @var string $selectedCycle monthly | yearly * @var bool $googleOn Sign in with Google is configured on this install. * @var array $googleProfile The Google-verified identity parked for this application ([] = none). * @var bool $placesOn A Google Maps key is present, so listings can be looked up. * @var bool $gbpOn Business Profile connect is configured (or sandboxed). * @var array $gbp A Business Profile connected at sign-up: refresh, locations, picked, details, menu ([] = none). */ $fieldErrors = \App\Support\Session::old('__errors', []); $fieldErrors = is_array($fieldErrors) ? $fieldErrors : []; $err = static function (string $field) use ($fieldErrors, $__env): string { return isset($fieldErrors[$field][0]) ? (string) $fieldErrors[$field][0] : ''; }; $payMethods = (array) ($payMethods ?? []); $plans = (array) ($plans ?? []); $planName = (string) ($selectedPlan['name'] ?? ''); $planValue = (string) ($selectedPlan['name'] ?? ''); $planSlug = (string) ($selectedPlan['slug'] ?? ''); $selectedCycle = (string) ($selectedCycle ?? 'monthly'); $google = (array) ($googleProfile ?? []); $viaGoogle = ($google['email'] ?? '') !== ''; $gbp = (array) ($gbp ?? []); $gbpConnected = ($gbp['refresh'] ?? '') !== ''; $gbpLocations = (array) ($gbp['locations'] ?? []); $gbpPicked = (array) ($gbp['picked'] ?? []); $placesOn = !empty($placesOn); $gbpOn = !empty($gbpOn); $googleOn = !empty($googleOn); // Step 1 rows: name, slug, the amount for each cadence, the monthly x 12 the annual price is struck // against, the cadence suffix, whether the tier sells annually and whether it needs a gateway. // Rendered here (money(), suffixes) so the script only swaps strings. $vsPlans = []; foreach ($plans as $p) { $own = (string) ($p['billing_cycle'] ?? 'monthly'); $price = (float) ($p['price'] ?? 0); $free = $price <= 0; $sells = \App\Models\Subscription::sellsAnnually($p); $vsPlans[] = [ 'name' => (string) $p['name'], 'slug' => (string) $p['slug'], 'sells' => $sells, 'free' => $free, 'monthly' => $free ? t_raw('auth.plan_free') : money(\App\Models\Subscription::priceFor($p, 'monthly')), 'yearly' => $sells ? money(\App\Models\Subscription::priceFor($p, 'yearly')) : '', 'yearly_full' => $sells ? money($price * 12) : '', 'cycle_m' => $free ? '' : ($own === 'lifetime' ? t_raw('vendor.billing.cycle_lifetime_suffix') : ($own === 'yearly' ? t_raw('partner.cycle_yearly') : t_raw('partner.cycle_monthly'))), 'cycle_y' => $sells ? t_raw('partner.cycle_yearly') : '', 'pay' => $free ? ((int) ($p['require_payment_method'] ?? 1) === 1) : $own !== 'lifetime', ]; } $vsCur = null; foreach ($vsPlans as $vp) { if ($vp['slug'] === $planSlug) { $vsCur = $vp; break; } } if ($vsCur === null && $vsPlans) { $vsCur = $vsPlans[0]; $planValue = $vsCur['name']; $planSlug = $vsCur['slug']; } $vsSave = (int) round(\App\Models\Subscription::annualDiscountPercent()); // A connected Business Profile that lists businesses IS the way the venue is entered: the fields open // under the listing picked, already filled, and "type the details" is not offered beside it. $gbpLive = $gbpConnected && $gbpLocations; // What the picked listing said, kept for a reload or a validation bounce; the page's own input wins. $fill = (array) ($gbp['details'] ?? []); $vsVal = static function (string $field, string $key) use ($fill, $__env): string { $own = (string) \App\Support\Session::old($field, ''); return $own !== '' ? $own : (string) ($fill[$key] ?? ''); }; $prefilled = $fill !== [] && (string) \App\Support\Session::old('rest_name', '') === ''; $pre = static function (string $field, string $key) use ($fill, $prefilled, $__env): string { return ($prefilled && (string) \App\Support\Session::old($field, '') === '' && (string) ($fill[$key] ?? '') !== '') ? ' is-prefilled' : ''; }; // Identity fields: a validation bounce's own input first, then the Google profile, then empty. $firstVal = (string) (\App\Support\Session::old('first_name', '') ?: ($google['first'] ?? '')); $lastVal = (string) (\App\Support\Session::old('last_name', '') ?: ($google['last'] ?? '')); $emailVal = $viaGoogle ? (string) $google['email'] : (string) \App\Support\Session::old('email', ''); // Steps 2 and 3 open on the Google offer when there is one; the fields unfold on request - or at once // when there is nothing to offer, when Google already answered, or when a bounce hands input back. $errIn = static function (array $fields) use ($fieldErrors, $__env): bool { foreach ($fields as $f) { if (!empty($fieldErrors[$f])) { return true; } } return false; }; $expand2 = !$googleOn || $viaGoogle || $firstVal !== '' || $emailVal !== '' || (string) \App\Support\Session::old('phone', '') !== '' || $errIn(['first_name', 'last_name', 'email', 'phone', 'password', 'password_confirm']); $offer3 = $placesOn || $gbpOn; $venueErr = $errIn(['rest_name', 'address', 'city', 'state', 'postcode', 'website', 'cuisine_id']); $expand3 = !$offer3 || !empty($gbpPicked) || $prefilled || (string) \App\Support\Session::old('rest_name', '') !== '' || $venueErr; $offerHidden3 = $expand3 && !$gbpLive; // Weekly hours, keyed 0=Sun..6=Sat like the hours table: the page's own input, else the listing's // (Google keys Monday..Sunday 1..7; a day it lists no period for is closed), else the 09:00-22:00 // rota Settings assumes for a day nobody has set. $vsDayKeys = [0 => 'sunday', 1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday']; $vsDayShort = [0 => 'sun', 1 => 'mon', 2 => 'tue', 3 => 'wed', 4 => 'thu', 5 => 'fri', 6 => 'sat']; $hoursOld = \App\Support\Session::old('hours', []); $hoursOld = is_array($hoursOld) ? $hoursOld : []; $gHours = is_array($fill['hours'] ?? null) ? $fill['hours'] : []; // Hours are OPTIONAL: the rota is saved only when the applicant set it or a listing supplied it; an // untouched editor saves nothing and the venue counts as open until Settings says otherwise. $hoursSet = (string) \App\Support\Session::old('hours_set', '') === '1' || $gHours !== []; $vsHours = []; for ($d = 0; $d < 7; $d++) { $row = ['open' => '09:00', 'close' => '22:00', 'closed' => false, 'google' => false]; if ($gHours) { $g = $gHours[$d === 0 ? 7 : $d] ?? null; $row['google'] = true; if (is_array($g) && $g) { $row['open'] = (string) ($g[0][0] ?? '09:00'); $row['close'] = (string) ($g[count($g) - 1][1] ?? '22:00'); } else { $row['closed'] = true; } } if (is_array($hoursOld[$d] ?? null)) { $row['open'] = ((string) ($hoursOld[$d]['open'] ?? '')) ?: $row['open']; $row['close'] = ((string) ($hoursOld[$d]['close'] ?? '')) ?: $row['close']; $row['closed'] = !empty($hoursOld[$d]['closed']); $row['google'] = false; } $vsHours[$d] = $row; } // The connected listing's menu (rows the pick fetched) and the ticks a bounce handed back. $menuRows = array_values((array) ($gbp['menu'] ?? [])); $menuPicks = \App\Support\Session::old('menu', []); $menuPicks = is_array($menuPicks) ? $menuPicks : []; $menuOn = $menuRows !== [] && !empty($gbpPicked['eligible']); // The step check: grey until the script finds the step complete, green then. $vsCheck = static function (int $n): string { return ''; }; $vsGateways = array_values(array_filter(['stripe', 'paypal'], static fn ($g) => \App\Services\Payment::isEnabled($g))); $consentLink = static function (string $slug, string $text): string { return cms_page_live($slug) ? '' . e($text) . '' : e($text); }; @endphp

{{ t_raw('auth.vendor_signup_title') }}

@php /* The page's messages belong UNDER its title band, inside the page the reader is looking at. */ @endphp
@partial('flash')
@csrf @php /* What a Google listing supplied and the fields do not show: the pin and the service options. Provisioning keeps them; a typed application posts them empty. */ @endphp
@php /* ---------------------------------------------------------------- 1. PLAN */ @endphp
1

{{ t_raw('auth.vs_step_plan') }}

{{ t_raw('auth.vs_sub_plan') }}

{!! $vsCheck(1) !!}
@if ($vsPlans) @php /* The same Monthly/Annual toggle the pricing page carries - an offer of the saving. */ @endphp
@foreach ($vsPlans as $vp) @php $annual = $selectedCycle === 'yearly' && $vp['sells']; @endphp
@endforeach
@else

{{ t_raw('auth.err_plan_unknown') }}

@endif
@php /* ---------------------------------------------------------------- 2. YOUR DETAILS */ @endphp
2

{{ t_raw('auth.your_details_step') }}

{{ t_raw('auth.vs_sub_details') }}

{!! $vsCheck(2) !!}
@if ($googleOn) @php /* Identity settled by Google: the chip says so and keeps the way out one click away. It and the offer are both in the page, one of them showing, so the pop-up's answer (and "Use a different account") swaps them in place - nothing typed elsewhere is lost to a reload. */ @endphp
{!! google_mark(18) !!} {!! t_raw('auth.vs_google_chip', [':email' => '' . e((string) ($google['email'] ?? '')) . '']) !!} {{ t_raw('auth.vs_google_other') }}
@endif
@if ($googleOn) @endif

{!! $err('first_name') !!}

{!! $err('last_name') !!}

{!! $err('email') !!}

{{ t_raw('auth.vs_google_locked') }}

{!! $err('phone') !!}

@php /* No password with Google: the pair stays in the page, hidden and not required, so the chip can come and go without a reload. submit() ignores a posted password anyway once the session holds a verified profile. */ @endphp

{!! $err('password') !!}

@partial('password-rules', ['for' => 'vs_password', 'id' => 'vs_password-hint'])

{!! $err('password_confirm') !!}

@php /* /vs-password-block */ @endphp
@php /* /vs-manual-2 */ @endphp
@php /* ---------------------------------------------------------------- 3. YOUR RESTAURANT */ @endphp
3

{{ t_raw('auth.your_restaurant_step') }}

{{ t_raw('auth.vs_sub_venue') }}

{!! $vsCheck(3) !!}
@if ($offer3)
@endif @if ($placesOn) @php /* Places lookup: the listing's public facts land in the fields below, in place. */ @endphp
@endif @if ($gbpOn) @php /* The owner's own listing: a second consent on the account that manages it (a pop-up). The block is a partial the page and the pop-up's answer both render, so a connect or a disconnect swaps it in place. */ @endphp
@partial('vendor-signup-venue-google', ['gbp' => $gbp])
@endif @if ($offer3)
@php /* /vs-google-3 */ @endphp @endif
@if ($offer3) @endif

{!! $prefilled ? t('auth.vs_filled') : '' !!}

@php /* ---- Restaurant details: the venue's own facts. */ @endphp
{{ t_raw('auth.vs_sec_details') }}

{!! $err('rest_name') !!}

{!! $err('address') !!}

{!! $err('city') !!}

{!! $err('state') !!}

{!! $err('postcode') !!}

{!! $err('website') !!}

@php /* ---- Opening hours: the weekly rota Settings > Opening hours saves, keyed 0=Sun..6=Sat. A day's switch is open/closed; a closed day keeps its times (hidden) so re-opening restores them. */ @endphp
{{ t_raw('auth.vs_hours_label') }}
@foreach ([1, 2, 3, 4, 5, 6, 0] as $d) @php $h = $vsHours[$d]; $dayName = t_raw('common.day_' . $vsDayKeys[$d]); $preH = $h['google'] ? ' is-prefilled' : ''; @endphp
{{ t_raw('common.day_short_' . $vsDayShort[$d]) }} {{ t_raw('common.closed') }}
@endforeach
@php /* ---- Menu: the connected listing's dishes, to tick through HERE - the ones ticked are written into the new menu the moment the account is created (a dish needs a price). The script paints the rows from the rows the pick fetched, the same way whether the pick was made on this visit or is read back. Shown only while a listing with a menu is picked. */ @endphp
{{ t_raw('auth.vs_menu_title') }}
{{ t_raw('auth.vs_menu_hint') }}
@php /* /vs-manual-3 */ @endphp
@php /* ---------------------------------------------------------------- 4. PAYMENT */ @endphp
4

{{ t_raw('auth.vs_step_pay') }}

{{ t_raw('auth.vs_sub_pay') }}

{!! $vsCheck(4) !!}
@php /* A paid tier is paid for by whichever gateway the applicant picks - rendered whenever two gateways are on so step 1 can show or hide it; shown (radios enabled) only when the selected tier needs one, the same condition show() resolved into $payMethods. */ @endphp @if (count($vsGateways) > 1)
{{ t_raw('vendor.billing.change_plan_method_label') }} @php $gwMeta = [ 'stripe' => [t('checkout.card'), t('checkout.card_desc')], 'paypal' => [t('checkout.paypal'), t('checkout.paypal_desc')], ]; $gwFirst = true; @endphp @foreach ($vsGateways as $gw) @php [$gwLabel, $gwDesc] = $gwMeta[$gw]; $gwChecked = (string) old('pay_method') !== '' ? (string) old('pay_method') === $gw : $gwFirst; @endphp
@php $gwFirst = false; @endphp @endforeach
@endif

{{ t_raw('auth.vs_no_payment') }}

{{ t_raw('auth.vs_pay_hint') }}

{!! \App\Services\Recaptcha::widget('vendor_signup') !!}
@php /* Pressing the button IS the agreement; the line says so, naming the button as it reads. */ @endphp

{{ t_raw('auth.already_partner_prefix') }} {{ t_raw('common.sign_in') }}

@endsection