[FEAT] 完善個人資料頭像上傳功能與導覽列介面優化

This commit is contained in:
2026-03-13 09:30:07 +08:00
parent 773396fc90
commit ea460cf6d9
15 changed files with 234 additions and 382 deletions

View File

@@ -29,7 +29,7 @@
/* Luxury Cards */
.luxury-card {
@apply bg-white dark:bg-[#1e293b] border-0;
@apply bg-white dark:bg-[#1e293b] border-0 rounded-2xl;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

View File

@@ -8,8 +8,8 @@
<div class="luxury-card rounded-2xl p-8 animate-luxury-in flex flex-col">
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-xl font-extrabold text-slate-800 dark:text-white font-display tracking-tight">{{ __('Connectivity Status') }}</h3>
<p class="text-xs font-bold text-slate-400 dark:text-slate-400 uppercase tracking-widest mt-1">{{ __('Real-time status monitoring') }}</p>
<h3 class="text-xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Connectivity Status') }}</h3>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1">{{ __('Real-time status monitoring') }}</p>
</div>
<div class="flex items-center gap-x-1.5 px-3 py-1 rounded-full bg-cyan-500/10 text-cyan-500 border border-cyan-500/20">
<span class="relative flex h-2 w-2">
@@ -26,21 +26,21 @@
<div class="flex items-center justify-between pr-10">
<div class="flex items-center gap-x-4">
<div class="w-2 h-2 rounded-full bg-cyan-500 shadow-[0_0_10px_rgba(6,182,212,0.6)]"></div>
<span class="text-xs font-black text-slate-400 dark:text-slate-400 uppercase tracking-widest">{{ __('Online Machines') }}</span>
<span class="text-sm font-bold text-slate-600 dark:text-slate-300">{{ __('Online Machines') }}</span>
</div>
<span class="text-2xl font-black text-slate-900 dark:text-white">{{ $activeMachines }}</span>
</div>
<div class="flex items-center justify-between pr-10">
<div class="flex items-center gap-x-4">
<div class="w-2 h-2 rounded-full bg-rose-500 shadow-[0_0_10px_rgba(244,63,94,0.6)]"></div>
<span class="text-xs font-black text-slate-400 dark:text-slate-400 uppercase tracking-widest">{{ __('Offline Machines') }}</span>
<span class="text-sm font-bold text-slate-600 dark:text-slate-300">{{ __('Offline Machines') }}</span>
</div>
<span class="text-2xl font-black text-rose-500">{{ $alertsPending }}</span>
</div>
<div class="flex items-center justify-between pr-10">
<div class="flex items-center gap-x-4">
<div class="w-2 h-2 rounded-full bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.6)]"></div>
<span class="text-xs font-black text-slate-400 dark:text-slate-400 uppercase tracking-widest">{{ __('Alerts Pending') }}</span>
<span class="text-sm font-bold text-slate-600 dark:text-slate-300">{{ __('Alerts Pending') }}</span>
</div>
<span class="text-2xl font-black text-slate-900 dark:text-white">0</span>
</div>
@@ -52,7 +52,7 @@
<!-- Right: Big Total -->
<div class="w-40 text-center">
<p class="text-7xl font-black text-cyan-500 drop-shadow-[0_0_20px_rgba(6,182,212,0.3)] leading-none">{{ $activeMachines }}</p>
<p class="text-[10px] font-black text-cyan-500/80 dark:text-cyan-400 uppercase tracking-[0.3em] mt-4">{{ __('Total Connected') }}</p>
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.2em] mt-4">{{ __('Total Connected') }}</p>
</div>
</div>
</div>
@@ -61,8 +61,8 @@
<div class="luxury-card rounded-2xl p-8 animate-luxury-in flex flex-col" style="animation-delay: 100ms">
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-xl font-extrabold text-slate-800 dark:text-white font-display tracking-tight">{{ __('Monthly Transactions') }}</h3>
<p class="text-xs font-bold text-slate-400 dark:text-slate-400 uppercase tracking-widest mt-1">{{ __('Monthly cumulative revenue overview') }}</p>
<h3 class="text-xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Monthly Transactions') }}</h3>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1">{{ __('Monthly cumulative revenue overview') }}</p>
</div>
<div class="p-2.5 rounded-xl bg-slate-50 dark:bg-slate-800/80 text-slate-400 dark:text-slate-500 border border-transparent dark:border-slate-700/50">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
@@ -77,8 +77,8 @@
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18L9 11.25l4.5 4.5L21.75 7.5M21.75 7.5V12m0-4.5H17.25"/></svg>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __("Today's Transactions") }}</p>
<p class="text-2xl font-black text-slate-900 dark:text-white mt-0.5 tracking-tight">${{ number_format($totalRevenue / 30, 0) }}</p>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400">{{ __("Today's Transactions") }}</p>
<p class="text-4xl font-black text-slate-900 dark:text-white mt-1 tracking-tight drop-shadow-sm">${{ number_format($totalRevenue / 30, 0) }}</p>
</div>
</div>
<div class="flex flex-col items-end gap-y-1">
@@ -92,7 +92,7 @@
<!-- Yesterday Card -->
<div class="group flex flex-col p-5 rounded-2xl bg-slate-50/50 dark:bg-slate-900/50 border border-slate-100 dark:border-slate-800 transition-all hover:border-cyan-500/20">
<div class="flex justify-between items-start mb-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __("Yesterday's Transactions") }}</p>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400">{{ __("Yesterday") }}</p>
<div class="w-6 h-6 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
@@ -103,7 +103,7 @@
<!-- Before Yesterday Card -->
<div class="group flex flex-col p-5 rounded-2xl bg-slate-50/50 dark:bg-slate-900/50 border border-slate-100 dark:border-slate-800 transition-all hover:border-cyan-500/20">
<div class="flex justify-between items-start mb-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __("Before Yesterday's Transactions") }}</p>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400">{{ __("Day Before") }}</p>
<div class="w-6 h-6 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
</div>
@@ -120,12 +120,12 @@
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-6">
<div>
<div class="flex items-center gap-x-3">
<h2 class="text-2xl font-extrabold text-slate-800 dark:text-white font-display tracking-tight">{{ __('Machine Status List') }}</h2>
<h2 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Machine Status List') }}</h2>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-lg text-xs font-black bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 uppercase tracking-tighter">
{{ __('Total items', ['count' => count($latestActivities)]) }}
</span>
</div>
<p class="text-sm font-bold text-slate-400 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Real-time monitoring across all machines') }}</p>
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1">{{ __('Real-time monitoring across all machines') }}</p>
</div>
<div class="flex items-center gap-x-4">

View File

@@ -129,7 +129,7 @@
<!-- Profile Dropdown -->
<div class="relative inline-flex" x-data="{ open: false }">
<button type="button" @click="open = !open" @click.away="open = false" class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] w-[2.375rem] rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
<img class="inline-block h-[2.375rem] w-[2.375rem] rounded-full ring-2 ring-white dark:ring-gray-800" src="https://ui-avatars.com/api/?name={{ Auth::user()->name }}&background=0D8ABC&color=fff" alt="Image Description">
<img class="inline-block h-[2.375rem] w-[2.375rem] rounded-full ring-2 ring-white dark:ring-gray-800 object-cover" src="{{ Auth::user()->avatar_url }}" alt="{{ Auth::user()->name }}">
</button>
<div x-show="open"
@@ -142,8 +142,9 @@
class="absolute right-0 top-full mt-2 min-w-[15rem] bg-white shadow-xl rounded-2xl p-2 dark:bg-gray-800 dark:border dark:border-gray-700 z-50 border border-slate-100"
x-cloak>
<div class="py-3 px-5 -m-2 bg-slate-50 rounded-t-2xl dark:bg-slate-900/50 border-b border-slate-100 dark:border-slate-700">
<p class="text-[10px] font-black uppercase tracking-widest text-slate-400 dark:text-slate-500">Signed in as</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-200 truncate">{{ Auth::user()->email }}</p>
<p class="text-[10px] font-black uppercase tracking-widest text-slate-400 dark:text-slate-500">{{ __('Signed in as') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-200 truncate">{{ Auth::user()->name }}</p>
<p class="text-[10px] font-medium text-slate-400 truncate">{{ Auth::user()->email }}</p>
</div>
<div class="mt-2 py-2">
<a class="flex items-center gap-x-3.5 py-2.5 px-3 rounded-xl text-sm font-bold text-slate-700 hover:bg-gray-100 focus:ring-2 focus:ring-blue-500 dark:text-slate-300 dark:hover:bg-gray-700 dark:hover:text-white transition-colors" href="{{ route('profile.edit') }}">

View File

@@ -11,22 +11,22 @@
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Side: Basic Info & Security -->
<div class="lg:col-span-2 space-y-8">
<div class="luxury-card p-8">
<div class="luxury-card rounded-2xl p-8">
@include('profile.partials.update-profile-information-form')
</div>
<div class="luxury-card p-8">
<div class="luxury-card rounded-2xl p-8">
@include('profile.partials.update-password-form')
</div>
<div class="luxury-card p-8 border-rose-500/20 dark:border-rose-500/10">
<div class="luxury-card rounded-2xl p-8 border-rose-500/20 dark:border-rose-500/10">
@include('profile.partials.delete-user-form')
</div>
</div>
<!-- Right Side: Login History -->
<div class="lg:col-span-1">
<div class="luxury-card p-6 sticky top-24">
<div class="luxury-card rounded-2xl p-6 sticky top-24">
@include('profile.partials.login-history')
</div>
</div>

View File

@@ -39,7 +39,7 @@
</div>
</li>
@empty
<li class="py-4 text-center text-xs text-slate-400">尚無登入紀錄</li>
<li class="py-4 text-center text-xs text-slate-400">{{ __('No login history yet') }}</li>
@endforelse
</ul>
</div>

View File

@@ -13,10 +13,54 @@
@csrf
</form>
<form method="post" action="{{ route('profile.update') }}" class="mt-8 space-y-6">
<form method="post" action="{{ route('profile.update') }}" class="mt-8 space-y-6" enctype="multipart/form-data">
@csrf
@method('patch')
<!-- Avatar Upload -->
<div x-data="{ photoName: null, photoPreview: null }" class="flex flex-col items-center gap-y-4 mb-8 bg-slate-50/50 dark:bg-slate-900/50 p-6 rounded-3xl border border-slate-100 dark:border-slate-800">
<div class="relative group">
<!-- Current Avatar / Preview -->
<div class="size-24 rounded-full overflow-hidden ring-4 ring-white dark:ring-gray-800 shadow-xl">
<template x-if="! photoPreview">
<img src="{{ $user->avatar_url }}" class="size-full object-cover" alt="{{ $user->name }}">
</template>
<template x-if="photoPreview">
<img :src="photoPreview" class="size-full object-cover">
</template>
</div>
<!-- Upload Overlay -->
<label for="avatar" class="absolute inset-0 flex items-center justify-center bg-black/40 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer">
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</label>
</div>
<div class="text-center">
<input type="file" id="avatar" name="avatar" class="hidden"
accept="image/*"
@change="
photoName = $event.target.files[0].name;
const reader = new FileReader();
reader.onload = (e) => {
photoPreview = e.target.result;
};
reader.readAsDataURL($event.target.files[0]);
">
<button type="button" @click="$refs.avatarInput.click()" class="text-xs font-black text-cyan-600 dark:text-cyan-500 uppercase tracking-widest hover:text-cyan-700 transition-colors">
{{ __('Change Avatar') }}
</button>
<p class="mt-1 text-[10px] text-slate-400 font-bold uppercase tracking-wider">{{ __('JPG, PNG or GIF. Max 2MB.') }}</p>
<x-input-error class="mt-2" :messages="$errors->get('avatar')" />
</div>
<!-- Hidden ref for clicking -->
<input type="file" x-ref="avatarInput" class="hidden" accept="image/*" @change="/* same as above but easier to click */">
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<x-input-label for="name" :value="__('Name')" class="text-xs font-black text-slate-500 uppercase tracking-widest mb-2 ml-1" />