dress/resources/js/pages/settings/security.tsx

239 lines
11 KiB
TypeScript

import SecurityController from '@/actions/App/Http/Controllers/Settings/SecurityController';
import PasswordInput from '@/components/password-input';
import TwoFactorRecoveryCodes from '@/components/two-factor-recovery-codes';
import TwoFactorSetupModal from '@/components/two-factor-setup-modal';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Field, FieldError } from "@/components/ui/field";
import { Label } from '@/components/ui/label';
import { useTwoFactorAuth } from '@/hooks/use-two-factor-auth';
import { edit } from '@/routes/security';
import { disable, enable } from '@/routes/two-factor';
import { Head, useForm } from '@inertiajs/react';
import { Loader, Save, ShieldCheck } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { toast } from 'sonner';
type Props = {
canManageTwoFactor?: boolean;
requiresConfirmation?: boolean;
twoFactorEnabled?: boolean;
};
export default function Security({
canManageTwoFactor = false,
requiresConfirmation = false,
twoFactorEnabled = false,
}: Props) {
const passwordInput = useRef<HTMLInputElement>(null);
const currentPasswordInput = useRef<HTMLInputElement>(null);
const {
qrCodeSvg,
hasSetupData,
manualSetupKey,
clearSetupData,
clearTwoFactorAuthData,
fetchSetupData,
recoveryCodesList,
fetchRecoveryCodes,
errors: tfaErrors,
} = useTwoFactorAuth();
const [showSetupModal, setShowSetupModal] = useState<boolean>(false);
const prevTwoFactorEnabled = useRef(twoFactorEnabled);
const { data, setData, put, processing, errors, reset } = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const { post: postEnable, processing: processingEnable } = useForm({});
const { post: postDisable, processing: processingDisable } = useForm({});
useEffect(() => {
if (prevTwoFactorEnabled.current && !twoFactorEnabled) {
clearTwoFactorAuthData();
}
prevTwoFactorEnabled.current = twoFactorEnabled;
}, [twoFactorEnabled, clearTwoFactorAuthData]);
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
put(SecurityController.update().url, {
preserveScroll: true,
onSuccess: () => {
toast.success('Kata sandi berhasil diperbarui.');
reset();
},
onError: (errors) => {
if (errors.password) {
passwordInput.current?.focus();
}
if (errors.current_password) {
currentPasswordInput.current?.focus();
}
},
});
};
const handleEnable2FA = (e: React.FormEvent) => {
e.preventDefault();
postEnable(enable().url, {
onSuccess: () => setShowSetupModal(true),
});
};
const handleDisable2FA = (e: React.FormEvent) => {
e.preventDefault();
postDisable(disable().url);
};
return (
<div className="flex flex-col gap-6">
<Head title="Keamanan Akun" />
<h1 className="sr-only">Keamanan Akun</h1>
<form onSubmit={onSubmit} className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Perbarui Kata Sandi</CardTitle>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<Field>
<Label htmlFor="current_password" required>Kata Sandi Saat Ini</Label>
<PasswordInput
id="current_password"
ref={currentPasswordInput}
value={data.current_password}
onChange={(e) => setData('current_password', e.target.value)}
className="w-full"
autoComplete="current-password"
placeholder="Masukkan kata sandi saat ini"
/>
<FieldError error={errors.current_password} label="Kata sandi saat ini" className="text-xs" />
</Field>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Field>
<Label htmlFor="password" required>Kata Sandi Baru</Label>
<PasswordInput
id="password"
ref={passwordInput}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
placeholder="Masukkan kata sandi baru"
autoComplete="new-password"
/>
<FieldError error={errors.password} label="Kata sandi baru" className="text-xs" />
</Field>
<Field>
<Label htmlFor="password_confirmation" required>Konfirmasi Kata Sandi Baru</Label>
<PasswordInput
id="password_confirmation"
value={data.password_confirmation}
onChange={(e) => setData('password_confirmation', e.target.value)}
placeholder="Ulangi kata sandi baru"
autoComplete="new-password"
/>
<FieldError error={errors.password_confirmation} label="Konfirmasi kata sandi baru" className="text-xs" />
</Field>
</div>
<div className="flex justify-end">
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</CardContent>
</Card>
{canManageTwoFactor && (
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Autentikasi Dua Faktor (2FA)</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">
{twoFactorEnabled
? "Anda akan diminta memasukkan PIN acak yang aman saat masuk, yang dapat Anda ambil dari aplikasi pendukung TOTP di ponsel Anda."
: "Saat Anda mengaktifkan autentikasi dua faktor, Anda akan diminta PIN aman saat masuk. PIN ini dapat diambil dari aplikasi pendukung TOTP di ponsel Anda."
}
</p>
{twoFactorEnabled ? (
<div className="space-y-4">
<Button
type="button"
variant="destructive"
onClick={handleDisable2FA}
disabled={processingDisable}
>
{processingDisable ? <Loader className="size-4 animate-spin" /> : null}
Nonaktifkan 2FA
</Button>
<TwoFactorRecoveryCodes
recoveryCodesList={recoveryCodesList}
fetchRecoveryCodes={fetchRecoveryCodes}
errors={tfaErrors}
/>
</div>
) : (
<div>
{hasSetupData ? (
<Button type="button" onClick={() => setShowSetupModal(true)}>
<ShieldCheck className="mr-2 h-4 w-4" />
Lanjutkan Penyiapan
</Button>
) : (
<Button
type="button"
onClick={handleEnable2FA}
disabled={processingEnable}
>
{processingEnable ? <Loader className="size-4 animate-spin" /> : null}
Aktifkan 2FA
</Button>
)}
</div>
)}
</CardContent>
</Card>
)}
</div>
</div>
</form>
{canManageTwoFactor && (
<TwoFactorSetupModal
isOpen={showSetupModal}
onClose={() => setShowSetupModal(false)}
requiresConfirmation={requiresConfirmation}
twoFactorEnabled={twoFactorEnabled}
qrCodeSvg={qrCodeSvg}
manualSetupKey={manualSetupKey}
clearSetupData={clearSetupData}
fetchSetupData={fetchSetupData}
errors={tfaErrors}
/>
)}
</div>
);
}
Security.layout = {
breadcrumbs: [
{
title: 'Keamanan Akun',
href: edit(),
},
],
};