@kky42/pievo-server 2.4.11 → 2.4.13
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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/SignIn-CDCdDD1a.js +1 -0
- package/.output/public/assets/{ui-s5IMg6RF.js → actionUi-D8m1FDHO.js} +1 -1
- package/.output/public/assets/app-PPhgN9iN.css +2 -0
- package/.output/public/assets/index-c0XPWXuz.js +10 -0
- package/.output/public/assets/loops._loopId-D7i_BGiE.js +67 -0
- package/.output/public/assets/loops._loopId_.runs._runId-CksSrEY7.js +3 -0
- package/.output/public/assets/{preload-helper-Bs7oCAsw.js → preload-helper-Zcmr31XG.js} +3 -3
- package/.output/public/assets/routes-79D939J4.js +1 -0
- package/.output/public/assets/routes-BYgl3Tmv.js +1 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_ssr/{MachinesModal-BhRw7oOx.mjs → MachinesModal-DBIP648Z.mjs} +18 -57
- package/.output/server/_ssr/{SignIn-BhoZIOUp.mjs → SignIn-Bp3uWaCQ.mjs} +3 -3
- package/.output/server/_ssr/{ui-DGz-q9MT.mjs → actionUi-C_WswNb1.mjs} +113 -2
- package/.output/server/_ssr/{artifactFiles-ChuJ4PzC.mjs → artifactFiles-BvVmixrs.mjs} +3 -3
- package/.output/server/_ssr/{artifactKind-DXumOv7c.mjs → artifactKind-DRCbYHZU.mjs} +6 -14
- package/.output/server/_ssr/artifacts-CzDJLaa6.mjs +2 -0
- package/.output/server/_ssr/{auth-LwPp_iim.mjs → auth-BV0MxVdo.mjs} +44 -124
- package/.output/server/_ssr/{SocialLinks-DVCLmql0.mjs → auth-client-RTpMpT4L.mjs} +143 -109
- package/.output/server/_ssr/boot-CsLXrLEa.mjs +2 -0
- package/.output/server/_ssr/{boot-BIxtZHU-.mjs → boot-U-CFI65B.mjs} +58 -331
- package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-CHMDkKqA.mjs → bun-sqlite-dialect-BW9W1_Ps-D9KUBJ-L.mjs} +2 -2
- package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-DYum6qky.mjs → d1-sqlite-dialect-BLC8LXE6-CVHRzH1n.mjs} +2 -2
- package/.output/server/_ssr/db-BTX-xORB.mjs +2 -0
- package/.output/server/_ssr/{dist-bWqs5sTy.mjs → dist-D7UDVZ5G.mjs} +2 -2
- package/.output/server/_ssr/{factory-pBwrjVTI.mjs → factory-DYFx1fg-.mjs} +2 -2
- package/.output/server/_ssr/{kysely-adapter-DMf5pFTf.mjs → kysely-adapter-CwdB7yBh.mjs} +1 -1
- package/.output/server/_ssr/logger-CwiKSMp6.mjs +2 -0
- package/.output/server/_ssr/{loopApi-Im6JSK0p.mjs → loopApi-C4nN0msL.mjs} +29 -90
- package/.output/server/_ssr/{loopProjection-DIpGR6rp.mjs → loopProjection-DMSNHux6.mjs} +2 -2
- package/.output/server/_ssr/{loops._loopId-B5iPseDG.mjs → loops._loopId-Q4pRYPip.mjs} +19 -45
- package/.output/server/_ssr/{loops._loopId_.runs._runId-DVtG0xYb.mjs → loops._loopId_.runs._runId-DNXVmCt5.mjs} +6 -13
- package/.output/server/_ssr/{loops._loopId_.runs._runId-BA-P6sGA.mjs → loops._loopId_.runs._runId-ty3AhkOi.mjs} +31 -23
- package/.output/server/_ssr/{machineFns-Bb9KNXun.mjs → machineFns-CkNms3zA.mjs} +41 -108
- package/.output/server/_ssr/{node-sqlite-dialect-Dho9YjXb.mjs → node-sqlite-dialect-B_g92oFK.mjs} +2 -2
- package/.output/server/_ssr/{router-DPz3uoUr.mjs → router-DCNZ2xLm.mjs} +26 -40
- package/.output/server/_ssr/routes-8JSF7bX_.mjs +27 -0
- package/.output/server/_ssr/{routes-FO0MFfYK.mjs → routes-B3fulW5I.mjs} +4 -9
- package/.output/server/_ssr/routes-BaanDO41.mjs +367 -0
- package/.output/server/_ssr/runDiff-D7CxAgYL.mjs +2 -0
- package/.output/server/_ssr/ssr.mjs +28 -88
- package/.output/server/_ssr/store-DSNbXxqz.mjs +2 -0
- package/.output/server/_ssr/tokens-DzUv7CCp.mjs +2 -0
- package/.output/server/_tanstack-start-manifest_v-DkmxsHDK.mjs +52 -0
- package/.output/server/drizzle/0002_remove_teams.sql +15 -0
- package/.output/server/drizzle/meta/_journal.json +7 -0
- package/.output/server/index.mjs +49 -98
- package/drizzle/0002_remove_teams.sql +15 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +2 -2
- package/.output/public/assets/MachinesModal-ByHejech.js +0 -1
- package/.output/public/assets/SignIn-Cf4HOpdF.js +0 -1
- package/.output/public/assets/actionUi-yYVvoOTn.js +0 -1
- package/.output/public/assets/app-IrlFuBXa.css +0 -2
- package/.output/public/assets/index-ia26HsP1.js +0 -10
- package/.output/public/assets/invite._token-B0EKVyrU.js +0 -1
- package/.output/public/assets/invite._token-tDfe3p0Z.js +0 -1
- package/.output/public/assets/lifecycleUi-C0DaC6NM.js +0 -1
- package/.output/public/assets/loops._loopId-CuYq_XgT.js +0 -67
- package/.output/public/assets/loops._loopId_.runs._runId-mOaF51Qu.js +0 -3
- package/.output/public/assets/routes-4N6mrQK_.js +0 -1
- package/.output/public/assets/routes-Dbl_3lt_.js +0 -1
- package/.output/public/assets/t._teamId-BVsl0EGm.js +0 -1
- package/.output/public/assets/t._teamId-CAUjvMZ6.js +0 -1
- package/.output/server/_ssr/DashboardView-BMHMrOfC.mjs +0 -842
- package/.output/server/_ssr/actionUi-Cf6DuEC2.mjs +0 -119
- package/.output/server/_ssr/artifacts-DOLHJHcd.mjs +0 -2
- package/.output/server/_ssr/boot-D8cj4uZj.mjs +0 -2
- package/.output/server/_ssr/db-BphSJs66.mjs +0 -2
- package/.output/server/_ssr/invite._token-CPSvdDox.mjs +0 -27
- package/.output/server/_ssr/invite._token-CRBsTSTZ.mjs +0 -84
- package/.output/server/_ssr/invite._token-CaTLt92a.mjs +0 -20
- package/.output/server/_ssr/lifecycleUi-DauKavgx.mjs +0 -50
- package/.output/server/_ssr/logger-EinA2J6y.mjs +0 -2
- package/.output/server/_ssr/routes-B9zD2h4Y.mjs +0 -26
- package/.output/server/_ssr/routes-BuILEmHs.mjs +0 -39
- package/.output/server/_ssr/runDiff-BLdot9mi.mjs +0 -2
- package/.output/server/_ssr/store-CFCp_ggC.mjs +0 -2
- package/.output/server/_ssr/t._teamId-1t5wOXrV.mjs +0 -43
- package/.output/server/_ssr/t._teamId-A37pQ8Vp.mjs +0 -32
- package/.output/server/_ssr/t._teamId-D1BTwhWf.mjs +0 -31
- package/.output/server/_ssr/teamCookie-B7hLOIie.mjs +0 -10
- package/.output/server/_ssr/teamFns-DfqtuBW2.mjs +0 -451
- package/.output/server/_ssr/teamFns-cI_tsiCv.mjs +0 -32
- package/.output/server/_ssr/tokens-CUz4LDLm.mjs +0 -2
- package/.output/server/_tanstack-start-manifest_v-CojB4Veh.mjs +0 -86
|
@@ -1,842 +0,0 @@
|
|
|
1
|
-
import { o as __toESM } from "../_runtime.mjs";
|
|
2
|
-
import { g as require_react, h as require_jsx_runtime, s as TooltipProvider } from "../_libs/@base-ui/react+[...].mjs";
|
|
3
|
-
import { d as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
4
|
-
import { _ as loadOlderRuns, d as getConfig, g as listMyTeams, h as listLoops, n as GitHubIcon, r as PievoLogo, t as GITHUB_URL, v as mintClaim } from "./SocialLinks-DVCLmql0.mjs";
|
|
5
|
-
import { T as selectCls, _ as inputCls, a as btn, l as btnSm, o as btnDanger, r as Pill, s as btnPrimary, t as ErrorBanner, v as labelCls } from "./ui-DGz-q9MT.mjs";
|
|
6
|
-
import { t as setActiveTeamCookie } from "./teamCookie-B7hLOIie.mjs";
|
|
7
|
-
import { a as getTeamDetail, d as revokeTeamInvite, f as setTeamMemberRole, i as deleteTeam, l as removeTeamMember, n as createTeam, o as leaveTeam, r as createTeamInvite, s as listManagedTeams, t as addTeamMember, u as renameTeam } from "./teamFns-cI_tsiCv.mjs";
|
|
8
|
-
import { a as ModalSection, c as listMachines, i as ModalHead, l as mergeRuns, n as MachinesModal, r as Modal, s as daemonConnectCommand, t as LoopOverview } from "./MachinesModal-BhRw7oOx.mjs";
|
|
9
|
-
//#region node_modules/.nitro/vite/services/ssr/assets/DashboardView-BMHMrOfC.js
|
|
10
|
-
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
11
|
-
var import_jsx_runtime = require_jsx_runtime();
|
|
12
|
-
function LoopCard({ loop, onOpen, onPickRun }) {
|
|
13
|
-
const en = loop.enabled;
|
|
14
|
-
const [older, setOlder] = (0, import_react.useState)([]);
|
|
15
|
-
const runs = (0, import_react.useMemo)(() => older.length ? mergeRuns(loop.runs ?? [], older) : loop.runs ?? [], [loop.runs, older]);
|
|
16
|
-
const newestRun = runs.at(-1);
|
|
17
|
-
const latestIncidentRun = newestRun?.reportIncident ? newestRun : void 0;
|
|
18
|
-
const loadMore = async () => {
|
|
19
|
-
const oldest = runs[0];
|
|
20
|
-
if (!oldest) return 0;
|
|
21
|
-
const more = await loadOlderRuns({ data: {
|
|
22
|
-
loopId: loop.id,
|
|
23
|
-
beforeTs: oldest.ts,
|
|
24
|
-
limit: 16
|
|
25
|
-
} });
|
|
26
|
-
if (more.length) setOlder((prev) => mergeRuns(prev, more));
|
|
27
|
-
return more.length;
|
|
28
|
-
};
|
|
29
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
30
|
-
onClick: () => onOpen(loop.id),
|
|
31
|
-
className: `mb-[18px] cursor-pointer rounded-card border border-hairline bg-surface px-[26px] pb-5 pt-[22px] shadow-card transition-colors hover:border-wire ${en || latestIncidentRun?.reportIncident || loop.pauseCause?.kind === "failure-streak" || loop.pauseCause?.kind === "blocked" ? "" : "opacity-60"}`,
|
|
32
|
-
style: { animation: "fadeIn .25s cubic-bezier(0.25,0.1,0.25,1) both" },
|
|
33
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopOverview, {
|
|
34
|
-
loop,
|
|
35
|
-
runs,
|
|
36
|
-
onLoadMore: loadMore,
|
|
37
|
-
onPickRun: (run) => onPickRun(loop.id, run),
|
|
38
|
-
onOpen: () => onOpen(loop.id)
|
|
39
|
-
})
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Header team entry. Always visible when team data exists: a single-team user
|
|
44
|
-
* sees a quiet pill naming the active team (so the workspace context is never
|
|
45
|
-
* invisible); anyone who can reach more than one team gets the select.
|
|
46
|
-
*
|
|
47
|
-
* Switching NAVIGATES to `/t/<id>` (the explicit team URL — bookmarkable, and
|
|
48
|
-
* each tab keeps its own team). The cookie is still written, now only as the
|
|
49
|
-
* last-used default that the bare `/` redirect falls back to (no longer an
|
|
50
|
-
* authorization key). The navigation (and the route's `key={teamId}` remount) is
|
|
51
|
-
* what re-scopes the dashboard, so no refresh callback is needed.
|
|
52
|
-
*/
|
|
53
|
-
function TeamSwitcher({ data }) {
|
|
54
|
-
const navigate = useNavigate();
|
|
55
|
-
if (!data || data.teams.length === 0) return null;
|
|
56
|
-
const selectTeam = (id) => {
|
|
57
|
-
setActiveTeamCookie(id);
|
|
58
|
-
navigate({
|
|
59
|
-
to: "/t/$teamId",
|
|
60
|
-
params: { teamId: id }
|
|
61
|
-
});
|
|
62
|
-
};
|
|
63
|
-
if (data.teams.length === 1) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
64
|
-
title: "Active team",
|
|
65
|
-
className: "inline-flex items-center rounded-full bg-raised px-3 py-1 text-label font-medium text-secondary",
|
|
66
|
-
children: data.teams[0].name
|
|
67
|
-
});
|
|
68
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", {
|
|
69
|
-
"aria-label": "Active team",
|
|
70
|
-
value: data.activeTeamId,
|
|
71
|
-
onChange: (e) => selectTeam(e.target.value),
|
|
72
|
-
className: "lp-select cursor-pointer rounded-full bg-raised py-1 pl-3 text-label font-medium text-secondary outline-none transition-colors hover:text-display",
|
|
73
|
-
children: data.teams.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
74
|
-
value: t.id,
|
|
75
|
-
children: t.name
|
|
76
|
-
}, t.id))
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
function inviteUrl(token) {
|
|
80
|
-
return `${typeof window !== "undefined" ? window.location.origin : ""}/invite/${token}`;
|
|
81
|
-
}
|
|
82
|
-
function CopyLink({ text }) {
|
|
83
|
-
const [done, setDone] = (0, import_react.useState)(false);
|
|
84
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
85
|
-
className: btnSm,
|
|
86
|
-
onClick: () => {
|
|
87
|
-
navigator.clipboard?.writeText(text);
|
|
88
|
-
setDone(true);
|
|
89
|
-
setTimeout(() => setDone(false), 1400);
|
|
90
|
-
},
|
|
91
|
-
children: done ? "Copied ✓" : "Copy link"
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Team settings — the owner-facing surface for the full lifecycle: create,
|
|
96
|
-
* rename, members (add-by-email + role + remove), invite links, leave, and
|
|
97
|
-
* delete (with the blocked-while-loops explanation). A master list of the
|
|
98
|
-
* caller's teams on the left; the selected team's detail below. Every action
|
|
99
|
-
* calls an EXPLICIT-teamId server fn (never the active cookie), so managing team
|
|
100
|
-
* B while the dashboard is on team A works. Owner-only controls are hidden for a
|
|
101
|
-
* plain member (the server re-authorizes regardless).
|
|
102
|
-
*/
|
|
103
|
-
function TeamsModal({ open, onClose, activeTeamId }) {
|
|
104
|
-
const navigate = useNavigate();
|
|
105
|
-
const [teams, setTeams] = (0, import_react.useState)([]);
|
|
106
|
-
const [selected, setSelected] = (0, import_react.useState)(null);
|
|
107
|
-
const [detail, setDetail] = (0, import_react.useState)(null);
|
|
108
|
-
const [err, setErr] = (0, import_react.useState)(null);
|
|
109
|
-
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
110
|
-
const [newName, setNewName] = (0, import_react.useState)("");
|
|
111
|
-
const [renameVal, setRenameVal] = (0, import_react.useState)("");
|
|
112
|
-
const [email, setEmail] = (0, import_react.useState)("");
|
|
113
|
-
const [addRole, setAddRole] = (0, import_react.useState)("member");
|
|
114
|
-
const [inviteRole, setInviteRole] = (0, import_react.useState)("member");
|
|
115
|
-
const [freshInvite, setFreshInvite] = (0, import_react.useState)(null);
|
|
116
|
-
const loadTeams = (0, import_react.useCallback)(async () => {
|
|
117
|
-
try {
|
|
118
|
-
setTeams(await listManagedTeams());
|
|
119
|
-
} catch {}
|
|
120
|
-
}, []);
|
|
121
|
-
const loadDetail = (0, import_react.useCallback)(async (teamId) => {
|
|
122
|
-
const d = await getTeamDetail({ data: teamId });
|
|
123
|
-
if ("error" in d) {
|
|
124
|
-
setErr(d.error);
|
|
125
|
-
setDetail(null);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
setDetail(d);
|
|
129
|
-
setRenameVal(d.name);
|
|
130
|
-
}, []);
|
|
131
|
-
(0, import_react.useEffect)(() => {
|
|
132
|
-
if (!open) {
|
|
133
|
-
setErr(null);
|
|
134
|
-
setSelected(null);
|
|
135
|
-
setDetail(null);
|
|
136
|
-
setFreshInvite(null);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
loadTeams();
|
|
140
|
-
}, [open, loadTeams]);
|
|
141
|
-
(0, import_react.useEffect)(() => {
|
|
142
|
-
if (!open || !selected) return;
|
|
143
|
-
setFreshInvite(null);
|
|
144
|
-
loadDetail(selected);
|
|
145
|
-
}, [
|
|
146
|
-
open,
|
|
147
|
-
selected,
|
|
148
|
-
loadDetail
|
|
149
|
-
]);
|
|
150
|
-
async function withBusy(fn) {
|
|
151
|
-
setBusy(true);
|
|
152
|
-
setErr(null);
|
|
153
|
-
try {
|
|
154
|
-
return await fn();
|
|
155
|
-
} finally {
|
|
156
|
-
setBusy(false);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
async function doCreate() {
|
|
160
|
-
const name = newName.trim();
|
|
161
|
-
if (!name) return;
|
|
162
|
-
await withBusy(async () => {
|
|
163
|
-
const r = await createTeam({ data: name });
|
|
164
|
-
if (!r.ok) return setErr(r.error);
|
|
165
|
-
setNewName("");
|
|
166
|
-
await loadTeams();
|
|
167
|
-
setSelected(r.id);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
async function doRename() {
|
|
171
|
-
if (!detail) return;
|
|
172
|
-
const name = renameVal.trim();
|
|
173
|
-
if (!name || name === detail.name) return;
|
|
174
|
-
await withBusy(async () => {
|
|
175
|
-
const r = await renameTeam({ data: {
|
|
176
|
-
teamId: detail.id,
|
|
177
|
-
name
|
|
178
|
-
} });
|
|
179
|
-
if (!r.ok) return setErr(r.error);
|
|
180
|
-
await loadTeams();
|
|
181
|
-
await loadDetail(detail.id);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
async function doDelete() {
|
|
185
|
-
if (!detail) return;
|
|
186
|
-
await withBusy(async () => {
|
|
187
|
-
const r = await deleteTeam({ data: detail.id });
|
|
188
|
-
if (!r.ok) return setErr(r.error);
|
|
189
|
-
const removed = detail.id;
|
|
190
|
-
setSelected(null);
|
|
191
|
-
setDetail(null);
|
|
192
|
-
await loadTeams();
|
|
193
|
-
if (removed === activeTeamId) {
|
|
194
|
-
onClose();
|
|
195
|
-
navigate({ to: "/" });
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
async function doLeave() {
|
|
200
|
-
if (!detail) return;
|
|
201
|
-
await withBusy(async () => {
|
|
202
|
-
const r = await leaveTeam({ data: detail.id });
|
|
203
|
-
if (!r.ok) return setErr(r.error);
|
|
204
|
-
const left = detail.id;
|
|
205
|
-
setSelected(null);
|
|
206
|
-
setDetail(null);
|
|
207
|
-
await loadTeams();
|
|
208
|
-
if (left === activeTeamId) {
|
|
209
|
-
onClose();
|
|
210
|
-
navigate({ to: "/" });
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
async function doAdd() {
|
|
215
|
-
if (!detail) return;
|
|
216
|
-
const addr = email.trim();
|
|
217
|
-
if (!addr) return;
|
|
218
|
-
await withBusy(async () => {
|
|
219
|
-
const r = await addTeamMember({ data: {
|
|
220
|
-
teamId: detail.id,
|
|
221
|
-
email: addr,
|
|
222
|
-
role: addRole
|
|
223
|
-
} });
|
|
224
|
-
if (!r.ok) return setErr(r.error);
|
|
225
|
-
setEmail("");
|
|
226
|
-
await loadDetail(detail.id);
|
|
227
|
-
await loadTeams();
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
async function doSetRole(userId, role) {
|
|
231
|
-
if (!detail) return;
|
|
232
|
-
await withBusy(async () => {
|
|
233
|
-
const r = await setTeamMemberRole({ data: {
|
|
234
|
-
teamId: detail.id,
|
|
235
|
-
userId,
|
|
236
|
-
role
|
|
237
|
-
} });
|
|
238
|
-
if (!r.ok) return setErr(r.error);
|
|
239
|
-
await loadDetail(detail.id);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
async function doRemove(userId) {
|
|
243
|
-
if (!detail) return;
|
|
244
|
-
await withBusy(async () => {
|
|
245
|
-
const r = await removeTeamMember({ data: {
|
|
246
|
-
teamId: detail.id,
|
|
247
|
-
userId
|
|
248
|
-
} });
|
|
249
|
-
if (!r.ok) return setErr(r.error);
|
|
250
|
-
await loadDetail(detail.id);
|
|
251
|
-
await loadTeams();
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
async function doInvite() {
|
|
255
|
-
if (!detail) return;
|
|
256
|
-
await withBusy(async () => {
|
|
257
|
-
const r = await createTeamInvite({ data: {
|
|
258
|
-
teamId: detail.id,
|
|
259
|
-
role: inviteRole
|
|
260
|
-
} });
|
|
261
|
-
if (!r.ok) return setErr(r.error);
|
|
262
|
-
setFreshInvite(r.token);
|
|
263
|
-
await loadDetail(detail.id);
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
async function doRevoke(token) {
|
|
267
|
-
if (!detail) return;
|
|
268
|
-
await withBusy(async () => {
|
|
269
|
-
const r = await revokeTeamInvite({ data: {
|
|
270
|
-
teamId: detail.id,
|
|
271
|
-
token
|
|
272
|
-
} });
|
|
273
|
-
if (!r.ok) return setErr(r.error);
|
|
274
|
-
if (freshInvite === token) setFreshInvite(null);
|
|
275
|
-
await loadDetail(detail.id);
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
const isOwner = detail?.role === "owner";
|
|
279
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, {
|
|
280
|
-
open,
|
|
281
|
-
onClose,
|
|
282
|
-
wide: true,
|
|
283
|
-
children: [
|
|
284
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHead, {
|
|
285
|
-
title: "Teams",
|
|
286
|
-
sub: "Create teams, manage members and roles, and invite people. Team management is owner-only; any member can create loops."
|
|
287
|
-
}),
|
|
288
|
-
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, {
|
|
289
|
-
message: err,
|
|
290
|
-
onDismiss: () => setErr(null),
|
|
291
|
-
className: "mb-2 mt-3"
|
|
292
|
-
}),
|
|
293
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
294
|
-
className: "mt-3 grid gap-6 md:grid-cols-[minmax(0,18rem)_minmax(0,1fr)]",
|
|
295
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
296
|
-
className: "min-w-0",
|
|
297
|
-
children: [
|
|
298
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalSection, { children: "My teams" }),
|
|
299
|
-
teams.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
300
|
-
className: "py-2 text-body text-secondary",
|
|
301
|
-
children: "No teams yet."
|
|
302
|
-
}),
|
|
303
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
304
|
-
className: "flex flex-col gap-1.5",
|
|
305
|
-
children: teams.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
306
|
-
onClick: () => setSelected(t.id),
|
|
307
|
-
className: `flex w-full items-center justify-between gap-2 rounded-control border px-3 py-2 text-left transition-colors ${selected === t.id ? "border-display bg-raised" : "border-hairline bg-surface hover:bg-raised"}`,
|
|
308
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
309
|
-
className: "min-w-0 truncate text-[15px] font-medium text-display",
|
|
310
|
-
children: t.name
|
|
311
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
312
|
-
className: "flex shrink-0 items-center gap-1.5",
|
|
313
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
314
|
-
tone: t.role === "owner" ? "ink" : "neutral",
|
|
315
|
-
children: t.role
|
|
316
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
317
|
-
className: "text-label text-secondary",
|
|
318
|
-
children: [
|
|
319
|
-
t.memberCount,
|
|
320
|
-
" member",
|
|
321
|
-
t.memberCount === 1 ? "" : "s"
|
|
322
|
-
]
|
|
323
|
-
})]
|
|
324
|
-
})]
|
|
325
|
-
}) }, t.id))
|
|
326
|
-
}),
|
|
327
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
328
|
-
className: "mt-4 rounded-control border border-hairline bg-raised px-3 py-3",
|
|
329
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
330
|
-
className: `${labelCls} mt-0`,
|
|
331
|
-
children: "New team"
|
|
332
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
333
|
-
className: "flex gap-2",
|
|
334
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
335
|
-
className: inputCls,
|
|
336
|
-
value: newName,
|
|
337
|
-
onChange: (e) => setNewName(e.target.value),
|
|
338
|
-
placeholder: "e.g. Growth Squad",
|
|
339
|
-
onKeyDown: (e) => e.key === "Enter" && void doCreate()
|
|
340
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
341
|
-
className: btnPrimary,
|
|
342
|
-
disabled: busy || !newName.trim(),
|
|
343
|
-
onClick: () => void doCreate(),
|
|
344
|
-
children: "Create"
|
|
345
|
-
})]
|
|
346
|
-
})]
|
|
347
|
-
})
|
|
348
|
-
]
|
|
349
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
350
|
-
className: "min-w-0",
|
|
351
|
-
children: !detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
352
|
-
className: "flex h-full items-center justify-center py-10 text-body text-secondary",
|
|
353
|
-
children: "Select a team to manage it, or create one."
|
|
354
|
-
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
355
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalSection, {
|
|
356
|
-
action: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
357
|
-
className: btnSm,
|
|
358
|
-
onClick: () => {
|
|
359
|
-
setActiveTeamCookie(detail.id);
|
|
360
|
-
onClose();
|
|
361
|
-
navigate({
|
|
362
|
-
to: "/t/$teamId",
|
|
363
|
-
params: { teamId: detail.id }
|
|
364
|
-
});
|
|
365
|
-
},
|
|
366
|
-
children: "Open dashboard →"
|
|
367
|
-
}),
|
|
368
|
-
children: [detail.name, detail.personal ? " · personal" : ""]
|
|
369
|
-
}),
|
|
370
|
-
isOwner && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
371
|
-
className: "mb-4",
|
|
372
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
373
|
-
className: "mb-1.5 mt-3 block text-label font-medium text-secondary",
|
|
374
|
-
children: "Team name"
|
|
375
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
376
|
-
className: "flex gap-2",
|
|
377
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
378
|
-
"aria-label": "Team name",
|
|
379
|
-
className: inputCls,
|
|
380
|
-
value: renameVal,
|
|
381
|
-
onChange: (e) => setRenameVal(e.target.value)
|
|
382
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
383
|
-
className: btn,
|
|
384
|
-
disabled: busy || !renameVal.trim() || renameVal.trim() === detail.name,
|
|
385
|
-
onClick: () => void doRename(),
|
|
386
|
-
children: "Rename"
|
|
387
|
-
})]
|
|
388
|
-
})]
|
|
389
|
-
}),
|
|
390
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalSection, { children: "Members" }),
|
|
391
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
392
|
-
className: "flex flex-col gap-2",
|
|
393
|
-
children: detail.members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", {
|
|
394
|
-
className: "flex items-center justify-between gap-3 rounded-control border border-hairline bg-surface px-3 py-2.5",
|
|
395
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
396
|
-
className: "flex min-w-0 flex-col",
|
|
397
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
398
|
-
className: "truncate text-[15px] font-medium text-display",
|
|
399
|
-
children: [m.email ?? m.displayName ?? m.userId, m.isSelf && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
400
|
-
className: "ml-1.5 text-label text-secondary",
|
|
401
|
-
children: "(you)"
|
|
402
|
-
})]
|
|
403
|
-
})
|
|
404
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
405
|
-
className: "flex shrink-0 items-center gap-2",
|
|
406
|
-
children: isOwner && !m.isSelf ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
407
|
-
"aria-label": "Role",
|
|
408
|
-
className: `${selectCls} w-auto py-1.5`,
|
|
409
|
-
value: m.role,
|
|
410
|
-
onChange: (e) => void doSetRole(m.userId, e.target.value),
|
|
411
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
412
|
-
value: "owner",
|
|
413
|
-
children: "owner"
|
|
414
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
415
|
-
value: "member",
|
|
416
|
-
children: "member"
|
|
417
|
-
})]
|
|
418
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
419
|
-
className: btnDanger,
|
|
420
|
-
disabled: busy,
|
|
421
|
-
onClick: () => void doRemove(m.userId),
|
|
422
|
-
children: "Remove"
|
|
423
|
-
})] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
424
|
-
tone: m.role === "owner" ? "ink" : "neutral",
|
|
425
|
-
children: m.role
|
|
426
|
-
})
|
|
427
|
-
})]
|
|
428
|
-
}, m.userId))
|
|
429
|
-
}),
|
|
430
|
-
isOwner && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
431
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
432
|
-
className: "mt-4 rounded-control border border-hairline bg-raised px-3 py-3",
|
|
433
|
-
children: [
|
|
434
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
435
|
-
className: `mb-1.5 mt-3 block text-label font-medium text-secondary mt-0`,
|
|
436
|
-
children: "Add a member by email"
|
|
437
|
-
}),
|
|
438
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
439
|
-
className: "flex flex-wrap gap-2",
|
|
440
|
-
children: [
|
|
441
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
442
|
-
className: `${inputCls} min-w-[12rem] flex-1`,
|
|
443
|
-
value: email,
|
|
444
|
-
onChange: (e) => setEmail(e.target.value),
|
|
445
|
-
placeholder: "teammate@example.com",
|
|
446
|
-
onKeyDown: (e) => e.key === "Enter" && void doAdd()
|
|
447
|
-
}),
|
|
448
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
449
|
-
"aria-label": "Role for new member",
|
|
450
|
-
className: `${selectCls} w-auto`,
|
|
451
|
-
value: addRole,
|
|
452
|
-
onChange: (e) => setAddRole(e.target.value),
|
|
453
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
454
|
-
value: "member",
|
|
455
|
-
children: "member"
|
|
456
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
457
|
-
value: "owner",
|
|
458
|
-
children: "owner"
|
|
459
|
-
})]
|
|
460
|
-
}),
|
|
461
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
462
|
-
className: btnPrimary,
|
|
463
|
-
disabled: busy || !email.trim(),
|
|
464
|
-
onClick: () => void doAdd(),
|
|
465
|
-
children: "Add"
|
|
466
|
-
})
|
|
467
|
-
]
|
|
468
|
-
}),
|
|
469
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
470
|
-
className: "mt-1.5 text-label text-secondary",
|
|
471
|
-
children: "Adds an existing Pievo account immediately. No account yet? Generate an invite link below."
|
|
472
|
-
})
|
|
473
|
-
]
|
|
474
|
-
}),
|
|
475
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalSection, { children: "Invite links" }),
|
|
476
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
477
|
-
className: "flex flex-wrap items-center gap-2",
|
|
478
|
-
children: [
|
|
479
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
480
|
-
"aria-label": "Invite role",
|
|
481
|
-
className: `${selectCls} w-auto`,
|
|
482
|
-
value: inviteRole,
|
|
483
|
-
onChange: (e) => setInviteRole(e.target.value),
|
|
484
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
485
|
-
value: "member",
|
|
486
|
-
children: "as member"
|
|
487
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
488
|
-
value: "owner",
|
|
489
|
-
children: "as owner"
|
|
490
|
-
})]
|
|
491
|
-
}),
|
|
492
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
493
|
-
className: btn,
|
|
494
|
-
disabled: busy,
|
|
495
|
-
onClick: () => void doInvite(),
|
|
496
|
-
children: "Generate invite link"
|
|
497
|
-
}),
|
|
498
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
499
|
-
className: "text-label text-secondary",
|
|
500
|
-
children: "Single-use · expires in 7 days"
|
|
501
|
-
})
|
|
502
|
-
]
|
|
503
|
-
}),
|
|
504
|
-
detail.invites.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
|
|
505
|
-
className: "mt-3 flex flex-col gap-2",
|
|
506
|
-
children: detail.invites.map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", {
|
|
507
|
-
className: "flex items-center justify-between gap-3 rounded-control border border-hairline bg-surface px-3 py-2.5",
|
|
508
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
509
|
-
className: "flex min-w-0 items-center gap-2",
|
|
510
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
|
|
511
|
-
tone: "neutral",
|
|
512
|
-
children: i.role
|
|
513
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
514
|
-
className: `min-w-0 truncate font-mono text-label ${freshInvite === i.token ? "text-display" : "text-secondary"}`,
|
|
515
|
-
children: inviteUrl(i.token)
|
|
516
|
-
})]
|
|
517
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
518
|
-
className: "flex shrink-0 items-center gap-2",
|
|
519
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyLink, { text: inviteUrl(i.token) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
520
|
-
className: btnDanger,
|
|
521
|
-
disabled: busy,
|
|
522
|
-
onClick: () => void doRevoke(i.token),
|
|
523
|
-
children: "Revoke"
|
|
524
|
-
})]
|
|
525
|
-
})]
|
|
526
|
-
}, i.token))
|
|
527
|
-
})
|
|
528
|
-
] }),
|
|
529
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
530
|
-
className: "mt-6 flex items-center justify-between gap-3 border-t border-hairline pt-4",
|
|
531
|
-
children: [!detail.personal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
532
|
-
className: btn,
|
|
533
|
-
disabled: busy,
|
|
534
|
-
onClick: () => void doLeave(),
|
|
535
|
-
children: "Leave team"
|
|
536
|
-
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
537
|
-
className: "text-label text-secondary",
|
|
538
|
-
children: "Your personal team can't be left or deleted."
|
|
539
|
-
}), isOwner && !detail.personal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
540
|
-
className: btnDanger,
|
|
541
|
-
disabled: busy || detail.loopCount > 0,
|
|
542
|
-
title: detail.loopCount > 0 ? `Move or delete this team's ${detail.loopCount} loop${detail.loopCount === 1 ? "" : "s"} first` : void 0,
|
|
543
|
-
onClick: () => void doDelete(),
|
|
544
|
-
children: ["Delete team", detail.loopCount > 0 ? ` (${detail.loopCount} loop${detail.loopCount === 1 ? "" : "s"})` : ""]
|
|
545
|
-
})]
|
|
546
|
-
})
|
|
547
|
-
] })
|
|
548
|
-
})]
|
|
549
|
-
})
|
|
550
|
-
]
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
function ComposeModal({ open, onClose, teamId }) {
|
|
554
|
-
const [token, setToken] = (0, import_react.useState)(null);
|
|
555
|
-
const [config, setConfig] = (0, import_react.useState)(null);
|
|
556
|
-
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
557
|
-
const [error, setError] = (0, import_react.useState)(null);
|
|
558
|
-
const [configError, setConfigError] = (0, import_react.useState)(false);
|
|
559
|
-
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
560
|
-
const command = token && config ? daemonConnectCommand(origin, token, config.pievoCli, config.customCli) : "";
|
|
561
|
-
(0, import_react.useEffect)(() => {
|
|
562
|
-
if (!open) return;
|
|
563
|
-
setToken(null);
|
|
564
|
-
setConfig(null);
|
|
565
|
-
setCopied(false);
|
|
566
|
-
setError(null);
|
|
567
|
-
setConfigError(false);
|
|
568
|
-
}, [open]);
|
|
569
|
-
(0, import_react.useEffect)(() => {
|
|
570
|
-
if (!open) return;
|
|
571
|
-
let active = true;
|
|
572
|
-
getConfig().then((value) => {
|
|
573
|
-
if (active) setConfig(value);
|
|
574
|
-
}).catch(() => {
|
|
575
|
-
if (active) setConfigError(true);
|
|
576
|
-
});
|
|
577
|
-
mintClaim({ data: teamId }).then((result) => {
|
|
578
|
-
if (!active) return;
|
|
579
|
-
if ("token" in result) setToken(result.token);
|
|
580
|
-
else setError(result.error);
|
|
581
|
-
}).catch(() => {
|
|
582
|
-
if (active) setError("could not mint a connect key");
|
|
583
|
-
});
|
|
584
|
-
return () => {
|
|
585
|
-
active = false;
|
|
586
|
-
};
|
|
587
|
-
}, [open, teamId]);
|
|
588
|
-
async function copyCommand() {
|
|
589
|
-
try {
|
|
590
|
-
await navigator.clipboard.writeText(command);
|
|
591
|
-
setCopied(true);
|
|
592
|
-
setTimeout(() => setCopied(false), 1500);
|
|
593
|
-
} catch {
|
|
594
|
-
setError("could not copy - select the command and copy manually");
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, {
|
|
598
|
-
open,
|
|
599
|
-
onClose,
|
|
600
|
-
children: [
|
|
601
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHead, { title: "New loop" }),
|
|
602
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
603
|
-
className: "mt-6",
|
|
604
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Step, {
|
|
605
|
-
number: "1",
|
|
606
|
-
continued: true,
|
|
607
|
-
title: "Install and connect the daemon",
|
|
608
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
609
|
-
className: "mt-1.5 text-body leading-snug text-secondary",
|
|
610
|
-
children: "Run this command in your terminal. It connects the daemon and installs the Pievo skill."
|
|
611
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
612
|
-
className: "mt-3 flex min-w-0 items-start gap-2",
|
|
613
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
|
|
614
|
-
className: "min-w-0 flex-1 overflow-x-auto whitespace-pre-wrap break-all rounded-control bg-display p-4 font-mono text-label leading-relaxed text-paper",
|
|
615
|
-
children: command || (configError ? "Could not load the connection command. Close and try again." : token ? "Loading CLI configuration…" : "Minting a connect key…")
|
|
616
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
617
|
-
className: btnSm,
|
|
618
|
-
onClick: () => void copyCommand(),
|
|
619
|
-
disabled: !command,
|
|
620
|
-
children: copied ? "Copied ✓" : "Copy"
|
|
621
|
-
})]
|
|
622
|
-
})]
|
|
623
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Step, {
|
|
624
|
-
number: "2",
|
|
625
|
-
title: "Start a new agent session",
|
|
626
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
627
|
-
className: "mt-1.5 text-body leading-snug text-secondary",
|
|
628
|
-
children: "After the command finishes, start a new Claude Code, Codex, or Pi session in the project where the loop should run, then say:"
|
|
629
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
630
|
-
className: "mt-3 rounded-control border border-hairline bg-raised p-4 text-body font-medium text-display",
|
|
631
|
-
children: "Create a Pievo loop."
|
|
632
|
-
})]
|
|
633
|
-
})]
|
|
634
|
-
}),
|
|
635
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
636
|
-
className: "mt-4 text-body text-accent",
|
|
637
|
-
children: ["Error: ", error]
|
|
638
|
-
})
|
|
639
|
-
]
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
function Step({ number, title, continued = false, children }) {
|
|
643
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
644
|
-
className: "flex gap-3.5",
|
|
645
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
646
|
-
className: "flex flex-col items-center",
|
|
647
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
648
|
-
className: "flex size-5 shrink-0 items-center justify-center rounded-full border border-display text-micro font-medium leading-none text-display",
|
|
649
|
-
children: number
|
|
650
|
-
}), continued && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mt-1 w-px flex-1 bg-hairline" })]
|
|
651
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
652
|
-
className: continued ? "min-w-0 flex-1 pb-7" : "min-w-0 flex-1",
|
|
653
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
|
|
654
|
-
className: "text-label font-semibold text-display",
|
|
655
|
-
children: title
|
|
656
|
-
}), children]
|
|
657
|
-
})]
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
/** Loops, machines, and teams change between polls.
|
|
661
|
-
*
|
|
662
|
-
* `teamId` (the `/t/<id>` route's team, in id form or undefined in open mode)
|
|
663
|
-
* scopes every list fn EXPLICITLY - so a tab on /t/A and one on /t/B show
|
|
664
|
-
* different teams simultaneously, independent of the shared last-used cookie. */
|
|
665
|
-
async function fetchLiveData(teamId) {
|
|
666
|
-
const [loops, machines, teams] = await Promise.all([
|
|
667
|
-
listLoops({ data: teamId }),
|
|
668
|
-
listMachines({ data: teamId }),
|
|
669
|
-
listMyTeams({ data: teamId })
|
|
670
|
-
]);
|
|
671
|
-
return {
|
|
672
|
-
loops,
|
|
673
|
-
machines,
|
|
674
|
-
teams
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* The dashboard body, shared by the `/` (open mode) and `/t/<teamId>` routes. It
|
|
679
|
-
* renders from its own fetch-then-set poll state, seeded once from the route
|
|
680
|
-
* loader's data, and scopes every fetch to `teamId` so the view is pinned to the
|
|
681
|
-
* URL's team (multi-tab safe). The route mounts it with `key={teamId}` so a
|
|
682
|
-
* team switch (a `/t/<id>` navigation) re-seeds state from the new loader data.
|
|
683
|
-
*/
|
|
684
|
-
function DashboardView({ teamId, initial }) {
|
|
685
|
-
const [live, setLive] = (0, import_react.useState)(null);
|
|
686
|
-
const { loops, machines, teams } = live ?? initial;
|
|
687
|
-
const online = machines.filter((m) => m.online).length;
|
|
688
|
-
const navigate = useNavigate();
|
|
689
|
-
const [composeOpen, setComposeOpen] = (0, import_react.useState)(false);
|
|
690
|
-
const [machinesOpen, setMachinesOpen] = (0, import_react.useState)(false);
|
|
691
|
-
const [teamsOpen, setTeamsOpen] = (0, import_react.useState)(false);
|
|
692
|
-
const refetch = (0, import_react.useCallback)(async () => {
|
|
693
|
-
try {
|
|
694
|
-
setLive(await fetchLiveData(teamId));
|
|
695
|
-
} catch {}
|
|
696
|
-
}, [teamId]);
|
|
697
|
-
const openRef = (0, import_react.useRef)(false);
|
|
698
|
-
openRef.current = composeOpen || machinesOpen || teamsOpen;
|
|
699
|
-
const anyRunning = loops.some((j) => j.running);
|
|
700
|
-
(0, import_react.useEffect)(() => {
|
|
701
|
-
const t = setInterval(() => {
|
|
702
|
-
if (!openRef.current) refetch();
|
|
703
|
-
}, anyRunning ? 3e3 : 1e4);
|
|
704
|
-
return () => clearInterval(t);
|
|
705
|
-
}, [refetch, anyRunning]);
|
|
706
|
-
const activeCount = loops.filter((loop) => loop.enabled && !loop.deleteRequestedAt).length;
|
|
707
|
-
const pausedCount = loops.filter((loop) => !loop.enabled && !loop.deleteRequestedAt).length;
|
|
708
|
-
const deletingCount = loops.filter((loop) => Boolean(loop.deleteRequestedAt)).length;
|
|
709
|
-
const lifecycleCounts = [
|
|
710
|
-
`${activeCount} active`,
|
|
711
|
-
`${pausedCount} paused`,
|
|
712
|
-
...deletingCount ? [`${deletingCount} deleting`] : []
|
|
713
|
-
].join(" · ");
|
|
714
|
-
const cardProps = () => ({
|
|
715
|
-
onOpen: (id) => void navigate({
|
|
716
|
-
to: "/loops/$loopId",
|
|
717
|
-
params: { loopId: id }
|
|
718
|
-
}),
|
|
719
|
-
onPickRun: (loopId, run) => void navigate({
|
|
720
|
-
to: "/loops/$loopId/runs/$runId",
|
|
721
|
-
params: {
|
|
722
|
-
loopId,
|
|
723
|
-
runId: run.id
|
|
724
|
-
}
|
|
725
|
-
})
|
|
726
|
-
});
|
|
727
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TooltipProvider, {
|
|
728
|
-
delay: 120,
|
|
729
|
-
children: [
|
|
730
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("header", {
|
|
731
|
-
className: "glass glass-bar sticky top-0 z-50",
|
|
732
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
733
|
-
className: "mx-auto flex max-w-[1180px] items-center gap-3 px-8 py-2.5",
|
|
734
|
-
children: [
|
|
735
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PievoLogo, { size: 30 }),
|
|
736
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
737
|
-
className: "text-[18px] font-semibold tracking-[-0.015em] text-display",
|
|
738
|
-
children: "Pievo"
|
|
739
|
-
}),
|
|
740
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TeamSwitcher, { data: teams }),
|
|
741
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-1" }),
|
|
742
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
|
|
743
|
-
href: GITHUB_URL,
|
|
744
|
-
target: "_blank",
|
|
745
|
-
rel: "noreferrer",
|
|
746
|
-
"aria-label": "GitHub repository",
|
|
747
|
-
title: "GitHub",
|
|
748
|
-
className: headerIconBtn,
|
|
749
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitHubIcon, { className: "size-[17px]" })
|
|
750
|
-
}),
|
|
751
|
-
teams && teams.teams.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
752
|
-
onClick: () => setTeamsOpen(true),
|
|
753
|
-
className: headerBtn,
|
|
754
|
-
children: "Teams"
|
|
755
|
-
}),
|
|
756
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
757
|
-
onClick: () => setMachinesOpen(true),
|
|
758
|
-
className: `${headerBtn} gap-1.5`,
|
|
759
|
-
children: [
|
|
760
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `inline-block size-1.5 rounded-full ${online ? "bg-rubik-green" : "bg-disabled"}` }),
|
|
761
|
-
online,
|
|
762
|
-
" ",
|
|
763
|
-
online === 1 ? "machine" : "machines",
|
|
764
|
-
" online"
|
|
765
|
-
]
|
|
766
|
-
}),
|
|
767
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
768
|
-
onClick: () => setComposeOpen(true),
|
|
769
|
-
className: "inline-flex shrink-0 cursor-pointer items-center rounded-full bg-display px-3.5 py-1.5 text-meta font-medium text-paper transition-opacity hover:opacity-85",
|
|
770
|
-
children: "New Loop"
|
|
771
|
-
})
|
|
772
|
-
]
|
|
773
|
-
})
|
|
774
|
-
}),
|
|
775
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
|
|
776
|
-
className: "mx-auto max-w-[1180px] px-8 pb-24",
|
|
777
|
-
children: [
|
|
778
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
779
|
-
className: "pb-2 pt-14 text-center",
|
|
780
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
781
|
-
className: "font-pixel text-[clamp(28px,4.5vw,38px)] leading-[1.15] text-display",
|
|
782
|
-
children: "What should happen while you sleep?"
|
|
783
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
784
|
-
className: "mt-7",
|
|
785
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
786
|
-
onClick: () => setComposeOpen(true),
|
|
787
|
-
className: "inline-flex cursor-pointer items-center gap-1.5 rounded-full bg-display px-6 py-2.5 text-body font-medium text-paper transition-opacity hover:opacity-85",
|
|
788
|
-
children: ["Start a new loop ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
789
|
-
"aria-hidden": true,
|
|
790
|
-
children: "→"
|
|
791
|
-
})]
|
|
792
|
-
})
|
|
793
|
-
})]
|
|
794
|
-
}),
|
|
795
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
796
|
-
className: "mb-5 mt-12 flex items-baseline gap-2.5",
|
|
797
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
|
|
798
|
-
className: "text-body font-semibold text-display",
|
|
799
|
-
children: "Loops"
|
|
800
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
801
|
-
className: "text-label text-secondary",
|
|
802
|
-
children: loops.length ? lifecycleCounts : ""
|
|
803
|
-
})]
|
|
804
|
-
}),
|
|
805
|
-
loops.length ? loops.map((j) => /* @__PURE__ */ (0, import_react.createElement)(LoopCard, {
|
|
806
|
-
loop: j,
|
|
807
|
-
...cardProps(),
|
|
808
|
-
key: j.id
|
|
809
|
-
})) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
810
|
-
className: "py-16 text-center",
|
|
811
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
812
|
-
className: "text-[15px] text-secondary",
|
|
813
|
-
children: "No loops yet"
|
|
814
|
-
}), !loops.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
815
|
-
className: "mt-1.5 text-body text-disabled",
|
|
816
|
-
children: "Create a scheduled prompt for a connected machine."
|
|
817
|
-
})]
|
|
818
|
-
})
|
|
819
|
-
]
|
|
820
|
-
}),
|
|
821
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComposeModal, {
|
|
822
|
-
open: composeOpen,
|
|
823
|
-
teamId,
|
|
824
|
-
onClose: () => setComposeOpen(false)
|
|
825
|
-
}),
|
|
826
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachinesModal, {
|
|
827
|
-
open: machinesOpen,
|
|
828
|
-
onClose: () => setMachinesOpen(false),
|
|
829
|
-
teamId
|
|
830
|
-
}),
|
|
831
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TeamsModal, {
|
|
832
|
-
open: teamsOpen,
|
|
833
|
-
onClose: () => setTeamsOpen(false),
|
|
834
|
-
activeTeamId: teamId
|
|
835
|
-
})
|
|
836
|
-
]
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
var headerBtn = "inline-flex shrink-0 cursor-pointer items-center rounded-full px-3 py-1.5 text-meta font-medium text-secondary transition-colors hover:bg-raised hover:text-display";
|
|
840
|
-
var headerIconBtn = "inline-flex shrink-0 cursor-pointer items-center rounded-full p-1.5 text-secondary transition-colors hover:bg-raised hover:text-display";
|
|
841
|
-
//#endregion
|
|
842
|
-
export { fetchLiveData as n, DashboardView as t };
|