Replace Upgrade to Pro with Report Issue for staff.
Deploy Ladill Frontdesk / deploy (push) Successful in 34s
Deploy Ladill Frontdesk / deploy (push) Successful in 34s
Non-owners submit Frontdesk issues to Ladill admin via Identity. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Frontdesk;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Frontdesk\Concerns\ScopesToAccount;
|
||||
use App\Services\Identity\IdentityTeamClient;
|
||||
use App\Support\StaffUx;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class IssueReportController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
abort_unless(! StaffUx::showBilling($request->user()), 403);
|
||||
|
||||
return view('frontdesk.issues.create');
|
||||
}
|
||||
|
||||
public function store(Request $request, IdentityTeamClient $identity): RedirectResponse
|
||||
{
|
||||
abort_unless(! StaffUx::showBilling($request->user()), 403);
|
||||
|
||||
$validated = $request->validate([
|
||||
'subject' => ['required', 'string', 'max:255'],
|
||||
'message' => ['required', 'string', 'max:5000'],
|
||||
'priority' => ['required', 'in:low,normal,high'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
$organization = $this->organization($request);
|
||||
$member = $this->member($request);
|
||||
|
||||
$context = trim(implode("\n", array_filter([
|
||||
'App: Ladill Frontdesk',
|
||||
'Organization: '.($organization->name ?? '—'),
|
||||
'Role: '.($member?->role ?? '—'),
|
||||
'Reporter: '.($user->email ?? $user->ownerRef()),
|
||||
'',
|
||||
$validated['message'],
|
||||
])));
|
||||
|
||||
try {
|
||||
$ticket = $identity->createSupportTicket($user->ownerRef(), [
|
||||
'subject' => $validated['subject'],
|
||||
'message' => $context,
|
||||
'priority' => $validated['priority'],
|
||||
'tags' => ['staff_issue', 'app:frontdesk'],
|
||||
], 'frontdesk');
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
|
||||
return back()
|
||||
->withInput()
|
||||
->with('error', 'Could not send your report right now. Please try again in a moment.');
|
||||
}
|
||||
|
||||
$ref = (string) ($ticket['ticket_ref'] ?? '');
|
||||
|
||||
return redirect()
|
||||
->route('frontdesk.dashboard')
|
||||
->with('success', $ref !== ''
|
||||
? "Issue reported to Ladill support ({$ref}). Our team will follow up."
|
||||
: 'Issue reported to Ladill support. Our team will follow up.');
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,24 @@ class IdentityTeamClient
|
||||
return (array) $response->json('data', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{subject: string, message: string, priority?: string, tags?: list<string>} $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function createSupportTicket(string $userPublicId, array $payload, string $app = 'frontdesk'): array
|
||||
{
|
||||
$response = $this->request('post', '/identity/support/tickets', array_filter([
|
||||
'user' => $userPublicId,
|
||||
'subject' => $payload['subject'] ?? '',
|
||||
'message' => $payload['message'] ?? '',
|
||||
'priority' => $payload['priority'] ?? 'normal',
|
||||
'app' => $app,
|
||||
'tags' => $payload['tags'] ?? ['staff_issue'],
|
||||
], fn ($value) => $value !== null && $value !== ''));
|
||||
|
||||
return (array) $response->json('data', []);
|
||||
}
|
||||
|
||||
private function request(string $method, string $path, array $query = []): Response
|
||||
{
|
||||
$url = rtrim((string) config('identity.api_url'), '/').$path;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<x-app-layout title="Report issue">
|
||||
<div class="mx-auto max-w-lg space-y-6">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold text-slate-900">Report an issue</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Send a problem to Ladill support. Account owners manage billing upgrades — staff use this for help with Frontdesk.</p>
|
||||
</div>
|
||||
|
||||
@if (session('error'))
|
||||
<p class="rounded-lg bg-rose-50 px-3 py-2 text-sm text-rose-800">{{ session('error') }}</p>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('frontdesk.issues.store') }}" class="space-y-4 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Subject</label>
|
||||
<input type="text" name="subject" value="{{ old('subject') }}" required maxlength="255"
|
||||
class="mt-1 w-full rounded-lg border-slate-300 text-sm"
|
||||
placeholder="e.g. Visitor check-in stuck">
|
||||
@error('subject')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Priority</label>
|
||||
<select name="priority" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
<option value="normal" @selected(old('priority', 'normal') === 'normal')>Normal</option>
|
||||
<option value="low" @selected(old('priority') === 'low')>Low</option>
|
||||
<option value="high" @selected(old('priority') === 'high')>High</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">What happened?</label>
|
||||
<textarea name="message" rows="6" required maxlength="5000"
|
||||
class="mt-1 w-full rounded-lg border-slate-300 text-sm"
|
||||
placeholder="Describe what you were doing and what went wrong…">{{ old('message') }}</textarea>
|
||||
@error('message')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary w-full">Send to Ladill support</button>
|
||||
</form>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -146,16 +146,27 @@
|
||||
<span class="flex-1 truncate">Settings</span>
|
||||
</a>
|
||||
|
||||
@php $proActive = request()->routeIs('frontdesk.pro.*'); @endphp
|
||||
@if (! empty($hasPaidPlan) || ! empty($isEnterprise))
|
||||
<a href="{{ route('frontdesk.pro.index') }}" class="group mt-0.5 flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] transition {{ $proActive ? '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 text-amber-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.25c.3 0 .58.18.7.46l2.36 5.5 5.96.5a.75.75 0 0 1 .43 1.31l-4.53 3.9 1.36 5.83a.75.75 0 0 1-1.12.81L12 17.77l-5.16 3a.75.75 0 0 1-1.12-.81l1.36-5.83-4.53-3.9a.75.75 0 0 1 .43-1.31l5.96-.5 2.36-5.5c.12-.28.4-.46.7-.46Z"/></svg>
|
||||
<span>{{ ! empty($isEnterprise) ? 'Frontdesk Enterprise' : 'Frontdesk Pro' }}</span>
|
||||
</a>
|
||||
@php
|
||||
$proActive = request()->routeIs('frontdesk.pro.*');
|
||||
$issueActive = request()->routeIs('frontdesk.issues.*');
|
||||
$isAccountOwner = \App\Support\StaffUx::showBilling(auth()->user());
|
||||
@endphp
|
||||
@if ($isAccountOwner)
|
||||
@if (! empty($hasPaidPlan) || ! empty($isEnterprise))
|
||||
<a href="{{ route('frontdesk.pro.index') }}" class="group mt-0.5 flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] transition {{ $proActive ? '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 text-amber-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.25c.3 0 .58.18.7.46l2.36 5.5 5.96.5a.75.75 0 0 1 .43 1.31l-4.53 3.9 1.36 5.83a.75.75 0 0 1-1.12.81L12 17.77l-5.16 3a.75.75 0 0 1-1.12-.81l1.36-5.83-4.53-3.9a.75.75 0 0 1 .43-1.31l5.96-.5 2.36-5.5c.12-.28.4-.46.7-.46Z"/></svg>
|
||||
<span>{{ ! empty($isEnterprise) ? 'Frontdesk Enterprise' : 'Frontdesk Pro' }}</span>
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route('frontdesk.pro.index') }}" class="group mt-0.5 flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-600 to-emerald-500 px-3 py-2 text-[13px] font-semibold text-white shadow-sm transition hover:opacity-95">
|
||||
<svg class="h-[18px] w-[18px] shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.25c.3 0 .58.18.7.46l2.36 5.5 5.96.5a.75.75 0 0 1 .43 1.31l-4.53 3.9 1.36 5.83a.75.75 0 0 1-1.12.81L12 17.77l-5.16 3a.75.75 0 0 1-1.12-.81l1.36-5.83-4.53-3.9a.75.75 0 0 1 .43-1.31l5.96-.5 2.36-5.5c.12-.28.4-.46.7-.46Z"/></svg>
|
||||
<span>Upgrade to Pro</span>
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<a href="{{ route('frontdesk.pro.index') }}" class="group mt-0.5 flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-600 to-emerald-500 px-3 py-2 text-[13px] font-semibold text-white shadow-sm transition hover:opacity-95">
|
||||
<svg class="h-[18px] w-[18px] shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.25c.3 0 .58.18.7.46l2.36 5.5 5.96.5a.75.75 0 0 1 .43 1.31l-4.53 3.9 1.36 5.83a.75.75 0 0 1-1.12.81L12 17.77l-5.16 3a.75.75 0 0 1-1.12-.81l1.36-5.83-4.53-3.9a.75.75 0 0 1 .43-1.31l5.96-.5 2.36-5.5c.12-.28.4-.46.7-.46Z"/></svg>
|
||||
<span>Upgrade to Pro</span>
|
||||
<a href="{{ route('frontdesk.issues.create') }}" class="group mt-0.5 flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-600 to-emerald-500 px-3 py-2 text-[13px] font-semibold text-white shadow-sm transition hover:opacity-95 {{ $issueActive ? 'ring-2 ring-indigo-300' : '' }}">
|
||||
<svg class="h-[18px] w-[18px] shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" /></svg>
|
||||
<span>Report Issue</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Http\Controllers\Frontdesk\EmployeePortalController;
|
||||
use App\Http\Controllers\Frontdesk\HostController;
|
||||
use App\Http\Controllers\Frontdesk\HostPortalController;
|
||||
use App\Http\Controllers\Frontdesk\IntegrationController;
|
||||
use App\Http\Controllers\Frontdesk\IssueReportController;
|
||||
use App\Http\Controllers\Frontdesk\KioskController;
|
||||
use App\Http\Controllers\Frontdesk\KioskDeviceController;
|
||||
use App\Http\Controllers\Frontdesk\MemberController;
|
||||
@@ -180,6 +181,9 @@ Route::middleware(['auth', 'platform.session'])->group(function () {
|
||||
Route::post('/pro/subscribe-prepaid', [ProController::class, 'subscribePrepaid'])->name('frontdesk.pro.subscribe-prepaid');
|
||||
Route::get('/pro/paystack/callback', [ProController::class, 'paystackCallback'])->name('frontdesk.pro.paystack.callback');
|
||||
|
||||
Route::get('/report-issue', [IssueReportController::class, 'create'])->name('frontdesk.issues.create');
|
||||
Route::post('/report-issue', [IssueReportController::class, 'store'])->name('frontdesk.issues.store');
|
||||
|
||||
Route::get('/settings', [SettingsController::class, 'edit'])->name('frontdesk.settings');
|
||||
Route::put('/settings', [SettingsController::class, 'update'])->name('frontdesk.settings.update');
|
||||
Route::post('/settings/upgrade', [ProController::class, 'subscribe'])->name('frontdesk.settings.upgrade');
|
||||
|
||||
Reference in New Issue
Block a user