web / frontend /src /components /modals /SettingsModal.tsx
Chandima Prabhath
feat: Add chat components and modals for enhanced user interaction
1904e4c
raw
history blame
8.54 kB
import { useState, useRef, useEffect } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { toast } from "@/components/ui/sonner";
import { storage, STORAGE_KEYS } from "@/lib/storage";
import { X, Settings, Moon, Sun, Globe, Shield, Database, Cloud } from "lucide-react";
interface SettingsModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
const THEME_OPTIONS = [
{ value: "light", label: "Light", icon: Sun },
{ value: "dark", label: "Dark", icon: Moon },
{ value: "system", label: "System", icon: Globe },
];
export const SettingsModal = ({ open, onOpenChange }: SettingsModalProps) => {
// Load settings from storage on mount
const [theme, setTheme] = useState(() => {
return storage.get<string>(STORAGE_KEYS.THEME) || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
});
const [apiEndpoint, setApiEndpoint] = useState(() =>
storage.get<string>(STORAGE_KEYS.API_ENDPOINT) || "https://insight-ai-api.hf.space"
);
const fileInputRef = useRef<HTMLInputElement>(null);
// Sync settings from storage when modal opens
useEffect(() => {
if (open) {
setTheme(storage.get<string>(STORAGE_KEYS.THEME));
setApiEndpoint(storage.get<string>(STORAGE_KEYS.API_ENDPOINT));
}
}, [open]);
const handleThemeChange = (newTheme: string) => {
setTheme(newTheme);
const root = window.document.documentElement;
if (newTheme === "dark") {
root.classList.add("dark");
} else if (newTheme === "light") {
root.classList.remove("dark");
} else {
// System theme
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
root.classList.add("dark");
} else {
root.classList.remove("dark");
}
}
storage.set(STORAGE_KEYS.THEME, newTheme);
toast.success("Theme updated successfully");
};
const handleSaveEndpoint = () => {
storage.set(STORAGE_KEYS.API_ENDPOINT, apiEndpoint);
toast.success("API endpoint saved successfully");
};
const handleClearChats = () => {
storage.set(STORAGE_KEYS.CHATS, []);
toast.success("Chat history cleared successfully");
window.location.reload();
};
const handleClearSources = () => {
storage.set(STORAGE_KEYS.SOURCES, []);
toast.success("Sources cleared successfully");
};
const handleResetSettings = () => {
// Reset all keys to their default values using storage lib
storage.resetToDefaults();
window.location.reload();
toast.success("Settings reset to defaults");
};
const handleExportStorage = () => {
const data = storage.export();
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "insight-storage-export.json";
a.click();
URL.revokeObjectURL(url);
toast.success("Storage exported successfully");
};
const handleImportStorage = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const result = e.target?.result as string;
const data = JSON.parse(result);
if (storage.import(data)) {
toast.success("Storage imported successfully. Please refresh the page.");
} else {
toast.error("Failed to import storage.");
}
} catch {
toast.error("Invalid file format.");
}
};
reader.readAsText(file);
event.target.value = "";
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-3xl">
<DialogHeader>
<DialogTitle className="flex items-center text-xl">
<Settings className="mr-2 h-5 w-5" />
Settings
</DialogTitle>
</DialogHeader>
<Separator />
<div className="max-h-[80dvh] md:max-h-[75vh] overflow-y-auto grid gap-6">
<div className="space-y-4">
<h3 className="text-sm font-medium flex items-center">
<Moon className="mr-2 h-4 w-4" />
Appearance
</h3>
<RadioGroup
value={theme}
onValueChange={handleThemeChange}
className="grid grid-cols-3 gap-2"
>
{THEME_OPTIONS.map(({ value, label, icon: Icon }) => (
<div key={value} className="flex flex-col items-center space-y-2">
<Label
htmlFor={value}
className={
`flex flex-col items-center justify-center w-full h-20 border rounded-md cursor-pointer transition-colors
${theme === value
? "bg-financial-accent/30 border-financial-accent/30 ring-2 ring-financial-accent"
: "hover:bg-financial-accent/30 hover:border-financial-accent/30"}
`
}
>
<Icon className="h-8 w-8 mb-2" />
{label}
</Label>
<RadioGroupItem value={value} id={value} className="sr-only" />
</div>
))}
</RadioGroup>
</div>
<Separator />
<div className="space-y-4">
<h3 className="text-sm font-medium flex items-center">
<Database className="mr-2 h-4 w-4" />
API Configuration
</h3>
<div className="space-y-2">
<Label htmlFor="api-endpoint">API Endpoint</Label>
<div className="flex space-x-2">
<Input
id="api-endpoint"
value={apiEndpoint}
onChange={(e) => setApiEndpoint(e.target.value)}
placeholder="Ex: http://localhost:8000"
/>
<Button onClick={handleSaveEndpoint} variant="outline">
<Cloud className="h-4 w-4 mr-2" />
Save
</Button>
</div>
</div>
</div>
<Separator />
<div className="space-y-4">
<h3 className="text-sm font-medium flex items-center">
<Shield className="mr-2 h-4 w-4" />
Data Management
</h3>
<div className="flex flex-wrap gap-3">
<Button
variant="outline"
onClick={handleClearChats}
>
Clear Chat History
</Button>
<Button
variant="outline"
onClick={handleClearSources}
>
Clear Sources
</Button>
<Button
variant="destructive"
onClick={handleResetSettings}
>
Reset All Settings
</Button>
</div>
</div>
<Separator />
<div className="space-y-4">
<h3 className="text-sm font-medium flex items-center">
<Cloud className="mr-2 h-4 w-4" />
Backup & Restore
</h3>
<div className="flex flex-wrap gap-3">
<Button
variant="outline"
onClick={handleExportStorage}
>
Export Data
</Button>
<Button
variant="outline"
onClick={() => fileInputRef.current?.click()}
>
Import Data
</Button>
<input
ref={fileInputRef}
type="file"
accept="application/json"
style={{ display: "none" }}
onChange={handleImportStorage}
/>
</div>
<div className="text-xs text-muted-foreground">
Export will download your data as a JSON file. Import will overwrite your current data.
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
};