milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
This commit is contained in:
@@ -4,11 +4,7 @@ import { formatTime } from "@/lib/format";
|
||||
import InlineError from "@/lib/InlineError";
|
||||
import { groupsQuery, ruleCreateMutation, ruleDeleteMutation, rulesQuery } from "@/lib/queries";
|
||||
import type { Rule, RuleAction, RuleKind } from "@/lib/types";
|
||||
|
||||
const TH_CLASS = "border-b border-zinc-300 px-3 py-2 text-left font-medium dark:border-zinc-700";
|
||||
const TD_CLASS = "border-b border-zinc-200 px-3 py-2 dark:border-zinc-800";
|
||||
const INPUT_CLASS =
|
||||
"mt-1 w-full rounded border border-zinc-300 bg-white px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-zinc-700 dark:bg-zinc-900";
|
||||
import { dangerLinkButtonClass, inputClass, primaryButtonClass, tableWrapClass, tdClass, thClass } from "@/ui/classes";
|
||||
|
||||
export default function RulesPage() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -44,16 +40,16 @@ export default function RulesPage() {
|
||||
{rules.length === 0 ? (
|
||||
<p className="mt-4 text-zinc-500">No allow or block rules yet. Create one below.</p>
|
||||
) : (
|
||||
<div className="mt-4 overflow-x-auto">
|
||||
<div className={tableWrapClass}>
|
||||
<table className="w-full min-w-max border-collapse text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={TH_CLASS}>Pattern</th>
|
||||
<th className={TH_CLASS}>Kind</th>
|
||||
<th className={TH_CLASS}>Action</th>
|
||||
<th className={TH_CLASS}>Group</th>
|
||||
<th className={TH_CLASS}>Created</th>
|
||||
<th className={TH_CLASS}>
|
||||
<th className={thClass}>Pattern</th>
|
||||
<th className={thClass}>Kind</th>
|
||||
<th className={thClass}>Action</th>
|
||||
<th className={thClass}>Group</th>
|
||||
<th className={thClass}>Created</th>
|
||||
<th className={thClass}>
|
||||
<span className="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
@@ -61,9 +57,9 @@ export default function RulesPage() {
|
||||
<tbody>
|
||||
{rules.map((rule) => (
|
||||
<tr key={rule.id}>
|
||||
<td className={`${TD_CLASS} font-medium`}>{rule.pattern}</td>
|
||||
<td className={TD_CLASS}>{rule.kind}</td>
|
||||
<td className={TD_CLASS}>
|
||||
<td className={`${tdClass} font-medium`}>{rule.pattern}</td>
|
||||
<td className={tdClass}>{rule.kind}</td>
|
||||
<td className={tdClass}>
|
||||
<span
|
||||
className={
|
||||
rule.action === "allow"
|
||||
@@ -74,14 +70,14 @@ export default function RulesPage() {
|
||||
{rule.action}
|
||||
</span>
|
||||
</td>
|
||||
<td className={TD_CLASS}>{rule.group}</td>
|
||||
<td className={TD_CLASS}>{formatTime(rule.created_at)}</td>
|
||||
<td className={TD_CLASS}>
|
||||
<td className={tdClass}>{rule.group}</td>
|
||||
<td className={tdClass}>{formatTime(rule.created_at)}</td>
|
||||
<td className={tdClass}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => deleteRule(rule)}
|
||||
disabled={remove.isPending}
|
||||
className="text-sm font-medium text-red-600 disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:text-red-400"
|
||||
className={dangerLinkButtonClass}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@@ -107,7 +103,7 @@ export default function RulesPage() {
|
||||
value={pattern}
|
||||
onChange={(event) => setPattern(event.target.value)}
|
||||
placeholder="ads.example.com or *.example.com"
|
||||
className={INPUT_CLASS}
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
@@ -119,7 +115,7 @@ export default function RulesPage() {
|
||||
id="rule-kind"
|
||||
value={kind}
|
||||
onChange={(event) => setKind(event.target.value as RuleKind)}
|
||||
className={INPUT_CLASS}
|
||||
className={inputClass}
|
||||
>
|
||||
<option value="exact">exact</option>
|
||||
<option value="wildcard">wildcard</option>
|
||||
@@ -133,7 +129,7 @@ export default function RulesPage() {
|
||||
id="rule-action"
|
||||
value={action}
|
||||
onChange={(event) => setAction(event.target.value as RuleAction)}
|
||||
className={INPUT_CLASS}
|
||||
className={inputClass}
|
||||
>
|
||||
<option value="allow">allow</option>
|
||||
<option value="block">block</option>
|
||||
@@ -147,7 +143,7 @@ export default function RulesPage() {
|
||||
id="rule-group"
|
||||
value={groupId}
|
||||
onChange={(event) => setGroupId(Number(event.target.value))}
|
||||
className={INPUT_CLASS}
|
||||
className={inputClass}
|
||||
>
|
||||
{groups.map((group) => (
|
||||
<option key={group.id} value={group.id}>
|
||||
@@ -157,11 +153,7 @@ export default function RulesPage() {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={create.isPending}
|
||||
className="rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||
>
|
||||
<button type="submit" disabled={create.isPending} className={primaryButtonClass}>
|
||||
{create.isPending ? "Creating…" : "Create rule"}
|
||||
</button>
|
||||
<InlineError error={create.error} />
|
||||
|
||||
Reference in New Issue
Block a user