Display applications in admin interface
This commit is contained in:
parent
a66c65e38a
commit
5a463656a7
44
app/Http/Middleware/IsAdmin.php
Normal file
44
app/Http/Middleware/IsAdmin.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class IsAdmin
|
||||
{
|
||||
/**
|
||||
* The Guard implementation.
|
||||
*
|
||||
* @var Guard
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* Create a new filter instance.
|
||||
*
|
||||
* @param Guard $auth
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Guard $auth)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!$this->auth->user()->is_admin) {
|
||||
session()->flash('error_msg', 'This resource is restricted to Administrators!');
|
||||
return redirect()->route('home');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
39
app/Livewire/ApplicationList.php
Normal file
39
app/Livewire/ApplicationList.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class ApplicationList extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $hideApproved = true;
|
||||
|
||||
public function updatingHideApproved()
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function approveApplication($uuid)
|
||||
{
|
||||
Application::where('uuid', $uuid)->update(['status' => 1]);
|
||||
}
|
||||
|
||||
public function rejectApplication($uuid)
|
||||
{
|
||||
Application::where('uuid', $uuid)->update(['status' => 2]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$applications = Application::with('uploads')
|
||||
->when($this->hideApproved !== false, fn($query) => $query->where('status', 0))
|
||||
->latest()
|
||||
->paginate(10);
|
||||
|
||||
return view('livewire.application-list', compact('applications'));
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ return new class extends Migration
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->boolean('is_admin')->default(false);
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
@ -7,28 +7,14 @@
|
||||
<flux:sidebar sticky stashable class="border-e border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:sidebar.toggle class="lg:hidden" icon="x-mark" />
|
||||
|
||||
<a href="{{ route('dashboard') }}" class="me-5 flex items-center space-x-2 rtl:space-x-reverse" wire:navigate>
|
||||
<x-app-logo />
|
||||
</a>
|
||||
|
||||
<flux:navlist variant="outline">
|
||||
<flux:navlist.group :heading="__('Platform')" class="grid">
|
||||
<flux:navlist.item icon="home" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>{{ __('Dashboard') }}</flux:navlist.item>
|
||||
<flux:navlist.item icon="document-text" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>{{ __('Applications') }}</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist>
|
||||
|
||||
<flux:spacer />
|
||||
|
||||
<flux:navlist variant="outline">
|
||||
<flux:navlist.item icon="folder-git-2" href="https://github.com/laravel/livewire-starter-kit" target="_blank">
|
||||
{{ __('Repository') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item icon="book-open-text" href="https://laravel.com/docs/starter-kits#livewire" target="_blank">
|
||||
{{ __('Documentation') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist>
|
||||
|
||||
<!-- Desktop User Menu -->
|
||||
<flux:dropdown position="bottom" align="start">
|
||||
<flux:profile
|
||||
|
@ -1,24 +1,3 @@
|
||||
<x-layouts.app :title="__('Applications')">
|
||||
<div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
|
||||
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
|
||||
<x-placeholder-pattern
|
||||
class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
|
||||
</div>
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
|
||||
<x-placeholder-pattern
|
||||
class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
|
||||
</div>
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
|
||||
<x-placeholder-pattern
|
||||
class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
|
||||
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
|
||||
</div>
|
||||
</div>
|
||||
@livewire('application-list')
|
||||
</x-layouts.app>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<flux:heading>Your application has been submitted</flux:heading>
|
||||
<flux:text class="mt-2">
|
||||
You can check its status here: <flux:link
|
||||
href="{{ route('application.status', ['uuid' => $applicationUuid]) }}">View Application Status
|
||||
href="{{ route('application', ['uuid' => $applicationUuid]) }}">View Application Status
|
||||
</flux:link>
|
||||
</flux:text>
|
||||
<flux:text class="mt-2">
|
||||
|
69
resources/views/livewire/application-list.blade.php
Normal file
69
resources/views/livewire/application-list.blade.php
Normal file
@ -0,0 +1,69 @@
|
||||
<div>
|
||||
<div class="max-w-5xl mx-auto p-6">
|
||||
<h1 class="text-2xl font-bold mb-4">Application Management</h1>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" wire:model.live="hideApproved" class="mr-2">
|
||||
Hide approved applications
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<table class="w-full border-collapse border border-gray-300 dark:border-neutral-950 rounded-lg">
|
||||
<thead class="bg-gray-100 dark:bg-neutral-900">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left">UUID</th>
|
||||
<th class="px-4 py-2">Status</th>
|
||||
<th class="px-4 py-2">Uploads</th>
|
||||
<th class="px-4 py-2">Submitted</th>
|
||||
<th class="px-4 py-2">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($applications as $app)
|
||||
<tr>
|
||||
<td class="px-4 py-2 font-mono text-sm">{{ $app->uuid }}</td>
|
||||
<td class="px-4 py-2">
|
||||
@if ($app->status === 0)
|
||||
<span class="text-yellow-600 font-semibold">Pending</span>
|
||||
@elseif ($app->status === 1)
|
||||
<span class="text-green-600 font-semibold">Approved</span>
|
||||
@elseif ($app->status === 2)
|
||||
<span class="text-red-600 font-semibold">Denied</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center">{{ $app->uploads->count() }}</td>
|
||||
<td class="px-4 py-2 text-sm">{{ $app->created_at->diffForHumans() }}</td>
|
||||
<td class="px-4 py-2">
|
||||
<flux:button size="sm" variant="primary"
|
||||
href="{{ route('application', ['uuid' => $app->uuid]) }}">
|
||||
View
|
||||
</flux:button>
|
||||
|
||||
@if ($app->status === 0)
|
||||
<flux:button class="cursor-pointer" size="sm"
|
||||
wire:click="approveApplication('{{ (string) $app->uuid }}')">
|
||||
Approve
|
||||
</flux:button>
|
||||
<flux:button class="cursor-pointer" size="sm" variant="danger"
|
||||
wire:click="rejectApplication('{{ (string) $app->uuid }}')">
|
||||
Reject
|
||||
</flux:button>
|
||||
@endif
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-gray-500">No applications found.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $applications->links() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -1,34 +1,41 @@
|
||||
<div wire:poll.10s="loadApplication" class="max-w-2xl mx-auto mt-10 flex flex-col gap-6">
|
||||
<div class="max-w-2xl mx-auto mt-10">
|
||||
<div class="p-5 mb-4 dark:bg-pink-950/50 rounded-lg">
|
||||
<flux:heading size="xl">Application Status</flux:heading>
|
||||
@php
|
||||
$status = [
|
||||
0 => 'Pending Review',
|
||||
1 => 'Approved',
|
||||
2 => 'Denied',
|
||||
];
|
||||
@endphp
|
||||
<flux:text class="mt-2 text-lg">
|
||||
<p>
|
||||
<strong>Status:</strong>
|
||||
{{ $status[$application->status] }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Created:</strong>
|
||||
{{ Carbon\Carbon::parse($application->created_at)->diffForHumans(['parts' => 2]) }}
|
||||
</p>
|
||||
</flux:text>
|
||||
</div>
|
||||
<div class="max-w-2xl md:w-2xl mx-auto mt-10">
|
||||
@if ($application->status === 0)
|
||||
<div class="p-5 mb-4 dark:bg-pink-950/50 rounded-lg">
|
||||
@elseif ($application->status === 1)
|
||||
<div class="p-5 mb-4 dark:bg-green-950/50 rounded-lg">
|
||||
@elseif ($application->status === 2)
|
||||
<div class="p-5 mb-4 dark:bg-red-950/50 rounded-lg">
|
||||
@endif
|
||||
|
||||
<h2 class="mt-4 font-semibold">Uploaded Photos:</h2>
|
||||
<div class="flex flex-wrap gap-4 mt-4">
|
||||
@foreach ($application->uploads as $upload)
|
||||
<div>
|
||||
<img class="h-48 w-fit object-cover rounded-2xl hover:-translate-y-1 hover:scale-110 transition duration-300 ease-in-out"
|
||||
src="{{ asset('storage/' . $upload->file_path) }}">
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<flux:heading size="xl">Application Status</flux:heading>
|
||||
@php
|
||||
$status = [
|
||||
0 => 'Pending Review',
|
||||
1 => 'Approved',
|
||||
2 => 'Denied',
|
||||
];
|
||||
@endphp
|
||||
<flux:text class="mt-2">
|
||||
<p>
|
||||
<strong>Status:</strong>
|
||||
{{ $status[$application->status] }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Created:</strong>
|
||||
{{ Carbon\Carbon::parse($application->created_at)->diffForHumans(['parts' => 2]) }}
|
||||
</p>
|
||||
</flux:text>
|
||||
</div>
|
||||
|
||||
<h2 class="mt-4 font-semibold">Uploaded Photos:</h2>
|
||||
<div class="flex flex-wrap gap-4 mt-4">
|
||||
@foreach ($application->uploads as $upload)
|
||||
<div>
|
||||
<img class="h-48 w-fit object-cover rounded-2xl hover:-translate-y-1 hover:scale-110 transition duration-300 ease-in-out"
|
||||
src="{{ asset('storage/' . $upload->file_path) }}">
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ Route::get('/', App\Livewire\ApplicationForm::class)->name('home');
|
||||
Route::get('/application-status/{uuid}', App\Livewire\ApplicationStatus::class)->name('application');
|
||||
|
||||
Route::view('dashboard', 'dashboard')
|
||||
->middleware(['auth', 'verified'])
|
||||
->middleware(['auth', 'verified', App\Http\Middleware\IsAdmin::class])
|
||||
->name('dashboard');
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user