• Settings backup and restore component

    Renders a card with two main actions: exporting current settings to a file and importing settings from a previously exported file. Handles all the file operations, validation, and user feedback for these processes.

    The export function creates a timestamped JSON file with properly formatted settings data. The import function validates incoming settings before applying them to ensure data integrity.

    Parameters

    • props: ImportExportSettingsProps

      Component properties

      Props for the ImportExportSettings component

      • currentSettings: SettingsSchema

        Current application settings object to export

      • onImport: (settings: SettingsSchema) => void

        Callback triggered when settings are successfully imported

    Returns Element

    React component for settings backup and restore

    export function ImportExportSettings({
    currentSettings,
    onImport,
    }: ImportExportSettingsProps) {
    // Reference to hidden file input element
    const fileInputRef = React.useRef<HTMLInputElement | null>(null);

    /**
    * Exports current settings to a JSON file
    *
    * Creates a formatted JSON file with the current settings and initiates
    * a download through the browser. The file includes a timestamp to prevent
    * accidental overwrites when exporting multiple times.
    */
    const handleExport = () => {
    try {
    // Create a JSON blob with formatted settings
    const settingsBlob = new Blob(
    [JSON.stringify(currentSettings, null, 2)],
    { type: "application/json" },
    );

    // Create download link
    const url = URL.createObjectURL(settingsBlob);
    const link = document.createElement("a");

    // Set filename with timestamp to prevent overwrites
    const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
    link.download = `spotify-skip-tracker-settings-${timestamp}.json`;
    link.href = url;

    // Trigger download
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    // Clean up URL object
    URL.revokeObjectURL(url);

    toast.success("Settings exported", {
    description: "Your settings have been exported successfully.",
    });
    } catch (error) {
    console.error("Failed to export settings:", error);
    toast.error("Export failed", {
    description: "Could not export settings. Please try again.",
    });
    }
    };

    /**
    * Handles the file selection for import
    *
    * Processes the selected JSON file, validates its structure as valid
    * settings data, and passes it to the parent component if valid.
    * Provides user feedback via toast notifications for both success
    * and failure cases.
    *
    * @param event - File input change event containing the selected file
    */
    const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;

    const reader = new FileReader();
    reader.onload = (e) => {
    try {
    const content = e.target?.result as string;
    const importedSettings = JSON.parse(content) as SettingsSchema;

    // Validate required fields
    if (!importedSettings) {
    throw new Error("Invalid settings file format");
    }

    // Apply imported settings
    onImport(importedSettings);

    toast.success("Settings imported", {
    description: "Your settings have been imported successfully.",
    });
    } catch (error) {
    console.error("Failed to import settings:", error);
    toast.error("Import failed", {
    description: "The selected file contains invalid settings data.",
    });
    }

    // Reset the file input so the same file can be selected again
    if (fileInputRef.current) {
    fileInputRef.current.value = "";
    }
    };

    reader.onerror = () => {
    toast.error("Read error", {
    description: "Failed to read the selected file.",
    });
    };

    reader.readAsText(file);
    };

    /**
    * Triggers file selection dialog
    *
    * Programmatically clicks the hidden file input element to open
    * the system file browser for selecting a settings file to import.
    */
    const handleImportClick = () => {
    fileInputRef.current?.click();
    };

    return (
    <div>
    <CardHeader className="px-0 pt-0">
    <CardTitle className="mb-1 flex items-center text-xl font-semibold">
    <FileJson className="text-primary mr-2 h-5 w-5" />
    Settings Backup & Restore
    </CardTitle>
    <p className="text-muted-foreground text-sm">
    Import and export your application settings
    </p>
    </CardHeader>

    <Card className="border-muted-foreground/20 shadow-sm">
    <CardContent className="p-6">
    <div className="flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
    <div className="flex-1">
    <div className="flex items-center space-x-2">
    <h3 className="text-base font-medium">Export Settings</h3>
    <TooltipProvider>
    <Tooltip>
    <TooltipTrigger asChild>
    <HelpCircle className="text-muted-foreground h-4 w-4" />
    </TooltipTrigger>
    <TooltipContent side="top" className="max-w-md">
    <p>
    Save your current settings to a file that can be backed
    up or transferred to another installation.
    </p>
    </TooltipContent>
    </Tooltip>
    </TooltipProvider>
    </div>
    <p className="text-muted-foreground mt-1 text-sm">
    Save your current configuration to a JSON file
    </p>
    <Button
    onClick={handleExport}
    variant="outline"
    className="mt-2 w-full"
    >
    <Download className="mr-2 h-4 w-4" />
    Export Settings
    </Button>
    </div>

    <div className="flex-1">
    <div className="flex items-center space-x-2">
    <h3 className="text-base font-medium">Import Settings</h3>
    <TooltipProvider>
    <Tooltip>
    <TooltipTrigger asChild>
    <HelpCircle className="text-muted-foreground h-4 w-4" />
    </TooltipTrigger>
    <TooltipContent side="top" className="max-w-md">
    <p>
    Load settings from a previously exported file. This will
    replace your current settings.
    </p>
    </TooltipContent>
    </Tooltip>
    </TooltipProvider>
    </div>
    <p className="text-muted-foreground mt-1 text-sm">
    Load a previously exported settings file
    </p>
    <Button
    onClick={handleImportClick}
    variant="outline"
    className="mt-2 w-full"
    >
    <Upload className="mr-2 h-4 w-4" />
    Import Settings
    </Button>

    {/* Hidden file input */}
    <input
    type="file"
    ref={fileInputRef}
    onChange={handleFileChange}
    accept=".json"
    className="hidden"
    />
    </div>
    </div>
    </CardContent>
    </Card>
    </div>
    );
    }