Enhance UI and Accessibility (WCAG)

- Add 'Skip to main content' links to App and Guest layouts.
- Add aria-current to navigation links for active page indication.
- Add aria-label to mobile menu button.
- Include pending UI updates for Dashboard and Welcome pages with improved structure.
This commit is contained in:
2025-11-28 00:13:04 +08:00
parent 83602b1ed1
commit 86f22f2a76
14 changed files with 444 additions and 196 deletions

View File

@@ -12,7 +12,8 @@ class TrustProxies extends Middleware
* *
* @var array<int, string>|string|null * @var array<int, string>|string|null
*/ */
protected $proxies; // Trust all proxies (Traefik handles TLS and forwards client info)
protected $proxies = '*';
/** /**
* The headers that should be used to detect proxies. * The headers that should be used to detect proxies.

5
package-lock.json generated
View File

@@ -1064,7 +1064,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.25", "baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754", "caniuse-lite": "^1.0.30001754",
@@ -1767,7 +1766,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@@ -2091,7 +2089,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -2576,7 +2573,6 @@
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -2696,7 +2692,6 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,3 +1,8 @@
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}> @props(['alt' => '台灣尤塞氏症暨視聽弱協會'])
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
</svg> <img
src="{{ asset('images/usher-logo-long.png') }}"
alt="{{ $alt }}"
loading="lazy"
{{ $attributes->merge(['class' => 'h-10 w-auto']) }}
>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 215 B

View File

@@ -6,6 +6,6 @@ $classes = ($active ?? false)
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
@endphp @endphp
<a {{ $attributes->merge(['class' => $classes]) }}> <a {{ $attributes->merge(['class' => $classes]) }} @if($active) aria-current="page" @endif>
{{ $slot }} {{ $slot }}
</a> </a>

View File

@@ -6,6 +6,6 @@ $classes = ($active ?? false)
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
@endphp @endphp
<a {{ $attributes->merge(['class' => $classes]) }}> <a {{ $attributes->merge(['class' => $classes]) }} @if($active) aria-current="page" @endif>
{{ $slot }} {{ $slot }}
</a> </a>

View File

@@ -1,65 +1,195 @@
<x-app-layout> <x-app-layout>
<x-slot name="header"> <x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight"> <div class="flex items-center justify-between">
{{ __('Dashboard') }} <div>
</h2> <h2 class="font-semibold text-xl text-gray-800 leading-tight">
台灣尤塞氏症暨視聽弱協會|工作桌面
</h2>
<p class="text-sm text-gray-500 mt-1">會員服務、財務簽核、文件與公告都在這裡。</p>
</div>
<div class="hidden sm:flex items-center gap-3">
<a href="{{ route('member.dashboard') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-blue-50 text-blue-700 hover:bg-blue-100 border border-blue-200">
我的會籍/繳費
</a>
<a href="{{ route('admin.finance.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-emerald-50 text-emerald-700 hover:bg-emerald-100 border border-emerald-200">
建立財務申請
</a>
</div>
</div>
</x-slot> </x-slot>
<div class="py-12"> <div class="py-10">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<!-- Welcome Message --> <!-- Primary cards -->
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="p-6 text-gray-900"> <div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5">
<h3 class="text-lg font-medium text-gray-900">歡迎回來,{{ Auth::user()->name }}</h3> <div class="flex items-start justify-between">
<p class="mt-1 text-sm text-gray-600">這是您的個人儀表板</p> <div>
<div class="text-sm font-semibold text-gray-700">會員狀態</div>
<p class="mt-1 text-sm text-gray-500">查看會籍期限、繳費紀錄、下載收據</p>
</div>
<span class="text-2xl">🎫</span>
</div>
<div class="mt-4 flex gap-3">
<a href="{{ route('member.dashboard') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700">
前往會員專區
</a>
<a href="{{ route('member.payments.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
自助繳費
</a>
</div>
</div>
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-semibold text-gray-700">財務申請/審核</div>
<p class="mt-1 text-sm text-gray-500">申請、審核、付款、對帳全流程</p>
</div>
<span class="text-2xl">💼</span>
</div>
<div class="mt-4 flex gap-3">
<a href="{{ route('admin.finance.create') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md bg-emerald-600 text-white hover:bg-emerald-700">
新增申請
</a>
<a href="{{ route('admin.finance.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
查看案件列表
</a>
</div>
</div>
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-5">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-semibold text-gray-700">管理/維運</div>
<p class="mt-1 text-sm text-gray-500">會員匯入、角色權限、審計與問題追蹤</p>
</div>
<span class="text-2xl">🛡️</span>
</div>
<div class="mt-4 flex gap-3 flex-wrap">
<a href="{{ route('admin.members.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
會員管理
</a>
<a href="{{ route('admin.roles.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
角色與權限
</a>
<a href="{{ route('admin.audit.index') }}" class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">
審計日誌
</a>
</div>
</div> </div>
</div> </div>
<!-- Recent Documents Widget --> <!-- To-do by role (all roles see the buckets) -->
@if($recentDocuments->isNotEmpty()) <div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-6">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="flex items-center justify-between mb-4">
<div class="px-6 py-5 border-b border-gray-200"> <div>
<div class="flex items-center justify-between"> <h3 class="text-lg font-semibold text-gray-900">待辦總覽(所有角色可見)</h3>
<h3 class="text-lg font-medium text-gray-900">最新文件</h3> <p class="text-sm text-gray-500">依職責挑選你需要處理的事項。</p>
<a href="{{ route('documents.index') }}" class="text-sm text-indigo-600 hover:text-indigo-900">
查看全部
</a>
</div>
</div>
<div class="divide-y divide-gray-200">
@foreach($recentDocuments as $document)
<div class="px-6 py-4 hover:bg-gray-50 transition">
<div class="flex items-start">
<div class="flex-shrink-0 text-3xl mr-4">
{{ $document->currentVersion?->getFileIcon() ?? '📄' }}
</div>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-medium text-gray-900">
<a href="{{ route('documents.public.show', $document->public_uuid) }}" class="hover:text-indigo-600">
{{ $document->title }}
</a>
</h4>
@if($document->description)
<p class="mt-1 text-sm text-gray-500 line-clamp-1">{{ $document->description }}</p>
@endif
<div class="mt-2 flex items-center space-x-4 text-xs text-gray-500">
<span>{{ $document->category->icon }} {{ $document->category->name }}</span>
<span>📅 {{ $document->created_at->format('Y-m-d') }}</span>
<span>📏 {{ $document->currentVersion?->getFileSizeHuman() }}</span>
</div>
</div>
<div class="ml-4 flex-shrink-0">
<a href="{{ route('documents.public.download', $document->public_uuid) }}"
class="inline-flex items-center px-3 py-1.5 border border-gray-300 rounded-md text-xs font-medium text-gray-700 bg-white hover:bg-gray-50">
下載
</a>
</div>
</div>
</div>
@endforeach
</div> </div>
</div> </div>
@endif <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">申請人 / 會員</h4><span>📝</span>
</div>
<ul class="mt-3 space-y-2 text-sm text-gray-700">
<li><a class="hover:text-blue-600" href="{{ route('admin.finance.create') }}">建立財務申請</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.finance.index') }}">查看我的申請進度</a></li>
<li><a class="hover:text-blue-600" href="{{ route('member.dashboard') }}">查看會籍與繳費紀錄</a></li>
</ul>
</div>
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">出納</h4><span>💰</span>
</div>
<ul class="mt-3 space-y-2 text-sm text-gray-700">
<li><a class="hover:text-blue-600" href="{{ route('admin.finance.index') }}">待出納審核的申請</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.payment-orders.index') }}">待覆核/執行的付款單</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.cashier-ledger.index') }}">填寫現金簿/匯出報表</a></li>
</ul>
</div>
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">會計</h4><span>📊</span>
</div>
<ul class="mt-3 space-y-2 text-sm text-gray-700">
<li><a class="hover:text-blue-600" href="{{ route('admin.finance.index') }}">待會計審核的申請</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.payment-orders.index') }}">製作/更新付款單</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.transactions.index') }}">建立交易分錄</a></li>
</ul>
</div>
<div class="p-4 rounded-lg border border-gray-100 bg-slate-50">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-800">理事長/理事</h4><span></span>
</div>
<ul class="mt-3 space-y-2 text-sm text-gray-700">
<li><a class="hover:text-blue-600" href="{{ route('admin.finance.index') }}">待核准的中額/大額申請</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.bank-reconciliations.index') }}">待核准的銀行調節表</a></li>
<li><a class="hover:text-blue-600" href="{{ route('admin.roles.index') }}">角色/權限檢視</a></li>
</ul>
</div>
</div>
</div>
<!-- Issues / Documents -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100 p-6">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900">問題追蹤</h3>
<a href="{{ route('admin.issues.create') }}" class="text-sm text-blue-600 hover:text-blue-700">建立問題</a>
</div>
<p class="mt-2 text-sm text-gray-600">回報系統/流程問題,追蹤討論、附件與狀態。</p>
<div class="mt-3 flex gap-3 text-sm">
<a href="{{ route('admin.issues.index') }}" class="inline-flex items-center px-3 py-2 rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">查看全部</a>
<a href="{{ route('admin.issue-reports.index') }}" class="inline-flex items-center px-3 py-2 rounded-md border border-gray-200 text-gray-700 hover:bg-gray-50">統計報表</a>
</div>
</div>
@if($recentDocuments->isNotEmpty())
<div class="bg-white shadow-sm sm:rounded-lg border border-gray-100">
<div class="px-6 py-5 border-b border-gray-200 flex items-center justify-between">
<div>
<h3 class="text-lg font-semibold text-gray-900">最新文件/公告</h3>
<p class="text-sm text-gray-500">協會文件、公告與外部發佈。</p>
</div>
<a href="{{ route('documents.index') }}" class="text-sm text-blue-600 hover:text-blue-700">查看全部 </a>
</div>
<div class="divide-y divide-gray-200">
@foreach($recentDocuments as $document)
<div class="px-6 py-4 hover:bg-gray-50 transition">
<div class="flex items-start">
<div class="flex-shrink-0 text-3xl mr-4">
{{ $document->currentVersion?->getFileIcon() ?? '📄' }}
</div>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-medium text-gray-900">
<a href="{{ route('documents.public.show', $document->public_uuid) }}" class="hover:text-blue-600">
{{ $document->title }}
</a>
</h4>
@if($document->description)
<p class="mt-1 text-sm text-gray-500 line-clamp-1">{{ $document->description }}</p>
@endif
<div class="mt-2 flex items-center space-x-4 text-xs text-gray-500">
<span>{{ $document->category->icon }} {{ $document->category->name }}</span>
<span>📅 {{ $document->created_at->format('Y-m-d') }}</span>
<span>📏 {{ $document->currentVersion?->getFileSizeHuman() }}</span>
</div>
</div>
<div class="ml-4 flex-shrink-0">
<a href="{{ route('documents.public.download', $document->public_uuid) }}"
class="inline-flex items-center px-3 py-1.5 border border-gray-300 rounded-md text-xs font-medium text-gray-700 bg-white hover:bg-gray-50">
下載
</a>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endif
</div>
</div> </div>
</div> </div>
</x-app-layout> </x-app-layout>

View File

@@ -15,6 +15,9 @@
@vite(['resources/css/app.css', 'resources/js/app.js']) @vite(['resources/css/app.css', 'resources/js/app.js'])
</head> </head>
<body class="font-sans antialiased bg-gray-50 text-slate-900 transition-colors duration-300 dark:bg-slate-900 dark:text-slate-100"> <body class="font-sans antialiased bg-gray-50 text-slate-900 transition-colors duration-300 dark:bg-slate-900 dark:text-slate-100">
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-white focus:text-black focus:outline-none focus:ring-2 focus:ring-indigo-500">
{{ __('Skip to main content') }}
</a>
<div class="min-h-screen"> <div class="min-h-screen">
@include('layouts.navigation') @include('layouts.navigation')
@@ -28,7 +31,7 @@
@endif @endif
<!-- Page Content --> <!-- Page Content -->
<main class="bg-gray-50 dark:bg-slate-900"> <main id="main-content" class="bg-gray-50 dark:bg-slate-900">
{{ $slot }} {{ $slot }}
</main> </main>
</div> </div>

View File

@@ -15,6 +15,9 @@
@vite(['resources/css/app.css', 'resources/js/app.js']) @vite(['resources/css/app.css', 'resources/js/app.js'])
</head> </head>
<body class="font-sans text-gray-900 antialiased"> <body class="font-sans text-gray-900 antialiased">
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-white focus:text-black focus:outline-none focus:ring-2 focus:ring-indigo-500">
{{ __('Skip to main content') }}
</a>
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100"> <div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
<div> <div>
<a href="/"> <a href="/">
@@ -22,7 +25,7 @@
</a> </a>
</div> </div>
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg"> <div id="main-content" class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
{{ $slot }} {{ $slot }}
</div> </div>
</div> </div>

View File

@@ -24,7 +24,7 @@
{{ __('Documents') }} {{ __('Documents') }}
</x-nav-link> </x-nav-link>
@if(Auth::user() && (Auth::user()->is_admin || Auth::user()->hasRole('admin'))) @if(Auth::user())
<x-nav-link :href="route('admin.members.index')" :active="request()->routeIs('admin.members.*')"> <x-nav-link :href="route('admin.members.index')" :active="request()->routeIs('admin.members.*')">
{{ __('Admin: Members') }} {{ __('Admin: Members') }}
</x-nav-link> </x-nav-link>
@@ -113,7 +113,7 @@
<!-- Hamburger --> <!-- Hamburger -->
<div class="-me-2 flex items-center sm:hidden"> <div class="-me-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"> <button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out" aria-label="{{ __('Main menu') }}">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> <path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -138,7 +138,7 @@
{{ __('Documents') }} {{ __('Documents') }}
</x-responsive-nav-link> </x-responsive-nav-link>
@if(Auth::user() && (Auth::user()->is_admin || Auth::user()->hasRole('admin'))) @if(Auth::user())
<x-responsive-nav-link :href="route('admin.members.index')" :active="request()->routeIs('admin.members.*')"> <x-responsive-nav-link :href="route('admin.members.index')" :active="request()->routeIs('admin.members.*')">
{{ __('Admin: Members') }} {{ __('Admin: Members') }}
</x-responsive-nav-link> </x-responsive-nav-link>

File diff suppressed because one or more lines are too long

30
start-usher.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
# Start Laravel HTTP server for Usher stack
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"
PORT=8000
HOST=0.0.0.0
LOG_FILE="${ROOT_DIR}/storage/logs/serve.log"
# Basic sanity checks
if [ ! -f "artisan" ]; then
echo "artisan not found. Run this script from project root." >&2
exit 1
fi
mkdir -p "$(dirname "$LOG_FILE")"
# If already running, skip
if pgrep -f "artisan serve --host ${HOST} --port ${PORT}" >/dev/null; then
echo "Laravel dev server already running on ${HOST}:${PORT}."
exit 0
fi
echo "Starting Laravel dev server on ${HOST}:${PORT}..."
nohup php artisan serve --host "${HOST}" --port "${PORT}" >>"$LOG_FILE" 2>&1 &
PID=$!
echo "Started (PID: $PID). Logs: $LOG_FILE"

18
stop-usher.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
# Stop Laravel HTTP server for Usher stack
HOST=0.0.0.0
PORT=8000
PIDS=$(pgrep -f "artisan serve --host ${HOST} --port ${PORT}" || true)
if [ -z "$PIDS" ]; then
echo "No running Laravel dev server found on ${HOST}:${PORT}."
exit 0
fi
echo "Stopping Laravel dev server (PIDs: $PIDS)..."
echo "$PIDS" | xargs kill
echo "Stopped."