feat: enhance user profile update functionality and improve sidebar user display
This commit is contained in:
parent
993f3599fc
commit
d053fc428c
@ -30,13 +30,21 @@ public function edit(Request $request): Response
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
$validated = $request->validated();
|
||||
$user = $request->user();
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
$user->fill(['email' => $validated['email']]);
|
||||
|
||||
if ($user->isDirty('email')) {
|
||||
$user->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
$user->save();
|
||||
|
||||
$user->userProfile()->updateOrCreate(
|
||||
[],
|
||||
['full_name' => $validated['name']],
|
||||
);
|
||||
|
||||
Inertia::flash('toast', ['type' => 'success', 'message' => __('Profile updated.')]);
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ public function share(Request $request): array
|
||||
...parent::share($request),
|
||||
'name' => config('app.name'),
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
'user' => $request->user()?->load('userProfile'),
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
];
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
@ -22,6 +23,8 @@ class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, HasRoles, Notifiable, SoftDeletes, TwoFactorAuthenticatable;
|
||||
|
||||
protected $with = ['userProfile'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@ -39,6 +42,13 @@ protected function active(Builder $query): void
|
||||
$query->where('is_active', true);
|
||||
}
|
||||
|
||||
protected function fullName(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn() => $this->userProfile?->full_name ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
public function attendances(): HasMany
|
||||
{
|
||||
return $this->hasMany(Attendance::class);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Breadcrumbs } from '@/components/breadcrumbs';
|
||||
import { NavUser } from '@/components/nav-user';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
||||
|
||||
@ -13,6 +14,9 @@ export function AppSidebarHeader({
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<NavUser />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { BookOpen, FolderGit2, LayoutGrid } from 'lucide-react';
|
||||
import { LayoutGrid } from 'lucide-react';
|
||||
import AppLogo from '@/components/app-logo';
|
||||
import { NavFooter } from '@/components/nav-footer';
|
||||
import { NavMain } from '@/components/nav-main';
|
||||
import { NavUser } from '@/components/nav-user';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
@ -18,25 +15,12 @@ import type { NavItem } from '@/types';
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
title: 'Dasbor',
|
||||
href: dashboard(),
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
];
|
||||
|
||||
const footerNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Repository',
|
||||
href: 'https://github.com/laravel/react-starter-kit',
|
||||
icon: FolderGit2,
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
href: 'https://laravel.com/docs/starter-kits#react',
|
||||
icon: BookOpen,
|
||||
},
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
return (
|
||||
<Sidebar collapsible="icon" variant="inset">
|
||||
@ -55,11 +39,6 @@ export function AppSidebar() {
|
||||
<SidebarContent>
|
||||
<NavMain items={mainNavItems} />
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<NavFooter items={footerNavItems} className="mt-auto" />
|
||||
<NavUser />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuItem
|
||||
} from '@/components/ui/sidebar';
|
||||
import { useCurrentUrl } from '@/hooks/use-current-url';
|
||||
import type { NavItem } from '@/types';
|
||||
@ -14,7 +13,6 @@ export function NavMain({ items = [] }: { items: NavItem[] }) {
|
||||
|
||||
return (
|
||||
<SidebarGroup className="px-2 py-0">
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
|
||||
@ -5,54 +5,33 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { UserInfo } from '@/components/user-info';
|
||||
import { UserMenuContent } from '@/components/user-menu-content';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
|
||||
export function NavUser() {
|
||||
const { auth } = usePage().props;
|
||||
const { state } = useSidebar();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (!auth.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="group text-sidebar-accent-foreground data-[state=open]:bg-sidebar-accent"
|
||||
data-test="sidebar-menu-button"
|
||||
>
|
||||
<UserInfo user={auth.user} />
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
align="end"
|
||||
side={
|
||||
isMobile
|
||||
? 'bottom'
|
||||
: state === 'collapsed'
|
||||
? 'left'
|
||||
: 'bottom'
|
||||
}
|
||||
>
|
||||
<UserMenuContent user={auth.user} />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
|
||||
data-test="header-user-menu-button"
|
||||
>
|
||||
<UserInfo user={auth.user} />
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-56 rounded-lg"
|
||||
align="end"
|
||||
>
|
||||
<UserMenuContent user={auth.user} />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,6 +8,10 @@ function getInitial(name: string): string {
|
||||
|
||||
export function useInitials(): GetInitialsFn {
|
||||
return useCallback((fullName: string): string => {
|
||||
if (!fullName) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const names = fullName.trim().split(/\s+/u).filter(Boolean);
|
||||
|
||||
if (names.length === 0) {
|
||||
|
||||
@ -12,6 +12,7 @@ export function useIsMobile() {
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Form, Head } from '@inertiajs/react';
|
||||
import InputError from '@/components/input-error';
|
||||
import PasswordInput from '@/components/password-input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -6,7 +7,6 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { store } from '@/routes/login';
|
||||
import { Form, Head } from '@inertiajs/react';
|
||||
|
||||
export default function Login() {
|
||||
return (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user