Compare commits

..

No commits in common. "62f4d090268da9a1e34a20624dfcf7d25bfcfe79" and "249a3c86b4ec1a8cba5cdf100cb92615b0d85302" have entirely different histories.

18 changed files with 2 additions and 234 deletions

View File

@ -1,32 +0,0 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
class AuthenticateUser
{
/**
* Resolve the user attempting to log in, rejecting inactive accounts.
*/
public function __invoke(Request $request): ?User
{
$user = User::where(Fortify::username(), $request->input(Fortify::username()))->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return null;
}
if (! $user->is_active) {
throw ValidationException::withMessages([
Fortify::username() => 'Akun Anda tidak aktif. Silakan hubungi administrator.',
]);
}
return $user;
}
}

View File

@ -4,7 +4,6 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Users\AdministratorRequest;
use App\Http\Requests\Admin\Users\UpdateUserStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\User;
use App\Services\Admin\Users\AdministratorService;
@ -78,11 +77,4 @@ public function resetPassword(User $user): RedirectResponse
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back();
}
public function updateUserStatus(UpdateUserStatusRequest $request, User $user): RedirectResponse
{
$this->service->updateUserStatus($user, $request->boolean('is_active'));
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status akun berhasil diperbarui.'])->back();
}
}

View File

@ -5,7 +5,6 @@
use App\Exports\LecturersExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Users\LecturerRequest;
use App\Http\Requests\Admin\Users\UpdateUserStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\User;
use App\Services\Admin\Master\DepartmentService;
@ -85,13 +84,6 @@ public function resetPassword(User $user): RedirectResponse
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back();
}
public function updateUserStatus(UpdateUserStatusRequest $request, User $user): RedirectResponse
{
$this->service->updateUserStatus($user, $request->boolean('is_active'));
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status akun berhasil diperbarui.'])->back();
}
public function export(PaginatedRequest $request): BinaryFileResponse
{
return Excel::download(new LecturersExport(

View File

@ -7,7 +7,6 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Users\Student\StudentRequest;
use App\Http\Requests\Admin\Users\Student\StudentStatusRequest;
use App\Http\Requests\Admin\Users\UpdateUserStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\User;
use App\Services\Admin\Master\DepartmentService;
@ -100,13 +99,6 @@ public function updateStatus(StudentStatusRequest $request, User $user): Redirec
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status mahasiswa berhasil diperbarui.'])->back();
}
public function updateUserStatus(UpdateUserStatusRequest $request, User $user): RedirectResponse
{
$this->service->updateUserStatus($user, $request->boolean('is_active'));
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status akun berhasil diperbarui.'])->back();
}
public function export(PaginatedRequest $request): BinaryFileResponse
{
return Excel::download(new StudentsExport(

View File

@ -1,20 +0,0 @@
<?php
namespace App\Http\Requests\Admin\Users;
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserStatusRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'is_active' => ['required', 'boolean'],
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Providers;
use App\Actions\Fortify\AuthenticateUser;
use App\Actions\Fortify\ResetUserPassword;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
@ -31,7 +30,6 @@ public function boot(): void
private function configureActions(): void
{
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::authenticateUsing(new AuthenticateUser);
}
private function configureViews(): void

View File

@ -84,9 +84,4 @@ public function resetPassword(User $user): void
'password' => Hash::make(config('app.default_password')),
]);
}
public function updateUserStatus(User $user, bool $isActive): void
{
$user->update(['is_active' => $isActive]);
}
}

View File

@ -115,9 +115,4 @@ public function resetPassword(User $user): void
'password' => Hash::make(config('app.default_password')),
]);
}
public function updateUserStatus(User $user, bool $isActive): void
{
$user->update(['is_active' => $isActive]);
}
}

View File

@ -126,9 +126,4 @@ public function updateStatus(User $user, string $status): void
{
$user->student()->update(['status' => $status]);
}
public function updateUserStatus(User $user, bool $isActive): void
{
$user->update(['is_active' => $isActive]);
}
}

View File

@ -1,33 +0,0 @@
import * as React from "react"
import { Switch as SwitchPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Switch({
className,
size = "default",
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
size?: "sm" | "default"
}) {
return (
<SwitchPrimitive.Root
data-slot="switch"
data-size={size}
className={cn(
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
className
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
)}
/>
</SwitchPrimitive.Root>
)
}
export { Switch }

View File

@ -1,17 +0,0 @@
import { Switch } from '@/components/ui/switch';
type UserActiveSwitchProps = {
isActive: boolean;
onChange: (isActive: boolean) => void;
};
export function UserActiveSwitch({ isActive, onChange }: UserActiveSwitchProps) {
return (
<div className="flex items-center gap-2">
<Switch checked={isActive} onCheckedChange={onChange} />
<span className="text-sm text-muted-foreground">
{isActive ? 'Aktif' : 'Nonaktif'}
</span>
</div>
);
}

View File

@ -2,13 +2,11 @@ import type { ColumnDef } from '@tanstack/react-table';
import { Key, Pencil, Trash2 } from 'lucide-react';
import { GenderBadge } from '@/components/gender-badge';
import { RowActions } from '@/components/row-actions';
import { UserActiveSwitch } from '@/components/user-active-switch';
export type Administrator = {
id: number;
username: string;
email: string;
is_active: boolean;
profile: { full_name: string; phone_number: string; gender: string } | null;
roles: { id: number; name: string }[];
};
@ -17,18 +15,12 @@ type CreateColumnsParams = {
handleEdit: (admin: Administrator) => void;
handleDeleteClick: (admin: Administrator) => void;
handleResetPassword: (admin: Administrator) => void;
handleUserStatusChange: (admin: Administrator, isActive: boolean) => void;
};
export function createAdministratorColumns(
params: CreateColumnsParams,
): ColumnDef<Administrator>[] {
const {
handleEdit,
handleDeleteClick,
handleResetPassword,
handleUserStatusChange,
} = params;
const { handleEdit, handleDeleteClick, handleResetPassword } = params;
return [
{
@ -65,18 +57,6 @@ export function createAdministratorColumns(
<GenderBadge gender={row.original.profile?.gender} />
),
},
{
accessorKey: 'is_active',
header: () => <span>Status Akun</span>,
cell: ({ row }) => (
<UserActiveSwitch
isActive={row.original.is_active}
onChange={(isActive) =>
handleUserStatusChange(row.original, isActive)
}
/>
),
},
{
id: 'actions',
header: () => <span className="block text-center">Aksi</span>,

View File

@ -16,7 +16,6 @@ import {
destroy,
edit,
reset_password,
update_user_status,
} from '@/routes/admin/users/administrators';
import type { Administrator } from './columns';
import { createAdministratorColumns } from './columns';
@ -92,21 +91,12 @@ export default function AdministratorIndex({ administrators, filters }: Props) {
);
}
function handleUserStatusChange(admin: Administrator, isActive: boolean) {
router.patch(
update_user_status.url(admin.id),
{ is_active: isActive },
{ preserveScroll: true },
);
}
const columns = createAdministratorColumns({
handleEdit: (admin) => {
router.get(edit.url(admin.id));
},
handleDeleteClick: (admin) => setDeleting(admin),
handleResetPassword: (admin) => setResetting(admin),
handleUserStatusChange,
});
return (

View File

@ -2,13 +2,11 @@ import type { ColumnDef } from '@tanstack/react-table';
import { Key, Pencil, Trash2 } from 'lucide-react';
import { GenderBadge } from '@/components/gender-badge';
import { RowActions } from '@/components/row-actions';
import { UserActiveSwitch } from '@/components/user-active-switch';
export type Lecturer = {
id: number;
username: string;
email: string;
is_active: boolean;
profile: { full_name: string; phone_number: string; gender: string } | null;
lecturer: {
lecturer_number: string;
@ -20,18 +18,12 @@ type CreateColumnsParams = {
handleEdit: (lecturer: Lecturer) => void;
handleDeleteClick: (lecturer: Lecturer) => void;
handleResetPassword: (lecturer: Lecturer) => void;
handleUserStatusChange: (lecturer: Lecturer, isActive: boolean) => void;
};
export function createLecturerColumns(
params: CreateColumnsParams,
): ColumnDef<Lecturer>[] {
const {
handleEdit,
handleDeleteClick,
handleResetPassword,
handleUserStatusChange,
} = params;
const { handleEdit, handleDeleteClick, handleResetPassword } = params;
return [
{
@ -75,18 +67,6 @@ export function createLecturerColumns(
<GenderBadge gender={row.original.profile?.gender} />
),
},
{
accessorKey: 'is_active',
header: () => <span>Status Akun</span>,
cell: ({ row }) => (
<UserActiveSwitch
isActive={row.original.is_active}
onChange={(isActive) =>
handleUserStatusChange(row.original, isActive)
}
/>
),
},
{
id: 'actions',
header: () => <span className="block text-center">Aksi</span>,

View File

@ -16,7 +16,6 @@ import {
edit,
destroy,
reset_password,
update_user_status,
exportMethod as exportLecturers,
} from '@/routes/admin/users/lecturers';
import type { Lecturer } from './columns';
@ -109,21 +108,12 @@ export default function LecturerIndex({
);
}
function handleUserStatusChange(lecturer: Lecturer, isActive: boolean) {
router.patch(
update_user_status.url(lecturer.id),
{ is_active: isActive },
{ preserveScroll: true },
);
}
const columns = createLecturerColumns({
handleEdit: (lecturer) => {
router.get(edit.url(lecturer.id));
},
handleDeleteClick: (lecturer) => setDeleting(lecturer),
handleResetPassword: (lecturer) => setResetting(lecturer),
handleUserStatusChange,
});
return (

View File

@ -3,13 +3,11 @@ import { Key, Pencil, Trash2 } from 'lucide-react';
import { GenderBadge } from '@/components/gender-badge';
import { RowActions } from '@/components/row-actions';
import { StudentStatusBadge } from '@/components/student-status-badge';
import { UserActiveSwitch } from '@/components/user-active-switch';
export type Student = {
id: number;
username: string;
email: string;
is_active: boolean;
profile: { full_name: string; phone_number: string; gender: string } | null;
student: {
student_number: string;
@ -26,7 +24,6 @@ type CreateColumnsParams = {
handleDeleteClick: (student: Student) => void;
handleResetPassword: (student: Student) => void;
handleStatusChange: (student: Student, status: string) => void;
handleUserStatusChange: (student: Student, isActive: boolean) => void;
statuses: StatusOption[];
};
@ -38,7 +35,6 @@ export function createStudentColumns(
handleDeleteClick,
handleResetPassword,
handleStatusChange,
handleUserStatusChange,
statuses,
} = params;
@ -102,18 +98,6 @@ export function createStudentColumns(
<GenderBadge gender={row.original.profile?.gender} />
),
},
{
accessorKey: 'is_active',
header: () => <span>Status Akun</span>,
cell: ({ row }) => (
<UserActiveSwitch
isActive={row.original.is_active}
onChange={(isActive) =>
handleUserStatusChange(row.original, isActive)
}
/>
),
},
{
id: 'actions',
header: () => <span className="block text-center">Aksi</span>,

View File

@ -17,7 +17,6 @@ import {
edit,
reset_password,
update_status,
update_user_status,
exportMethod as exportStudents,
index as studentsIndex,
} from '@/routes/admin/users/students';
@ -128,14 +127,6 @@ export default function StudentIndex({
);
}
function handleUserStatusChange(student: Student, isActive: boolean) {
router.patch(
update_user_status.url(student.id),
{ is_active: isActive },
{ preserveScroll: true },
);
}
const columns = createStudentColumns({
handleEdit: (student) => {
router.get(edit.url(student.id));
@ -143,7 +134,6 @@ export default function StudentIndex({
handleDeleteClick: (student) => setDeleting(student),
handleResetPassword: (student) => setResetting(student),
handleStatusChange,
handleUserStatusChange,
statuses,
});

View File

@ -119,17 +119,14 @@
Route::prefix('admin/users')->name('admin.users.')->group(function () {
Route::resource('lecturers', LecturerController::class)->except(['show'])->parameters(['lecturers' => 'user']);
Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password');
Route::patch('lecturers/{user}/user-status', [LecturerController::class, 'updateUserStatus'])->name('lecturers.update_user_status');
Route::get('lecturers/export', [LecturerController::class, 'export'])->name('lecturers.export');
Route::resource('students', StudentController::class)->except(['show'])->parameters(['students' => 'user']);
Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password');
Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status');
Route::patch('students/{user}/user-status', [StudentController::class, 'updateUserStatus'])->name('students.update_user_status');
Route::get('students/export', [StudentController::class, 'export'])->name('students.export');
Route::resource('administrators', AdministratorController::class)->except(['show'])->parameters(['administrators' => 'user']);
Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password');
Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status');
});
});