Move Branches and Team into Settings as Pro features.
Deploy Ladill Frontdesk / deploy (push) Successful in 48s

Gate multi-branch and team management behind paid plans, nest their
routes under Settings, and remove them from the sidebar Administration
section.
This commit is contained in:
isaacclad
2026-07-16 08:13:42 +00:00
parent 59433f2b7b
commit 5526a10342
19 changed files with 377 additions and 50 deletions
@@ -1,6 +1,7 @@
<x-app-layout title="Add branch">
<div class="mx-auto max-w-lg">
<h1 class="text-xl font-semibold text-slate-900">Add branch</h1>
<a href="{{ route('frontdesk.branches.index') }}" class="text-sm text-slate-500 hover:text-slate-800">&larr; Branches</a>
<h1 class="mt-2 text-xl font-semibold text-slate-900">Add branch</h1>
<form method="POST" action="{{ route('frontdesk.branches.store') }}" class="mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-6">
@csrf
<div><label class="block text-sm font-medium">Name</label><input type="text" name="name" required class="mt-1 w-full rounded-lg border-slate-300 text-sm"></div>
@@ -1,6 +1,7 @@
<x-app-layout title="Edit branch">
<div class="mx-auto max-w-lg">
<h1 class="text-xl font-semibold text-slate-900">Edit branch</h1>
<a href="{{ route('frontdesk.branches.index') }}" class="text-sm text-slate-500 hover:text-slate-800">&larr; Branches</a>
<h1 class="mt-2 text-xl font-semibold text-slate-900">Edit branch</h1>
<form method="POST" action="{{ route('frontdesk.branches.update', $branch) }}" class="mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-6">
@csrf @method('PUT')
<div><label class="block text-sm font-medium">Name</label><input type="text" name="name" value="{{ old('name', $branch->name) }}" required class="mt-1 w-full rounded-lg border-slate-300 text-sm"></div>
@@ -7,8 +7,9 @@
<x-app-layout title="Branches">
<div class="space-y-6">
<a href="{{ route('frontdesk.settings') }}" class="inline-flex items-center gap-1 text-sm text-slate-500 hover:text-slate-800">&larr; Settings</a>
<x-frontdesk.page-hero
badge="Locations · Buildings · Reception desks"
badge="Settings · Locations · Buildings"
title="Branches"
description="Organize your sites, buildings, and reception desks so visits and devices stay scoped to the right location."
:stats="[
@@ -1,6 +1,7 @@
<x-app-layout title="Add member">
<div class="mx-auto max-w-lg">
<h1 class="text-xl font-semibold text-slate-900">Invite team member</h1>
<a href="{{ route('frontdesk.members.index') }}" class="text-sm text-slate-500 hover:text-slate-800">&larr; Team</a>
<h1 class="mt-2 text-xl font-semibold text-slate-900">Invite team member</h1>
<form method="POST" x-data action="{{ route('frontdesk.members.store') }}" class="mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-6">
@csrf
@@ -1,7 +1,8 @@
<x-app-layout title="Team">
<div class="space-y-6">
<a href="{{ route('frontdesk.settings') }}" class="inline-flex items-center gap-1 text-sm text-slate-500 hover:text-slate-800">&larr; Settings</a>
<x-frontdesk.page-hero
badge="Roles · Access · Branch scope"
badge="Settings · Roles · Access"
title="Team"
description="Invite colleagues, assign Frontdesk roles, and limit branch access for reception and security staff."
:stats="[
@@ -72,11 +72,12 @@
<div class="flex flex-col rounded-2xl border border-slate-200 bg-white p-6 {{ $planKey === 'free' ? 'ring-2 ring-indigo-500' : '' }}">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Free</p>
<p class="mt-2 text-3xl font-bold text-slate-900">GHS 0</p>
<p class="mt-1 text-sm text-slate-500">1 branch · 1 kiosk</p>
<p class="mt-1 text-sm text-slate-500">1 branch · 1 kiosk · owner only</p>
<ul class="mt-5 flex-1 space-y-2 text-sm text-slate-700">
<li>Core check-in & badges</li>
<li>100 host emails / month</li>
<li>Watchlist & hosts</li>
<li class="text-slate-400">Multi-branch & team (Pro)</li>
</ul>
@if ($planKey === 'free')
<p class="mt-6 text-sm font-medium text-indigo-700">Current plan</p>
@@ -97,6 +98,8 @@
= {{ $currency }} <span x-text="fmt(proTotal())"></span>/mo
</p>
<ul class="mt-5 flex-1 space-y-2 text-sm text-slate-700">
<li>Multi-branch locations & buildings</li>
<li>Team roles & invitations</li>
<li>Unlimited host email alerts</li>
<li>Webhooks & iCal feeds</li>
<li>Scheduled report exports</li>
@@ -15,7 +15,7 @@
({{ $activeBranchCount }} active).
@endif
@else
<span class="font-medium text-slate-900">Free</span> one branch, one kiosk, core check-in and badges. Host alerts billed after {{ number_format($freeEmailAllowance) }} free emails per month.
<span class="font-medium text-slate-900">Free</span> one branch (setup only), one kiosk, owner-only access. Multi-branch and team require Pro. Host alerts billed after {{ number_format($freeEmailAllowance) }} free emails per month.
@endif
</p>
@if (! $hasPaidPlan)
@@ -47,6 +47,45 @@
</x-settings.card>
@endif
@if ($canViewBranches || $canViewTeam)
<div class="grid gap-4 sm:grid-cols-2">
@if ($canViewBranches)
<a href="{{ route('frontdesk.branches.index') }}"
class="group rounded-2xl border border-slate-200 bg-white p-5 shadow-sm transition hover:border-indigo-200 hover:shadow-md">
<div class="flex items-start justify-between gap-3">
<div>
<p class="text-sm font-semibold text-slate-900 group-hover:text-indigo-700">Branches</p>
<p class="mt-1 text-sm text-slate-500">Locations, buildings, and reception desks.</p>
</div>
@if (! $hasBranchesFeature)
<span class="shrink-0 rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-700">Pro</span>
@endif
</div>
<p class="mt-4 text-xs font-medium text-indigo-600">
{{ $hasBranchesFeature ? 'Manage branches →' : 'Upgrade to unlock →' }}
</p>
</a>
@endif
@if ($canViewTeam)
<a href="{{ route('frontdesk.members.index') }}"
class="group rounded-2xl border border-slate-200 bg-white p-5 shadow-sm transition hover:border-indigo-200 hover:shadow-md">
<div class="flex items-start justify-between gap-3">
<div>
<p class="text-sm font-semibold text-slate-900 group-hover:text-indigo-700">Team</p>
<p class="mt-1 text-sm text-slate-500">Invite colleagues and assign Frontdesk roles.</p>
</div>
@if (! $hasTeamFeature)
<span class="shrink-0 rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-700">Pro</span>
@endif
</div>
<p class="mt-4 text-xs font-medium text-indigo-600">
{{ $hasTeamFeature ? 'Manage team →' : 'Upgrade to unlock →' }}
</p>
</a>
@endif
</div>
@endif
<form method="POST" action="{{ route('frontdesk.settings.update') }}" enctype="multipart/form-data" class="space-y-6">
@csrf @method('PUT')
@@ -0,0 +1,21 @@
<x-app-layout :title="$title">
<div class="mx-auto max-w-lg">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-400">Settings</p>
<h1 class="mt-1 text-xl font-semibold text-slate-900">{{ $title }}</h1>
<p class="mt-2 text-sm text-slate-600">{{ $description }}</p>
<div class="mt-6 rounded-2xl border border-indigo-200 bg-indigo-50 p-6">
<h2 class="font-semibold text-indigo-900">Upgrade to Pro</h2>
<p class="mt-2 text-sm text-indigo-800">
GHS {{ number_format($proPriceMinor / 100, 0) }} / branch / month from your Ladill wallet
multi-branch locations, team roles, integrations, and more.
</p>
<a href="{{ route('frontdesk.pro.index') }}" class="btn-primary mt-4 inline-flex">View Pro plans</a>
<p class="mt-3 text-xs text-indigo-700">
<a href="{{ route('frontdesk.wallet') }}" class="underline">Top up your wallet</a> if you need more balance.
</p>
</div>
<a href="{{ route('frontdesk.settings') }}" class="mt-6 inline-block text-sm text-slate-600 hover:text-slate-900">&larr; Back to settings</a>
</div>
</x-app-layout>
+10 -22
View File
@@ -98,15 +98,13 @@
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />'];
}
$adminNav = [];
if ($permissions->can($member, 'admin.branches.view')) {
$adminNav[] = ['name' => 'Branches', 'route' => route('frontdesk.branches.index'), 'active' => request()->routeIs('frontdesk.branches.*') || request()->routeIs('frontdesk.buildings.*') || request()->routeIs('frontdesk.desks.*'),
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m-1.5 3h1.5m3-9H15m-1.5 3H15m-1.5 3H15m-1.5 3H15M9 21v-3.375c0-.621.504-1.125 1.125-1.125h3.75c.621 0 1.125.504 1.125 1.125V21" />'];
}
if ($permissions->can($member, 'admin.members.view')) {
$adminNav[] = ['name' => 'Team', 'route' => route('frontdesk.members.index'), 'active' => request()->routeIs('frontdesk.members.*'),
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z" />'];
}
$settingsActive = request()->routeIs('frontdesk.settings')
|| request()->routeIs('frontdesk.branches.*')
|| request()->routeIs('frontdesk.buildings.*')
|| request()->routeIs('frontdesk.desks.*')
|| request()->routeIs('frontdesk.members.*')
|| request()->routeIs('frontdesk.integrations.*')
|| request()->routeIs('frontdesk.settings.badge*');
@endphp
<nav class="flex-1 space-y-0.5 overflow-y-auto px-3 py-4">
@foreach($nav as $item)
@@ -116,7 +114,7 @@
</a>
@endforeach
@if (count($adminNav) > 0 || count($complianceNav) > 0)
@if (count($complianceNav) > 0)
<p class="mb-1 mt-4 px-3 text-[10px] font-semibold uppercase tracking-wider text-slate-400">Compliance</p>
@foreach ($complianceNav as $item)
<a href="{{ $item['route'] }}" class="group flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] transition {{ $item['active'] ? 'bg-indigo-50 text-indigo-700 font-semibold' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
@@ -125,22 +123,12 @@
</a>
@endforeach
@endif
@if (count($adminNav) > 0)
<p class="mb-1 mt-4 px-3 text-[10px] font-semibold uppercase tracking-wider text-slate-400">Administration</p>
@foreach ($adminNav as $item)
<a href="{{ $item['route'] }}" class="group flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] transition {{ $item['active'] ? 'bg-indigo-50 text-indigo-700 font-semibold' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
<svg class="{{ $item['iconSizeClass'] ?? 'h-[18px] w-[18px]' }} shrink-0 {{ $item['active'] ? 'text-indigo-600' : 'text-slate-400' }}" viewBox="{{ $item['iconViewBox'] ?? '0 0 24 24' }}" fill="none" stroke="currentColor" stroke-width="{{ $item['iconStrokeWidth'] ?? 1.5 }}">{!! $item['icon'] !!}</svg>
<span>{{ $item['name'] }}</span>
</a>
@endforeach
@endif
</nav>
<div class="shrink-0 border-t border-slate-200 px-3 py-3">
<a href="{{ route('frontdesk.settings') }}"
class="group flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] transition {{ request()->routeIs('frontdesk.settings') && ! request()->routeIs('frontdesk.pro.*') ? 'bg-indigo-50 text-indigo-700 font-semibold' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
<svg class="h-[18px] w-[18px] shrink-0 {{ request()->routeIs('frontdesk.settings') && ! request()->routeIs('frontdesk.pro.*') ? 'text-indigo-600' : 'text-slate-400' }}" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
class="group flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] transition {{ $settingsActive ? 'bg-indigo-50 text-indigo-700 font-semibold' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
<svg class="h-[18px] w-[18px] shrink-0 {{ $settingsActive ? 'text-indigo-600' : 'text-slate-400' }}" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.241.437-.613.43-.992a6.932 6.932 0 0 1 0-.255c.007-.378-.138-.75-.43-.991l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.281Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
<span class="flex-1 truncate">Settings</span>