205 lines
7.9 KiB
TypeScript
205 lines
7.9 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { getFileContents } from '../webdav';
|
|
|
|
interface GeneralPageProps {
|
|
config: any;
|
|
setConfig: (config: any) => void;
|
|
}
|
|
|
|
export default function GeneralPage({ config, setConfig }: GeneralPageProps) {
|
|
const [timezones, setTimezones] = useState<string[] | null>(null);
|
|
const [tzError, setTzError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
getFileContents('/.sys/timezones.json')
|
|
.then(async (blob) => {
|
|
const parsed = JSON.parse(await blob.text());
|
|
if (Array.isArray(parsed)) {
|
|
setTimezones(parsed.map((e: any) => (typeof e === 'string' ? e : String(e.name ?? e.value ?? e))));
|
|
} else if (parsed && typeof parsed === 'object') {
|
|
setTimezones(Object.keys(parsed));
|
|
} else {
|
|
setTzError(true);
|
|
}
|
|
})
|
|
.catch(() => setTzError(true));
|
|
}, []);
|
|
|
|
const updateSetting = (path: string[], value: any) => {
|
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
let current = newConfig;
|
|
|
|
for (let i = 0; i < path.length - 1; i++) {
|
|
current = current[path[i]];
|
|
}
|
|
|
|
current[path[path.length - 1]] = value;
|
|
setConfig(newConfig);
|
|
};
|
|
|
|
const general = config.general || {};
|
|
|
|
return (
|
|
<div className="p-4 space-y-4">
|
|
<h2 className="text-sm text-neutral-500">General Settings</h2>
|
|
|
|
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
|
<div className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">Appearance</label>
|
|
<select
|
|
value={general.appearance?.split('|')[0] || 'auto'}
|
|
onChange={(e) => updateSetting(['general', 'appearance'], e.target.value)}
|
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
>
|
|
<option value="light">Light</option>
|
|
<option value="dark">Dark</option>
|
|
<option value="auto">Auto</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">Language</label>
|
|
<select
|
|
value={general.language?.split('|')[0] || 'en'}
|
|
onChange={(e) => updateSetting(['general', 'language'], e.target.value)}
|
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
>
|
|
<option value="en">English</option>
|
|
<option value="es">Spanish</option>
|
|
<option value="de">German</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">Timezone</label>
|
|
{!tzError && timezones ? (
|
|
<select
|
|
value={general.timezone || ''}
|
|
onChange={(e) => updateSetting(['general', 'timezone'], e.target.value)}
|
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
>
|
|
{general.timezone && !timezones.includes(general.timezone) && (
|
|
<option value={general.timezone}>{general.timezone}</option>
|
|
)}
|
|
{timezones.map((tz) => (
|
|
<option key={tz} value={tz}>{tz}</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<input
|
|
type="text"
|
|
value={general.timezone || ''}
|
|
onChange={(e) => updateSetting(['general', 'timezone'], e.target.value)}
|
|
placeholder={timezones === null && !tzError ? 'Loading…' : 'America/Los_Angeles'}
|
|
disabled={timezones === null && !tzError}
|
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg disabled:opacity-50"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">Country</label>
|
|
<input
|
|
type="text"
|
|
value={general.country || ''}
|
|
onChange={(e) => updateSetting(['general', 'country'], e.target.value)}
|
|
placeholder="US"
|
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
/>
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<label className="text-sm text-neutral-500 block mb-2">Device Name</label>
|
|
<input
|
|
type="text"
|
|
value={general.devicename || ''}
|
|
onChange={(e) => updateSetting(['general', 'devicename'], e.target.value)}
|
|
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
|
|
/>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">Rotation Sounds</label>
|
|
<button
|
|
onClick={() => updateSetting(['general', 'rotationsounds'], general.rotationsounds ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
general.rotationsounds ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
general.rotationsounds ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">Config Enabled</label>
|
|
<button
|
|
onClick={() => updateSetting(['general', 'configenabled'], general.configenabled ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
general.configenabled ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
general.configenabled ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">Status Wait Enabled</label>
|
|
<button
|
|
onClick={() => updateSetting(['general', 'status_wait_enabled'], general.status_wait_enabled ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
general.status_wait_enabled ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
general.status_wait_enabled ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">Encrypt Passphrase</label>
|
|
<button
|
|
onClick={() => updateSetting(['general', 'encrypt_passphrase'], general.encrypt_passphrase ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
general.encrypt_passphrase ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
general.encrypt_passphrase ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-4 flex items-center justify-between">
|
|
<label className="text-sm text-neutral-500">Reset with Host</label>
|
|
<button
|
|
onClick={() => updateSetting(['general', 'reset_with_host'], general.reset_with_host ? 0 : 1)}
|
|
className={`relative w-12 h-6 rounded-full transition-colors ${
|
|
general.reset_with_host ? 'bg-blue-600' : 'bg-neutral-300'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
|
|
general.reset_with_host ? 'translate-x-6' : 'translate-x-0.5'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|