mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-30 14:23:37 +00:00
refactor(ui): replace settings page with deprecation notice
- Remove complex settings editor (217 lines) - Add simple deprecation redirect to API Profiles page - Improve UX by consolidating settings functionality
This commit is contained in:
+37
-216
@@ -1,225 +1,46 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Button } from '@/components/ui/button';
|
||||
/**
|
||||
* Settings Page - Deprecated
|
||||
* Settings functionality has been moved to API Profiles page
|
||||
*/
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { MaskedInput } from '@/components/ui/masked-input';
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog';
|
||||
import { Edit, Save, X } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '@/lib/api-client';
|
||||
|
||||
interface Settings {
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface SettingsResponse {
|
||||
profile: string;
|
||||
settings: Settings;
|
||||
mtime: number;
|
||||
path: string;
|
||||
}
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Construction, ArrowRight } from 'lucide-react';
|
||||
|
||||
export function SettingsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const profile = searchParams.get('profile');
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editedSettings, setEditedSettings] = useState<Settings | null>(null);
|
||||
const [conflictDialog, setConflictDialog] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Fetch profiles for selector
|
||||
const { data: profilesData } = useQuery({
|
||||
queryKey: ['profiles'],
|
||||
queryFn: () => api.profiles.list(),
|
||||
});
|
||||
|
||||
// Fetch settings for selected profile
|
||||
const { data, isLoading, refetch } = useQuery<SettingsResponse>({
|
||||
queryKey: ['settings', profile],
|
||||
queryFn: () => fetch(`/api/settings/${profile}/raw`).then((r) => r.json()),
|
||||
enabled: !!profile,
|
||||
});
|
||||
|
||||
// Use edited settings when in edit mode, otherwise use data directly
|
||||
const currentSettings = useMemo(() => {
|
||||
return editMode && editedSettings ? editedSettings : data?.settings;
|
||||
}, [editMode, editedSettings, data?.settings]);
|
||||
|
||||
// Save mutation
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const res = await fetch(`/api/settings/${profile}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
settings: editedSettings,
|
||||
expectedMtime: data?.mtime,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status === 409) {
|
||||
throw new Error('CONFLICT');
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to save');
|
||||
}
|
||||
|
||||
return res.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['settings', profile] });
|
||||
setEditedSettings(null);
|
||||
setEditMode(false);
|
||||
toast.success('Settings saved');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
if (error.message === 'CONFLICT') {
|
||||
setConflictDialog(true);
|
||||
} else {
|
||||
toast.error(error.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
saveMutation.mutate();
|
||||
};
|
||||
|
||||
const handleConflictResolve = async (overwrite: boolean) => {
|
||||
setConflictDialog(false);
|
||||
if (overwrite) {
|
||||
// Refetch to get new mtime, then save
|
||||
await refetch();
|
||||
saveMutation.mutate();
|
||||
} else {
|
||||
// Discard local changes
|
||||
setEditedSettings(null);
|
||||
setEditMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const updateEnvValue = (key: string, value: string) => {
|
||||
setEditedSettings((prev) => ({
|
||||
...prev,
|
||||
env: {
|
||||
...prev?.env,
|
||||
[key]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Settings Editor</h1>
|
||||
<select
|
||||
className="border rounded px-3 py-2"
|
||||
value={profile || ''}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setSearchParams({ profile: e.target.value })
|
||||
}
|
||||
>
|
||||
<option value="">Select profile...</option>
|
||||
{profilesData?.profiles.map((p) => (
|
||||
<option key={p.name} value={p.name}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold">Settings</h1>
|
||||
|
||||
{!profile && (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-muted-foreground">Select a profile to view/edit settings.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{profile && isLoading && (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-muted-foreground">Loading...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{profile && data && currentSettings && (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Environment Variables</CardTitle>
|
||||
<div className="flex gap-2">
|
||||
{!editMode ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setEditedSettings(data.settings);
|
||||
setEditMode(true);
|
||||
}}
|
||||
>
|
||||
<Edit className="w-4 h-4 mr-2" /> Edit
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setEditedSettings(null);
|
||||
setEditMode(false);
|
||||
}}
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" /> Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={saveMutation.isPending}>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
{saveMutation.isPending ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{Object.entries(currentSettings.env || {}).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
<Label>{key}</Label>
|
||||
{key.includes('TOKEN') || key.includes('KEY') ? (
|
||||
<MaskedInput
|
||||
value={value}
|
||||
onChange={(e) => updateEnvValue(key, e.target.value)}
|
||||
disabled={!editMode}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => updateEnvValue(key, e.target.value)}
|
||||
disabled={!editMode}
|
||||
className="font-mono"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="pt-4 text-xs text-muted-foreground">
|
||||
<p>Path: {data.path}</p>
|
||||
<p>Last modified: {new Date(data.mtime).toLocaleString()}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
open={conflictDialog}
|
||||
title="File Modified Externally"
|
||||
description="This settings file was modified by another process. Overwrite with your changes or discard?"
|
||||
confirmText="Overwrite"
|
||||
variant="destructive"
|
||||
onConfirm={() => handleConflictResolve(true)}
|
||||
onCancel={() => handleConflictResolve(false)}
|
||||
/>
|
||||
<Card className="border-yellow-500/50 bg-yellow-500/5">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-yellow-600">
|
||||
<Construction className="w-5 h-5" />
|
||||
Page Relocated
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-muted-foreground">
|
||||
The settings editor has been integrated into the <strong>API Profiles</strong> page for
|
||||
a better user experience.
|
||||
</p>
|
||||
<p className="text-muted-foreground">To edit environment variables for a profile:</p>
|
||||
<ol className="list-decimal list-inside text-muted-foreground space-y-1 ml-2">
|
||||
<li>Go to API Profiles page</li>
|
||||
<li>Click the actions menu (...) on any profile</li>
|
||||
<li>Select "Edit Settings"</li>
|
||||
</ol>
|
||||
<div className="pt-2">
|
||||
<Button asChild>
|
||||
<Link to="/api">
|
||||
Go to API Profiles
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user