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