feat: dark-tech UI redesign + markdown patch cards
Redesign the frontend with a dark-tech theme: add Tailwind + PostCSS, design tokens, and shadcn-style primitives (Button/Card/Input/Dialog/ DropdownMenu/Tabs/ScrollArea/etc.); restyle the app shell, navigation, sidebar tree, content view, markdown rendering, editors, modals and settings panels. Behavior/props unchanged; Font Awesome replaced with lucide-react. Add the patch cards feature UI: patch-queries hooks and a PatchCards component rendered below the markdown body, with an Add Patch button and create/edit dialog. Fix tree expandability: folders with an index page now expand on name click (and navigate), and the chevron+folder icon is one larger toggle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,25 @@ import {
|
||||
useDeleteWebhook,
|
||||
} from "../../../utils/queries/webhook-queries";
|
||||
import {useUpdatePathSetting} from "../../../utils/queries/path-setting-queries";
|
||||
import { Plus, Save, Pencil, Trash2, Check } from "lucide-react";
|
||||
import { Button } from "../../ui/button";
|
||||
import { Input, Label } from "../../ui/input";
|
||||
import { Spinner } from "../../ui/misc";
|
||||
|
||||
const SELECT_CLASS =
|
||||
"flex h-9 w-full rounded-md border border-input bg-background/60 px-3 py-1 text-sm text-foreground transition-colors focus-visible:outline-none focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-ring/40";
|
||||
|
||||
const CheckboxRow = ({ checked, onChange, label }) => (
|
||||
<label className="flex cursor-pointer items-center gap-2 text-sm text-foreground">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 accent-primary"
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{label}
|
||||
</label>
|
||||
);
|
||||
|
||||
const WebhookSettingPanel = ({pathSetting, onClose}) => {
|
||||
|
||||
@@ -213,219 +232,179 @@ const WebhookSettingPanel = ({pathSetting, onClose}) => {
|
||||
};
|
||||
|
||||
return setting ? (
|
||||
<div className="box" style={{ marginTop: "1rem" }}>
|
||||
<h4 className="title is-5">Webhook Setting</h4>
|
||||
<div className="field">
|
||||
<label className="label">Select or Create a Webhook</label>
|
||||
<div className="field has-addons">
|
||||
<div className="control is-expanded">
|
||||
<div className="mt-4 space-y-5 rounded-lg border border-border bg-surface/40 p-5">
|
||||
<h4 className="font-mono text-sm font-semibold uppercase tracking-wide text-foreground">
|
||||
Webhook Setting
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
<Label>Select or Create a Webhook</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1">
|
||||
{isWebhooksLoading ? (
|
||||
<p>Loading...</p>
|
||||
<Spinner label="Loading webhooks" />
|
||||
) : (
|
||||
<div className="select is-fullwidth">
|
||||
<select
|
||||
value={selectedUrl}
|
||||
onChange={(e) => setSelectedUrl(e.target.value)}
|
||||
>
|
||||
<option value="">(none)</option>
|
||||
{webhooks.map((hook) => (
|
||||
<option key={hook.id} value={hook.hook_url}>
|
||||
{hook.hook_url}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<select
|
||||
className={SELECT_CLASS}
|
||||
value={selectedUrl}
|
||||
onChange={(e) => setSelectedUrl(e.target.value)}
|
||||
>
|
||||
<option value="">(none)</option>
|
||||
{webhooks.map((hook) => (
|
||||
<option key={hook.id} value={hook.hook_url}>
|
||||
{hook.hook_url}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
<div className="control">
|
||||
<button
|
||||
type="button"
|
||||
className="button is-primary"
|
||||
onClick={handleCreateWebhook}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCreateWebhook}
|
||||
>
|
||||
<Plus className="h-4 w-4" /> Add
|
||||
</Button>
|
||||
</div>
|
||||
{setting?.webhook_id && (
|
||||
<div className="buttons" style={{ marginTop: "0.5rem" }}>
|
||||
<button
|
||||
<div className="flex flex-wrap gap-2 pt-1">
|
||||
<Button
|
||||
type="button"
|
||||
className="button is-info"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleUpdateWebhook}
|
||||
>
|
||||
Update Webhook URL
|
||||
</button>
|
||||
<button
|
||||
<Pencil className="h-4 w-4" /> Update Webhook URL
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
className="button is-danger"
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleDeleteWebhook}
|
||||
>
|
||||
Delete Webhook
|
||||
</button>
|
||||
<Trash2 className="h-4 w-4" /> Delete Webhook
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked)}
|
||||
/>
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
<CheckboxRow
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked)}
|
||||
label="Enabled"
|
||||
/>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">On Events</label>
|
||||
<div className="box">
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isOnMarkdownCreated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("MARKDOWN_CREATED", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Markdown Created
|
||||
</label>
|
||||
<br />
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isOnMarkdownUpdated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("MARKDOWN_UPDATED", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Markdown Updated
|
||||
</label>
|
||||
<br />
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isOnMarkdownDeleted}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("MARKDOWN_DELETED", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Markdown Deleted
|
||||
</label>
|
||||
</div>
|
||||
<div className="column">
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isOnPathCreated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("PATH_CREATED", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Path Created
|
||||
</label>
|
||||
<br />
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isOnPathUpdated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("PATH_UPDATED", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Path Updated
|
||||
</label>
|
||||
<br />
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isOnPathDeleted}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("PATH_DELETED", e.target.checked)
|
||||
}
|
||||
/>
|
||||
Path Deleted
|
||||
</label>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>On Events</Label>
|
||||
<div className="grid grid-cols-1 gap-3 rounded-md border border-border bg-background/40 p-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<CheckboxRow
|
||||
checked={isOnMarkdownCreated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("MARKDOWN_CREATED", e.target.checked)
|
||||
}
|
||||
label="Markdown Created"
|
||||
/>
|
||||
<CheckboxRow
|
||||
checked={isOnMarkdownUpdated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("MARKDOWN_UPDATED", e.target.checked)
|
||||
}
|
||||
label="Markdown Updated"
|
||||
/>
|
||||
<CheckboxRow
|
||||
checked={isOnMarkdownDeleted}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("MARKDOWN_DELETED", e.target.checked)
|
||||
}
|
||||
label="Markdown Deleted"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<CheckboxRow
|
||||
checked={isOnPathCreated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("PATH_CREATED", e.target.checked)
|
||||
}
|
||||
label="Path Created"
|
||||
/>
|
||||
<CheckboxRow
|
||||
checked={isOnPathUpdated}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("PATH_UPDATED", e.target.checked)
|
||||
}
|
||||
label="Path Updated"
|
||||
/>
|
||||
<CheckboxRow
|
||||
checked={isOnPathDeleted}
|
||||
onChange={(e) =>
|
||||
handleTriggerEventsUpdate("PATH_DELETED", e.target.checked)
|
||||
}
|
||||
label="Path Deleted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isRecursive}
|
||||
onChange={(e) => setIsRecursive(e.target.checked)}
|
||||
/>
|
||||
Recursive
|
||||
</label>
|
||||
</div>
|
||||
<CheckboxRow
|
||||
checked={isRecursive}
|
||||
onChange={(e) => setIsRecursive(e.target.checked)}
|
||||
label="Recursive"
|
||||
/>
|
||||
|
||||
<div className="field">
|
||||
<label className="label">Additional Headers</label>
|
||||
<div className="box">
|
||||
<div className="space-y-2">
|
||||
<Label>Additional Headers</Label>
|
||||
<div className="space-y-3 rounded-md border border-border bg-background/40 p-4">
|
||||
{headerList.map((h, idx) => (
|
||||
<div className="columns" key={idx}>
|
||||
<div className="column">
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
placeholder="key"
|
||||
value={h.key}
|
||||
onChange={(e) => handleHeaderChange(idx, "key", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="column">
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
placeholder="value"
|
||||
value={h.value}
|
||||
onChange={(e) =>
|
||||
handleHeaderChange(idx, "value", e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2" key={idx}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="key"
|
||||
value={h.key}
|
||||
onChange={(e) => handleHeaderChange(idx, "key", e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="value"
|
||||
value={h.value}
|
||||
onChange={(e) =>
|
||||
handleHeaderChange(idx, "value", e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="button is-small is-info"
|
||||
onClick={handleAddHeader}
|
||||
>
|
||||
+ Header
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="button is-small is-success"
|
||||
onClick={handleApplyHeaders}
|
||||
style={{ marginLeft: "0.5rem" }}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleAddHeader}
|
||||
>
|
||||
<Plus className="h-4 w-4" /> Header
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={handleApplyHeaders}
|
||||
>
|
||||
<Check className="h-4 w-4" /> Apply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
className="button is-primary"
|
||||
onClick={handleSaveWebhookSetting}
|
||||
>
|
||||
Save Webhook Setting
|
||||
</button>
|
||||
<Save className="h-4 w-4" /> Save Webhook Setting
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="button is-primary"
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCreateWebhookSetting}
|
||||
>
|
||||
Create Webhook Setting
|
||||
</button>
|
||||
<Plus className="h-4 w-4" /> Create Webhook Setting
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user