This is a Laravel 8 project and I'm in the process if building the user management module. I have name, email, phone, password and role. I would like to validate the phone field if there is a value otherwise it's nullable. Similarly, with the password field. require when creating a new user. ignore it when in edit mode and password is not to be changed.
Here's my backend code:
namespace App\Http\Livewire\Admin;
use App\Models\User;
use Hash;
use Livewire\Component;
use Livewire\WithPagination;
use Log;
use Spatie\Permission\Models\Role;
class Users extends Component
{
use WithPagination;
public int $currentPage = 1;
public int $perPage = 10;
public string $success = '';
public bool $showModal = false;
public $authUser;
public $userId;
public $user;
public $name;
public $phone;
public $email;
public $password;
public $password_confirmation;
public $roles = [];
public $selectedRole;
protected $rules = [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required_if:exists()|confirmed|min:8',
'selectedRole' => 'required|string',
];
public function mount(): void
{
$this->roles = Role::all();
$this->authUser = Auth()->user();
}
public function updated(string $propertyName): void
{
$this->validateOnly($propertyName, $this->rules);
}
public function create(): void
{
$this->showModal = true;
$this->resetErrorBag();
$this->resetValidation();
$this->user = null;
$this->userId = null;
}
/**
* @throws \JsonException
*/
public function edit($userId): void
{
try {
$this->showModal = true;
$this->userId = $userId;
$user = User::findOrFail($userId);
$this->name = $user->name;
$this->email = $user->email;
$this->phone = $user->phone;
$this->selectedRole = $user->selectedRole;
} catch (\Exception $ex) {
$error = sprintf('[%s],[%d] ERROR:[%s]', __METHOD__, __LINE__,
json_encode($ex->getMessage(), JSON_THROW_ON_ERROR | true));
Log::error($error);
}
}
public function store(): void
{
$validatedData = $this->validate($this->rules);
$validatedData['password'] = Hash::make($validatedData['password']);
$user = User::updateOrcreate(['id' => $this->userId], $validatedData);
$user->assignRole($validatedData['selectedRole']);
$this->reset();
$this->resetValidation();
$updateOrCreateVerb = ($this->userId) ? 'updated' : 'created';
$this->success = "User {$user->name} was {$updateOrCreateVerb} successfully!";
$this->close();
}
public function show($userId): void
{
$this->reset();
$this->showModal = true;
$this->userId = $userId;
$this->user = User::find($userId);
}
public function delete($userId): void
{
try {
$user = User::find($userId);
if ($user) {
$user->delete();
}
} catch (\Exception $ex) {
Log::error($ex->getMessage());
}
}
public function close(): void
{
$this->showModal = false;
}
public function resetSuccess(): void
{
$this->reset('success');
}
public function render()
{
return view('livewire.admin.users', [
'roles' => $this->roles,
'users' => User::with('roles')->latest()->orderBy('id', 'ASC')->paginate($this->perPage),
]);
}
}
Here's my view code:
<div>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('User Management') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="inline-block mb-5">
<button wire:click.prevent="create" type="button" class="bg-indigo-500 hover:bg-indigo-600 text-white text-sm
py-2.5 px-5 rounded-md transition duration-500 ease-in-out transform hover:-translate-y-1 shadow-l">
Create New User
</button>
</div>
<div class="inline-block min-w-full shadow overflow-hidden">
<table class="min-w-full leading-normal">
<thead>
<tr>
<th class="px-3 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
{{ __('Name') }}
</th>
<th class="px-3 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
{{ __('Email') }}
</th>
<th class="px-3 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
{{ __('Phone') }}
</th>
<th class="px-3 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
{{ __('Roles') }}
</th>
<th class="px-5 py-3 border-b-2 border-black bg-black text-left text-xs font-semibold text-white uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody>
@forelse ($users as $user)
<tr>
<td class="px-5 py-2 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif">
{{ $user->name }}
</td>
<td class="px-5 py-2 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif">
{{ $user->email }}
</td>
<td class="px-5 py-2 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif">
{{ $user->phone }}
</td>
<td class="px-5 py-2 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif">
@forelse($user->getRoleNames() as $roleName)
<span class="inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-green-100 bg-green-600 rounded-full">
{{ $roleName }}
</span>
@empty
{{ __('No Roles Found') }}
@endforelse
</td>
<td class="px-5 py-2 bg-white text-sm @if (!$loop->last) border-gray-200 border-b @endif text-right">
<div class="inline-block whitespace-no-wrap">
<button wire:click.prevent="show({{ $user->id }})" type="button" class="bg-green-500 hover:bg-green-700 text-white font-bold px-4 py-1 rounded">
<x-icons.eyeball/>
</button>
<button wire:click.prevent="edit({{ $user->id }})" type="button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 py-1 rounded">
<x-icons.editpad />
</button>
<button wire:click.prevent="$emit('triggerDelete',{{ $user }})" class="bg-red-500 hover:bg-red-700 text-white font-bold px-4 py-1 rounded">
<x-icons.trashcan />
</button>
</div>
</td>
</tr>
@empty
<tr>
<td>
{{ __('No Users Found') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{ $users->links() }}
</div>
</div>
<div class="@if (!$showModal) hidden @endif flex items-center justify-center fixed left-0 bottom-0 w-full h-full bg-gray-800 bg-opacity-90">
{{-- Modal --}}
<div class="bg-white rounded-lg w-2/5">
@if ($errors->any())
<div class="relative m-4 px-4 py-3 text-sm bg-red-100 border border-red-400 text-red-700 rounded" role="alert">
<strong class="font-bold">Oops!</strong><span class="block sm:inline">There are some errors with your submission.</span>
</div>
@endif
@if ($success)
<div class="relative m-4 px-4 py-3 text-sm bg-green-100 border border-green-400 text-green-700 rounded" role="alert">
<span class="block sm:inline">{{ $success }}</span>
<span wire:click="resetSuccess" class="absolute top-0 bottom-0 right-0 px-4 py-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</span>
</div>
@endif
<form wire:submit.prevent="store">
@csrf
<div class="flex flex-col items-start p-4">
<div class="flex items-center w-full mb-4">
<div class="text-gray-900 font-medium text-lg">{{ $userId ? 'Edit User' : 'Create New User' }}</div>
<svg wire:click="close" class="ml-auto fill-current text-gray-700 w-6 h-6 cursor-pointer" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
<path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"/>
</svg>
</div>
<div class="w-full">
<label class="block font-semibold text-sm text-gray-700" for="name">Name</label>
<input wire:model.defer="name" id="name" name="name" type="text"
class="text-sm sm:text-base rounded-lg border border-gray-400 w-full pl-2 pr-4 py-2 mb-2 focus:outline-none focus:border-blue-400"
autocomplete="name"/>
@error('name') <span class="text-xs text-red-500 mt-1">{{ $message }}</span> @enderror
</div>
<div class="w-full">
<div class="flex flex-wrap -mx-3 sm:-mx-4 md:-mx-4 lg:-mx-4 xl:-mx-4 overflow-hidden">
<div class="my-2 px-3 w-1/2 sm:my-2 sm:px-4 sm:w-1/2 md:my-2 md:px-4 md:w-1/2 lg:my-2 lg:px-3 lg:w-1/2 xl:my-2 xl:px-4 xl:w-1/2 overflow-hidden">
<label class="block font-semibold text-sm text-gray-700" for="email">Email</label>
<input wire:model.defer="email" id="email" name="email" type="email"
class="text-sm sm:text-base rounded-lg border border-gray-400 w-full pl-2 pr-4 py-2 focus:outline-none focus:border-blue-400"
autocomplete="email"/>
@error('email') <span class="text-xs text-red-500 mt-1">{{ $message }}</span> @enderror
</div>
<div class="my-2 px-3 w-1/2 sm:my-2 sm:px-2 sm:w-1/2 md:my-2 md:px-4 md:w-1/2 lg:my-2 lg:px-3 lg:w-1/2 xl:my-2 xl:px-4 xl:w-1/2 overflow-hidden">
<label class="block font-semibold text-sm text-gray-700" for="phone">Phone</label>
<input wire:model.defer="phone" id="phone" name="guard_name" type="tel"
class="text-sm sm:text-base rounded-lg border border-gray-400 w-full pl-2 pr-4 py-2 focus:outline-none focus:border-blue-400"/>
@error('phone') <span class="text-xs text-red-500 mt-1">{{ $message }}</span> @enderror
</div>
</div>
</div>
<div class="w-full">
<div class="flex flex-wrap -mx-3 sm:-mx-4 md:-mx-4 lg:-mx-4 xl:-mx-4 overflow-hidden">
<div class="my-2 px-3 w-1/2 sm:my-2 sm:px-4 sm:w-1/2 md:my-2 md:px-4 md:w-1/2 lg:my-2 lg:px-3 lg:w-1/2 xl:my-2 xl:px-4 xl:w-1/2 overflow-hidden">
<label class="block font-semibold text-sm text-gray-700" for="password">Password</label>
<input wire:model.defer="password" id="password" name="password" type="password"
class="text-sm sm:text-base rounded-lg border border-gray-400 w-full pl-2 pr-4 py-2 focus:outline-none focus:border-blue-400"/>
@error('password') <span class="text-xs text-red-500 mt-1">{{ $message }}</span> @enderror
</div>
<div class="my-2 px-3 w-1/2 sm:my-2 sm:px-4 sm:w-1/2 md:my-2 md:px-4 md:w-1/2 lg:my-2 lg:px-3 lg:w-1/2 xl:my-2 xl:px-4 xl:w-1/2 overflow-hidden">
<label class="block font-semibold text-sm text-gray-700" for="password_confirmation">Password Confirmation</label>
<input wire:model.defer="password_confirmation" id="password_confirmation" name="password_confirmation" type="password"
class="text-sm sm:text-base rounded-lg border border-gray-400 w-full pl-2 pr-4 py-2 focus:outline-none focus:border-blue-400"/>
@error('password_confirmation') <span class="text-xs text-red-500 mt-1">{{ $message }}</span> @enderror
</div>
</div>
</div>
<div class="w-full">
<label class="block mt-4 mb-2 font-bold text-sm text-gray-700" for="roles">Roles</label>
<div class="grid grid-cols-5 grid-rows-2 gap-1 lg:grid-cols-5">
@foreach($roles as $role)
<label for="roles">
<input wire:model.defer="selectedRole" id="selectedRole" name=selectedRole" class="form-checkbox" type="radio" value="{{ $role->name }}" @if ( old('selectedRole', $this->selectedRole)) checked @endif>
<span class="ml-2 font-bold">{{ $role->name }}</span>
</label>
@endforeach
</div>
@error('roles') <span class="text-xs text-red-500 mt-1">{{ $message }}</span> @enderror
</div>
<div class="ml-auto mt-5">
<button wire:click="close" type="button" class="bg-gray-500 text-white font-bold py-2 px-4 rounded" data-dismiss="modal">
Close
</button>
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
{{ $userId ? 'Save Changes' : 'Save' }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@10/dist/sweetalert2.min.css">
@endpush
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
@this.on('triggerDelete', userId => {
Swal.fire({
title: 'Are You Sure?',
text: 'User record will be deleted!',
type: "warning",
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Delete!'
}).then((result) => {
if (result.value) {
@this.call('delete',userId)
} else {
console.log("Canceled");
}
});
});
})
</script>
@endpush