import React, { useState, useEffect } from 'react'; import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, useForm, usePage } from '@inertiajs/react'; import { PageProps } from '@/types'; import { swal } from '@/lib/swal'; import axios from 'axios'; // 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 SystemSettingsProps extends PageProps { settings: Record; } /* ─── Reusable Components ─────────────────── */ function SectionCard({ title, description, children, delay = '0s' }: { title: string; description: string; children: React.ReactNode; delay?: string }) { return (

{title}

{description}

{children}
); } function ToggleItem({ label, description, checked, onChange }: { label: string; description: string; checked: boolean; onChange: (v: boolean) => void }) { return (
{label}
{description}
); } function InputField({ label, value, onChange, type = 'text', placeholder = '', error = '', required = false, maxLength, id }: any) { return (
onChange(e.target.value)} placeholder={placeholder} maxLength={maxLength} className={`input-field${error ? ' is-error' : ''}`} /> {error &&

{error}

}
); } const SYSTEM_TABS = ['general', 'security', 'email', 'mobile'] as const; type SystemTab = typeof SYSTEM_TABS[number]; function getSystemTabFromHash(): SystemTab { const hash = window.location.hash.replace('#', '') as SystemTab; return SYSTEM_TABS.includes(hash) ? hash : 'general'; } export default function SystemSettings({ settings }: SystemSettingsProps) { const [activeTab, setActiveTab] = useState(getSystemTabFromHash); useEffect(() => { const onHashChange = () => setActiveTab(getSystemTabFromHash()); window.addEventListener('hashchange', onHashChange); return () => window.removeEventListener('hashchange', onHashChange); }, []); const switchTab = (tab: SystemTab) => { window.location.hash = tab; setActiveTab(tab); }; const { auth } = usePage().props; const [testRecipient, setTestRecipient] = useState(auth?.user?.email || ''); const [testingEmail, setTestingEmail] = useState(false); const handleSendTestEmail = async () => { if (!testRecipient) return; setTestingEmail(true); try { const response = await axios.post(route('system.settings.test-email'), { recipient: testRecipient, mail_host: data.settings.mail_host, mail_port: data.settings.mail_port, mail_username: data.settings.mail_username, mail_password: data.settings.mail_password, mail_encryption: data.settings.mail_encryption, mail_from_address: data.settings.mail_from_address, mail_from_name: data.settings.mail_from_name, }); if (response.data.success) { swal.success('Success', response.data.message || 'Test email sent successfully!'); } else { swal.error('SMTP Error', response.data.message || 'Failed to send test email.'); } } catch (error: any) { swal.error('Error', error.response?.data?.message || error.message || 'An error occurred.'); } finally { setTestingEmail(false); } }; const { data, setData, post, processing, errors } = useForm({ settings: { app_name: settings.app_name || '', app_logo_text: settings.app_logo_text || 'B', app_description: settings.app_description || '', allow_registration: settings.allow_registration === '1' || settings.allow_registration === true, require_email_verification: settings.require_email_verification === '1' || settings.require_email_verification === true, password_minimum_length: parseInt(settings.password_minimum_length) || 8, password_require_symbols: settings.password_require_symbols === '1' || settings.password_require_symbols === true, password_require_numbers: settings.password_require_numbers === '1' || settings.password_require_numbers === true, password_require_mixed_case: settings.password_require_mixed_case === '1' || settings.password_require_mixed_case === true, two_factor_totp_enabled: settings.two_factor_totp_enabled === '1' || settings.two_factor_totp_enabled === true, two_factor_email_enabled: settings.two_factor_email_enabled === '1' || settings.two_factor_email_enabled === true, oauth_google_enabled: settings.oauth_google_enabled === '1' || settings.oauth_google_enabled === true, oauth_google_client_id: settings.oauth_google_client_id || '', oauth_google_client_secret: settings.oauth_google_client_secret || '', oauth_github_enabled: settings.oauth_github_enabled === '1' || settings.oauth_github_enabled === true, oauth_github_client_id: settings.oauth_github_client_id || '', oauth_github_client_secret: settings.oauth_github_client_secret || '', mail_host: settings.mail_host || '', mail_port: settings.mail_port || '', mail_username: settings.mail_username || '', mail_password: settings.mail_password || '', mail_encryption: settings.mail_encryption || 'tls', mail_from_address: settings.mail_from_address || '', mail_from_name: settings.mail_from_name || '', primary_color: settings.primary_color || '#D4A017', // Mobile App android_latest_version: settings.android_latest_version || '1.0.0', android_min_version: settings.android_min_version || '1.0.0', android_maintenance_mode: settings.android_maintenance_mode === '1' || settings.android_maintenance_mode === true, android_playstore_url: settings.android_playstore_url || '', }, logo_file: null as File | null, _method: 'PATCH', }); const [files, setFiles] = useState([]); const handleChange = (key: string, value: any) => { setData('settings', { ...data.settings, [key]: value }); }; const handleSave = (e: React.SyntheticEvent) => { e.preventDefault(); post(route('system.settings.update'), { preserveScroll: true, onSuccess: () => swal.success('Saved', 'System settings updated successfully.'), }); }; return ( {/* Premium Header Row */}

System Configuration

Manage global application behavior and external protocols

{/* Tabbed Navigation Row */}
{/* Content Area */}
{activeTab === 'general' && (
{settings.app_logo ? : (data.settings.app_logo_text || 'B')}
{ setFiles(items); setData('logo_file', items[0]?.file as File || null); }} allowMultiple={false} maxFiles={1} labelIdle='Drop Logo here' />
handleChange('app_name', v)} required placeholder="e.g. Biiskit Platform" /> handleChange('app_logo_text', v)} maxLength={3} required placeholder="e.g. B" />
handleChange('allow_registration', v)} /> handleChange('require_email_verification', v)} />
)} {activeTab === 'security' && (
handleChange('password_require_symbols', v)} /> handleChange('password_require_numbers', v)} /> handleChange('password_require_mixed_case', v)} />
Minimum Length handleChange('password_minimum_length', parseInt(e.target.value))} className="w-16 h-10 input-field text-center px-2 text-[#D4A017] font-bold" />
handleChange('two_factor_totp_enabled', v)} /> handleChange('two_factor_email_enabled', v)} />
{['google', 'github'].map(prov => (
{prov} Login
handleChange(`oauth_${prov}_client_id`, v)} placeholder={`Enter ${prov} Client ID`} /> handleChange(`oauth_${prov}_client_secret`, v)} placeholder="••••••••" />
))}
)} {activeTab === 'email' && (
handleChange('mail_host', v)} placeholder="e.g. smtp.mailtrap.io" required />
handleChange('mail_port', v)} placeholder="e.g. 587" required />
handleChange('mail_username', v)} placeholder="e.g. user_key_123" /> handleChange('mail_password', v)} placeholder="••••••••" />
handleChange('mail_from_address', v)} placeholder="e.g. no-reply@app.com" required /> handleChange('mail_from_name', v)} placeholder="e.g. System Admin" required />

This test will use the SMTP values entered in the form above. You don't need to save the configuration first to test it.

)} {activeTab === 'mobile' && (
handleChange('android_latest_version', v)} placeholder="e.g. 1.2.0" /> handleChange('android_min_version', v)} placeholder="e.g. 1.0.5" />
handleChange('android_playstore_url', v)} placeholder="https://play.google.com/store/apps/details?id=..." />
handleChange('android_maintenance_mode', v)} />

Warning

Enabling maintenance mode will return a 503 error to all mobile devices connecting to the API.

)}
); }