Some checks failed
tests / ci (pull_request) Has been cancelled
- Moved profile and security settings from the general settings route to a dedicated admin settings route. - Created new components and pages for managing user profile and security settings. - Updated user menu to link to the new admin settings pages. - Removed old settings pages and routes that are no longer in use. - Added tests for profile and security settings functionality.
390 lines
18 KiB
TypeScript
390 lines
18 KiB
TypeScript
import { Form, Head, Link, usePage } from '@inertiajs/react';
|
|
import { format } from 'date-fns';
|
|
import { useState } from 'react';
|
|
import ProfileController from '@/actions/App/Http/Controllers/Admin/Settings/ProfileController';
|
|
import { DatePicker } from '@/components/date-picker';
|
|
import Heading from '@/components/heading';
|
|
import InputError from '@/components/input-error';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { PhoneInput } from '@/components/ui/phone-input';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { edit } from '@/routes/admin/settings/profile';
|
|
import { send } from '@/routes/verification';
|
|
import type { Auth } from '@/types';
|
|
|
|
const roleLabels: Record<string, string> = {
|
|
mahasiswa: 'Mahasiswa',
|
|
dosen: 'Dosen',
|
|
'staff-admin': 'Staf Admin',
|
|
'staff-keuangan': 'Staf Keuangan',
|
|
};
|
|
|
|
type Profile = {
|
|
full_name: string | null;
|
|
phone_number: string | null;
|
|
address: string | null;
|
|
gender: 'male' | 'female' | null;
|
|
birth_date: string | null;
|
|
birth_place: string | null;
|
|
} | null;
|
|
|
|
type StudentRoleData = {
|
|
student_number: string;
|
|
department: string | null;
|
|
enrollment_year: number;
|
|
academic_advisor: string | null;
|
|
};
|
|
|
|
type LecturerRoleData = {
|
|
lecturer_number: string;
|
|
department: string | null;
|
|
};
|
|
|
|
type AdminRoleData = {
|
|
roles: string[];
|
|
};
|
|
|
|
type PageProps = {
|
|
auth: Auth;
|
|
};
|
|
|
|
type Props = {
|
|
mustVerifyEmail: boolean;
|
|
status?: string;
|
|
profile: Profile;
|
|
role: 'mahasiswa' | 'dosen' | 'admin';
|
|
roleData: StudentRoleData | LecturerRoleData | AdminRoleData | null;
|
|
};
|
|
|
|
export default function Profile({
|
|
mustVerifyEmail,
|
|
status,
|
|
profile,
|
|
role,
|
|
roleData,
|
|
}: Props) {
|
|
const { auth } = usePage<PageProps>().props;
|
|
|
|
const [gender, setGender] = useState(profile?.gender ?? '');
|
|
const [birthDate, setBirthDate] = useState<Date | undefined>(
|
|
profile?.birth_date ? new Date(profile.birth_date) : undefined,
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Head title="Pengaturan Profil" />
|
|
|
|
<h1 className="sr-only">Pengaturan Profil</h1>
|
|
|
|
<div className="space-y-6">
|
|
<Heading
|
|
variant="small"
|
|
title="Profil"
|
|
description="Perbarui nama pengguna dan alamat email Anda"
|
|
/>
|
|
|
|
<Form
|
|
{...ProfileController.update.form()}
|
|
options={{
|
|
preserveScroll: true,
|
|
}}
|
|
className="space-y-6"
|
|
>
|
|
{({ processing, errors }) => (
|
|
<>
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="username">
|
|
Nama pengguna
|
|
</Label>
|
|
|
|
<Input
|
|
id="username"
|
|
defaultValue={auth.user.username}
|
|
name="username"
|
|
required
|
|
autoComplete="username"
|
|
placeholder="Nama pengguna"
|
|
/>
|
|
|
|
<InputError message={errors.username} />
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="email">Alamat email</Label>
|
|
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
defaultValue={auth.user.email}
|
|
name="email"
|
|
required
|
|
autoComplete="email"
|
|
placeholder="Alamat email"
|
|
/>
|
|
|
|
<InputError message={errors.email} />
|
|
</div>
|
|
</div>
|
|
|
|
{mustVerifyEmail &&
|
|
auth.user.email_verified_at === null && (
|
|
<div>
|
|
<p className="-mt-2 text-sm text-muted-foreground">
|
|
Alamat email Anda belum
|
|
diverifikasi.{' '}
|
|
<Link
|
|
href={send()}
|
|
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"
|
|
>
|
|
Klik di sini untuk mengirim
|
|
ulang email verifikasi.
|
|
</Link>
|
|
</p>
|
|
|
|
{status ===
|
|
'verification-link-sent' && (
|
|
<div className="mt-2 text-sm font-medium text-green-600">
|
|
Tautan verifikasi baru telah
|
|
dikirim ke alamat email Anda.
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid gap-4 border-t pt-6 md:grid-cols-2">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="full_name">
|
|
Nama Lengkap
|
|
</Label>
|
|
<Input
|
|
id="full_name"
|
|
name="full_name"
|
|
defaultValue={profile?.full_name ?? ''}
|
|
placeholder="Masukkan nama lengkap"
|
|
/>
|
|
<InputError message={errors.full_name} />
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="phone_number">
|
|
Nomor Telepon
|
|
</Label>
|
|
<PhoneInput
|
|
id="phone_number"
|
|
name="phone_number"
|
|
value={profile?.phone_number ?? ''}
|
|
placeholder="08xx xxxx xxxx"
|
|
/>
|
|
<InputError message={errors.phone_number} />
|
|
</div>
|
|
<div className="grid gap-4 md:col-span-2 md:grid-cols-3">
|
|
<div className="grid gap-2">
|
|
<Label>Jenis Kelamin</Label>
|
|
<input
|
|
type="hidden"
|
|
name="gender"
|
|
value={gender}
|
|
/>
|
|
<RadioGroup
|
|
value={gender}
|
|
onValueChange={setGender}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<RadioGroupItem
|
|
value="male"
|
|
id="male"
|
|
/>
|
|
<Label
|
|
htmlFor="male"
|
|
className="font-normal"
|
|
>
|
|
Laki-laki
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<RadioGroupItem
|
|
value="female"
|
|
id="female"
|
|
/>
|
|
<Label
|
|
htmlFor="female"
|
|
className="font-normal"
|
|
>
|
|
Perempuan
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
<InputError message={errors.gender} />
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Tempat Lahir</Label>
|
|
<Input
|
|
name="birth_place"
|
|
defaultValue={
|
|
profile?.birth_place ?? ''
|
|
}
|
|
placeholder="Masukkan tempat lahir"
|
|
/>
|
|
<InputError
|
|
message={errors.birth_place}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Tanggal Lahir</Label>
|
|
<input
|
|
type="hidden"
|
|
name="birth_date"
|
|
value={
|
|
birthDate
|
|
? format(
|
|
birthDate,
|
|
'yyyy-MM-dd',
|
|
)
|
|
: ''
|
|
}
|
|
/>
|
|
<DatePicker
|
|
value={birthDate}
|
|
onChange={setBirthDate}
|
|
/>
|
|
<InputError
|
|
message={errors.birth_date}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="grid gap-2 md:col-span-2">
|
|
<Label htmlFor="address">Alamat</Label>
|
|
<Textarea
|
|
id="address"
|
|
name="address"
|
|
defaultValue={profile?.address ?? ''}
|
|
placeholder="Masukkan alamat"
|
|
/>
|
|
<InputError message={errors.address} />
|
|
</div>
|
|
</div>
|
|
|
|
{role === 'mahasiswa' && roleData && (
|
|
<div className="grid gap-4 border-t pt-6 md:grid-cols-2">
|
|
<div className="grid gap-2">
|
|
<Label>NIM</Label>
|
|
<Input
|
|
disabled
|
|
value={
|
|
(roleData as StudentRoleData)
|
|
.student_number
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Jurusan</Label>
|
|
<Input
|
|
disabled
|
|
value={
|
|
(roleData as StudentRoleData)
|
|
.department ?? '-'
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Tahun Masuk</Label>
|
|
<Input
|
|
disabled
|
|
value={
|
|
(roleData as StudentRoleData)
|
|
.enrollment_year
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Dosen Wali</Label>
|
|
<Input
|
|
disabled
|
|
value={
|
|
(roleData as StudentRoleData)
|
|
.academic_advisor ?? '-'
|
|
}
|
|
/>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground md:col-span-2">
|
|
Informasi akademik dikelola oleh admin
|
|
dan tidak dapat diubah sendiri.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{role === 'dosen' && roleData && (
|
|
<div className="grid gap-4 border-t pt-6 md:grid-cols-2">
|
|
<div className="grid gap-2">
|
|
<Label>NIDN</Label>
|
|
<Input
|
|
disabled
|
|
value={
|
|
(roleData as LecturerRoleData)
|
|
.lecturer_number
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Jurusan</Label>
|
|
<Input
|
|
disabled
|
|
value={
|
|
(roleData as LecturerRoleData)
|
|
.department ?? '-'
|
|
}
|
|
/>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground md:col-span-2">
|
|
Informasi akademik dikelola oleh admin
|
|
dan tidak dapat diubah sendiri.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{role === 'admin' && roleData && (
|
|
<div className="grid gap-4 border-t pt-6">
|
|
<div className="grid gap-2">
|
|
<Label>Peran</Label>
|
|
<Input
|
|
disabled
|
|
value={(
|
|
roleData as AdminRoleData
|
|
).roles
|
|
.map((r) => roleLabels[r] ?? r)
|
|
.join(', ')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
disabled={processing}
|
|
data-test="update-profile-button"
|
|
>
|
|
Simpan
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</Form>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
Profile.layout = {
|
|
breadcrumbs: [
|
|
{
|
|
title: 'Pengaturan Profil',
|
|
href: edit(),
|
|
},
|
|
],
|
|
};
|