Dismissable alert use, DataGrid rework
This commit is contained in:
@@ -3,6 +3,7 @@ import type { ApiSettings, LoginResponse } from "../../types";
|
||||
import { login } from "../../api/auth";
|
||||
import Button from "../../components/Button";
|
||||
import FormField from "../../components/FormField";
|
||||
import DismissibleAlert from "../../components/DismissibleAlert";
|
||||
|
||||
export default function LoginModal({
|
||||
settings,
|
||||
@@ -42,7 +43,7 @@ export default function LoginModal({
|
||||
</header>
|
||||
<div className="modal-body form-grid">
|
||||
<div className="login-hint">Development default: user <strong>admin@example.local</strong>, password <strong>dev-admin</strong>.</div>
|
||||
{error && <div className="alert danger">{error}</div>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
<FormField label="Email">
|
||||
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</FormField>
|
||||
|
||||
@@ -86,8 +86,8 @@ export default function AttachmentsDataPage({ settings, campaignId }: { settings
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing attachments." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function CampaignAuditPage({ settings, campaignId }: { settings:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
|
||||
<LoadingFrame loading={loading} label="Loading audit data…">
|
||||
<Card title="Recent audit events">
|
||||
|
||||
@@ -156,9 +156,9 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning}>{fieldNameWarning}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning} floating>{fieldNameWarning}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing fields." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function CampaignJsonView({ settings, campaignId }: { settings: A
|
||||
<Button onClick={() => downloadJson(filename, campaignJson)} disabled={!version}>Download JSON</Button>
|
||||
</div>
|
||||
</div>
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
<LoadingFrame loading={loading} label="Loading JSON…">
|
||||
<Card>
|
||||
{!loading || version ? <pre className="code-panel">{JSON.stringify(campaignJson, null, 2)}</pre> : <pre className="code-panel">{"{}"}</pre>}
|
||||
|
||||
@@ -115,7 +115,7 @@ export default function CampaignListPage({ settings }: { settings: ApiSettings }
|
||||
<DismissibleAlert tone="warning">Sign in with your user account or configure an automation API key under Settings to load campaigns.</DismissibleAlert>
|
||||
)}
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
|
||||
<div className="page-heading split workspace-heading">
|
||||
<div>
|
||||
|
||||
@@ -96,8 +96,8 @@ export default function CampaignOverviewPage({ settings, campaignId }: { setting
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{message && <DismissibleAlert tone="success" resetKey={message}>{message}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{message && <DismissibleAlert tone="success" resetKey={message} floating>{message}</DismissibleAlert>}
|
||||
|
||||
<LoadingFrame loading={loading} label="Loading campaign overview…">
|
||||
<Card title="Campaign identity">
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function CampaignReportPage({ settings, campaignId }: { settings:
|
||||
<Button onClick={reload} disabled={loading}>Reload</Button>
|
||||
</div>
|
||||
</div>
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
<LoadingFrame loading={loading} label="Loading report data…">
|
||||
<div className="dashboard-grid">
|
||||
<Card title="Report summary">
|
||||
|
||||
@@ -68,8 +68,8 @@ export default function GlobalSettingsPage({ settings, campaignId }: { settings:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing global settings." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
|
||||
@@ -261,8 +261,8 @@ export default function MailSettingsPage({ settings, campaignId }: { settings: A
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing server settings." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
@@ -331,7 +331,7 @@ export default function MailSettingsPage({ settings, campaignId }: { settings: A
|
||||
<Button variant="danger" onClick={clearMockMailbox} disabled={mailActionState === "mock" || mockMessages.length === 0}>Clear</Button>
|
||||
</div>
|
||||
</div>
|
||||
{mockError && <DismissibleAlert tone="danger" resetKey={mockError}>{mockError}</DismissibleAlert>}
|
||||
{mockError && <DismissibleAlert tone="danger" resetKey={mockError} floating>{mockError}</DismissibleAlert>}
|
||||
<DataGrid
|
||||
id={`campaign-${campaignId}-server-mock-mailbox`}
|
||||
rows={mockMessages}
|
||||
@@ -411,23 +411,23 @@ function MailActionResult({ result }: { result: MailConnectionTestResponse | nul
|
||||
if (!result) return null;
|
||||
const authenticated = result.details?.authenticated;
|
||||
return (
|
||||
<div className={`alert ${result.ok ? "success" : "danger"}`}>
|
||||
<DismissibleAlert tone={result.ok ? "success" : "danger"} resetKey={`${result.ok}:${result.message}`}>
|
||||
{result.message}
|
||||
{result.ok && typeof authenticated === "boolean" && (
|
||||
<span> Authentication: {authenticated ? "credentials accepted" : "not used"}.</span>
|
||||
)}
|
||||
</div>
|
||||
</DismissibleAlert>
|
||||
);
|
||||
}
|
||||
|
||||
function FolderLookupResult({ result, disabled, onUseDetected }: { result: MailImapFolderListResponse | null; disabled?: boolean; onUseDetected: () => void }) {
|
||||
if (!result) return null;
|
||||
if (!result.ok) {
|
||||
return <div className="alert danger">{result.message}</div>;
|
||||
return <DismissibleAlert tone="danger" resetKey={result.message}>{result.message}</DismissibleAlert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alert success">
|
||||
<DismissibleAlert tone="success" resetKey={`${result.message}:${result.detected_sent_folder || ""}`}>
|
||||
<p>{result.message}</p>
|
||||
<p>Detected Sent folder: <strong>{result.detected_sent_folder || "—"}</strong></p>
|
||||
{result.detected_sent_folder && <Button onClick={onUseDetected} disabled={disabled}>Use detected folder</Button>}
|
||||
@@ -439,6 +439,6 @@ function FolderLookupResult({ result, disabled, onUseDetected }: { result: MailI
|
||||
{result.folders.length > 12 && <span className="field-chip">+{result.folders.length - 12} more</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DismissibleAlert>
|
||||
);
|
||||
}
|
||||
@@ -136,8 +136,8 @@ export default function RecipientDataPage({ settings, campaignId }: { settings:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing sender or recipient profiles." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
|
||||
@@ -81,8 +81,8 @@ export default function RecipientDetailsPage({ settings, campaignId }: { setting
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing recipient data." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
|
||||
@@ -148,9 +148,9 @@ export default function SendDataPage({ settings, campaignId }: { settings: ApiSe
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{sendMessage && <DismissibleAlert tone="info" resetKey={sendMessage}>{sendMessage}</DismissibleAlert>}
|
||||
{mockMessage && <DismissibleAlert tone="info" resetKey={mockMessage}>{mockMessage}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{sendMessage && <DismissibleAlert tone="info" resetKey={sendMessage} floating>{sendMessage}</DismissibleAlert>}
|
||||
{mockMessage && <DismissibleAlert tone="info" resetKey={mockMessage} floating>{mockMessage}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Send snapshot. Copy to edit." />}
|
||||
|
||||
<LoadingFrame loading={loading || queuedOrSending} label={queuedOrSending ? "Refreshing send state…" : "Loading send data…"}>
|
||||
|
||||
@@ -142,8 +142,8 @@ export default function TemplateDataPage({ settings, campaignId }: { settings: A
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} reload={reload} message="Create an editable copy before changing the template." />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function ReviewWorkflowCards({
|
||||
|
||||
return (
|
||||
<>
|
||||
{actionMessage && <DismissibleAlert tone="info" resetKey={actionMessage}>{actionMessage}</DismissibleAlert>}
|
||||
{actionMessage && <DismissibleAlert tone="info" resetKey={actionMessage} floating>{actionMessage}</DismissibleAlert>}
|
||||
|
||||
<Card
|
||||
title="Review actions"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Button from "../../../components/Button";
|
||||
import DismissibleAlert from "../../../components/DismissibleAlert";
|
||||
|
||||
type NavigationAction = () => void;
|
||||
|
||||
@@ -146,7 +147,7 @@ export function CampaignUnsavedChangesProvider({ children }: { children: ReactNo
|
||||
</header>
|
||||
<div className="modal-body">
|
||||
<p>{registration.message ?? "This campaign page has unsaved changes. Save them before leaving, or discard the changes and continue."}</p>
|
||||
{saveError && <div className="alert danger">{saveError}</div>}
|
||||
{saveError && <DismissibleAlert tone="danger" resetKey={saveError}>{saveError}</DismissibleAlert>}
|
||||
</div>
|
||||
<footer className="modal-footer unsaved-changes-actions">
|
||||
<Button onClick={() => setPendingAction(null)} disabled={saving}>Cancel</Button>
|
||||
|
||||
@@ -117,8 +117,8 @@ export default function CreateWizard({ settings, campaignId }: { settings: ApiSe
|
||||
</div>
|
||||
<div className="save-state">{saveState}</div>
|
||||
</div>
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError}>{localError}</DismissibleAlert>}
|
||||
{validationMessage && <DismissibleAlert tone="info" resetKey={validationMessage}>{validationMessage}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{validationMessage && <DismissibleAlert tone="info" resetKey={validationMessage} floating>{validationMessage}</DismissibleAlert>}
|
||||
<Card>
|
||||
{draft && activeStep === "basics" && <BasicsStep draft={draft} patch={patch} />}
|
||||
{draft && activeStep === "sender" && <SenderStep draft={draft} patch={patch} />}
|
||||
|
||||
@@ -1102,6 +1102,8 @@ export default function FilesPage({ settings }: { settings: ApiSettings }) {
|
||||
return `${label} ${sortDirection === "asc" ? "↑" : "↓"}`;
|
||||
}
|
||||
|
||||
const noticeTone = message.startsWith("No files") ? "warning" : message.endsWith("…") ? "info" : "success";
|
||||
|
||||
const toolbar = (
|
||||
<div className="file-manager-toolbar" aria-label="File actions">
|
||||
<Button variant="primary" onClick={() => openDialog("upload", toolbarTarget())} disabled={busy || !activeSpace}><UploadCloud size={16} aria-hidden="true" /> Upload</Button>
|
||||
@@ -1119,6 +1121,9 @@ export default function FilesPage({ settings }: { settings: ApiSettings }) {
|
||||
{error && (
|
||||
<DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>
|
||||
)}
|
||||
{message && !error && (
|
||||
<DismissibleAlert tone={noticeTone} resetKey={message} floating>{message}</DismissibleAlert>
|
||||
)}
|
||||
|
||||
<div className={`file-manager-shell ${busy ? "is-loading" : ""}`}>
|
||||
<aside className="file-tree-panel" aria-label="File spaces and folders">
|
||||
|
||||
@@ -7,6 +7,7 @@ import PageTitle from "../../components/PageTitle";
|
||||
import ToggleSwitch from "../../components/ToggleSwitch";
|
||||
import { apiFetch } from "../../api/client";
|
||||
import ModuleSubnav, { type ModuleSubnavGroup } from "../../layout/ModuleSubnav";
|
||||
import DismissibleAlert from "../../components/DismissibleAlert";
|
||||
|
||||
type SettingsSection = "interface" | "workspace" | "local-connection" | "notifications";
|
||||
|
||||
@@ -134,7 +135,7 @@ export default function SettingsPage({ settings, onSettingsChange }: { settings:
|
||||
<div className="button-row compact-actions">
|
||||
<Button variant="primary" onClick={testConnection} disabled={testing}>{testing ? "Testing…" : "Test connection"}</Button>
|
||||
</div>
|
||||
{testResult && <div className={`alert ${testResult.startsWith("Connection successful") ? "success" : "warning"}`}>{testResult}</div>}
|
||||
{testResult && <DismissibleAlert tone={testResult.startsWith("Connection successful") ? "success" : "warning"} resetKey={testResult}>{testResult}</DismissibleAlert>}
|
||||
</div>
|
||||
</Card>
|
||||
<Card title="Session state">
|
||||
|
||||
Reference in New Issue
Block a user