43 - Delete dead Breeze auth/settings boilerplate, slim auth routes to register-only
This commit is contained in:
parent
b1d0ab793c
commit
04fbda48fd
25 changed files with 3 additions and 1387 deletions
|
|
@ -1,51 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Auth\LoginRequest;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class AuthenticatedSessionController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the login page.
|
|
||||||
*/
|
|
||||||
public function create(Request $request): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('auth/login', [
|
|
||||||
'canResetPassword' => Route::has('password.request'),
|
|
||||||
'status' => $request->session()->get('status'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an incoming authentication request.
|
|
||||||
*/
|
|
||||||
public function store(LoginRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$request->authenticate();
|
|
||||||
|
|
||||||
$request->session()->regenerate();
|
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy an authenticated session.
|
|
||||||
*/
|
|
||||||
public function destroy(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
Auth::guard('web')->logout();
|
|
||||||
|
|
||||||
$request->session()->invalidate();
|
|
||||||
$request->session()->regenerateToken();
|
|
||||||
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class ConfirmablePasswordController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the confirm password page.
|
|
||||||
*/
|
|
||||||
public function show(): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('auth/confirm-password');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm the user's password.
|
|
||||||
*/
|
|
||||||
public function store(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
if (! Auth::guard('web')->validate([
|
|
||||||
'email' => $request->user()->email,
|
|
||||||
'password' => $request->password,
|
|
||||||
])) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'password' => __('auth.password'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$request->session()->put('auth.password_confirmed_at', time());
|
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class EmailVerificationNotificationController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Send a new email verification notification.
|
|
||||||
*/
|
|
||||||
public function store(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
$request->user()->sendEmailVerificationNotification();
|
|
||||||
|
|
||||||
return back()->with('status', 'verification-link-sent');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class EmailVerificationPromptController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the email verification prompt page.
|
|
||||||
*/
|
|
||||||
public function __invoke(Request $request): Response|RedirectResponse
|
|
||||||
{
|
|
||||||
return $request->user()->hasVerifiedEmail()
|
|
||||||
? redirect()->intended(route('dashboard', absolute: false))
|
|
||||||
: Inertia::render('auth/verify-email', ['status' => $request->session()->get('status')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Auth\Events\PasswordReset;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Facades\Password;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Validation\Rules;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class NewPasswordController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the password reset page.
|
|
||||||
*/
|
|
||||||
public function create(Request $request): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('auth/reset-password', [
|
|
||||||
'email' => $request->email,
|
|
||||||
'token' => $request->route('token'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an incoming new password request.
|
|
||||||
*
|
|
||||||
* @throws ValidationException
|
|
||||||
*/
|
|
||||||
public function store(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'token' => 'required',
|
|
||||||
'email' => 'required|email',
|
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Here we will attempt to reset the user's password. If it is successful we
|
|
||||||
// will update the password on an actual user model and persist it to the
|
|
||||||
// database. Otherwise we will parse the error and return the response.
|
|
||||||
$status = Password::reset(
|
|
||||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
|
||||||
function (User $user) use ($request) {
|
|
||||||
$user->forceFill([
|
|
||||||
'password' => Hash::make($request->password),
|
|
||||||
'remember_token' => Str::random(60),
|
|
||||||
])->save();
|
|
||||||
|
|
||||||
event(new PasswordReset($user));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the password was successfully reset, we will redirect the user back to
|
|
||||||
// the application's home authenticated view. If there is an error we can
|
|
||||||
// redirect them back to where they came from with their error message.
|
|
||||||
if ($status == Password::PasswordReset) {
|
|
||||||
return to_route('login')->with('status', __($status));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'email' => [__($status)],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Password;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class PasswordResetLinkController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the password reset link request page.
|
|
||||||
*/
|
|
||||||
public function create(Request $request): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('auth/forgot-password', [
|
|
||||||
'status' => $request->session()->get('status'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an incoming password reset link request.
|
|
||||||
*
|
|
||||||
* @throws ValidationException
|
|
||||||
*/
|
|
||||||
public function store(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'email' => 'required|email',
|
|
||||||
]);
|
|
||||||
|
|
||||||
Password::sendResetLink(
|
|
||||||
$request->only('email')
|
|
||||||
);
|
|
||||||
|
|
||||||
return back()->with('status', __('A reset link will be sent if the account exists.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Auth\Events\Verified;
|
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
|
|
||||||
class VerifyEmailController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Mark the authenticated user's email address as verified.
|
|
||||||
*/
|
|
||||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->user()->markEmailAsVerified()) {
|
|
||||||
/** @var MustVerifyEmail $user */
|
|
||||||
$user = $request->user();
|
|
||||||
|
|
||||||
event(new Verified($user));
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Settings;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Validation\Rules\Password;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class PasswordController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the user's password settings page.
|
|
||||||
*/
|
|
||||||
public function edit(): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('settings/password');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the user's password.
|
|
||||||
*/
|
|
||||||
public function update(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$validated = $request->validate([
|
|
||||||
'current_password' => ['required', 'current_password'],
|
|
||||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$request->user()->update([
|
|
||||||
'password' => Hash::make($validated['password']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Settings;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Settings\ProfileUpdateRequest;
|
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class ProfileController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the user's profile settings page.
|
|
||||||
*/
|
|
||||||
public function edit(Request $request): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('settings/profile', [
|
|
||||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
|
||||||
'status' => $request->session()->get('status'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the user's profile settings.
|
|
||||||
*/
|
|
||||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$request->user()->fill($request->validated());
|
|
||||||
|
|
||||||
if ($request->user()->isDirty('email')) {
|
|
||||||
$request->user()->email_verified_at = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$request->user()->save();
|
|
||||||
|
|
||||||
return to_route('profile.edit');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the user's account.
|
|
||||||
*/
|
|
||||||
public function destroy(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'password' => ['required', 'current_password'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$user = $request->user();
|
|
||||||
|
|
||||||
Auth::logout();
|
|
||||||
|
|
||||||
$user->delete();
|
|
||||||
|
|
||||||
$request->session()->invalidate();
|
|
||||||
$request->session()->regenerateToken();
|
|
||||||
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/DropdownMenu';
|
|
||||||
import { useAppearance } from '@/hooks/use-appearance';
|
|
||||||
import { Monitor, Moon, Sun } from 'lucide-react';
|
|
||||||
import { HTMLAttributes } from 'react';
|
|
||||||
|
|
||||||
export default function AppearanceToggleDropdown({ className = '', ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
||||||
const { appearance, updateAppearance } = useAppearance();
|
|
||||||
|
|
||||||
const getCurrentIcon = () => {
|
|
||||||
switch (appearance) {
|
|
||||||
case 'dark':
|
|
||||||
return <Moon className="h-5 w-5" />;
|
|
||||||
case 'light':
|
|
||||||
return <Sun className="h-5 w-5" />;
|
|
||||||
default:
|
|
||||||
return <Monitor className="h-5 w-5" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className} {...props}>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon" className="h-9 w-9 rounded-md">
|
|
||||||
{getCurrentIcon()}
|
|
||||||
<span className="sr-only">Toggle theme</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => updateAppearance('light')}>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<Sun className="h-5 w-5" />
|
|
||||||
Light
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => updateAppearance('dark')}>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<Moon className="h-5 w-5" />
|
|
||||||
Dark
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => updateAppearance('system')}>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<Monitor className="h-5 w-5" />
|
|
||||||
System
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import { Appearance, useAppearance } from '@/hooks/use-appearance';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { LucideIcon, Monitor, Moon, Sun } from 'lucide-react';
|
|
||||||
import { HTMLAttributes } from 'react';
|
|
||||||
|
|
||||||
export default function AppearanceToggleTab({ className = '', ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
||||||
const { appearance, updateAppearance } = useAppearance();
|
|
||||||
|
|
||||||
const tabs: { value: Appearance; icon: LucideIcon; label: string }[] = [
|
|
||||||
{ value: 'light', icon: Sun, label: 'Light' },
|
|
||||||
{ value: 'dark', icon: Moon, label: 'Dark' },
|
|
||||||
{ value: 'system', icon: Monitor, label: 'System' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn('inline-flex gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-800', className)} {...props}>
|
|
||||||
{tabs.map(({ value, icon: Icon, label }) => (
|
|
||||||
<button
|
|
||||||
key={value}
|
|
||||||
onClick={() => updateAppearance(value)}
|
|
||||||
className={cn(
|
|
||||||
'flex items-center rounded-md px-3.5 py-1.5 transition-colors',
|
|
||||||
appearance === value
|
|
||||||
? 'bg-white shadow-xs dark:bg-neutral-700 dark:text-neutral-100'
|
|
||||||
: 'text-neutral-500 hover:bg-neutral-200/60 hover:text-black dark:text-neutral-400 dark:hover:bg-neutral-700/60',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon className="-ml-1 h-4 w-4" />
|
|
||||||
<span className="ml-1.5 text-sm">{label}</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import Heading from '@/components/heading';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { type NavItem } from '@/types';
|
|
||||||
import { Link } from '@inertiajs/react';
|
|
||||||
import { type PropsWithChildren } from 'react';
|
|
||||||
|
|
||||||
const sidebarNavItems: NavItem[] = [
|
|
||||||
{
|
|
||||||
title: 'Profile',
|
|
||||||
href: '/settings/profile',
|
|
||||||
icon: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Password',
|
|
||||||
href: '/settings/password',
|
|
||||||
icon: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Appearance',
|
|
||||||
href: '/settings/appearance',
|
|
||||||
icon: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function SettingsLayout({ children }: PropsWithChildren) {
|
|
||||||
// When server-side rendering, we only render the layout on the client...
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="px-4 py-6">
|
|
||||||
<Heading title="Settings" description="Manage your profile and account settings" />
|
|
||||||
|
|
||||||
<div className="flex flex-col space-y-8 lg:flex-row lg:space-y-0 lg:space-x-12">
|
|
||||||
<aside className="w-full max-w-xl lg:w-48">
|
|
||||||
<nav className="flex flex-col space-y-1 space-x-0">
|
|
||||||
{sidebarNavItems.map((item, index) => (
|
|
||||||
<Button
|
|
||||||
key={`${item.href}-${index}`}
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
asChild
|
|
||||||
className={cn('w-full justify-start', {
|
|
||||||
'bg-muted': currentPath === item.href,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Link href={item.href} prefetch>
|
|
||||||
{item.title}
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<Separator className="my-6 md:hidden" />
|
|
||||||
|
|
||||||
<div className="flex-1 md:max-w-2xl">
|
|
||||||
<section className="max-w-xl space-y-12">{children}</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// Components
|
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import AuthLayout from '@/layouts/auth-layout';
|
|
||||||
|
|
||||||
export default function ConfirmPassword() {
|
|
||||||
const { data, setData, post, processing, errors, reset } = useForm<Required<{ password: string }>>({
|
|
||||||
password: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
post(route('password.confirm'), {
|
|
||||||
onFinish: () => reset('password'),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthLayout
|
|
||||||
title="Confirm your password"
|
|
||||||
description="This is a secure area of the application. Please confirm your password before continuing."
|
|
||||||
>
|
|
||||||
<Head title="Confirm password" />
|
|
||||||
|
|
||||||
<form onSubmit={submit}>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Password"
|
|
||||||
autoComplete="current-password"
|
|
||||||
value={data.password}
|
|
||||||
autoFocus
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.password} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button className="w-full" disabled={processing}>
|
|
||||||
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
|
|
||||||
Confirm password
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
// Components
|
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import TextLink from '@/components/TextLink';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import AuthLayout from '@/layouts/auth-layout';
|
|
||||||
|
|
||||||
export default function ForgotPassword({ status }: { status?: string }) {
|
|
||||||
const { data, setData, post, processing, errors } = useForm<Required<{ email: string }>>({
|
|
||||||
email: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
post(route('password.email'));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthLayout title="Forgot password" description="Enter your email to receive a password reset link">
|
|
||||||
<Head title="Forgot password" />
|
|
||||||
|
|
||||||
{status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<form onSubmit={submit}>
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="email">Email address</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
autoComplete="off"
|
|
||||||
value={data.email}
|
|
||||||
autoFocus
|
|
||||||
onChange={(e) => setData('email', e.target.value)}
|
|
||||||
placeholder="email@example.com"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.email} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="my-6 flex items-center justify-start">
|
|
||||||
<Button className="w-full" disabled={processing}>
|
|
||||||
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
|
|
||||||
Email password reset link
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="space-x-1 text-center text-sm text-muted-foreground">
|
|
||||||
<span>Or, return to</span>
|
|
||||||
<TextLink href={route('login')}>log in</TextLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import TextLink from '@/components/TextLink';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import AuthLayout from '@/layouts/auth-layout';
|
|
||||||
|
|
||||||
type LoginForm = {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
remember: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface LoginProps {
|
|
||||||
status?: string;
|
|
||||||
canResetPassword: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Login({ status, canResetPassword }: LoginProps) {
|
|
||||||
const { data, setData, post, processing, errors, reset } = useForm<Required<LoginForm>>({
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
remember: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
post(route('login'), {
|
|
||||||
onFinish: () => reset('password'),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthLayout title="Log in to your account" description="Enter your email and password below to log in">
|
|
||||||
<Head title="Log in" />
|
|
||||||
|
|
||||||
<form className="flex flex-col gap-6" onSubmit={submit}>
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="email">Email address</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
required
|
|
||||||
autoFocus
|
|
||||||
tabIndex={1}
|
|
||||||
autoComplete="email"
|
|
||||||
value={data.email}
|
|
||||||
onChange={(e) => setData('email', e.target.value)}
|
|
||||||
placeholder="email@example.com"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.email} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
{canResetPassword && (
|
|
||||||
<TextLink href={route('password.request')} className="ml-auto text-sm" tabIndex={5}>
|
|
||||||
Forgot password?
|
|
||||||
</TextLink>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
tabIndex={2}
|
|
||||||
autoComplete="current-password"
|
|
||||||
value={data.password}
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
placeholder="Password"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.password} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<Checkbox
|
|
||||||
id="remember"
|
|
||||||
name="remember"
|
|
||||||
checked={data.remember}
|
|
||||||
onClick={() => setData('remember', !data.remember)}
|
|
||||||
tabIndex={3}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="remember">Remember me</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button type="submit" className="mt-4 w-full" tabIndex={4} disabled={processing}>
|
|
||||||
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
|
|
||||||
Log in
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground">
|
|
||||||
Don't have an account?{' '}
|
|
||||||
<TextLink href={route('register')} tabIndex={5}>
|
|
||||||
Sign up
|
|
||||||
</TextLink>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import TextLink from '@/components/TextLink';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import AuthLayout from '@/layouts/auth-layout';
|
|
||||||
|
|
||||||
type RegisterForm = {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
password_confirmation: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Register() {
|
|
||||||
const { data, setData, post, processing, errors, reset } = useForm<Required<RegisterForm>>({
|
|
||||||
name: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
password_confirmation: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
post(route('register'), {
|
|
||||||
onFinish: () => reset('password', 'password_confirmation'),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthLayout title="Create an account" description="Enter your details below to create your account">
|
|
||||||
<Head title="Register" />
|
|
||||||
<form className="flex flex-col gap-6" onSubmit={submit}>
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="name">Name</Label>
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
autoFocus
|
|
||||||
tabIndex={1}
|
|
||||||
autoComplete="name"
|
|
||||||
value={data.name}
|
|
||||||
onChange={(e) => setData('name', e.target.value)}
|
|
||||||
disabled={processing}
|
|
||||||
placeholder="Full name"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.name} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="email">Email address</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
required
|
|
||||||
tabIndex={2}
|
|
||||||
autoComplete="email"
|
|
||||||
value={data.email}
|
|
||||||
onChange={(e) => setData('email', e.target.value)}
|
|
||||||
disabled={processing}
|
|
||||||
placeholder="email@example.com"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.email} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
tabIndex={3}
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={data.password}
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
disabled={processing}
|
|
||||||
placeholder="Password"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.password} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password_confirmation">Confirm password</Label>
|
|
||||||
<Input
|
|
||||||
id="password_confirmation"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
tabIndex={4}
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={data.password_confirmation}
|
|
||||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
|
||||||
disabled={processing}
|
|
||||||
placeholder="Confirm password"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.password_confirmation} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button type="submit" className="mt-2 w-full" tabIndex={5} disabled={processing}>
|
|
||||||
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
|
|
||||||
Create account
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground">
|
|
||||||
Already have an account?{' '}
|
|
||||||
<TextLink href={route('login')} tabIndex={6}>
|
|
||||||
Log in
|
|
||||||
</TextLink>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import AuthLayout from '@/layouts/auth-layout';
|
|
||||||
|
|
||||||
interface ResetPasswordProps {
|
|
||||||
token: string;
|
|
||||||
email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResetPasswordForm = {
|
|
||||||
token: string;
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
password_confirmation: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ResetPassword({ token, email }: ResetPasswordProps) {
|
|
||||||
const { data, setData, post, processing, errors, reset } = useForm<Required<ResetPasswordForm>>({
|
|
||||||
token: token,
|
|
||||||
email: email,
|
|
||||||
password: '',
|
|
||||||
password_confirmation: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
post(route('password.store'), {
|
|
||||||
onFinish: () => reset('password', 'password_confirmation'),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthLayout title="Reset password" description="Please enter your new password below">
|
|
||||||
<Head title="Reset password" />
|
|
||||||
|
|
||||||
<form onSubmit={submit}>
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="email">Email</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
autoComplete="email"
|
|
||||||
value={data.email}
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
readOnly
|
|
||||||
onChange={(e) => setData('email', e.target.value)}
|
|
||||||
/>
|
|
||||||
<InputError message={errors.email} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={data.password}
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoFocus
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
placeholder="Password"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.password} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password_confirmation">Confirm password</Label>
|
|
||||||
<Input
|
|
||||||
id="password_confirmation"
|
|
||||||
type="password"
|
|
||||||
name="password_confirmation"
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={data.password_confirmation}
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
|
||||||
placeholder="Confirm password"
|
|
||||||
/>
|
|
||||||
<InputError message={errors.password_confirmation} className="mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button type="submit" className="mt-4 w-full" disabled={processing}>
|
|
||||||
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
|
|
||||||
Reset password
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// Components
|
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import TextLink from '@/components/TextLink';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import AuthLayout from '@/layouts/auth-layout';
|
|
||||||
|
|
||||||
export default function VerifyEmail({ status }: { status?: string }) {
|
|
||||||
const { post, processing } = useForm({});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
post(route('verification.send'));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthLayout title="Verify email" description="Please verify your email address by clicking on the link we just emailed to you.">
|
|
||||||
<Head title="Email verification" />
|
|
||||||
|
|
||||||
{status === 'verification-link-sent' && (
|
|
||||||
<div className="mb-4 text-center text-sm font-medium text-green-600">
|
|
||||||
A new verification link has been sent to the email address you provided during registration.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form onSubmit={submit} className="space-y-6 text-center">
|
|
||||||
<Button disabled={processing} variant="secondary">
|
|
||||||
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
|
|
||||||
Resend verification email
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<TextLink href={route('logout')} method="post" className="mx-auto block text-sm">
|
|
||||||
Log out
|
|
||||||
</TextLink>
|
|
||||||
</form>
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { Head } from '@inertiajs/react';
|
|
||||||
|
|
||||||
import AppearanceTabs from '@/components/Settings/AppearanceTabs';
|
|
||||||
import HeadingSmall from '@/components/HeadingSmall';
|
|
||||||
import { type BreadcrumbItem } from '@/types';
|
|
||||||
|
|
||||||
import AppLayout from '@/layouts/app-layout';
|
|
||||||
import SettingsLayout from '@/layouts/settings/layout';
|
|
||||||
|
|
||||||
const breadcrumbs: BreadcrumbItem[] = [
|
|
||||||
{
|
|
||||||
title: 'Appearance settings',
|
|
||||||
href: '/settings/appearance',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Appearance() {
|
|
||||||
return (
|
|
||||||
<AppLayout breadcrumbs={breadcrumbs}>
|
|
||||||
<Head title="Appearance settings" />
|
|
||||||
|
|
||||||
<SettingsLayout>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<HeadingSmall title="Appearance settings" description="Update your account's appearance settings" />
|
|
||||||
<AppearanceTabs />
|
|
||||||
</div>
|
|
||||||
</SettingsLayout>
|
|
||||||
</AppLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import AppLayout from '@/layouts/app-layout';
|
|
||||||
import SettingsLayout from '@/layouts/settings/layout';
|
|
||||||
import { type BreadcrumbItem } from '@/types';
|
|
||||||
import { Transition } from '@headlessui/react';
|
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
|
||||||
import { FormEventHandler, useRef } from 'react';
|
|
||||||
|
|
||||||
import HeadingSmall from '@/components/HeadingSmall';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
|
|
||||||
const breadcrumbs: BreadcrumbItem[] = [
|
|
||||||
{
|
|
||||||
title: 'Password settings',
|
|
||||||
href: '/settings/password',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Password() {
|
|
||||||
const passwordInput = useRef<HTMLInputElement>(null);
|
|
||||||
const currentPasswordInput = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
|
|
||||||
current_password: '',
|
|
||||||
password: '',
|
|
||||||
password_confirmation: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatePassword: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
put(route('password.update'), {
|
|
||||||
preserveScroll: true,
|
|
||||||
onSuccess: () => reset(),
|
|
||||||
onError: (errors) => {
|
|
||||||
if (errors.password) {
|
|
||||||
reset('password', 'password_confirmation');
|
|
||||||
passwordInput.current?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.current_password) {
|
|
||||||
reset('current_password');
|
|
||||||
currentPasswordInput.current?.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppLayout breadcrumbs={breadcrumbs}>
|
|
||||||
<Head title="Password settings" />
|
|
||||||
|
|
||||||
<SettingsLayout>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<HeadingSmall title="Update password" description="Ensure your account is using a long, random password to stay secure" />
|
|
||||||
|
|
||||||
<form onSubmit={updatePassword} className="space-y-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="current_password">Current password</Label>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="current_password"
|
|
||||||
ref={currentPasswordInput}
|
|
||||||
value={data.current_password}
|
|
||||||
onChange={(e) => setData('current_password', e.target.value)}
|
|
||||||
type="password"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoComplete="current-password"
|
|
||||||
placeholder="Current password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.current_password} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password">New password</Label>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
ref={passwordInput}
|
|
||||||
value={data.password}
|
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
|
||||||
type="password"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoComplete="new-password"
|
|
||||||
placeholder="New password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.password} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="password_confirmation">Confirm password</Label>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="password_confirmation"
|
|
||||||
value={data.password_confirmation}
|
|
||||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
|
||||||
type="password"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
autoComplete="new-password"
|
|
||||||
placeholder="Confirm password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError message={errors.password_confirmation} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button disabled={processing}>Save password</Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
show={recentlySuccessful}
|
|
||||||
enter="transition ease-in-out"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
leave="transition ease-in-out"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<p className="text-sm text-neutral-600">Saved</p>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</SettingsLayout>
|
|
||||||
</AppLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
import { type BreadcrumbItem, type SharedData } from '@/types';
|
|
||||||
import { Transition } from '@headlessui/react';
|
|
||||||
import { Head, Link, useForm, usePage } from '@inertiajs/react';
|
|
||||||
import { FormEventHandler } from 'react';
|
|
||||||
|
|
||||||
import DeleteUser from '@/components/Settings/DeleteUser';
|
|
||||||
import HeadingSmall from '@/components/HeadingSmall';
|
|
||||||
import InputError from '@/components/InputError';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import AppLayout from '@/layouts/app-layout';
|
|
||||||
import SettingsLayout from '@/layouts/settings/layout';
|
|
||||||
|
|
||||||
const breadcrumbs: BreadcrumbItem[] = [
|
|
||||||
{
|
|
||||||
title: 'Profile settings',
|
|
||||||
href: '/settings/profile',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type ProfileForm = {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail: boolean; status?: string }) {
|
|
||||||
const { auth } = usePage<SharedData>().props;
|
|
||||||
|
|
||||||
const { data, setData, patch, errors, processing, recentlySuccessful } = useForm<Required<ProfileForm>>({
|
|
||||||
name: auth.user.name,
|
|
||||||
email: auth.user.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
patch(route('profile.update'), {
|
|
||||||
preserveScroll: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppLayout breadcrumbs={breadcrumbs}>
|
|
||||||
<Head title="Profile settings" />
|
|
||||||
|
|
||||||
<SettingsLayout>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<HeadingSmall title="Profile information" description="Update your name and email address" />
|
|
||||||
|
|
||||||
<form onSubmit={submit} className="space-y-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="name">Name</Label>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
value={data.name}
|
|
||||||
onChange={(e) => setData('name', e.target.value)}
|
|
||||||
required
|
|
||||||
autoComplete="name"
|
|
||||||
placeholder="Full name"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError className="mt-2" message={errors.name} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="email">Email address</Label>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
className="mt-1 block w-full"
|
|
||||||
value={data.email}
|
|
||||||
onChange={(e) => setData('email', e.target.value)}
|
|
||||||
required
|
|
||||||
autoComplete="username"
|
|
||||||
placeholder="Email address"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputError className="mt-2" message={errors.email} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{mustVerifyEmail && auth.user.email_verified_at === null && (
|
|
||||||
<div>
|
|
||||||
<p className="-mt-4 text-sm text-muted-foreground">
|
|
||||||
Your email address is unverified.{' '}
|
|
||||||
<Link
|
|
||||||
href={route('verification.send')}
|
|
||||||
method="post"
|
|
||||||
as="button"
|
|
||||||
className="text-foreground underline decoration-neutral-300 underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-neutral-500"
|
|
||||||
>
|
|
||||||
Click here to resend the verification email.
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{status === 'verification-link-sent' && (
|
|
||||||
<div className="mt-2 text-sm font-medium text-green-600">
|
|
||||||
A new verification link has been sent to your email address.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button disabled={processing}>Save</Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
show={recentlySuccessful}
|
|
||||||
enter="transition ease-in-out"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
leave="transition ease-in-out"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<p className="text-sm text-neutral-600">Saved</p>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DeleteUser />
|
|
||||||
</SettingsLayout>
|
|
||||||
</AppLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
|
||||||
use App\Http\Controllers\Auth\ConfirmablePasswordController;
|
|
||||||
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
|
|
||||||
use App\Http\Controllers\Auth\EmailVerificationPromptController;
|
|
||||||
use App\Http\Controllers\Auth\NewPasswordController;
|
|
||||||
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
|
||||||
use App\Http\Controllers\Auth\RegisteredUserController;
|
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||||
use App\Http\Controllers\Auth\VerifyEmailController;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::middleware('guest')->group(function () {
|
// First-run setup only — gated by User::exists() in the controller
|
||||||
Route::get('register', [RegisteredUserController::class, 'create'])
|
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
|
||||||
->name('register');
|
|
||||||
|
|
||||||
Route::post('register', [RegisteredUserController::class, 'store']);
|
Route::post('register', [RegisteredUserController::class, 'store']);
|
||||||
|
|
||||||
Route::get('login', [AuthenticatedSessionController::class, 'create'])
|
|
||||||
->name('login');
|
|
||||||
|
|
||||||
Route::post('login', [AuthenticatedSessionController::class, 'store']);
|
|
||||||
|
|
||||||
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
|
|
||||||
->name('password.request');
|
|
||||||
|
|
||||||
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
|
|
||||||
->name('password.email');
|
|
||||||
|
|
||||||
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
|
|
||||||
->name('password.reset');
|
|
||||||
|
|
||||||
Route::post('reset-password', [NewPasswordController::class, 'store'])
|
|
||||||
->name('password.store');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
|
||||||
Route::get('verify-email', EmailVerificationPromptController::class)
|
|
||||||
->name('verification.notice');
|
|
||||||
|
|
||||||
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
|
|
||||||
->middleware(['signed', 'throttle:6,1'])
|
|
||||||
->name('verification.verify');
|
|
||||||
|
|
||||||
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
|
|
||||||
->middleware('throttle:6,1')
|
|
||||||
->name('verification.send');
|
|
||||||
|
|
||||||
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
|
|
||||||
->name('password.confirm');
|
|
||||||
|
|
||||||
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
|
|
||||||
|
|
||||||
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
|
|
||||||
->name('logout');
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use App\Http\Controllers\Settings\PasswordController;
|
|
||||||
use App\Http\Controllers\Settings\ProfileController;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
|
||||||
Route::redirect('settings', '/settings/profile');
|
|
||||||
|
|
||||||
Route::get('settings/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
|
||||||
Route::patch('settings/profile', [ProfileController::class, 'update'])->name('profile.update');
|
|
||||||
Route::delete('settings/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
|
||||||
|
|
||||||
Route::get('settings/password', [PasswordController::class, 'edit'])->name('password.edit');
|
|
||||||
Route::put('settings/password', [PasswordController::class, 'update'])->name('password.update');
|
|
||||||
|
|
||||||
Route::get('settings/appearance', function () {
|
|
||||||
return Inertia::render('settings/appearance');
|
|
||||||
})->name('appearance');
|
|
||||||
});
|
|
||||||
|
|
@ -47,5 +47,4 @@
|
||||||
Route::post('/', [MilestoneController::class, 'store'])->name('store');
|
Route::post('/', [MilestoneController::class, 'store'])->name('store');
|
||||||
});
|
});
|
||||||
|
|
||||||
require __DIR__.'/settings.php';
|
|
||||||
require __DIR__.'/auth.php';
|
require __DIR__.'/auth.php';
|
||||||
|
|
|
||||||
BIN
testing
Normal file
BIN
testing
Normal file
Binary file not shown.
Loading…
Reference in a new issue