mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-06 00:47:52 +00:00
d393d06885
* refactor(ui): replace Sonner with custom Sera UI-inspired toast system Replace the Sonner toast library with a custom implementation inspired by Sera UI's glassmorphism design. The new system uses an external store pattern with useSyncExternalStore for React integration, keeping the same toast.success()/error()/warning() API so all 172 call sites required only an import path change. Key changes: - New toast-store.ts: singleton store with identical API to Sonner - New toast.tsx: Sera UI-faithful Notification component with Framer Motion animations, frosted glass (backdrop-blur-xl), type gradient overlays, animated progress bar (green→blue→sky gradient), and hover:scale-105 - Removed sonner and next-themes dependencies - Rewired all 19 consumer files to import from the new store * fix(ui): resolve ESLint errors in toast system - Use const for listeners Set (prefer-const) - Initialize startRef with 0 instead of Date.now() to satisfy react-hooks/purity rule, set actual value inside useEffect
113 lines
5.2 KiB
TypeScript
113 lines
5.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { toast } from '@/components/ui/toast-store';
|
|
import { apiFetch } from '@/lib/api';
|
|
import { RefreshCw } from 'lucide-react';
|
|
import type { PatchableSettings } from './types';
|
|
|
|
interface AppStoreSectionProps {
|
|
settings: PatchableSettings;
|
|
onSettingChange: <K extends keyof PatchableSettings>(key: K, value: PatchableSettings[K]) => void;
|
|
isLoading: boolean;
|
|
/** Called after a successful save so the parent can update serverSettingsRef */
|
|
onSaved: (key: keyof PatchableSettings, value: string) => void;
|
|
}
|
|
|
|
function SettingsSkeleton() {
|
|
return (
|
|
<div className="space-y-6">
|
|
<Skeleton className="h-8 w-48" />
|
|
<Skeleton className="h-4 w-64" />
|
|
<div className="space-y-4 bg-glass border border-glass-border p-4 rounded-lg">
|
|
<Skeleton className="h-10 w-full" />
|
|
<Skeleton className="h-10 w-full" />
|
|
<Skeleton className="h-10 w-full" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function AppStoreSection({ settings, onSettingChange, isLoading, onSaved }: AppStoreSectionProps) {
|
|
const [isSavingRegistry, setIsSavingRegistry] = useState(false);
|
|
|
|
const saveRegistrySettings = async () => {
|
|
setIsSavingRegistry(true);
|
|
try {
|
|
const res = await apiFetch('/settings', {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ template_registry_url: settings.template_registry_url ?? '' }),
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({}));
|
|
toast.error(err?.error || err?.message || 'Failed to save registry settings.');
|
|
return;
|
|
}
|
|
onSaved('template_registry_url', settings.template_registry_url ?? '');
|
|
await apiFetch('/templates/refresh-cache', { method: 'POST' });
|
|
toast.success('Registry saved. App Store will reload from the new source.');
|
|
} catch (e: unknown) {
|
|
toast.error((e as Error)?.message || 'Failed to save registry settings.');
|
|
} finally {
|
|
setIsSavingRegistry(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h3 className="text-lg font-medium tracking-tight">App Store Registry</h3>
|
|
<p className="text-sm text-muted-foreground">Configure the template source used by the App Store.</p>
|
|
</div>
|
|
|
|
{isLoading ? <SettingsSkeleton /> : (
|
|
<>
|
|
<div className="space-y-6 bg-glass border border-glass-border p-4 rounded-lg">
|
|
<div className="space-y-1">
|
|
<Label className="text-base">Default Registry</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
LinuxServer.io - <span className="font-mono">https://api.linuxserver.io/api/v1/images</span>
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">Used when no custom registry is set.</p>
|
|
</div>
|
|
|
|
<div className="space-y-3 pt-4 border-t border-glass-border">
|
|
<div className="space-y-1">
|
|
<Label className="text-base">Custom Registry URL</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
Provide a URL pointing to a <span className="font-medium">Portainer v2</span> compatible template JSON file. Overrides the default registry.
|
|
</p>
|
|
</div>
|
|
<Input
|
|
placeholder="https://example.com/templates.json"
|
|
value={settings.template_registry_url ?? ''}
|
|
onChange={(e) => onSettingChange('template_registry_url', e.target.value)}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">Leave empty to use the default LinuxServer.io registry.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => onSettingChange('template_registry_url', '')}
|
|
disabled={isSavingRegistry || !settings.template_registry_url}
|
|
>
|
|
Reset to Default
|
|
</Button>
|
|
<Button onClick={saveRegistrySettings} disabled={isSavingRegistry}>
|
|
{isSavingRegistry
|
|
? <><RefreshCw className="w-4 h-4 mr-2 animate-spin" />Saving...</>
|
|
: 'Save & Refresh'
|
|
}
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|