User Name Split
Replace name with required first_name and last_name fields. Do this while the users table is empty.
Migration
bash
php artisan make:migration split_user_name_column --table=usersFile: database/migrations/xxxx_xx_xx_xxxxxx_split_user_name_column.php
php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('name', 'first_name');
$table->string('last_name');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('first_name', 'name');
$table->dropColumn('last_name');
});
}
};bash
php artisan migrateThe migration only renames name and adds last_name; it does not split existing values. Rollback drops last_name, so it also needs care once users exist.
User Model
Keep the generated auth traits and apply the name changes, including getPasskeyDisplayName(); otherwise passkey prompts still expect name.
File: app/Models/User.php
php
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Laravel\Fortify\Contracts\PasskeyUser;
use Laravel\Fortify\PasskeyAuthenticatable;
use Laravel\Fortify\TwoFactorAuthenticatable;
/**
* @property int $id
* @property string $first_name
* @property string $last_name
* @property string $email
* @property Carbon|null $email_verified_at
* @property string $password
* @property string|null $two_factor_secret
* @property string|null $two_factor_recovery_codes
* @property Carbon|null $two_factor_confirmed_at
* @property string|null $remember_token
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*/
#[Fillable(['first_name', 'last_name', 'email', 'password'])]
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
class User extends Authenticatable implements PasskeyUser
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable, PasskeyAuthenticatable, TwoFactorAuthenticatable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'two_factor_confirmed_at' => 'datetime',
];
}
/**
* Get the user's full name.
*/
public function fullName(): string
{
return trim($this->first_name . ' ' . $this->last_name);
}
/**
* Use the full name in passkey prompts.
*/
public function getPasskeyDisplayName(): string
{
return $this->fullName();
}
/**
* Get the user's initials.
*/
public function initials(): string
{
return Str::upper(Str::substr($this->first_name, 0, 1) . Str::substr($this->last_name, 0, 1));
}
}ProfileValidationRules Concern
File: app/Concerns/ProfileValidationRules.php
php
<?php
namespace App\Concerns;
use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Validation\Rule;
trait ProfileValidationRules
{
/**
* Get the validation rules used to validate user profiles.
*
* @return array<string, array<int, ValidationRule|array<mixed>|string>>
*/
protected function profileRules(?int $userId = null): array
{
return [
'first_name' => $this->nameRules(),
'last_name' => $this->nameRules(),
'email' => $this->emailRules($userId),
];
}
/**
* Get the validation rules used to validate user names.
*
* @return array<int, ValidationRule|array<mixed>|string>
*/
protected function nameRules(): array
{
return ['required', 'string', 'max:255'];
}
/**
* Get the validation rules used to validate user emails.
*
* @return array<int, ValidationRule|array<mixed>|string>
*/
protected function emailRules(?int $userId = null): array
{
return [
'required',
'string',
'email',
'max:255',
$userId === null
? Rule::unique(User::class)
: Rule::unique(User::class)->ignore($userId),
];
}
}In the baseline starter, ProfileUpdateRequest uses this concern and ProfileController fills validated data. No separate field rewrite needed in either file; recheck if the starter changes.
CreateNewUser Action
File: app/Actions/Fortify/CreateNewUser.php
php
<?php
namespace App\Actions\Fortify;
use App\Concerns\PasswordValidationRules;
use App\Concerns\ProfileValidationRules;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules, ProfileValidationRules;
/**
* Validate and create a newly registered user.
*
* @param array<string, string> $input
*/
public function create(array $input): User
{
Validator::make($input, [
...$this->profileRules(),
'password' => $this->passwordRules(),
])->validate();
return User::create([
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
'email' => $input['email'],
'password' => $input['password'],
]);
}
}User Factory
File: database/factories/UserFactory.php
php
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends Factory<User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'first_name' => fake()->firstName(),
'last_name' => fake()->lastName(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
'two_factor_secret' => null,
'two_factor_recovery_codes' => null,
'two_factor_confirmed_at' => null,
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn(array $attributes) => [
'email_verified_at' => null,
]);
}
/**
* Indicate that the model has two-factor authentication configured.
*/
public function withTwoFactor(): static
{
return $this->state(fn(array $attributes) => [
'two_factor_secret' => encrypt('secret'),
'two_factor_recovery_codes' => encrypt(json_encode(['recovery-code-1'])),
'two_factor_confirmed_at' => now(),
]);
}
}Seed Data
Update the generated seeder too. Step 04 replaces it with role-aware local users.
File: database/seeders/DatabaseSeeder.php
php
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'first_name' => 'Test',
'last_name' => 'User',
'email' => 'test@example.com',
]);
}
}User Type
Replace User.name with both fields. Keep the other exports: Auth, Passkey, and TwoFactorConfigContent.
File: resources/js/types/auth.ts
ts
export type User = {
id: number;
first_name: string;
last_name: string;
email: string;
avatar?: string;
two_factor_enabled?: boolean;
email_verified_at: string | null;
created_at: string;
updated_at: string;
[key: string]: unknown;
};
export type Auth = {
user: User;
};
export type Passkey = {
id: number;
name: string;
authenticator: string | null;
created_at_diff: string;
last_used_at_diff: string | null;
};
export type TwoFactorConfigContent = {
title: string;
description: string;
buttonText: string;
};Register Page
File: resources/js/pages/auth/Register.svelte
svelte
<script module lang="ts">
export const layout = {
title: 'Create an account',
description: 'Enter your details below to create your account',
};
</script>
<script lang="ts">
import { Form } from '@inertiajs/svelte';
import AppHead from '@/components/AppHead.svelte';
import InputError from '@/components/InputError.svelte';
import PasswordInput from '@/components/PasswordInput.svelte';
import TextLink from '@/components/TextLink.svelte';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Spinner } from '@/components/ui/spinner';
import { login } from '@/routes';
import { store } from '@/routes/register';
let { passwordRules }: { passwordRules: string } = $props();
</script>
<AppHead title="Register" />
<Form
{...store.form()}
resetOnSuccess={['password', 'password_confirmation']}
class="flex flex-col gap-6"
>
{#snippet children({ errors, processing })}
<div class="grid gap-6">
<div class="grid gap-2">
<Label for="first_name">First Name</Label>
<Input
id="first_name"
type="text"
required
autocomplete="given-name"
name="first_name"
placeholder="First name"
/>
<InputError message={errors.first_name} />
</div>
<div class="grid gap-2">
<Label for="last_name">Last Name</Label>
<Input
id="last_name"
type="text"
required
autocomplete="family-name"
name="last_name"
placeholder="Last name"
/>
<InputError message={errors.last_name} />
</div>
<div class="grid gap-2">
<Label for="email">Email address</Label>
<Input
id="email"
type="email"
required
autocomplete="email"
name="email"
placeholder="email@example.com"
/>
<InputError message={errors.email} />
</div>
<div class="grid gap-2">
<Label for="password">Password</Label>
<PasswordInput
id="password"
required
autocomplete="new-password"
name="password"
placeholder="Password"
passwordrules={passwordRules}
/>
<InputError message={errors.password} />
</div>
<div class="grid gap-2">
<Label for="password_confirmation">Confirm password</Label>
<PasswordInput
id="password_confirmation"
required
autocomplete="new-password"
name="password_confirmation"
placeholder="Confirm password"
passwordrules={passwordRules}
/>
<InputError message={errors.password_confirmation} />
</div>
<Button
type="submit"
class="mt-2 w-full"
disabled={processing}
data-test="register-user-button"
>
{#if processing}<Spinner />{/if}
Create account
</Button>
</div>
<div class="text-center text-sm text-muted-foreground">
Already have an account?
<TextLink href={login()} class="underline underline-offset-4">
Log in
</TextLink>
</div>
{/snippet}
</Form>Profile Settings Page
File: resources/js/pages/settings/Profile.svelte
svelte
<script module lang="ts">
import { edit } from '@/routes/profile';
export const layout = {
breadcrumbs: [
{
title: 'Profile settings',
href: edit(),
},
],
};
</script>
<script lang="ts">
import { Form, page } from '@inertiajs/svelte';
import ProfileController from '@/actions/App/Http/Controllers/Settings/ProfileController';
import AppHead from '@/components/AppHead.svelte';
import DeleteUser from '@/components/DeleteUser.svelte';
import Heading from '@/components/Heading.svelte';
import InputError from '@/components/InputError.svelte';
import TextLink from '@/components/TextLink.svelte';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { send } from '@/routes/verification';
const user = $derived(page.props.auth.user);
</script>
<AppHead title="Profile settings" />
<h1 class="sr-only">Profile settings</h1>
<div class="flex flex-col space-y-6">
<Heading
variant="small"
title="Profile"
description="Update your name and email address"
/>
<Form
{...ProfileController.update.form()}
class="space-y-6"
options={{ preserveScroll: true }}
>
{#snippet children({ errors, processing })}
<div class="grid gap-2">
<Label for="first_name">First Name</Label>
<Input
id="first_name"
name="first_name"
class="mt-1 block w-full"
value={user.first_name}
required
autocomplete="given-name"
placeholder="First name"
/>
<InputError class="mt-2" message={errors.first_name} />
</div>
<div class="grid gap-2">
<Label for="last_name">Last Name</Label>
<Input
id="last_name"
name="last_name"
class="mt-1 block w-full"
value={user.last_name}
required
autocomplete="family-name"
placeholder="Last name"
/>
<InputError class="mt-2" message={errors.last_name} />
</div>
<div class="grid gap-2">
<Label for="email">Email address</Label>
<Input
id="email"
type="email"
name="email"
class="mt-1 block w-full"
value={user.email}
required
autocomplete="username"
placeholder="Email address"
/>
<InputError class="mt-2" message={errors.email} />
</div>
{#if Boolean(page.props.mustVerifyEmail) && !user.email_verified_at}
<div>
<p class="-mt-4 text-sm text-muted-foreground">
Your email address is unverified.
<TextLink href={send()} as="button">
Click here to re-send the verification email.
</TextLink>
</p>
{#if page.props.status === 'verification-link-sent'}
<div class="mt-2 text-sm font-medium text-green-600">
A new verification link has been sent to your email
address.
</div>
{/if}
</div>
{/if}
<div class="flex items-center gap-4">
<Button
type="submit"
disabled={processing}
data-test="update-profile-button">Save</Button
>
</div>
{/snippet}
</Form>
</div>
<DeleteUser />UserInfo Component
File: resources/js/components/UserInfo.svelte
svelte
<script lang="ts">
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/components/ui/avatar';
import { getInitials } from '@/lib/initials';
import type { User } from '@/types';
let {
user,
showEmail = false,
}: {
user: User;
showEmail?: boolean;
} = $props();
const showAvatar = $derived(user.avatar && user.avatar !== '');
</script>
<Avatar class="h-8 w-8 overflow-hidden rounded-lg">
{#if showAvatar}
<AvatarImage src={user.avatar!} alt={user.first_name + ' ' + user.last_name} />
{/if}
<AvatarFallback class="rounded-lg text-black dark:text-white">
{getInitials(user.first_name + ' ' + user.last_name)}
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">{user.first_name + ' ' + user.last_name}</span>
{#if showEmail}
<span class="truncate text-xs text-muted-foreground">{user.email}</span>
{/if}
</div>AppHeader Avatar
Update the avatar alt text and initials. Keep any navigation changes.
File: resources/js/components/AppHeader.svelte
svelte
<script lang="ts">
import { Link, page } from '@inertiajs/svelte';
import BookOpen from '@lucide/svelte/icons/book-open';
import Folder from '@lucide/svelte/icons/folder';
import LayoutGrid from '@lucide/svelte/icons/layout-grid';
import Menu from '@lucide/svelte/icons/menu';
import Search from '@lucide/svelte/icons/search';
import AppLogo from '@/components/AppLogo.svelte';
import AppLogoIcon from '@/components/AppLogoIcon.svelte';
import Breadcrumbs from '@/components/Breadcrumbs.svelte';
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuList,
navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import UserMenuContent from '@/components/UserMenuContent.svelte';
import { currentUrlState } from '@/lib/currentUrl.svelte';
import { getInitials } from '@/lib/initials';
import { toUrl } from '@/lib/utils';
import { dashboard } from '@/routes';
import type { BreadcrumbItem, NavItem } from '@/types';
let {
breadcrumbs = [],
}: {
breadcrumbs?: BreadcrumbItem[];
} = $props();
const auth = $derived(page.props.auth);
const url = currentUrlState();
const activeItemStyles =
'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100';
const mainNavItems: NavItem[] = [
{
title: 'Dashboard',
href: dashboard(),
icon: LayoutGrid,
},
];
const rightNavItems: NavItem[] = [
{
title: 'Repository',
href: 'https://github.com/laravel/svelte-starter-kit',
icon: Folder,
},
{
title: 'Documentation',
href: 'https://laravel.com/docs/starter-kits#svelte',
icon: BookOpen,
},
];
</script>
<div>
<div class="border-b border-sidebar-border/80">
<div class="mx-auto flex h-16 items-center px-4 md:max-w-7xl">
<!-- Mobile Menu -->
<div class="lg:hidden">
<Sheet>
<SheetTrigger asChild>
{#snippet children(props)}
<Button
variant="ghost"
size="icon"
class="mr-2 h-9 w-9"
onclick={props.onclick}
aria-expanded={props['aria-expanded']}
>
<Menu class="h-5 w-5" />
</Button>
{/snippet}
</SheetTrigger>
<SheetContent side="left" class="w-[300px] p-6">
<SheetTitle class="sr-only">Navigation menu</SheetTitle>
<SheetHeader class="flex justify-start text-left">
<AppLogoIcon
class="size-6 fill-current text-black dark:text-white"
/>
</SheetHeader>
<div
class="flex h-full flex-1 flex-col justify-between space-y-4 pt-6 pb-10"
>
<nav class="-mx-3 space-y-1">
{#each mainNavItems as item (toUrl(item.href))}
<Link
href={toUrl(item.href)}
class="flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-accent {url.whenCurrentUrl(
item.href,
url.currentUrl,
activeItemStyles,
'',
) ?? ''}"
>
{#if item.icon}
<item.icon class="h-5 w-5" />
{/if}
{item.title}
</Link>
{/each}
</nav>
<div class="flex flex-col space-y-4">
{#each rightNavItems as item (toUrl(item.href))}
<a
href={toUrl(item.href)}
target="_blank"
rel="noopener noreferrer"
class="flex items-center space-x-2 text-sm font-medium"
>
{#if item.icon}
<item.icon class="h-5 w-5" />
{/if}
<span>{item.title}</span>
</a>
{/each}
</div>
</div>
</SheetContent>
</Sheet>
</div>
<Link href={toUrl(dashboard())} class="flex items-center gap-x-2">
<AppLogo />
</Link>
<!-- Desktop Menu -->
<div class="hidden h-full lg:flex lg:flex-1">
<NavigationMenu class="ml-10 flex h-full items-stretch">
<NavigationMenuList
class="flex h-full items-stretch space-x-2"
>
{#each mainNavItems as item (toUrl(item.href))}
<NavigationMenuItem
class="relative flex h-full items-center"
>
<Link
class="{navigationMenuTriggerStyle()} {url.whenCurrentUrl(
item.href,
url.currentUrl,
activeItemStyles,
'',
) ?? ''} h-9 cursor-pointer px-4"
href={toUrl(item.href)}
>
{#if item.icon}
<item.icon class="mr-2 h-4 w-4" />
{/if}
{item.title}
</Link>
{#if url.isCurrentUrl(item.href, url.currentUrl)}
<div
class="absolute bottom-0 left-0 h-0.5 w-full translate-y-px bg-black dark:bg-white"
></div>
{/if}
</NavigationMenuItem>
{/each}
</NavigationMenuList>
</NavigationMenu>
</div>
<div class="ml-auto flex items-center space-x-2">
<div class="relative flex items-center space-x-1">
<Button
variant="ghost"
size="icon"
class="group h-9 w-9 cursor-pointer"
>
<Search
class="size-5 opacity-80 group-hover:opacity-100"
/>
</Button>
<div class="hidden space-x-1 lg:flex">
{#each rightNavItems as item (toUrl(item.href))}
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger>
{#snippet child({ props })}
<a
href={toUrl(item.href)}
target="_blank"
rel="noopener noreferrer"
{...props}
class="inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground h-9 w-9 group cursor-pointer"
>
<span class="sr-only"
>{item.title}</span
>
<item.icon
class="size-5 opacity-80 group-hover:opacity-100"
/>
</a>
{/snippet}
</TooltipTrigger>
<TooltipContent>
<p>{item.title}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
{/each}
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
{#snippet children(props)}
<Button
variant="ghost"
size="icon"
class="relative size-10 w-auto rounded-full p-1 focus-within:ring-2 focus-within:ring-primary"
onclick={props.onclick}
aria-expanded={props['aria-expanded']}
data-state={props['data-state']}
>
<Avatar
class="size-8 overflow-hidden rounded-full"
>
{#if auth.user?.avatar}
<AvatarImage
src={auth.user.avatar}
alt={(auth.user?.first_name ?? '') + ' ' + (auth.user?.last_name ?? '')}
/>
{/if}
<AvatarFallback
class="rounded-lg bg-neutral-200 font-semibold text-black dark:bg-neutral-700 dark:text-white"
>
{getInitials((auth.user?.first_name ?? '') + ' ' + (auth.user?.last_name ?? ''))}
</AvatarFallback>
</Avatar>
</Button>
{/snippet}
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-56">
{#if auth.user}
<UserMenuContent user={auth.user} />
{/if}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
{#if breadcrumbs.length > 1}
<div class="flex w-full border-b border-sidebar-border/70">
<div
class="mx-auto flex h-12 w-full items-center justify-start px-4 text-neutral-500 md:max-w-7xl"
>
<Breadcrumbs {breadcrumbs} />
</div>
</div>
{/if}
</div>Update Tests
Keep the starter's feature guards and any project-specific assertions.
Registration
Send both name fields. Keep the generated skipUnlessFortifyHas helper in the test setup.
File: tests/Feature/Auth/RegistrationTest.php
php
<?php
use Laravel\Fortify\Features;
beforeEach(function () {
$this->skipUnlessFortifyHas(Features::registration());
});
test('registration screen can be rendered', function () {
$response = $this->get(route('register'));
$response->assertOk();
});
test('new users can register', function () {
$response = $this->post(route('register.store'), [
'first_name' => 'Test',
'last_name' => 'User',
'email' => 'test@example.com',
'password' => 'password',
'password_confirmation' => 'password',
]);
$this->assertAuthenticated();
$response->assertRedirect(route('dashboard', absolute: false));
});Profile Updates
Submit and assert both fields; retain email-verification and account-deletion checks.
File: tests/Feature/Settings/ProfileUpdateTest.php
php
<?php
use App\Models\User;
test('profile page is displayed', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->get(route('profile.edit'));
$response->assertOk();
});
test('profile information can be updated', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->patch(route('profile.update'), [
'first_name' => 'Test',
'last_name' => 'User',
'email' => 'test@example.com',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('profile.edit'));
$user->refresh();
expect($user->first_name)->toBe('Test');
expect($user->last_name)->toBe('User');
expect($user->email)->toBe('test@example.com');
expect($user->email_verified_at)->toBeNull();
});
test('email verification status is unchanged when the email address is unchanged', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->patch(route('profile.update'), [
'first_name' => 'Test',
'last_name' => 'User',
'email' => $user->email,
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('profile.edit'));
expect($user->refresh()->email_verified_at)->not->toBeNull();
});
test('user can delete their account', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->delete(route('profile.destroy'), [
'password' => 'password',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('home'));
$this->assertGuest();
expect($user->fresh())->toBeNull();
});
test('correct password must be provided to delete account', function () {
$user = User::factory()->create();
$response = $this
->actingAs($user)
->from(route('profile.edit'))
->delete(route('profile.destroy'), [
'password' => 'wrong-password',
]);
$response
->assertSessionHasErrors('password')
->assertRedirect(route('profile.edit'));
expect($user->fresh())->not->toBeNull();
});Step 04 adds RoleSeeder to registration tests.
Verify
Find remaining user-name references. Leave unrelated name fields alone, including passkey labels and the contact form.
bash
rg -n "name|first_name|last_name" tests database/seeders app
rg -n 'user\??\.name' resources/jsbash
composer lint
composer ci:check
npm run build
php artisan migrate:fresh --seedRegister, update both names in Settings, and check the avatar initials and passkey display name.
Commit
bash
git add -A
git commit -m '03 - User Name Split'