import React, { useState, useEffect } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, usePage, useForm, router } from '@inertiajs/react';
import { PageProps } from '@/types';
import Swal from 'sweetalert2';
import { swal } from '@/lib/swal';
// FilePond
import { FilePond, registerPlugin } from 'react-filepond';
import 'filepond/dist/filepond.min.css';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
registerPlugin(FilePondPluginImagePreview, FilePondPluginFileValidateType);
interface SettingsProps extends PageProps {
mustVerifyEmail: boolean;
status?: string;
twoFactorSettings: {
totp_allowed: boolean;
email_allowed: boolean;
};
twoFactor: {
enabled: boolean;
qr_code: string | null;
secret: string | null;
email_enabled: boolean;
smtp_configured: boolean;
recovery_codes: string[];
};
}
/* ─── Reusable Components from System Settings ─────────────────── */
function SectionCard({ title, description, children, delay = '0s', variant = 'default' }: { title: string; description: string; children: React.ReactNode; delay?: string; variant?: 'default' | 'danger' }) {
const isDanger = variant === 'danger';
return (
);
}
function InputField({ label, id, type = 'text', value, onChange, error, placeholder, required = false }: any) {
return (
{label} {required && * }
{error &&
{error}
}
);
}
const SETTINGS_TABS = ['profile', 'security', '2fa', 'danger'] as const;
type SettingsTab = typeof SETTINGS_TABS[number];
function getSettingsTabFromHash(): SettingsTab {
const hash = window.location.hash.replace('#', '') as SettingsTab;
return SETTINGS_TABS.includes(hash) ? hash : 'profile';
}
export default function SettingsIndex({ twoFactor, twoFactorSettings }: SettingsProps) {
const { user } = usePage().props.auth;
const [activeTab, setActiveTab] = useState(() => {
const hash = getSettingsTabFromHash();
if (hash === '2fa' && !(twoFactorSettings.totp_allowed || twoFactorSettings.email_allowed)) {
return 'profile';
}
return hash;
});
useEffect(() => {
const onHashChange = () => {
const hash = getSettingsTabFromHash();
if (hash === '2fa' && !(twoFactorSettings.totp_allowed || twoFactorSettings.email_allowed)) {
setActiveTab('profile');
} else {
setActiveTab(hash);
}
};
window.addEventListener('hashchange', onHashChange);
return () => window.removeEventListener('hashchange', onHashChange);
}, [twoFactorSettings]);
const switchTab = (tab: SettingsTab) => {
window.location.hash = tab;
setActiveTab(tab);
};
const { data: profileData, setData: setProfileData, post: postProfile, processing: profileProcessing, errors: profileErrors } = useForm({
first_name: user.first_name || '',
last_name: user.last_name || '',
email: user.email || '',
phone: (user as any).phone || '',
bio: (user as any).bio || '',
avatar_file: null as File | null,
_method: 'PATCH',
});
const [avatarFiles, setAvatarFiles] = useState([]);
const handleProfileSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
postProfile(route('profile.update'), {
preserveScroll: true,
onSuccess: () => swal.success('Saved', 'Profile updated successfully.'),
});
};
const { data: passwordData, setData: setPasswordData, put: putPassword, processing: passwordProcessing, errors: passwordErrors, reset: resetPassword } = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const handlePasswordSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
putPassword(route('password.update'), {
preserveScroll: true,
onSuccess: () => {
resetPassword();
swal.success('Saved', 'Password updated successfully.');
},
});
};
const handleDeleteAccount = async () => {
const result = await swal.confirm('Delete account?', 'This action is irreversible.', 'Delete Permanently');
if (result.isConfirmed) {
const { value: password } = await Swal.fire({
title: 'Confirm Password',
input: 'password',
inputPlaceholder: 'Enter your current password',
showCancelButton: true,
confirmButtonText: 'Delete',
confirmButtonColor: '#dc2626',
});
if (password) {
router.delete(route('profile.destroy'), {
data: { password }, preserveScroll: true,
onSuccess: () => swal.success('Deleted', 'Account removed.'),
onError: (errs) => swal.error('Error', errs.password || 'Incorrect password.'),
});
}
}
};
const initials = `${user.first_name?.charAt(0) || ''}${user.last_name?.charAt(0) || ''}`.toUpperCase();
// 2FA
const [copiedSecret, setCopiedSecret] = useState(false);
const [showCodes, setShowCodes] = useState(false);
const twoFactorForm = useForm({ code: '' });
const copySecret = () => {
navigator.clipboard.writeText(twoFactor.secret || '');
setCopiedSecret(true);
setTimeout(() => setCopiedSecret(false), 2000);
};
const handleEnable2FA = async (e: React.SyntheticEvent) => {
e.preventDefault();
if (twoFactor.email_enabled) {
const warning = await swal.confirm(
'Deactivate Email 2FA?',
'Enabling Google Authenticator will automatically deactivate Email Two-Factor Authentication. Do you want to proceed?',
'Proceed'
);
if (!warning.isConfirmed) return;
}
twoFactorForm.post(route('two-factor.enable'), {
preserveScroll: true,
onSuccess: () => { twoFactorForm.reset(); swal.success('Enabled', '2FA is now active on your account.'); },
});
};
const handleDisable2FA = async () => {
const { value: password } = await Swal.fire({
title: 'Disable 2FA',
text: 'Enter your password to confirm.',
input: 'password',
inputPlaceholder: 'Your current password',
showCancelButton: true,
confirmButtonText: 'Disable',
confirmButtonColor: '#dc2626',
});
if (password) {
router.post(route('two-factor.disable'), { password }, {
preserveScroll: true,
onSuccess: () => swal.success('Disabled', '2FA has been disabled.'),
});
}
};
const handleRegenerate = async () => {
const result = await swal.confirm('Regenerate Codes?', 'Old recovery codes will be invalidated.', 'Regenerate');
if (result.isConfirmed) {
router.post(route('two-factor.recovery-codes'), {}, {
preserveScroll: true,
onSuccess: () => { setShowCodes(true); swal.success('Regenerated', 'New recovery codes generated.'); },
});
}
};
const handleToggleEmail2FA = async (enable: boolean) => {
if (enable && !twoFactor.smtp_configured) {
Swal.fire({
icon: 'warning',
title: 'SMTP Mail Server Not Set Up',
text: 'Two-Factor Authentication via Email cannot be enabled because the system SMTP configurations are not set up yet. Please configure the SMTP settings under System Settings (or contact your Administrator) first.',
confirmButtonColor: '#3D4E4B',
});
return;
}
if (enable && twoFactor.enabled) {
const warning = await swal.confirm(
'Switch to Email 2FA?',
'Enabling Email 2FA will automatically deactivate your Google Authenticator setup. Do you want to proceed?',
'Proceed'
);
if (!warning.isConfirmed) return;
}
const { value: password } = await Swal.fire({
title: enable ? 'Enable Email 2FA' : 'Disable Email 2FA',
text: 'Enter your password to confirm.',
input: 'password',
inputPlaceholder: 'Your current password',
showCancelButton: true,
confirmButtonText: 'Confirm',
confirmButtonColor: '#3D4E4B',
});
if (password) {
router.post(route('two-factor.email.toggle'), { password, enabled: enable }, {
preserveScroll: true,
onSuccess: () => swal.success('Success', `Email Two-Factor Authentication has been ${enable ? 'enabled' : 'disabled'} successfully.`),
onError: (errs) => swal.error('Error', errs.password || 'Incorrect password.'),
});
}
};
return (
{/* Header Section */}
Account Settings
Manage your personal credentials and security preferences
{/* Tabs Row */}
switchTab('profile')}
className={`relative pb-3 px-1 mr-8 text-sm font-bold tracking-tight transition-colors ${activeTab === 'profile' ? 'text-[#3D4E4B]' : 'text-gray-400 hover:text-[#3D4E4B]'}`}>
Profile Information
{activeTab === 'profile' && }
switchTab('security')}
className={`relative pb-3 px-1 mr-8 text-sm font-bold tracking-tight transition-colors ${activeTab === 'security' ? 'text-[#3D4E4B]' : 'text-gray-400 hover:text-[#3D4E4B]'}`}>
Security & Password
{activeTab === 'security' && }
{(twoFactorSettings.totp_allowed || twoFactorSettings.email_allowed) && (
switchTab('2fa')}
className={`relative pb-3 px-1 mr-8 text-sm font-bold tracking-tight transition-colors ${activeTab === '2fa' ? 'text-[#3D4E4B]' : 'text-gray-400 hover:text-[#3D4E4B]'}`}>
Two-Factor Auth
{activeTab === '2fa' && }
)}
switchTab('danger')}
className={`relative pb-3 px-1 text-sm font-bold tracking-tight transition-colors ${activeTab === 'danger' ? 'text-red-600' : 'text-gray-400 hover:text-red-600'}`}>
Danger Zone
{activeTab === 'danger' && }
{/* Content Area */}
{activeTab === 'profile' && (
{user.avatar_url ?
: initials}
{ setAvatarFiles(items); setProfileData('avatar_file', items[0]?.file as File || null); }}
allowMultiple={false} maxFiles={1} labelIdle='Drop Photo here' />
)}
{activeTab === 'security' && (
setPasswordData('current_password', e.target.value)}
error={passwordErrors.current_password} required placeholder="••••••••" />
setPasswordData('password', e.target.value)}
error={passwordErrors.password} required placeholder="Enter new password" />
setPasswordData('password_confirmation', e.target.value)}
error={(passwordErrors as any).password_confirmation} required placeholder="Repeat new password" />
{passwordProcessing ? 'Updating...' : 'Update Password'}
)}
{activeTab === '2fa' && (
Two-Factor Authentication (2FA)
2FA menambah lapisan keamanan ekstra dengan meminta kode OTP dari aplikasi authenticator setiap kali login.
{twoFactorSettings.totp_allowed && (
!twoFactor.enabled ? (
{twoFactor.qr_code &&
}
{[
'Install aplikasi authenticator (Google Authenticator, Authy, 1Password)',
'Scan QR code atau masukkan manual key di bawah',
'Masukkan kode 6 digit dari aplikasi untuk mengaktifkan',
].map((step, i) => (
))}
Manual Key
{twoFactor.secret}
{copiedSecret ? '✓ Copied' : 'Copy'}
Verification Code *
twoFactorForm.setData('code', e.target.value)}
className="input-field w-full text-center tracking-[0.5em] font-bold text-lg"
placeholder="000000" />
{twoFactorForm.errors.code &&
{twoFactorForm.errors.code}
}
{twoFactorForm.processing ? 'Verifying...' : 'Enable Two-Factor Authentication'}
) : (
Two-Factor Authentication Aktif
Akun Anda dilindungi dengan TOTP authentication.
Disable 2FA
setShowCodes(!showCodes)} type="button"
className="h-8 px-4 text-xs font-bold text-[#3D4E4B] border border-gray-200 rounded-lg hover:bg-gray-50 transition-all">
{showCodes ? 'Hide' : 'Show Codes'}
Regenerate
{showCodes && twoFactor.recovery_codes.length > 0 && (
{twoFactor.recovery_codes.map((code, i) => (
{code}
))}
⚠ Setiap kode hanya bisa digunakan sekali.
)}
)
)}
{/* Email 2FA Setup */}
{twoFactorSettings.email_allowed && (
Two-Factor Authentication via Email
{twoFactor.email_enabled
? 'Email 2FA is active. Every login attempt will require an OTP code sent to your email.'
: 'Receive a 6-digit OTP code in your email to secure your login sessions.'}
{!twoFactor.smtp_configured && (
⚠
SMTP Mail Server is not configured. Setup required.
)}
{twoFactor.email_enabled ? (
handleToggleEmail2FA(false)} type="button"
className="h-9 px-5 text-xs font-bold text-red-500 border border-red-200 rounded-xl hover:bg-red-50 transition-all">
Disable Email 2FA
) : (
handleToggleEmail2FA(true)} type="button"
className="h-9 px-5 text-xs font-bold text-[#3D4E4B] border border-gray-200 rounded-xl hover:bg-gray-50 transition-all">
Enable Email 2FA
)}
)}
)}
{activeTab === 'danger' && (
Delete Account Permanently
Proceeding will scrub your identity, logs, and settings from our database. This action is irreversible.
Delete Account
)}
);
}