feat(settings): data-safe editors, deep-linked search, Data/About/Cost pages
- EditorModal loads once per path (a parent re-render no longer reloads the file over the user's typing), refuses Save after a failed load, opens a truncated (>256 KB) file read-only, and asks before overwriting a file that changed on disk or could not be re-read. - Settings/Hooks editors unset only keys from the snapshot taken at Edit, warn when the file changed underneath, keep a renamed key's value, and refuse a new key named like a redacted one (env) or a read-only hook event, which used to replace the secret/hooks. - Project scope shows its resolved folder and is disabled inside the plugin cache. Account-profile, pricing-row and pet deletes confirm; two-click actions disarm after 3 s; unsaved drafts ask before closing. - Search indexes every Claude Code / Codex sub-tab, opens it via ?tab= and scrolls to + highlights the row; the active config tab is in the URL. - General splits into General / Data / About; Pricing moves to a Cost group with one shared load; config tabs group as Content / Extensions / Config with arrow-key roving; Promise.allSettled per tab. - Data: browser-preferences export/import (allowlisted keys), per-section reset, housekeeping dry-run first, clear-all behind a typed phrase. Server: purge days must be a whole number >= 1 (INVALID_PURGE_DAYS) and dry_run returns exactly what the real run would delete. - Alerts can be edited; webhooks/alerts/remote-source failures toast; copies go through copyText; modals get dialog roles and useDialog. - Sagi: Ctrl/Cmd+B only when shown and not typing, default spot clear of row controls, arrow keys move it, reduced motion honoured; the dead /run launch link goes to the workspace.
This commit is contained in:
@@ -1,46 +1,18 @@
|
||||
/**
|
||||
* @file ToastNotice.tsx
|
||||
* @description Bottom-right success/error toast for the config explorer.
|
||||
* Tool-agnostic — the page owns the 5s auto-dismiss timer and passes the
|
||||
* message down.
|
||||
* @description The config explorers' `{kind, message}` toast shape and
|
||||
* `showToast`, which hands it to the app-wide toast queue (`lib/toast.ts`): a
|
||||
* success fades on its own, an error stays until dismissed. The panels keep
|
||||
* taking an `onToast(Toast)` prop, so this is the one place it is rendered.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { AlertCircle, Check, X } from "lucide-react";
|
||||
import { toast } from "../../lib/toast";
|
||||
|
||||
export type Toast = { kind: "success" | "error"; message: string } | null;
|
||||
|
||||
export function ToastNotice({
|
||||
toast,
|
||||
onDismiss,
|
||||
}: {
|
||||
toast: NonNullable<Toast>;
|
||||
onDismiss: () => void;
|
||||
}) {
|
||||
const isErr = toast.kind === "error";
|
||||
return (
|
||||
<div className="fixed bottom-6 right-6 z-50 max-w-md">
|
||||
<div
|
||||
className={`rounded-lg border px-3 py-2 shadow-lg flex items-start gap-2 ${
|
||||
isErr
|
||||
? "border-status-danger/50 bg-status-danger/15 text-status-danger"
|
||||
: "border-status-success/40 bg-status-success/10 text-status-success"
|
||||
}`}
|
||||
>
|
||||
{isErr ? (
|
||||
<AlertCircle className="w-4 h-4 flex-shrink-0 mt-0.5" />
|
||||
) : (
|
||||
<Check className="w-4 h-4 flex-shrink-0 mt-0.5" />
|
||||
)}
|
||||
<span className="text-xs leading-relaxed flex-1 break-all">{toast.message}</span>
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className="text-current/70 hover:text-current p-0.5"
|
||||
aria-label="dismiss"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
/** Show a config-page toast through the global queue. */
|
||||
export function showToast(t: NonNullable<Toast>): void {
|
||||
if (t.kind === "error") toast.error(t.message);
|
||||
else toast.success(t.message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user