refactor: clean up code formatting and improve readability across multiple files

This commit is contained in:
Abhimanyu Saharan
2026-02-08 21:17:26 +05:30
parent e03125a382
commit 60744ddfac
24 changed files with 811 additions and 778 deletions

View File

@@ -162,6 +162,7 @@ Clerk should be **off** unless you set a real `pk_test_...` or `pk_live_...` pub
If you see repeated proxy errors (often `ECONNRESET`), make sure your dev server hostname and browser URL match (e.g. `localhost` vs `127.0.0.1`), and that your origin is included in `allowedDevOrigins`.
Notes:
- Local dev should work via `http://localhost:3000` and `http://127.0.0.1:3000`.
- LAN dev should work via the configured LAN IP (e.g. `http://192.168.1.101:3000`) **only** if you bind the dev server to all interfaces (`npm run dev:lan`).
- If you bind Next to `127.0.0.1` only, remote LAN clients wont connect.

View File

@@ -212,190 +212,193 @@ export default function AgentDetailPage() {
</div>
) : (
<div className="flex h-full flex-col gap-6 rounded-2xl surface-panel p-8">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
Agents
</p>
<h1 className="text-2xl font-semibold text-strong">
{agent?.name ?? "Agent"}
</h1>
<p className="text-sm text-muted">
Review agent health, session binding, and recent activity.
</p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => router.push("/agents")}>
Back to agents
</Button>
{agent ? (
<Link
href={`/agents/${agent.id}/edit`}
className="inline-flex h-10 items-center justify-center rounded-xl border border-[color:var(--border)] px-4 text-sm font-semibold text-muted transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
>
Edit
</Link>
) : null}
{agent ? (
<Button variant="outline" onClick={() => setDeleteOpen(true)}>
Delete
</Button>
) : null}
</div>
</div>
{error ? (
<div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{error}
</div>
) : null}
{isLoading ? (
<div className="flex flex-1 items-center justify-center text-sm text-muted">
Loading agent details
</div>
) : agent ? (
<div className="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
<div className="space-y-6">
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] p-5">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Overview
</p>
<p className="mt-1 text-lg font-semibold text-strong">
{agent.name}
</p>
</div>
<StatusPill status={agentStatus} />
</div>
<div className="mt-4 grid gap-4 md:grid-cols-2">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Agent ID
</p>
<p className="mt-1 text-sm text-muted">{agent.id}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Session key
</p>
<p className="mt-1 text-sm text-muted">
{agent.openclaw_session_id ?? "—"}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Board
</p>
{agent.is_gateway_main ? (
<p className="mt-1 text-sm text-strong">
Gateway main (no board)
</p>
) : linkedBoard ? (
<Link
href={`/boards/${linkedBoard.id}`}
className="mt-1 inline-flex text-sm font-medium text-[color:var(--accent)] transition hover:underline"
>
{linkedBoard.name}
</Link>
) : (
<p className="mt-1 text-sm text-strong"></p>
)}
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Last seen
</p>
<p className="mt-1 text-sm text-strong">
{formatRelative(agent.last_seen_at)}
</p>
<p className="text-xs text-quiet">
{formatTimestamp(agent.last_seen_at)}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Updated
</p>
<p className="mt-1 text-sm text-muted">
{formatTimestamp(agent.updated_at)}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Created
</p>
<p className="mt-1 text-sm text-muted">
{formatTimestamp(agent.created_at)}
</p>
</div>
</div>
</div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] p-5">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Health
</p>
<StatusPill status={agentStatus} />
</div>
<div className="mt-4 grid gap-3 text-sm text-muted">
<div className="flex items-center justify-between">
<span>Heartbeat window</span>
<span>{formatRelative(agent.last_seen_at)}</span>
</div>
<div className="flex items-center justify-between">
<span>Session binding</span>
<span>
{agent.openclaw_session_id ? "Bound" : "Unbound"}
</span>
</div>
<div className="flex items-center justify-between">
<span>Status</span>
<span className="text-strong">{agentStatus}</span>
</div>
</div>
</div>
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
Agents
</p>
<h1 className="text-2xl font-semibold text-strong">
{agent?.name ?? "Agent"}
</h1>
<p className="text-sm text-muted">
Review agent health, session binding, and recent activity.
</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
onClick={() => router.push("/agents")}
>
Back to agents
</Button>
{agent ? (
<Link
href={`/agents/${agent.id}/edit`}
className="inline-flex h-10 items-center justify-center rounded-xl border border-[color:var(--border)] px-4 text-sm font-semibold text-muted transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
>
Edit
</Link>
) : null}
{agent ? (
<Button variant="outline" onClick={() => setDeleteOpen(true)}>
Delete
</Button>
) : null}
</div>
</div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-5">
<div className="mb-4 flex items-center justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Activity
</p>
<p className="text-xs text-quiet">
{agentEvents.length} events
</p>
</div>
<div className="space-y-3">
{agentEvents.length === 0 ? (
<div className="rounded-lg border border-dashed border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted">
No activity yet for this agent.
</div>
) : (
agentEvents.map((event) => (
<div
key={event.id}
className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted"
>
<p className="font-medium text-strong">
{event.message ?? event.event_type}
{error ? (
<div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3 text-xs text-muted">
{error}
</div>
) : null}
{isLoading ? (
<div className="flex flex-1 items-center justify-center text-sm text-muted">
Loading agent details
</div>
) : agent ? (
<div className="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
<div className="space-y-6">
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] p-5">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Overview
</p>
<p className="mt-1 text-xs text-quiet">
{formatTimestamp(event.created_at)}
<p className="mt-1 text-lg font-semibold text-strong">
{agent.name}
</p>
</div>
))
)}
<StatusPill status={agentStatus} />
</div>
<div className="mt-4 grid gap-4 md:grid-cols-2">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Agent ID
</p>
<p className="mt-1 text-sm text-muted">{agent.id}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Session key
</p>
<p className="mt-1 text-sm text-muted">
{agent.openclaw_session_id ?? "—"}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Board
</p>
{agent.is_gateway_main ? (
<p className="mt-1 text-sm text-strong">
Gateway main (no board)
</p>
) : linkedBoard ? (
<Link
href={`/boards/${linkedBoard.id}`}
className="mt-1 inline-flex text-sm font-medium text-[color:var(--accent)] transition hover:underline"
>
{linkedBoard.name}
</Link>
) : (
<p className="mt-1 text-sm text-strong"></p>
)}
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Last seen
</p>
<p className="mt-1 text-sm text-strong">
{formatRelative(agent.last_seen_at)}
</p>
<p className="text-xs text-quiet">
{formatTimestamp(agent.last_seen_at)}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Updated
</p>
<p className="mt-1 text-sm text-muted">
{formatTimestamp(agent.updated_at)}
</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Created
</p>
<p className="mt-1 text-sm text-muted">
{formatTimestamp(agent.created_at)}
</p>
</div>
</div>
</div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] p-5">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Health
</p>
<StatusPill status={agentStatus} />
</div>
<div className="mt-4 grid gap-3 text-sm text-muted">
<div className="flex items-center justify-between">
<span>Heartbeat window</span>
<span>{formatRelative(agent.last_seen_at)}</span>
</div>
<div className="flex items-center justify-between">
<span>Session binding</span>
<span>
{agent.openclaw_session_id ? "Bound" : "Unbound"}
</span>
</div>
<div className="flex items-center justify-between">
<span>Status</span>
<span className="text-strong">{agentStatus}</span>
</div>
</div>
</div>
</div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-5">
<div className="mb-4 flex items-center justify-between">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
Activity
</p>
<p className="text-xs text-quiet">
{agentEvents.length} events
</p>
</div>
<div className="space-y-3">
{agentEvents.length === 0 ? (
<div className="rounded-lg border border-dashed border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted">
No activity yet for this agent.
</div>
) : (
agentEvents.map((event) => (
<div
key={event.id}
className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 text-sm text-muted"
>
<p className="font-medium text-strong">
{event.message ?? event.event_type}
</p>
<p className="mt-1 text-xs text-quiet">
{formatTimestamp(event.created_at)}
</p>
</div>
))
)}
</div>
</div>
</div>
</div>
) : (
<div className="flex flex-1 items-center justify-center text-sm text-muted">
Agent not found.
</div>
)}
) : (
<div className="flex flex-1 items-center justify-center text-sm text-muted">
Agent not found.
</div>
)}
</div>
)}
</SignedIn>

View File

@@ -214,184 +214,189 @@ export default function NewAgentPage() {
Basic configuration
</p>
<div className="mt-4 space-y-6">
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Agent name <span className="text-red-500">*</span>
</label>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="e.g. Deploy bot"
disabled={isLoading}
/>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Agent name <span className="text-red-500">*</span>
</label>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="e.g. Deploy bot"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Role
</label>
<Input
value={identityProfile.role}
onChange={(event) =>
setIdentityProfile((current) => ({
...current,
role: event.target.value,
}))
}
placeholder="e.g. Founder, Social Media Manager"
disabled={isLoading}
/>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Board <span className="text-red-500">*</span>
</label>
<SearchableSelect
ariaLabel="Select board"
value={displayBoardId}
onValueChange={setBoardId}
options={getBoardOptions(boards)}
placeholder="Select board"
searchPlaceholder="Search boards..."
emptyMessage="No matching boards."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={boards.length === 0}
/>
{boards.length === 0 ? (
<p className="text-xs text-slate-500">
Create a board before adding agents.
</p>
) : null}
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Emoji
</label>
<Select
value={identityProfile.emoji}
onValueChange={(value) =>
setIdentityProfile((current) => ({
...current,
emoji: value,
}))
}
disabled={isLoading}
>
<SelectTrigger>
<SelectValue placeholder="Select emoji" />
</SelectTrigger>
<SelectContent>
{EMOJI_OPTIONS.map((option) => (
<SelectItem
key={option.value}
value={option.value}
>
{option.glyph} {option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Personality & behavior
</p>
<div className="mt-4 space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Role
Communication style
</label>
<Input
value={identityProfile.role}
value={identityProfile.communication_style}
onChange={(event) =>
setIdentityProfile((current) => ({
...current,
role: event.target.value,
communication_style: event.target.value,
}))
}
placeholder="e.g. Founder, Social Media Manager"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Soul template
</label>
<Textarea
value={soulTemplate}
onChange={(event) =>
setSoulTemplate(event.target.value)
}
rows={10}
disabled={isLoading}
/>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Schedule & notifications
</p>
<div className="mt-4 grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Board <span className="text-red-500">*</span>
Interval
</label>
<Input
value={heartbeatEvery}
onChange={(event) =>
setHeartbeatEvery(event.target.value)
}
placeholder="e.g. 10m"
disabled={isLoading}
/>
<p className="text-xs text-slate-500">
How often this agent runs HEARTBEAT.md (10m, 30m, 2h).
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Target
</label>
<SearchableSelect
ariaLabel="Select board"
value={displayBoardId}
onValueChange={setBoardId}
options={getBoardOptions(boards)}
placeholder="Select board"
searchPlaceholder="Search boards..."
emptyMessage="No matching boards."
ariaLabel="Select heartbeat target"
value={heartbeatTarget}
onValueChange={setHeartbeatTarget}
options={HEARTBEAT_TARGET_OPTIONS}
placeholder="Select target"
searchPlaceholder="Search targets..."
emptyMessage="No matching targets."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={boards.length === 0}
/>
{boards.length === 0 ? (
<p className="text-xs text-slate-500">
Create a board before adding agents.
</p>
) : null}
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Emoji
</label>
<Select
value={identityProfile.emoji}
onValueChange={(value) =>
setIdentityProfile((current) => ({
...current,
emoji: value,
}))
}
disabled={isLoading}
>
<SelectTrigger>
<SelectValue placeholder="Select emoji" />
</SelectTrigger>
<SelectContent>
{EMOJI_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.glyph} {option.label}
</SelectItem>
))}
</SelectContent>
</Select>
/>
</div>
</div>
</div>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Personality & behavior
</p>
<div className="mt-4 space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Communication style
</label>
<Input
value={identityProfile.communication_style}
onChange={(event) =>
setIdentityProfile((current) => ({
...current,
communication_style: event.target.value,
}))
}
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Soul template
</label>
<Textarea
value={soulTemplate}
onChange={(event) => setSoulTemplate(event.target.value)}
rows={10}
disabled={isLoading}
/>
{errorMessage ? (
<div className="rounded-lg border border-slate-200 bg-white p-3 text-sm text-slate-600 shadow-sm">
{errorMessage}
</div>
) : null}
<div className="flex flex-wrap items-center gap-3">
<Button type="submit" disabled={isLoading}>
{isLoading ? "Creating…" : "Create agent"}
</Button>
<Button
variant="outline"
type="button"
onClick={() => router.push("/agents")}
>
Back to agents
</Button>
</div>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Schedule & notifications
</p>
<div className="mt-4 grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Interval
</label>
<Input
value={heartbeatEvery}
onChange={(event) =>
setHeartbeatEvery(event.target.value)
}
placeholder="e.g. 10m"
disabled={isLoading}
/>
<p className="text-xs text-slate-500">
How often this agent runs HEARTBEAT.md (10m, 30m, 2h).
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Target
</label>
<SearchableSelect
ariaLabel="Select heartbeat target"
value={heartbeatTarget}
onValueChange={setHeartbeatTarget}
options={HEARTBEAT_TARGET_OPTIONS}
placeholder="Select target"
searchPlaceholder="Search targets..."
emptyMessage="No matching targets."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={isLoading}
/>
</div>
</div>
</div>
{errorMessage ? (
<div className="rounded-lg border border-slate-200 bg-white p-3 text-sm text-slate-600 shadow-sm">
{errorMessage}
</div>
) : null}
<div className="flex flex-wrap items-center gap-3">
<Button type="submit" disabled={isLoading}>
{isLoading ? "Creating…" : "Create agent"}
</Button>
<Button
variant="outline"
type="button"
onClick={() => router.push("/agents")}
>
Back to agents
</Button>
</div>
</form>
</form>
)}
</div>
</main>

View File

@@ -350,88 +350,88 @@ export default function AgentsPage() {
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead className="sticky top-0 z-10 bg-slate-50 text-xs font-semibold uppercase tracking-wider text-slate-500">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} className="px-6 py-3">
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
<thead className="sticky top-0 z-10 bg-slate-50 text-xs font-semibold uppercase tracking-wider text-slate-500">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} className="px-6 py-3">
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
))}
</tr>
))}
</thead>
<tbody className="divide-y divide-slate-100">
{agentsQuery.isLoading ? (
<tr>
<td colSpan={columns.length} className="px-6 py-8">
<span className="text-sm text-slate-500">
Loading
</span>
</td>
</tr>
) : table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-slate-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-6 py-4">
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</thead>
<tbody className="divide-y divide-slate-100">
{agentsQuery.isLoading ? (
<tr>
<td colSpan={columns.length} className="px-6 py-8">
<span className="text-sm text-slate-500">
Loading
</span>
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={columns.length} className="px-6 py-16">
<div className="flex flex-col items-center justify-center text-center">
<div className="mb-4 rounded-full bg-slate-50 p-4">
<svg
className="h-16 w-16 text-slate-300"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-900">
No agents yet
</h3>
<p className="mb-6 max-w-md text-sm text-slate-500">
Create your first agent to start executing tasks
on this board.
</p>
<Link
href="/agents/new"
className={buttonVariants({
size: "md",
variant: "primary",
})}
>
Create your first agent
</Link>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</tr>
) : table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-slate-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-6 py-4">
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={columns.length} className="px-6 py-16">
<div className="flex flex-col items-center justify-center text-center">
<div className="mb-4 rounded-full bg-slate-50 p-4">
<svg
className="h-16 w-16 text-slate-300"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-900">
No agents yet
</h3>
<p className="mb-6 max-w-md text-sm text-slate-500">
Create your first agent to start executing
tasks on this board.
</p>
<Link
href="/agents/new"
className={buttonVariants({
size: "md",
variant: "primary",
})}
>
Create your first agent
</Link>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
{agentsQuery.error ? (
<p className="mt-4 text-sm text-red-500">

View File

@@ -710,7 +710,13 @@ export default function BoardGroupDetailPage() {
} finally {
setIsHeartbeatApplying(false);
}
}, [canManageHeartbeat, groupId, heartbeatEvery, includeBoardLeads, isSignedIn]);
}, [
canManageHeartbeat,
groupId,
heartbeatEvery,
includeBoardLeads,
isSignedIn,
]);
return (
<DashboardShell>
@@ -850,7 +856,8 @@ export default function BoardGroupDetailPage() {
heartbeatEvery === value
? "bg-slate-900 text-white"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
!canManageHeartbeat && "opacity-50 cursor-not-allowed",
!canManageHeartbeat &&
"opacity-50 cursor-not-allowed",
)}
disabled={!canManageHeartbeat}
onClick={() => {
@@ -913,7 +920,9 @@ export default function BoardGroupDetailPage() {
variant="outline"
onClick={() => void applyHeartbeat()}
disabled={
isHeartbeatApplying || !heartbeatEvery || !canManageHeartbeat
isHeartbeatApplying ||
!heartbeatEvery ||
!canManageHeartbeat
}
title={
canManageHeartbeat

View File

@@ -206,7 +206,9 @@ const resolveBoardAccess = (
if (member.all_boards_read) {
return { canRead: true, canWrite: false };
}
const entry = member.board_access?.find((access) => access.board_id === boardId);
const entry = member.board_access?.find(
(access) => access.board_id === boardId,
);
if (!entry) {
return { canRead: false, canWrite: false };
}
@@ -2199,7 +2201,9 @@ export default function BoardDetailPage() {
async (approvalId: string, status: "approved" | "rejected") => {
if (!isSignedIn || !boardId) return;
if (!canWrite) {
pushToast("Read-only access. You do not have permission to update approvals.");
pushToast(
"Read-only access. You do not have permission to update approvals.",
);
return;
}
setApprovalsUpdatingId(approvalId);
@@ -3033,7 +3037,9 @@ export default function BoardDetailPage() {
<Button
size="sm"
onClick={handlePostComment}
disabled={!canWrite || isPostingComment || !newComment.trim()}
disabled={
!canWrite || isPostingComment || !newComment.trim()
}
title={canWrite ? "Send message" : "Read-only access"}
>
{isPostingComment ? "Sending…" : "Send message"}
@@ -3516,7 +3522,10 @@ export default function BoardDetailPage() {
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleCreateTask} disabled={!canWrite || isCreating}>
<Button
onClick={handleCreateTask}
disabled={!canWrite || isCreating}
>
{isCreating ? "Creating…" : "Create task"}
</Button>
</DialogFooter>
@@ -3611,9 +3620,7 @@ export default function BoardDetailPage() {
toast.tone === "error" ? "bg-rose-500" : "bg-emerald-500",
)}
/>
<p className="flex-1 text-sm text-slate-700">
{toast.message}
</p>
<p className="flex-1 text-sm text-slate-700">{toast.message}</p>
<button
type="button"
className="text-xs text-slate-400 hover:text-slate-600"

View File

@@ -282,7 +282,7 @@ export default function NewBoardPage() {
{isLoading ? "Creating…" : "Create board"}
</Button>
</div>
</form>
</form>
)}
</div>
</main>

View File

@@ -269,121 +269,123 @@ export default function EditGatewayPage() {
/>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway URL <span className="text-red-500">*</span>
</label>
<div className="relative">
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway URL <span className="text-red-500">*</span>
</label>
<div className="relative">
<Input
value={resolvedGatewayUrl}
onChange={(event) => {
setGatewayUrl(event.target.value);
setGatewayUrlError(null);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
onBlur={runGatewayCheck}
placeholder="ws://gateway:18789"
disabled={isLoading}
className={
gatewayUrlError ? "border-red-500" : undefined
}
/>
<button
type="button"
onClick={runGatewayCheck}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
aria-label="Check gateway connection"
>
{gatewayCheckStatus === "checking" ? (
<RefreshCcw className="h-4 w-4 animate-spin" />
) : gatewayCheckStatus === "success" ? (
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
) : gatewayCheckStatus === "error" ? (
<XCircle className="h-4 w-4 text-red-500" />
) : (
<RefreshCcw className="h-4 w-4" />
)}
</button>
</div>
{gatewayUrlError ? (
<p className="text-xs text-red-500">{gatewayUrlError}</p>
) : gatewayCheckMessage ? (
<p
className={
gatewayCheckStatus === "success"
? "text-xs text-emerald-600"
: "text-xs text-red-500"
}
>
{gatewayCheckMessage}
</p>
) : null}
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway token
</label>
<Input
value={resolvedGatewayUrl}
value={resolvedGatewayToken}
onChange={(event) => {
setGatewayUrl(event.target.value);
setGatewayUrlError(null);
setGatewayToken(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
onBlur={runGatewayCheck}
placeholder="ws://gateway:18789"
placeholder="Bearer token"
disabled={isLoading}
className={gatewayUrlError ? "border-red-500" : undefined}
/>
<button
type="button"
onClick={runGatewayCheck}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
aria-label="Check gateway connection"
>
{gatewayCheckStatus === "checking" ? (
<RefreshCcw className="h-4 w-4 animate-spin" />
) : gatewayCheckStatus === "success" ? (
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
) : gatewayCheckStatus === "error" ? (
<XCircle className="h-4 w-4 text-red-500" />
) : (
<RefreshCcw className="h-4 w-4" />
)}
</button>
</div>
{gatewayUrlError ? (
<p className="text-xs text-red-500">{gatewayUrlError}</p>
) : gatewayCheckMessage ? (
<p
className={
gatewayCheckStatus === "success"
? "text-xs text-emerald-600"
: "text-xs text-red-500"
}
>
{gatewayCheckMessage}
</p>
) : null}
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway token
</label>
<Input
value={resolvedGatewayToken}
onChange={(event) => {
setGatewayToken(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
onBlur={runGatewayCheck}
placeholder="Bearer token"
disabled={isLoading}
/>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Main session key <span className="text-red-500">*</span>
</label>
<Input
value={resolvedMainSessionKey}
onChange={(event) => {
setMainSessionKey(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
placeholder={DEFAULT_MAIN_SESSION_KEY}
disabled={isLoading}
/>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Main session key <span className="text-red-500">*</span>
</label>
<Input
value={resolvedMainSessionKey}
onChange={(event) => {
setMainSessionKey(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
placeholder={DEFAULT_MAIN_SESSION_KEY}
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Workspace root <span className="text-red-500">*</span>
</label>
<Input
value={resolvedWorkspaceRoot}
onChange={(event) => setWorkspaceRoot(event.target.value)}
placeholder={DEFAULT_WORKSPACE_ROOT}
disabled={isLoading}
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Workspace root <span className="text-red-500">*</span>
</label>
<Input
value={resolvedWorkspaceRoot}
onChange={(event) => setWorkspaceRoot(event.target.value)}
placeholder={DEFAULT_WORKSPACE_ROOT}
{errorMessage ? (
<p className="text-sm text-red-500">{errorMessage}</p>
) : null}
<div className="flex justify-end gap-3">
<Button
type="button"
variant="ghost"
onClick={() => router.push("/gateways")}
disabled={isLoading}
/>
>
Back
</Button>
<Button type="submit" disabled={isLoading || !canSubmit}>
{isLoading ? "Saving…" : "Save changes"}
</Button>
</div>
</div>
{errorMessage ? (
<p className="text-sm text-red-500">{errorMessage}</p>
) : null}
<div className="flex justify-end gap-3">
<Button
type="button"
variant="ghost"
onClick={() => router.push("/gateways")}
disabled={isLoading}
>
Back
</Button>
<Button type="submit" disabled={isLoading || !canSubmit}>
{isLoading ? "Saving…" : "Save changes"}
</Button>
</div>
</form>
</form>
)}
</div>
</main>

View File

@@ -230,119 +230,121 @@ export default function NewGatewayPage() {
/>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway URL <span className="text-red-500">*</span>
</label>
<div className="relative">
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway URL <span className="text-red-500">*</span>
</label>
<div className="relative">
<Input
value={gatewayUrl}
onChange={(event) => {
setGatewayUrl(event.target.value);
setGatewayUrlError(null);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
onBlur={runGatewayCheck}
placeholder="ws://gateway:18789"
disabled={isLoading}
className={
gatewayUrlError ? "border-red-500" : undefined
}
/>
<button
type="button"
onClick={runGatewayCheck}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
aria-label="Check gateway connection"
>
{gatewayCheckStatus === "checking" ? (
<RefreshCcw className="h-4 w-4 animate-spin" />
) : gatewayCheckStatus === "success" ? (
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
) : gatewayCheckStatus === "error" ? (
<XCircle className="h-4 w-4 text-red-500" />
) : (
<RefreshCcw className="h-4 w-4" />
)}
</button>
</div>
{gatewayUrlError ? (
<p className="text-xs text-red-500">{gatewayUrlError}</p>
) : gatewayCheckMessage ? (
<p
className={
gatewayCheckStatus === "success"
? "text-xs text-emerald-600"
: "text-xs text-red-500"
}
>
{gatewayCheckMessage}
</p>
) : null}
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway token
</label>
<Input
value={gatewayUrl}
value={gatewayToken}
onChange={(event) => {
setGatewayUrl(event.target.value);
setGatewayUrlError(null);
setGatewayToken(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
onBlur={runGatewayCheck}
placeholder="ws://gateway:18789"
placeholder="Bearer token"
disabled={isLoading}
className={gatewayUrlError ? "border-red-500" : undefined}
/>
<button
type="button"
onClick={runGatewayCheck}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
aria-label="Check gateway connection"
>
{gatewayCheckStatus === "checking" ? (
<RefreshCcw className="h-4 w-4 animate-spin" />
) : gatewayCheckStatus === "success" ? (
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
) : gatewayCheckStatus === "error" ? (
<XCircle className="h-4 w-4 text-red-500" />
) : (
<RefreshCcw className="h-4 w-4" />
)}
</button>
</div>
{gatewayUrlError ? (
<p className="text-xs text-red-500">{gatewayUrlError}</p>
) : gatewayCheckMessage ? (
<p
className={
gatewayCheckStatus === "success"
? "text-xs text-emerald-600"
: "text-xs text-red-500"
}
>
{gatewayCheckMessage}
</p>
) : null}
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway token
</label>
<Input
value={gatewayToken}
onChange={(event) => {
setGatewayToken(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
onBlur={runGatewayCheck}
placeholder="Bearer token"
disabled={isLoading}
/>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Main session key <span className="text-red-500">*</span>
</label>
<Input
value={mainSessionKey}
onChange={(event) => {
setMainSessionKey(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
placeholder={DEFAULT_MAIN_SESSION_KEY}
disabled={isLoading}
/>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Main session key <span className="text-red-500">*</span>
</label>
<Input
value={mainSessionKey}
onChange={(event) => {
setMainSessionKey(event.target.value);
setGatewayCheckStatus("idle");
setGatewayCheckMessage(null);
}}
placeholder={DEFAULT_MAIN_SESSION_KEY}
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Workspace root <span className="text-red-500">*</span>
</label>
<Input
value={workspaceRoot}
onChange={(event) => setWorkspaceRoot(event.target.value)}
placeholder={DEFAULT_WORKSPACE_ROOT}
disabled={isLoading}
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Workspace root <span className="text-red-500">*</span>
</label>
<Input
value={workspaceRoot}
onChange={(event) => setWorkspaceRoot(event.target.value)}
placeholder={DEFAULT_WORKSPACE_ROOT}
{error ? <p className="text-sm text-red-500">{error}</p> : null}
<div className="flex justify-end gap-3">
<Button
type="button"
variant="ghost"
onClick={() => router.push("/gateways")}
disabled={isLoading}
/>
>
Cancel
</Button>
<Button type="submit" disabled={isLoading || !canSubmit}>
{isLoading ? "Creating…" : "Create gateway"}
</Button>
</div>
</div>
{error ? <p className="text-sm text-red-500">{error}</p> : null}
<div className="flex justify-end gap-3">
<Button
type="button"
variant="ghost"
onClick={() => router.push("/gateways")}
disabled={isLoading}
>
Cancel
</Button>
<Button type="submit" disabled={isLoading || !canSubmit}>
{isLoading ? "Creating…" : "Create gateway"}
</Button>
</div>
</form>
</form>
)}
</div>
</main>

View File

@@ -283,93 +283,93 @@ export default function GatewaysPage() {
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead className="sticky top-0 z-10 bg-slate-50 text-xs font-semibold uppercase tracking-wider text-slate-500">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} className="px-6 py-3">
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
<thead className="sticky top-0 z-10 bg-slate-50 text-xs font-semibold uppercase tracking-wider text-slate-500">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} className="px-6 py-3">
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
))}
</tr>
))}
</thead>
<tbody className="divide-y divide-slate-100">
{gatewaysQuery.isLoading ? (
<tr>
<td colSpan={columns.length} className="px-6 py-8">
<span className="text-sm text-slate-500">
Loading
</span>
</td>
</tr>
) : table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-slate-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-6 py-4">
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</thead>
<tbody className="divide-y divide-slate-100">
{gatewaysQuery.isLoading ? (
<tr>
<td colSpan={columns.length} className="px-6 py-8">
<span className="text-sm text-slate-500">
Loading
</span>
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={columns.length} className="px-6 py-16">
<div className="flex flex-col items-center justify-center text-center">
<div className="mb-4 rounded-full bg-slate-50 p-4">
<svg
className="h-16 w-16 text-slate-300"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect
x="2"
y="7"
width="20"
height="14"
rx="2"
ry="2"
/>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" />
</svg>
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-900">
No gateways yet
</h3>
<p className="mb-6 max-w-md text-sm text-slate-500">
Create your first gateway to connect boards and
start managing your OpenClaw connections.
</p>
<Link
href="/gateways/new"
className={buttonVariants({
size: "md",
variant: "primary",
})}
>
Create your first gateway
</Link>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</tr>
) : table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-slate-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-6 py-4">
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={columns.length} className="px-6 py-16">
<div className="flex flex-col items-center justify-center text-center">
<div className="mb-4 rounded-full bg-slate-50 p-4">
<svg
className="h-16 w-16 text-slate-300"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect
x="2"
y="7"
width="20"
height="14"
rx="2"
ry="2"
/>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" />
</svg>
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-900">
No gateways yet
</h3>
<p className="mb-6 max-w-md text-sm text-slate-500">
Create your first gateway to connect boards
and start managing your OpenClaw connections.
</p>
<Link
href="/gateways/new"
className={buttonVariants({
size: "md",
variant: "primary",
})}
>
Create your first gateway
</Link>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
{gatewaysQuery.error ? (
<p className="mt-4 text-sm text-red-500">

View File

@@ -114,8 +114,15 @@ export default function InvitePage() {
</SignedOut>
<SignedIn>
<form className="flex flex-wrap items-center gap-3" onSubmit={handleAccept}>
<Button type="submit" size="md" disabled={!isReady || isSubmitting || accepted}>
<form
className="flex flex-wrap items-center gap-3"
onSubmit={handleAccept}
>
<Button
type="submit"
size="md"
disabled={!isReady || isSubmitting || accepted}
>
{accepted
? "Invite accepted"
: isSubmitting

View File

@@ -76,7 +76,9 @@ type AccessScope = "all" | "custom";
type BoardAccessState = Record<string, { read: boolean; write: boolean }>;
const buildAccessList = (access: BoardAccessState): OrganizationBoardAccessSpec[] =>
const buildAccessList = (
access: BoardAccessState,
): OrganizationBoardAccessSpec[] =>
Object.entries(access)
.filter(([, entry]) => entry.read || entry.write)
.map(([boardId, entry]) => ({
@@ -319,9 +321,8 @@ export default function OrganizationPage() {
const [inviteScope, setInviteScope] = useState<AccessScope>("all");
const [inviteAllRead, setInviteAllRead] = useState(true);
const [inviteAllWrite, setInviteAllWrite] = useState(false);
const [inviteAccess, setInviteAccess] = useState<BoardAccessState>(
defaultBoardAccess,
);
const [inviteAccess, setInviteAccess] =
useState<BoardAccessState>(defaultBoardAccess);
const [inviteError, setInviteError] = useState<string | null>(null);
const [copiedInviteId, setCopiedInviteId] = useState<string | null>(null);
@@ -331,9 +332,8 @@ export default function OrganizationPage() {
const [accessAllRead, setAccessAllRead] = useState(false);
const [accessAllWrite, setAccessAllWrite] = useState(false);
const [accessRole, setAccessRole] = useState("member");
const [accessMap, setAccessMap] = useState<BoardAccessState>(
defaultBoardAccess,
);
const [accessMap, setAccessMap] =
useState<BoardAccessState>(defaultBoardAccess);
const [accessError, setAccessError] = useState<string | null>(null);
const orgQuery = useGetMyOrgApiV1OrganizationsMeGet<
@@ -426,32 +426,33 @@ export default function OrganizationPage() {
},
});
const createInviteMutation = useCreateOrgInviteApiV1OrganizationsMeInvitesPost<
ApiError
>({
mutation: {
onSuccess: (result) => {
if (result.status === 200) {
setInviteEmail("");
setInviteRole("member");
setInviteScope("all");
setInviteAllRead(true);
setInviteAllWrite(false);
setInviteAccess(defaultBoardAccess);
setInviteError(null);
queryClient.invalidateQueries({
queryKey: getListOrgInvitesApiV1OrganizationsMeInvitesGetQueryKey({
limit: 200,
}),
});
setInviteDialogOpen(false);
}
const createInviteMutation =
useCreateOrgInviteApiV1OrganizationsMeInvitesPost<ApiError>({
mutation: {
onSuccess: (result) => {
if (result.status === 200) {
setInviteEmail("");
setInviteRole("member");
setInviteScope("all");
setInviteAllRead(true);
setInviteAllWrite(false);
setInviteAccess(defaultBoardAccess);
setInviteError(null);
queryClient.invalidateQueries({
queryKey: getListOrgInvitesApiV1OrganizationsMeInvitesGetQueryKey(
{
limit: 200,
},
),
});
setInviteDialogOpen(false);
}
},
onError: (err) => {
setInviteError(err.message || "Unable to create invite.");
},
},
onError: (err) => {
setInviteError(err.message || "Unable to create invite.");
},
},
});
});
const revokeInviteMutation =
useRevokeOrgInviteApiV1OrganizationsMeInvitesInviteIdDelete<ApiError>({
@@ -472,9 +473,11 @@ export default function OrganizationPage() {
mutation: {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getListOrgMembersApiV1OrganizationsMeMembersGetQueryKey({
limit: 200,
}),
queryKey: getListOrgMembersApiV1OrganizationsMeMembersGetQueryKey(
{
limit: 200,
},
),
});
if (activeMemberId) {
queryClient.invalidateQueries({
@@ -535,9 +538,7 @@ export default function OrganizationPage() {
}, [inviteDialogOpen]);
const orgName =
orgQuery.data?.status === 200
? orgQuery.data.data.name
: "Organization";
orgQuery.data?.status === 200 ? orgQuery.data.data.name : "Organization";
const handleInviteSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -710,7 +711,10 @@ export default function OrganizationPage() {
<h1 className="text-2xl font-semibold tracking-tight text-slate-900">
Organization
</h1>
<Badge variant="outline" className="flex items-center gap-2">
<Badge
variant="outline"
className="flex items-center gap-2"
>
<Building2 className="h-3.5 w-3.5" />
{orgName}
</Badge>

View File

@@ -15,7 +15,10 @@ import {
import { useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator";
import { type getMyMembershipApiV1OrganizationsMeMemberGetResponse, useGetMyMembershipApiV1OrganizationsMeMemberGet } from "@/api/generated/organizations/organizations";
import {
type getMyMembershipApiV1OrganizationsMeMemberGetResponse,
useGetMyMembershipApiV1OrganizationsMeMemberGet,
} from "@/api/generated/organizations/organizations";
import {
type healthzHealthzGetResponse,
useHealthzHealthzGet,
@@ -65,7 +68,7 @@ export function DashboardSidebar() {
? "System status unavailable"
: "System degraded";
return (
return (
<aside className="flex h-full w-64 flex-col border-r border-slate-200 bg-white">
<div className="flex-1 px-3 py-4">
<p className="px-3 text-xs font-semibold uppercase tracking-wider text-slate-500">

View File

@@ -91,8 +91,8 @@ export function OrgSwitcher() {
},
});
const createOrgMutation = useCreateOrganizationApiV1OrganizationsPost<ApiError>(
{
const createOrgMutation =
useCreateOrganizationApiV1OrganizationsPost<ApiError>({
mutation: {
onSuccess: () => {
setOrgName("");
@@ -110,8 +110,7 @@ export function OrgSwitcher() {
setOrgError(err.message || "Unable to create organization.");
},
},
},
);
});
const handleOrgChange = (value: string) => {
if (value === "__create__") {