@agent-native/dispatch 0.15.13 → 0.15.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,12 +3,18 @@ import {
3
3
  useActionQuery,
4
4
  } from "@agent-native/core/client/hooks";
5
5
  import { useOrg } from "@agent-native/core/client/org";
6
+ import { IconChevronRight } from "@tabler/icons-react";
6
7
  import { useMemo, useState } from "react";
7
8
  import { toast } from "sonner";
8
9
 
9
10
  import { ActionQueryError } from "../../components/action-query-error";
10
11
  import { DispatchShell } from "../../components/dispatch-shell";
11
12
  import { Button } from "../../components/ui/button";
13
+ import {
14
+ Collapsible,
15
+ CollapsibleContent,
16
+ CollapsibleTrigger,
17
+ } from "../../components/ui/collapsible";
12
18
  import { Input } from "../../components/ui/input";
13
19
  import { Switch } from "../../components/ui/switch";
14
20
 
@@ -53,67 +59,79 @@ export default function ApprovalsRoute() {
53
59
  description="Review durable dispatch changes before they apply."
54
60
  >
55
61
  <div className="grid gap-4 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
56
- <section className="rounded-2xl border bg-card p-5">
57
- <h2 className="text-lg font-semibold text-foreground">
58
- Approval policy
59
- </h2>
60
- {settingsQuery.isError ? (
61
- <ActionQueryError
62
- className="mt-4"
63
- error={settingsQuery.error}
64
- onRetry={() => void settingsQuery.refetch()}
65
- />
66
- ) : (
67
- <div className="mt-4 space-y-4">
68
- <label className="flex items-center justify-between rounded-xl border px-4 py-3">
69
- <div>
70
- <div className="text-sm font-medium text-foreground">
71
- Require approval for durable changes
62
+ <Collapsible className="rounded-2xl border bg-card">
63
+ <CollapsibleTrigger className="group flex w-full cursor-pointer items-center justify-between gap-3 p-5 text-left hover:bg-muted/20">
64
+ <span>
65
+ <span className="block text-lg font-semibold text-foreground">
66
+ Approval policy
67
+ </span>
68
+ <span className="mt-1 block text-sm text-muted-foreground">
69
+ {settings?.enabled
70
+ ? "Durable changes require approval."
71
+ : "Durable changes do not require approval."}
72
+ </span>
73
+ </span>
74
+ <IconChevronRight className="h-5 w-5 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
75
+ </CollapsibleTrigger>
76
+ <CollapsibleContent className="border-t px-5 pb-5 pt-4">
77
+ {settingsQuery.isError ? (
78
+ <ActionQueryError
79
+ error={settingsQuery.error}
80
+ onRetry={() => void settingsQuery.refetch()}
81
+ />
82
+ ) : (
83
+ <div className="space-y-4">
84
+ <label className="flex items-center justify-between rounded-xl border px-4 py-3">
85
+ <div>
86
+ <div className="text-sm font-medium text-foreground">
87
+ Require approval for durable changes
88
+ </div>
89
+ <div className="mt-1 text-xs text-muted-foreground">
90
+ {hasOrg
91
+ ? "Applies to saved destinations, shared dream proposals, All-app agent resources, and dispatch settings."
92
+ : "Requires a team workspace. Set one up on the Team page."}
93
+ </div>
72
94
  </div>
73
- <div className="mt-1 text-xs text-muted-foreground">
74
- {hasOrg
75
- ? "Applies to saved destinations, shared dream proposals, All-app agent resources, and dispatch settings."
76
- : "Requires a team workspace. Set one up on the Team page."}
95
+ <Switch
96
+ checked={settings?.enabled || false}
97
+ disabled={!hasOrg || savePolicy.isPending}
98
+ onCheckedChange={(checked) =>
99
+ savePolicy.mutate({
100
+ enabled: checked,
101
+ approverEmails:
102
+ settings?.approverEmails || approverList,
103
+ })
104
+ }
105
+ />
106
+ </label>
107
+ <div className="space-y-2">
108
+ <div className="text-sm font-medium text-foreground">
109
+ Approver emails
77
110
  </div>
111
+ <Input
112
+ value={emails}
113
+ onChange={(event) => setEmails(event.target.value)}
114
+ placeholder={(settings?.approverEmails || []).join(", ")}
115
+ disabled={!hasOrg}
116
+ />
117
+ <Button
118
+ className="w-full"
119
+ variant="outline"
120
+ disabled={!hasOrg || savePolicy.isPending}
121
+ onClick={() =>
122
+ savePolicy.mutate({
123
+ enabled: settings?.enabled || false,
124
+ approverEmails: approverList,
125
+ })
126
+ }
127
+ >
128
+ Save approvers
129
+ </Button>
78
130
  </div>
79
- <Switch
80
- checked={settings?.enabled || false}
81
- disabled={!hasOrg || savePolicy.isPending}
82
- onCheckedChange={(checked) =>
83
- savePolicy.mutate({
84
- enabled: checked,
85
- approverEmails: settings?.approverEmails || approverList,
86
- })
87
- }
88
- />
89
- </label>
90
- <div className="space-y-2">
91
- <div className="text-sm font-medium text-foreground">
92
- Approver emails
93
- </div>
94
- <Input
95
- value={emails}
96
- onChange={(event) => setEmails(event.target.value)}
97
- placeholder={(settings?.approverEmails || []).join(", ")}
98
- disabled={!hasOrg}
99
- />
100
- <Button
101
- className="w-full"
102
- variant="outline"
103
- disabled={!hasOrg || savePolicy.isPending}
104
- onClick={() =>
105
- savePolicy.mutate({
106
- enabled: settings?.enabled || false,
107
- approverEmails: approverList,
108
- })
109
- }
110
- >
111
- Save approvers
112
- </Button>
113
131
  </div>
114
- </div>
115
- )}
116
- </section>
132
+ )}
133
+ </CollapsibleContent>
134
+ </Collapsible>
117
135
 
118
136
  <section className="rounded-2xl border bg-card p-5">
119
137
  <h2 className="text-lg font-semibold text-foreground">
@@ -3,6 +3,7 @@ import {
3
3
  useActionQuery,
4
4
  } from "@agent-native/core/client/hooks";
5
5
  import { useT } from "@agent-native/core/client/i18n";
6
+ import { IconChevronRight } from "@tabler/icons-react";
6
7
  import { useState } from "react";
7
8
  import { toast } from "sonner";
8
9
 
@@ -21,6 +22,11 @@ import {
21
22
  AlertDialogTrigger,
22
23
  } from "../../components/ui/alert-dialog";
23
24
  import { Button } from "../../components/ui/button";
25
+ import {
26
+ Collapsible,
27
+ CollapsibleContent,
28
+ CollapsibleTrigger,
29
+ } from "../../components/ui/collapsible";
24
30
  import { Input } from "../../components/ui/input";
25
31
  import {
26
32
  Select,
@@ -88,6 +94,7 @@ export default function DestinationsRoute() {
88
94
  threadRef: "",
89
95
  notes: "",
90
96
  });
97
+ const [addOpen, setAddOpen] = useState(false);
91
98
 
92
99
  const upsert = useActionMutation("upsert-destination", {
93
100
  onSuccess: () => {
@@ -112,93 +119,108 @@ export default function DestinationsRoute() {
112
119
  >
113
120
  <div className="flex flex-col gap-4">
114
121
  <TaskQueueHealth />
115
- <div className="grid gap-4 xl:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
116
- <section className="rounded-2xl border bg-card p-5">
117
- <h2 className="text-lg font-semibold text-foreground">
118
- {t("dispatch.pages.savedDestinations")}
119
- </h2>
120
- {destinationsQuery.isError ? (
121
- <ActionQueryError
122
- className="mt-4"
123
- error={destinationsQuery.error}
124
- onRetry={() => void destinationsQuery.refetch()}
125
- />
126
- ) : (
127
- <div className="mt-4 space-y-3">
128
- {(data || []).map((destination: any) => (
129
- <div
130
- key={destination.id}
131
- className="rounded-xl border bg-muted/30 p-4"
132
- >
133
- <div className="flex items-start justify-between gap-3">
134
- <div>
135
- <div className="text-sm font-medium text-foreground">
136
- {destination.name}
137
- </div>
138
- <div className="mt-1 text-xs text-muted-foreground">
139
- {destination.platform} · {destination.destination}
140
- {destination.threadRef
141
- ? ` · thread ${destination.threadRef}`
142
- : ""}
143
- </div>
144
- {destination.notes && (
145
- <p className="mt-2 text-sm text-muted-foreground">
146
- {destination.notes}
147
- </p>
148
- )}
122
+ <section className="rounded-2xl border bg-card p-5">
123
+ <h2 className="text-lg font-semibold text-foreground">
124
+ {t("dispatch.pages.savedDestinations")}
125
+ </h2>
126
+ {destinationsQuery.isError ? (
127
+ <ActionQueryError
128
+ className="mt-4"
129
+ error={destinationsQuery.error}
130
+ onRetry={() => void destinationsQuery.refetch()}
131
+ />
132
+ ) : (
133
+ <div className="mt-4 divide-y">
134
+ {(data || []).map((destination: any) => (
135
+ <div key={destination.id} className="py-4 first:pt-0">
136
+ <div className="flex items-start justify-between gap-3">
137
+ <div>
138
+ <div className="text-sm font-medium text-foreground">
139
+ {destination.name}
149
140
  </div>
150
- <AlertDialog>
151
- <AlertDialogTrigger asChild>
152
- <Button variant="outline" size="sm">
153
- {t("dispatch.pages.delete")}
154
- </Button>
155
- </AlertDialogTrigger>
156
- <AlertDialogContent>
157
- <AlertDialogHeader>
158
- <AlertDialogTitle>
159
- {t("dispatch.pages.deleteDestinationTitle")}
160
- </AlertDialogTitle>
161
- <AlertDialogDescription>
162
- {t(
163
- "dispatch.pages.deleteDestinationDescription",
164
- {
165
- name: destination.name,
166
- },
167
- )}
168
- </AlertDialogDescription>
169
- </AlertDialogHeader>
170
- <AlertDialogFooter>
171
- <AlertDialogCancel>
172
- {t("dispatch.pages.cancel")}
173
- </AlertDialogCancel>
174
- <AlertDialogAction
175
- onClick={() =>
176
- remove.mutate({ id: destination.id })
177
- }
178
- >
179
- {t("dispatch.pages.delete")}
180
- </AlertDialogAction>
181
- </AlertDialogFooter>
182
- </AlertDialogContent>
183
- </AlertDialog>
141
+ <div className="mt-1 text-xs text-muted-foreground">
142
+ {destination.platform} · {destination.destination}
143
+ {destination.threadRef
144
+ ? ` · thread ${destination.threadRef}`
145
+ : ""}
146
+ </div>
147
+ {destination.notes && (
148
+ <p className="mt-2 text-sm text-muted-foreground">
149
+ {destination.notes}
150
+ </p>
151
+ )}
184
152
  </div>
185
- <QuickSendRow destination={destination} />
186
- </div>
187
- ))}
188
- {(data?.length || 0) === 0 && (
189
- <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
190
- {t("dispatch.pages.noDestinations")}
153
+ <AlertDialog>
154
+ <AlertDialogTrigger asChild>
155
+ <Button variant="outline" size="sm">
156
+ {t("dispatch.pages.delete")}
157
+ </Button>
158
+ </AlertDialogTrigger>
159
+ <AlertDialogContent>
160
+ <AlertDialogHeader>
161
+ <AlertDialogTitle>
162
+ {t("dispatch.pages.deleteDestinationTitle")}
163
+ </AlertDialogTitle>
164
+ <AlertDialogDescription>
165
+ {t("dispatch.pages.deleteDestinationDescription", {
166
+ name: destination.name,
167
+ })}
168
+ </AlertDialogDescription>
169
+ </AlertDialogHeader>
170
+ <AlertDialogFooter>
171
+ <AlertDialogCancel>
172
+ {t("dispatch.pages.cancel")}
173
+ </AlertDialogCancel>
174
+ <AlertDialogAction
175
+ onClick={() =>
176
+ remove.mutate({ id: destination.id })
177
+ }
178
+ >
179
+ {t("dispatch.pages.delete")}
180
+ </AlertDialogAction>
181
+ </AlertDialogFooter>
182
+ </AlertDialogContent>
183
+ </AlertDialog>
191
184
  </div>
192
- )}
193
- </div>
194
- )}
195
- </section>
185
+ <Collapsible className="mt-3">
186
+ <CollapsibleTrigger className="group flex w-full cursor-pointer items-center gap-1.5 text-left text-xs font-medium text-muted-foreground hover:text-foreground">
187
+ <IconChevronRight className="h-3.5 w-3.5 transition-transform group-data-[state=open]:rotate-90" />
188
+ <span>Send a test message</span>
189
+ </CollapsibleTrigger>
190
+ <CollapsibleContent>
191
+ <QuickSendRow destination={destination} />
192
+ </CollapsibleContent>
193
+ </Collapsible>
194
+ </div>
195
+ ))}
196
+ {(data?.length || 0) === 0 && (
197
+ <div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
198
+ {t("dispatch.pages.noDestinations")}
199
+ </div>
200
+ )}
201
+ </div>
202
+ )}
203
+ </section>
196
204
 
197
- <section className="rounded-2xl border bg-card p-5">
198
- <h2 className="text-lg font-semibold text-foreground">
199
- {t("dispatch.pages.addDestination")}
200
- </h2>
201
- <div className="mt-4 space-y-3">
205
+ <Collapsible
206
+ open={addOpen}
207
+ onOpenChange={setAddOpen}
208
+ className="rounded-2xl border bg-card"
209
+ >
210
+ <CollapsibleTrigger className="group flex w-full cursor-pointer items-center justify-between gap-3 p-5 text-left hover:bg-muted/20">
211
+ <span>
212
+ <span className="block text-lg font-semibold text-foreground">
213
+ {t("dispatch.pages.addDestination")}
214
+ </span>
215
+ <span className="mt-1 block text-sm text-muted-foreground">
216
+ Save a reusable delivery target for scheduled or agent-sent
217
+ messages.
218
+ </span>
219
+ </span>
220
+ <IconChevronRight className="h-5 w-5 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
221
+ </CollapsibleTrigger>
222
+ <CollapsibleContent className="border-t px-5 pb-5 pt-4">
223
+ <div className="space-y-3">
202
224
  <Input
203
225
  value={form.name}
204
226
  onChange={(event) =>
@@ -279,8 +301,8 @@ export default function DestinationsRoute() {
279
301
  {t("dispatch.pages.saveDestination")}
280
302
  </Button>
281
303
  </div>
282
- </section>
283
- </div>
304
+ </CollapsibleContent>
305
+ </Collapsible>
284
306
  </div>
285
307
  </DispatchShell>
286
308
  );