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:
h z
2026-05-16 17:28:13 +01:00
parent 045c7c51d6
commit 952387d50f
54 changed files with 4503 additions and 1765 deletions

View File

@@ -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)}
/>
&nbsp; 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)
}
/>
&nbsp; Markdown Created
</label>
<br />
<label className="checkbox">
<input
type="checkbox"
checked={isOnMarkdownUpdated}
onChange={(e) =>
handleTriggerEventsUpdate("MARKDOWN_UPDATED", e.target.checked)
}
/>
&nbsp; Markdown Updated
</label>
<br />
<label className="checkbox">
<input
type="checkbox"
checked={isOnMarkdownDeleted}
onChange={(e) =>
handleTriggerEventsUpdate("MARKDOWN_DELETED", e.target.checked)
}
/>
&nbsp; Markdown Deleted
</label>
</div>
<div className="column">
<label className="checkbox">
<input
type="checkbox"
checked={isOnPathCreated}
onChange={(e) =>
handleTriggerEventsUpdate("PATH_CREATED", e.target.checked)
}
/>
&nbsp; Path Created
</label>
<br />
<label className="checkbox">
<input
type="checkbox"
checked={isOnPathUpdated}
onChange={(e) =>
handleTriggerEventsUpdate("PATH_UPDATED", e.target.checked)
}
/>
&nbsp; Path Updated
</label>
<br />
<label className="checkbox">
<input
type="checkbox"
checked={isOnPathDeleted}
onChange={(e) =>
handleTriggerEventsUpdate("PATH_DELETED", e.target.checked)
}
/>
&nbsp; 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)}
/>
&nbsp; 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>
);
}