Deploy Ladill Transfer / deploy (push) Successful in 41s
Remove desktop header search, wire mobile search to the transfers and files page, refine file icons and Shared badge, and align Transfers toolbar with Files. Co-authored-by: Cursor <cursoragent@cursor.com>
266 lines
10 KiB
PHP
266 lines
10 KiB
PHP
@php
|
|
$mobileFullScreenPage = request()->routeIs('account.settings')
|
|
|| request()->routeIs('transfer.transfers.create')
|
|
|| request()->routeIs('transfer.transfers.show');
|
|
|
|
$qrMobilePage = request()->routeIs('transfer.transfers.create') || request()->routeIs('transfer.transfers.show');
|
|
@endphp
|
|
<!DOCTYPE html>
|
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" @class(['qr-mobile-page' => $qrMobilePage])>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
@include('partials.favicon')
|
|
<title>{{ $title ?? 'Dashboard' }} - Ladill</title>
|
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
|
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
</head>
|
|
<body class="font-sans antialiased bg-slate-100" x-data="{ sidebarOpen: false }">
|
|
<div class="min-h-screen max-lg:min-h-dvh">
|
|
{{-- Mobile sidebar overlay --}}
|
|
<div x-show="sidebarOpen" x-cloak class="fixed inset-0 z-30 bg-black/50 lg:hidden" @click="sidebarOpen = false"></div>
|
|
|
|
{{-- Sidebar — full product nav (Bird and other extracted apps have their
|
|
own nav in their own apps). --}}
|
|
@php $sidebarPartial = 'partials.sidebar'; @endphp
|
|
<aside x-cloak class="fixed inset-y-0 left-0 z-40 w-64 transform transition-transform lg:translate-x-0"
|
|
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'">
|
|
@include($sidebarPartial)
|
|
</aside>
|
|
|
|
{{-- Main content --}}
|
|
<div class="flex min-h-screen flex-col min-w-0 lg:pl-64">
|
|
<div class="{{ $mobileFullScreenPage ? 'hidden lg:block' : '' }}">
|
|
@include('partials.topbar-qr')
|
|
</div>
|
|
|
|
<div @class([
|
|
'px-6 pt-4',
|
|
'hidden lg:block' => $mobileFullScreenPage,
|
|
])>
|
|
@include('partials.flash')
|
|
</div>
|
|
|
|
<main @class([
|
|
'flex-1 lg:p-6 lg:pb-6',
|
|
'px-4 pt-4 max-lg:pb-[calc(4rem+max(env(safe-area-inset-bottom,0px),20px))]' => $qrMobilePage,
|
|
'p-0 pb-24 lg:p-6 lg:pb-6' => $mobileFullScreenPage && ! $qrMobilePage,
|
|
'p-6 pb-24 lg:pb-6' => ! $mobileFullScreenPage,
|
|
])>
|
|
{{ $slot }}
|
|
</main>
|
|
</div>
|
|
|
|
{{-- Mobile Bottom Navigation (hidden on QR create/show which have their own action bar) --}}
|
|
@unless(request()->routeIs('transfer.transfers.create') || request()->routeIs('transfer.transfers.show'))
|
|
@php
|
|
$navUser = auth()->user();
|
|
$navInitials = collect(explode(' ', trim((string) $navUser?->name)))
|
|
->filter()->take(2)
|
|
->map(fn ($part) => strtoupper(substr($part, 0, 1)))
|
|
->implode('');
|
|
@endphp
|
|
@include('partials.mobile-bottom-nav', [
|
|
'homeUrl' => route('transfer.dashboard'),
|
|
'homeActive' => request()->routeIs('transfer.dashboard'),
|
|
'searchUrl' => route('transfer.search'),
|
|
'searchActive' => request()->routeIs('transfer.search'),
|
|
'notificationsUrl' => route('notifications.index'),
|
|
'notificationsActive' => request()->routeIs('notifications.*'),
|
|
'unreadUrl' => route('notifications.unread'),
|
|
'profileActive' => request()->routeIs('account.settings'),
|
|
'profileName' => $navUser?->name ?? '',
|
|
'profileSubtitle' => $navUser?->email ?? '',
|
|
'profileMenuItems' => [
|
|
['type' => 'link', 'label' => 'Settings', 'href' => route('account.settings')],
|
|
['type' => 'link', 'label' => 'Account Settings', 'href' => ladill_account_url('account-settings')],
|
|
['type' => 'link', 'label' => 'Overview', 'href' => route('transfer.dashboard')],
|
|
['type' => 'logout', 'label' => 'Logout', 'action' => route('logout')],
|
|
],
|
|
'avatarUrl' => $navUser?->avatarUrl(),
|
|
'initials' => $navInitials !== '' ? $navInitials : 'U',
|
|
])
|
|
@endunless
|
|
|
|
@if ($qrMobilePage)
|
|
<div class="qr-mobile-bottom-bleed lg:hidden" aria-hidden="true"></div>
|
|
@endif
|
|
</div>
|
|
<script>
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('notificationDropdown', (config = {}) => ({
|
|
open: false,
|
|
loading: false,
|
|
notifications: [],
|
|
unreadCount: 0,
|
|
unreadUrl: config.unreadUrl || '/notifications/unread',
|
|
markReadUrl: config.markReadUrl || '/notifications/__ID__/read',
|
|
markAllReadUrl: config.markAllReadUrl || '/notifications/mark-all-read',
|
|
indexUrl: config.indexUrl || '/notifications',
|
|
csrfToken: config.csrfToken || document.querySelector('meta[name="csrf-token"]')?.content || '',
|
|
|
|
init() {
|
|
this.fetchUnread();
|
|
setInterval(() => this.fetchUnread(), 60000);
|
|
},
|
|
|
|
async fetchUnread() {
|
|
try {
|
|
const res = await fetch(this.unreadUrl, {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
const data = await res.json();
|
|
this.notifications = data.notifications || [];
|
|
this.unreadCount = data.unread_count || 0;
|
|
} catch (e) {
|
|
console.error('Failed to fetch notifications', e);
|
|
}
|
|
},
|
|
|
|
toggle() {
|
|
this.open = !this.open;
|
|
if (this.open) {
|
|
this.loading = true;
|
|
this.fetchUnread().finally(() => this.loading = false);
|
|
}
|
|
},
|
|
|
|
async markRead(id) {
|
|
const url = this.markReadUrl.replace('__ID__', id);
|
|
try {
|
|
await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': this.csrfToken,
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
});
|
|
this.notifications = this.notifications.filter(n => n.id !== id);
|
|
this.unreadCount = Math.max(0, this.unreadCount - 1);
|
|
} catch (e) {
|
|
console.error('Failed to mark notification as read', e);
|
|
}
|
|
},
|
|
|
|
async markAllRead() {
|
|
try {
|
|
await fetch(this.markAllReadUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': this.csrfToken,
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
});
|
|
this.notifications = [];
|
|
this.unreadCount = 0;
|
|
} catch (e) {
|
|
console.error('Failed to mark all notifications as read', e);
|
|
}
|
|
},
|
|
|
|
getIconBg(icon) {
|
|
const map = {
|
|
website: 'bg-blue-50',
|
|
domain: 'bg-emerald-50',
|
|
hosting: 'bg-violet-50',
|
|
email: 'bg-pink-50',
|
|
billing: 'bg-amber-50',
|
|
lead: 'bg-cyan-50',
|
|
success: 'bg-green-50',
|
|
};
|
|
return map[icon] || 'bg-slate-100';
|
|
},
|
|
|
|
getIconColor(icon) {
|
|
const map = {
|
|
website: 'text-blue-600',
|
|
domain: 'text-emerald-600',
|
|
hosting: 'text-violet-600',
|
|
email: 'text-pink-600',
|
|
billing: 'text-amber-600',
|
|
lead: 'text-cyan-600',
|
|
success: 'text-green-600',
|
|
};
|
|
return map[icon] || 'text-slate-500';
|
|
},
|
|
}));
|
|
|
|
Alpine.data('balanceGate', (config = {}) => ({
|
|
balance: Number(config.balance ?? 0),
|
|
price: Number(config.price ?? 0),
|
|
modalId: config.modalId ?? null,
|
|
topupUrl: config.topupUrl ?? null,
|
|
href: config.href ?? null,
|
|
requiresPayment: Object.prototype.hasOwnProperty.call(config, 'requiresPayment')
|
|
? config.requiresPayment !== false
|
|
: true,
|
|
needsTopupFor(requiresPayment = this.requiresPayment) {
|
|
if (requiresPayment === false) {
|
|
return false;
|
|
}
|
|
|
|
return this.balance <= 0 || this.balance < this.price;
|
|
},
|
|
needsTopup() {
|
|
return this.needsTopupFor(this.requiresPayment);
|
|
},
|
|
openTopup() {
|
|
if (this.topupUrl) {
|
|
window.location.href = this.topupUrl;
|
|
return;
|
|
}
|
|
|
|
if (! this.modalId) {
|
|
return;
|
|
}
|
|
|
|
window.dispatchEvent(new CustomEvent('open-modal', {
|
|
detail: this.modalId,
|
|
bubbles: true,
|
|
}));
|
|
},
|
|
goOrTopup(event, href = null, requiresPayment = null) {
|
|
const paymentRequired = requiresPayment === null
|
|
? this.requiresPayment
|
|
: requiresPayment !== false;
|
|
|
|
if (this.needsTopupFor(paymentRequired)) {
|
|
event?.preventDefault();
|
|
this.openTopup();
|
|
return;
|
|
}
|
|
|
|
const target = href ?? this.href;
|
|
if (target) {
|
|
window.location.href = target;
|
|
}
|
|
},
|
|
submitOrTopup(event, form) {
|
|
if (this.needsTopup()) {
|
|
event?.preventDefault();
|
|
this.openTopup();
|
|
return;
|
|
}
|
|
|
|
if (event?.type === 'submit') {
|
|
return;
|
|
}
|
|
|
|
const targetForm = form || event?.target?.closest?.('form') || this.$refs?.createForm;
|
|
targetForm?.requestSubmit();
|
|
},
|
|
}));
|
|
});
|
|
</script>
|
|
@include('partials.afia')
|
|
@include('partials.sso-keepalive')
|
|
@include('partials.confirm-prompt')
|
|
</body>
|
|
</html>
|