@protobase/ui 0.1.0 → 0.1.1
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/dist/app/app.js +6 -1
- package/dist/app/pages/project-ui.d.ts +6 -1
- package/dist/assistant-dock/assistant-dock.d.ts +15 -0
- package/dist/assistant-dock/assistant-dock.js +81 -0
- package/dist/assistant-dock/chat-message.js +9 -0
- package/dist/assistant-dock/composer.js +39 -0
- package/dist/assistant-dock/dock-item.d.ts +4 -0
- package/dist/assistant-dock/dock-item.js +37 -0
- package/dist/assistant-dock/model.d.ts +84 -0
- package/dist/assistant-dock/model.js +26 -0
- package/dist/assistant-dock/plan-card.d.ts +13 -0
- package/dist/assistant-dock/plan-card.js +133 -0
- package/dist/assistant-dock/progress-card.js +18 -0
- package/dist/assistant-dock/query-card.js +52 -0
- package/dist/assistant-dock/queue-card.js +31 -0
- package/dist/assistant-dock/result-card.js +77 -0
- package/dist/assistant-panel/content.d.ts +7 -0
- package/dist/assistant-panel/step-list.js +30 -0
- package/dist/auth/auth-provider.js +7 -0
- package/dist/auth/sign-in-form.js +76 -60
- package/dist/auth/sign-in-page.js +5 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/package.json +4 -4
package/dist/app/app.js
CHANGED
|
@@ -14,7 +14,7 @@ import { CreatePage } from "./create/create-page.js";
|
|
|
14
14
|
import { ListPage } from "./list-page.js";
|
|
15
15
|
import { userMenuFromMeta } from "./user-menu-from-meta.js";
|
|
16
16
|
import { navFromMeta } from "./nav-from-meta.js";
|
|
17
|
-
import { ProjectUiProvider } from "./pages/project-ui.js";
|
|
17
|
+
import { ProjectUiProvider, useProjectUi } from "./pages/project-ui.js";
|
|
18
18
|
import { ComposedPage } from "./pages/composed-page.js";
|
|
19
19
|
import { RecordPage } from "./record-page.js";
|
|
20
20
|
import { useGlobalSearch } from "./search/use-global-search.js";
|
|
@@ -47,6 +47,9 @@ const Routes = ({ workspace, sidebarMode }) => {
|
|
|
47
47
|
const model = route.resource ? meta.resources[route.resource] : void 0;
|
|
48
48
|
const record = useRecord(route.resource ?? "", route.key ?? "", Boolean(model && route.key && route.key !== "new"));
|
|
49
49
|
const title = model && record.data ? recordTitle(model, view, record.data.record, route.key ?? "") : route.key;
|
|
50
|
+
const { shell } = useProjectUi();
|
|
51
|
+
const Actions = shell?.actions;
|
|
52
|
+
const RightPanel = shell?.rightPanel;
|
|
50
53
|
const breadcrumb = breadcrumbFor({
|
|
51
54
|
basePath,
|
|
52
55
|
route,
|
|
@@ -74,6 +77,8 @@ const Routes = ({ workspace, sidebarMode }) => {
|
|
|
74
77
|
groups: search.groups,
|
|
75
78
|
onSelect: open
|
|
76
79
|
} : void 0,
|
|
80
|
+
actions: Actions && /* @__PURE__ */ jsx(Actions, {}),
|
|
81
|
+
rightPanel: RightPanel && /* @__PURE__ */ jsx(RightPanel, {}),
|
|
77
82
|
children: !route.resource ? /* @__PURE__ */ jsx(Notice, {
|
|
78
83
|
title: "Choose a resource",
|
|
79
84
|
children: "Pick one from the sidebar."
|
|
@@ -14,11 +14,16 @@ export type ActionContext = {
|
|
|
14
14
|
export type ActionHandler = (context: ActionContext) => void | Promise<void>;
|
|
15
15
|
/**
|
|
16
16
|
* The project's own React code for composed pages: custom components by the name their layout declares
|
|
17
|
-
* (`component('UsageChart')`),
|
|
17
|
+
* (`component('UsageChart')`), handlers for named actions that have no built-in behaviour, and the shell's slots.
|
|
18
18
|
*/
|
|
19
19
|
export type ProjectUi = {
|
|
20
20
|
components?: Record<string, ComponentType<any>>;
|
|
21
21
|
actions?: Record<string, ActionHandler>;
|
|
22
|
+
/** Rendered in the shell on every page: `actions` at the end of the top bar, `rightPanel` beside the page. */
|
|
23
|
+
shell?: {
|
|
24
|
+
actions?: ComponentType;
|
|
25
|
+
rightPanel?: ComponentType;
|
|
26
|
+
};
|
|
22
27
|
};
|
|
23
28
|
/** Typed identity, for `export default defineUi({ components, actions })` in `protobase.ui.tsx`. */
|
|
24
29
|
export declare const defineUi: (ui: ProjectUi) => ProjectUi;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DockItem } from "./model.js";
|
|
2
|
+
import { DockItemHandlers } from "./dock-item.js";
|
|
3
|
+
//#region src/assistant-dock/assistant-dock.d.ts
|
|
4
|
+
export type AssistantDockProps = DockItemHandlers & {
|
|
5
|
+
/** The chat in order: messages and the cards of its tasks and queries. */
|
|
6
|
+
items: DockItem[];
|
|
7
|
+
/** The organization's credits; Approve needs the plan's quote. */
|
|
8
|
+
balance: number;
|
|
9
|
+
replying?: boolean;
|
|
10
|
+
onSend?: (text: string) => void;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
};
|
|
13
|
+
/** The assistant beside the app: chat, plans to approve, the queue, progress, results and query tables. */
|
|
14
|
+
export declare const AssistantDock: ({ items, balance, replying, onSend, onClose, onRequestChanges, ...handlers }: AssistantDockProps) => import("react").JSX.Element;
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { Composer } from "./composer.js";
|
|
3
|
+
import { formatCredits } from "./model.js";
|
|
4
|
+
import { DockItemView } from "./dock-item.js";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Sparkles, X } from "lucide-react";
|
|
8
|
+
//#region src/assistant-dock/assistant-dock.tsx
|
|
9
|
+
/** The assistant beside the app: chat, plans to approve, the queue, progress, results and query tables. */
|
|
10
|
+
const AssistantDock = ({ items, balance, replying, onSend, onClose, onRequestChanges, ...handlers }) => {
|
|
11
|
+
const list = useRef(null);
|
|
12
|
+
const input = useRef(null);
|
|
13
|
+
const [revising, setRevising] = useState(false);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const element = list.current;
|
|
16
|
+
if (element) element.scrollTop = element.scrollHeight;
|
|
17
|
+
}, [items.length]);
|
|
18
|
+
const requestChanges = (taskId) => {
|
|
19
|
+
setRevising(true);
|
|
20
|
+
input.current?.focus();
|
|
21
|
+
onRequestChanges?.(taskId);
|
|
22
|
+
};
|
|
23
|
+
const send = (text) => {
|
|
24
|
+
setRevising(false);
|
|
25
|
+
onSend?.(text);
|
|
26
|
+
};
|
|
27
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
28
|
+
"aria-label": "Assistant",
|
|
29
|
+
className: "flex h-full w-[380px] max-w-full shrink-0 flex-col border-l border-border bg-surface",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsxs("header", {
|
|
32
|
+
className: "flex h-12 shrink-0 items-center gap-2 border-b border-border bg-background px-4",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "size-4 text-primary" }),
|
|
35
|
+
/* @__PURE__ */ jsx("h2", {
|
|
36
|
+
className: "flex-1 text-[13px] font-semibold",
|
|
37
|
+
children: "Assistant"
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ jsx("span", {
|
|
40
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
41
|
+
children: formatCredits(balance)
|
|
42
|
+
}),
|
|
43
|
+
onClose && /* @__PURE__ */ jsx(Button, {
|
|
44
|
+
variant: "ghost",
|
|
45
|
+
size: "sm",
|
|
46
|
+
className: "w-7 px-0",
|
|
47
|
+
"aria-label": "Close",
|
|
48
|
+
onClick: onClose,
|
|
49
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ jsxs("div", {
|
|
54
|
+
ref: list,
|
|
55
|
+
className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
|
|
56
|
+
children: [items.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
57
|
+
className: "m-auto max-w-60 text-center text-[13px] text-muted-foreground",
|
|
58
|
+
children: "Ask for a change to this app, or a question about its data."
|
|
59
|
+
}), items.map((item) => /* @__PURE__ */ jsx(DockItemView, {
|
|
60
|
+
item,
|
|
61
|
+
balance,
|
|
62
|
+
handlers: {
|
|
63
|
+
...handlers,
|
|
64
|
+
onRequestChanges: requestChanges
|
|
65
|
+
}
|
|
66
|
+
}, item.id))]
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ jsx("footer", {
|
|
69
|
+
className: "shrink-0 border-t border-border bg-background p-3",
|
|
70
|
+
children: /* @__PURE__ */ jsx(Composer, {
|
|
71
|
+
inputRef: input,
|
|
72
|
+
onSend: send,
|
|
73
|
+
replying,
|
|
74
|
+
placeholder: revising ? "What should change in the plan?" : "Ask for a change or about your data..."
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { AssistantDock };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
//#region src/assistant-dock/chat-message.tsx
|
|
4
|
+
const ChatMessage = ({ from, text }) => /* @__PURE__ */ jsx("div", {
|
|
5
|
+
className: cn("whitespace-pre-wrap break-words text-[13px]", from === "user" ? "ml-8 self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground shadow-sm" : "mr-4 text-foreground"),
|
|
6
|
+
children: text
|
|
7
|
+
});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { ChatMessage };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Input } from "../primitives/input.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { SendHorizontal } from "lucide-react";
|
|
6
|
+
//#region src/assistant-dock/composer.tsx
|
|
7
|
+
const Composer = ({ onSend, replying, placeholder, inputRef }) => {
|
|
8
|
+
const [text, setText] = useState("");
|
|
9
|
+
const message = text.trim();
|
|
10
|
+
const send = () => {
|
|
11
|
+
if (!message || replying) return;
|
|
12
|
+
onSend?.(message);
|
|
13
|
+
setText("");
|
|
14
|
+
};
|
|
15
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
16
|
+
className: "flex gap-2",
|
|
17
|
+
onSubmit: (event) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
send();
|
|
20
|
+
},
|
|
21
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
22
|
+
ref: inputRef,
|
|
23
|
+
"aria-label": "Message",
|
|
24
|
+
placeholder,
|
|
25
|
+
value: text,
|
|
26
|
+
onChange: (event) => setText(event.target.value),
|
|
27
|
+
wrapperClassName: "flex-1"
|
|
28
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
29
|
+
type: "submit",
|
|
30
|
+
variant: "primary",
|
|
31
|
+
className: "w-8 px-0",
|
|
32
|
+
"aria-label": "Send",
|
|
33
|
+
disabled: !message || replying,
|
|
34
|
+
children: /* @__PURE__ */ jsx(SendHorizontal, { className: "size-4" })
|
|
35
|
+
})]
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { Composer };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ChatMessage } from "./chat-message.js";
|
|
2
|
+
import { PlanCard } from "./plan-card.js";
|
|
3
|
+
import { ProgressCard } from "./progress-card.js";
|
|
4
|
+
import { QueryCard } from "./query-card.js";
|
|
5
|
+
import { QueueCard } from "./queue-card.js";
|
|
6
|
+
import { ResultCard } from "./result-card.js";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/assistant-dock/dock-item.tsx
|
|
9
|
+
const DockItemView = ({ item, balance, handlers }) => {
|
|
10
|
+
switch (item.kind) {
|
|
11
|
+
case "user":
|
|
12
|
+
case "assistant": return /* @__PURE__ */ jsx(ChatMessage, {
|
|
13
|
+
from: item.kind,
|
|
14
|
+
text: item.text
|
|
15
|
+
});
|
|
16
|
+
case "queue": return /* @__PURE__ */ jsx(QueueCard, {
|
|
17
|
+
taskId: item.taskId,
|
|
18
|
+
request: item.request,
|
|
19
|
+
ahead: item.ahead,
|
|
20
|
+
waitingOnPlan: item.waitingOnPlan,
|
|
21
|
+
onCancel: handlers.onCancel
|
|
22
|
+
});
|
|
23
|
+
case "progress": return /* @__PURE__ */ jsx(ProgressCard, {
|
|
24
|
+
phase: item.phase,
|
|
25
|
+
steps: item.steps
|
|
26
|
+
});
|
|
27
|
+
case "plan": return /* @__PURE__ */ jsx(PlanCard, {
|
|
28
|
+
plan: item.plan,
|
|
29
|
+
balance,
|
|
30
|
+
...handlers
|
|
31
|
+
});
|
|
32
|
+
case "result": return /* @__PURE__ */ jsx(ResultCard, { result: item.result });
|
|
33
|
+
case "query": return /* @__PURE__ */ jsx(QueryCard, { query: item.query });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { DockItemView };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Step } from "../assistant-panel/content.js";
|
|
2
|
+
//#region src/assistant-dock/model.d.ts
|
|
3
|
+
export type PlanSize = 'S' | 'M' | 'L';
|
|
4
|
+
/** A plan as the planner returned it and central priced it. */
|
|
5
|
+
export type Plan = {
|
|
6
|
+
taskId: string;
|
|
7
|
+
/** 1 for the first plan; "Ask for changes" and "Update plan" make the next one. */
|
|
8
|
+
revision: number;
|
|
9
|
+
summary: string;
|
|
10
|
+
steps: Array<{
|
|
11
|
+
title: string;
|
|
12
|
+
files?: string[];
|
|
13
|
+
}>;
|
|
14
|
+
migrations: Array<{
|
|
15
|
+
description: string;
|
|
16
|
+
destructive: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
risks: string[];
|
|
19
|
+
size: PlanSize;
|
|
20
|
+
/** The quote: what Approve holds. */
|
|
21
|
+
credits: number;
|
|
22
|
+
/** The branch moved on GitHub since the plan was made. */
|
|
23
|
+
outdated: boolean;
|
|
24
|
+
/** Tasks behind this one in the app's queue, waiting until it is answered. */
|
|
25
|
+
waiting: number;
|
|
26
|
+
/** `dropped`: the platform dropped it and refunded the deposit (repository replaced, app archived). */
|
|
27
|
+
status: 'awaiting' | 'approved' | 'canceled' | 'superseded' | 'dropped';
|
|
28
|
+
};
|
|
29
|
+
export type JobResult = {
|
|
30
|
+
taskId: string;
|
|
31
|
+
outcome: 'live' | 'failed' | 'capped';
|
|
32
|
+
summary: string;
|
|
33
|
+
commit?: {
|
|
34
|
+
sha: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
};
|
|
37
|
+
/** The migrations the job applied; they stay live when it fails. */
|
|
38
|
+
dbChanges: string[];
|
|
39
|
+
/** A finished change GitHub refused, kept on `protobase/task-<n>`. */
|
|
40
|
+
keptOnBranch?: string;
|
|
41
|
+
/** What was charged: the quote when live, 0 otherwise. */
|
|
42
|
+
credits: number;
|
|
43
|
+
};
|
|
44
|
+
export type QueryResult = {
|
|
45
|
+
sql: string;
|
|
46
|
+
columns: string[];
|
|
47
|
+
rows: unknown[][];
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
};
|
|
50
|
+
export type DockItem = {
|
|
51
|
+
kind: 'user';
|
|
52
|
+
id: string;
|
|
53
|
+
text: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'assistant';
|
|
56
|
+
id: string;
|
|
57
|
+
text: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'queue';
|
|
60
|
+
id: string;
|
|
61
|
+
taskId: string;
|
|
62
|
+
request: string;
|
|
63
|
+
ahead: number;
|
|
64
|
+
waitingOnPlan: boolean;
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'progress';
|
|
67
|
+
id: string;
|
|
68
|
+
taskId: string;
|
|
69
|
+
phase: 'planning' | 'running';
|
|
70
|
+
steps: Step[];
|
|
71
|
+
} | {
|
|
72
|
+
kind: 'plan';
|
|
73
|
+
id: string;
|
|
74
|
+
plan: Plan;
|
|
75
|
+
} | {
|
|
76
|
+
kind: 'result';
|
|
77
|
+
id: string;
|
|
78
|
+
result: JobResult;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'query';
|
|
81
|
+
id: string;
|
|
82
|
+
query: QueryResult;
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/assistant-dock/model.ts
|
|
2
|
+
const formatCredits = (credits) => `${credits} ${credits === 1 ? "credit" : "credits"}`;
|
|
3
|
+
/** Approve is offered only on a plan awaiting an answer, and only when the balance covers the quote. */
|
|
4
|
+
const approval = (plan, balance) => {
|
|
5
|
+
if (plan.status !== "awaiting") return { enabled: false };
|
|
6
|
+
if (balance < plan.credits) return {
|
|
7
|
+
enabled: false,
|
|
8
|
+
reason: `Needs ${formatCredits(plan.credits)}; the balance is ${formatCredits(balance)}.`
|
|
9
|
+
};
|
|
10
|
+
return { enabled: true };
|
|
11
|
+
};
|
|
12
|
+
const approveLabel = (plan) => `${plan.outdated ? "Approve anyway" : "Approve"} · ${formatCredits(plan.credits)}`;
|
|
13
|
+
const queueText = (ahead, waitingOnPlan) => {
|
|
14
|
+
if (waitingOnPlan) return "Waiting for the plan above to be approved or canceled.";
|
|
15
|
+
if (ahead === 0) return "Next in line.";
|
|
16
|
+
return `Waiting, ${ahead} ${ahead === 1 ? "task" : "tasks"} ahead.`;
|
|
17
|
+
};
|
|
18
|
+
const waitingText = (waiting) => waiting === 0 ? void 0 : `${waiting} ${waiting === 1 ? "task waits" : "tasks wait"} on this plan.`;
|
|
19
|
+
/** A query cell as text: `null` stays visible, objects and arrays as JSON. */
|
|
20
|
+
const formatCell = (value) => {
|
|
21
|
+
if (value === null || value === void 0) return "null";
|
|
22
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
23
|
+
return String(value);
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { approval, approveLabel, formatCell, formatCredits, queueText, waitingText };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plan } from "./model.js";
|
|
2
|
+
//#region src/assistant-dock/plan-card.d.ts
|
|
3
|
+
export type PlanCardProps = {
|
|
4
|
+
plan: Plan;
|
|
5
|
+
balance: number;
|
|
6
|
+
onApprove?: (taskId: string) => void;
|
|
7
|
+
/** Asks for the next revision; the dock moves to the chat box for what should change. */
|
|
8
|
+
onRequestChanges?: (taskId: string) => void;
|
|
9
|
+
onCancel?: (taskId: string) => void;
|
|
10
|
+
/** A free re-plan from the moved branch. */
|
|
11
|
+
onUpdatePlan?: (taskId: string) => void;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Badge } from "../primitives/badge.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { approval, approveLabel, formatCredits, waitingText } from "./model.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { AlertTriangle, GitBranch, ListChecks } from "lucide-react";
|
|
6
|
+
//#region src/assistant-dock/plan-card.tsx
|
|
7
|
+
const statusNote = {
|
|
8
|
+
awaiting: void 0,
|
|
9
|
+
approved: "Approved. The credits are held until the change is live.",
|
|
10
|
+
canceled: "Canceled. The 1-credit deposit was kept.",
|
|
11
|
+
superseded: "Replaced by a newer plan.",
|
|
12
|
+
dropped: "The repository changed, so this plan was dropped and the deposit refunded."
|
|
13
|
+
};
|
|
14
|
+
const PlanCard = ({ plan, balance, onApprove, onRequestChanges, onCancel, onUpdatePlan }) => {
|
|
15
|
+
const approve = approval(plan, balance);
|
|
16
|
+
const awaiting = plan.status === "awaiting";
|
|
17
|
+
const waiting = awaiting ? waitingText(plan.waiting) : void 0;
|
|
18
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
19
|
+
"aria-label": `Plan, revision ${plan.revision}`,
|
|
20
|
+
className: "shrink-0 overflow-hidden rounded-lg border border-primary-border bg-background shadow-sm",
|
|
21
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
22
|
+
className: "flex items-center gap-2 bg-primary-soft px-3 py-2",
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsx(ListChecks, { className: "size-3.5 text-primary-text" }),
|
|
25
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
26
|
+
className: "flex-1 text-[13px] font-semibold text-primary-text",
|
|
27
|
+
children: ["Plan", plan.revision > 1 && `, revision ${plan.revision}`]
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ jsxs(Badge, {
|
|
30
|
+
tone: "blue",
|
|
31
|
+
dot: false,
|
|
32
|
+
children: ["Size ", plan.size]
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ jsx("span", {
|
|
35
|
+
className: "text-xs font-medium text-primary-text",
|
|
36
|
+
children: formatCredits(plan.credits)
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
40
|
+
className: "space-y-2.5 px-3 py-2.5",
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsx("p", {
|
|
43
|
+
className: "text-[13px]",
|
|
44
|
+
children: plan.summary
|
|
45
|
+
}),
|
|
46
|
+
plan.steps.length > 0 && /* @__PURE__ */ jsx("ol", {
|
|
47
|
+
className: "list-decimal space-y-1 pl-5 text-xs text-muted-foreground",
|
|
48
|
+
children: plan.steps.map((step) => /* @__PURE__ */ jsx("li", { children: step.title }, step.title))
|
|
49
|
+
}),
|
|
50
|
+
plan.migrations.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
51
|
+
className: "space-y-1",
|
|
52
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
53
|
+
className: "text-xs font-semibold",
|
|
54
|
+
children: "Database changes"
|
|
55
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
56
|
+
className: "space-y-1",
|
|
57
|
+
children: plan.migrations.map((migration) => /* @__PURE__ */ jsxs("li", {
|
|
58
|
+
className: "flex items-start gap-2 text-xs",
|
|
59
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
60
|
+
className: "min-w-0 flex-1",
|
|
61
|
+
children: migration.description
|
|
62
|
+
}), migration.destructive && /* @__PURE__ */ jsx(Badge, {
|
|
63
|
+
tone: "red",
|
|
64
|
+
children: "Destructive"
|
|
65
|
+
})]
|
|
66
|
+
}, migration.description))
|
|
67
|
+
})]
|
|
68
|
+
}),
|
|
69
|
+
plan.risks.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
70
|
+
className: "space-y-1",
|
|
71
|
+
children: plan.risks.map((risk) => /* @__PURE__ */ jsxs("li", {
|
|
72
|
+
className: "flex items-start gap-2 text-xs text-warning-text",
|
|
73
|
+
children: [/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 size-3 shrink-0" }), risk]
|
|
74
|
+
}, risk))
|
|
75
|
+
}),
|
|
76
|
+
awaiting && plan.outdated && /* @__PURE__ */ jsxs("p", {
|
|
77
|
+
role: "status",
|
|
78
|
+
className: "flex items-start gap-2 rounded-md bg-warning-soft px-2.5 py-2 text-xs text-warning-text",
|
|
79
|
+
children: [/* @__PURE__ */ jsx(GitBranch, { className: "mt-0.5 size-3 shrink-0" }), "The branch moved on GitHub since this plan was made. Approving runs it on the new code."]
|
|
80
|
+
}),
|
|
81
|
+
waiting && /* @__PURE__ */ jsxs("p", {
|
|
82
|
+
className: "text-xs text-muted-foreground",
|
|
83
|
+
children: [waiting, " Cancel frees the queue."]
|
|
84
|
+
}),
|
|
85
|
+
statusNote[plan.status] && /* @__PURE__ */ jsx("p", {
|
|
86
|
+
className: "text-xs text-muted-foreground",
|
|
87
|
+
children: statusNote[plan.status]
|
|
88
|
+
}),
|
|
89
|
+
awaiting && /* @__PURE__ */ jsxs("div", {
|
|
90
|
+
className: "space-y-2 pt-0.5",
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ jsx(Button, {
|
|
93
|
+
variant: "primary",
|
|
94
|
+
className: "w-full",
|
|
95
|
+
disabled: !approve.enabled,
|
|
96
|
+
onClick: () => onApprove?.(plan.taskId),
|
|
97
|
+
children: approveLabel(plan)
|
|
98
|
+
}),
|
|
99
|
+
approve.reason && /* @__PURE__ */ jsx("p", {
|
|
100
|
+
className: "text-xs text-danger-text",
|
|
101
|
+
children: approve.reason
|
|
102
|
+
}),
|
|
103
|
+
/* @__PURE__ */ jsxs("div", {
|
|
104
|
+
className: "flex gap-2",
|
|
105
|
+
children: [
|
|
106
|
+
plan.outdated && /* @__PURE__ */ jsx(Button, {
|
|
107
|
+
size: "sm",
|
|
108
|
+
className: "flex-1",
|
|
109
|
+
onClick: () => onUpdatePlan?.(plan.taskId),
|
|
110
|
+
children: "Update plan"
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ jsx(Button, {
|
|
113
|
+
size: "sm",
|
|
114
|
+
className: "flex-1",
|
|
115
|
+
onClick: () => onRequestChanges?.(plan.taskId),
|
|
116
|
+
children: "Ask for changes"
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ jsx(Button, {
|
|
119
|
+
size: "sm",
|
|
120
|
+
variant: "ghost",
|
|
121
|
+
onClick: () => onCancel?.(plan.taskId),
|
|
122
|
+
children: "Cancel"
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
})]
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { PlanCard };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StepList } from "../assistant-panel/step-list.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/assistant-dock/progress-card.tsx
|
|
4
|
+
const titles = {
|
|
5
|
+
planning: "Planning",
|
|
6
|
+
running: "Making the change"
|
|
7
|
+
};
|
|
8
|
+
/** What the planning run or the job is doing, step by step. */
|
|
9
|
+
const ProgressCard = ({ phase, steps }) => /* @__PURE__ */ jsxs("section", {
|
|
10
|
+
"aria-label": titles[phase],
|
|
11
|
+
className: "shrink-0 space-y-2 rounded-lg border border-border-strong bg-background px-3 py-2.5 shadow-sm",
|
|
12
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
13
|
+
className: "text-xs font-semibold",
|
|
14
|
+
children: titles[phase]
|
|
15
|
+
}), /* @__PURE__ */ jsx(StepList, { steps })]
|
|
16
|
+
});
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ProgressCard };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { formatCell } from "./model.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Database } from "lucide-react";
|
|
4
|
+
//#region src/assistant-dock/query-card.tsx
|
|
5
|
+
/** A read-only query the chat ran, with its rows as a table. */
|
|
6
|
+
const QueryCard = ({ query }) => /* @__PURE__ */ jsxs("section", {
|
|
7
|
+
"aria-label": "Query result",
|
|
8
|
+
className: "shrink-0 overflow-hidden rounded-lg border border-border-strong bg-background shadow-sm",
|
|
9
|
+
children: [
|
|
10
|
+
/* @__PURE__ */ jsxs("header", {
|
|
11
|
+
className: "flex items-start gap-2 bg-surface px-3 py-2",
|
|
12
|
+
children: [/* @__PURE__ */ jsx(Database, { className: "mt-0.5 size-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("code", {
|
|
13
|
+
className: "min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-[11px] text-foreground",
|
|
14
|
+
children: query.sql
|
|
15
|
+
})]
|
|
16
|
+
}),
|
|
17
|
+
query.rows.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
18
|
+
className: "px-3 py-2 text-xs text-muted-foreground",
|
|
19
|
+
children: "No rows."
|
|
20
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
21
|
+
className: "max-h-64 overflow-auto",
|
|
22
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
23
|
+
className: "w-full border-collapse text-left text-[11px]",
|
|
24
|
+
children: [/* @__PURE__ */ jsx("thead", {
|
|
25
|
+
className: "sticky top-0 bg-background",
|
|
26
|
+
children: /* @__PURE__ */ jsx("tr", { children: query.columns.map((column) => /* @__PURE__ */ jsx("th", {
|
|
27
|
+
scope: "col",
|
|
28
|
+
className: "border-b border-border px-2 py-1 font-semibold whitespace-nowrap",
|
|
29
|
+
children: column
|
|
30
|
+
}, column)) })
|
|
31
|
+
}), /* @__PURE__ */ jsx("tbody", { children: query.rows.map((row, index) => /* @__PURE__ */ jsx("tr", {
|
|
32
|
+
className: "border-b border-border last:border-b-0",
|
|
33
|
+
children: row.map((value, column) => /* @__PURE__ */ jsx("td", {
|
|
34
|
+
className: value === null ? "px-2 py-1 whitespace-nowrap text-faint-foreground" : "px-2 py-1 whitespace-nowrap",
|
|
35
|
+
children: formatCell(value)
|
|
36
|
+
}, column))
|
|
37
|
+
}, index)) })]
|
|
38
|
+
})
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ jsxs("footer", {
|
|
41
|
+
className: "border-t border-border px-3 py-1.5 text-[11px] text-muted-foreground",
|
|
42
|
+
children: [
|
|
43
|
+
query.rows.length,
|
|
44
|
+
" ",
|
|
45
|
+
query.rows.length === 1 ? "row" : "rows",
|
|
46
|
+
query.truncated && ", cut off at the limit"
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
//#endregion
|
|
52
|
+
export { QueryCard };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { queueText } from "./model.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Clock } from "lucide-react";
|
|
5
|
+
//#region src/assistant-dock/queue-card.tsx
|
|
6
|
+
/** A task waiting in the app's queue, with its deposit billed. */
|
|
7
|
+
const QueueCard = ({ taskId, request, ahead, waitingOnPlan, onCancel }) => /* @__PURE__ */ jsxs("section", {
|
|
8
|
+
"aria-label": "Queued change",
|
|
9
|
+
className: "flex shrink-0 items-start gap-2.5 rounded-lg border border-border-strong bg-background px-3 py-2.5 shadow-sm",
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ jsx(Clock, { className: "mt-0.5 size-3.5 shrink-0 text-muted-foreground" }),
|
|
12
|
+
/* @__PURE__ */ jsxs("div", {
|
|
13
|
+
className: "min-w-0 flex-1 space-y-0.5",
|
|
14
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
15
|
+
className: "truncate text-[13px] font-medium",
|
|
16
|
+
children: request
|
|
17
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
18
|
+
className: "text-xs text-muted-foreground",
|
|
19
|
+
children: queueText(ahead, waitingOnPlan)
|
|
20
|
+
})]
|
|
21
|
+
}),
|
|
22
|
+
/* @__PURE__ */ jsx(Button, {
|
|
23
|
+
variant: "ghost",
|
|
24
|
+
size: "sm",
|
|
25
|
+
onClick: () => onCancel?.(taskId),
|
|
26
|
+
children: "Cancel"
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
//#endregion
|
|
31
|
+
export { QueueCard };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { formatCredits } from "./model.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { CircleCheck, CircleX, GitCommitHorizontal } from "lucide-react";
|
|
5
|
+
//#region src/assistant-dock/result-card.tsx
|
|
6
|
+
const headings = {
|
|
7
|
+
live: "Live now",
|
|
8
|
+
failed: "The change failed",
|
|
9
|
+
capped: "The change hit its limit"
|
|
10
|
+
};
|
|
11
|
+
/** How a job ended: live with its commit, or failed or capped at no cost, with the database changes that stay live. */
|
|
12
|
+
const ResultCard = ({ result }) => {
|
|
13
|
+
const live = result.outcome === "live";
|
|
14
|
+
const Icon = live ? CircleCheck : CircleX;
|
|
15
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
16
|
+
"aria-label": headings[result.outcome],
|
|
17
|
+
className: cn("shrink-0 overflow-hidden rounded-lg border bg-background shadow-sm", live ? "border-success/40" : "border-danger/40"),
|
|
18
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
19
|
+
className: cn("flex items-center gap-2 px-3 py-2", live ? "bg-success-soft text-success-text" : "bg-danger-soft text-danger-text"),
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Icon, { className: "size-3.5" }),
|
|
22
|
+
/* @__PURE__ */ jsx("h3", {
|
|
23
|
+
className: "flex-1 text-[13px] font-semibold",
|
|
24
|
+
children: headings[result.outcome]
|
|
25
|
+
}),
|
|
26
|
+
/* @__PURE__ */ jsx("span", {
|
|
27
|
+
className: "text-xs font-medium",
|
|
28
|
+
children: live ? formatCredits(result.credits) : "No charge"
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
32
|
+
className: "space-y-2.5 px-3 py-2.5",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx("p", {
|
|
35
|
+
className: "text-[13px]",
|
|
36
|
+
children: result.summary
|
|
37
|
+
}),
|
|
38
|
+
result.commit && /* @__PURE__ */ jsxs("div", {
|
|
39
|
+
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
40
|
+
children: [/* @__PURE__ */ jsx(GitCommitHorizontal, { className: "size-3.5" }), result.commit.url ? /* @__PURE__ */ jsx("a", {
|
|
41
|
+
className: "font-mono text-primary-text hover:underline",
|
|
42
|
+
href: result.commit.url,
|
|
43
|
+
target: "_blank",
|
|
44
|
+
rel: "noreferrer",
|
|
45
|
+
children: result.commit.sha.slice(0, 7)
|
|
46
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
47
|
+
className: "font-mono",
|
|
48
|
+
children: result.commit.sha.slice(0, 7)
|
|
49
|
+
})]
|
|
50
|
+
}),
|
|
51
|
+
result.keptOnBranch && /* @__PURE__ */ jsxs("p", {
|
|
52
|
+
className: "text-xs text-muted-foreground",
|
|
53
|
+
children: [
|
|
54
|
+
"GitHub did not accept the push. The code is kept on the branch ",
|
|
55
|
+
/* @__PURE__ */ jsx("code", {
|
|
56
|
+
className: "font-mono",
|
|
57
|
+
children: result.keptOnBranch
|
|
58
|
+
}),
|
|
59
|
+
"."
|
|
60
|
+
]
|
|
61
|
+
}),
|
|
62
|
+
result.dbChanges.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
63
|
+
className: "space-y-1",
|
|
64
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
65
|
+
className: "text-xs font-semibold",
|
|
66
|
+
children: live ? "Database changes" : "Database changes that stay live"
|
|
67
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
68
|
+
className: "space-y-0.5 font-mono text-[11px] text-muted-foreground",
|
|
69
|
+
children: result.dbChanges.map((change) => /* @__PURE__ */ jsx("li", { children: change }, change))
|
|
70
|
+
})]
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
})]
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
export { ResultCard };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Spinner } from "../primitives/spinner.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Check, X } from "lucide-react";
|
|
5
|
+
//#region src/assistant-panel/step-list.tsx
|
|
6
|
+
const StepList = ({ steps }) => /* @__PURE__ */ jsx("ul", {
|
|
7
|
+
className: "shrink-0 space-y-2",
|
|
8
|
+
children: steps.map((step) => /* @__PURE__ */ jsxs("li", {
|
|
9
|
+
className: "flex items-start gap-2.5 text-[13px]",
|
|
10
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
11
|
+
className: "mt-0.5 flex size-4 shrink-0 items-center justify-center",
|
|
12
|
+
children: [
|
|
13
|
+
step.state === "running" && /* @__PURE__ */ jsx(Spinner, { className: "size-3.5 text-primary" }),
|
|
14
|
+
step.state === "done" && /* @__PURE__ */ jsx(Check, {
|
|
15
|
+
className: "size-3.5 text-success",
|
|
16
|
+
strokeWidth: 2.5
|
|
17
|
+
}),
|
|
18
|
+
step.state === "failed" && /* @__PURE__ */ jsx(X, {
|
|
19
|
+
className: "size-3.5 text-danger",
|
|
20
|
+
strokeWidth: 2.5
|
|
21
|
+
})
|
|
22
|
+
]
|
|
23
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
24
|
+
className: cn(step.state === "running" ? "text-foreground" : "text-muted-foreground", step.state === "failed" && "text-danger-text"),
|
|
25
|
+
children: step.label
|
|
26
|
+
})]
|
|
27
|
+
}, step.label))
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { StepList };
|
|
@@ -12,6 +12,7 @@ const useAuth = () => {
|
|
|
12
12
|
const AuthProvider = ({ session: given, children }) => {
|
|
13
13
|
const [state, setState] = useState({ kind: "loading" });
|
|
14
14
|
const [passwordReset, setPasswordReset] = useState(false);
|
|
15
|
+
const [socialProviders, setSocialProviders] = useState([]);
|
|
15
16
|
const markSignedOut = useCallback(() => setState((current) => current.kind === "signed-in" ? { kind: "signed-out" } : current), []);
|
|
16
17
|
const signedOut = useRef(markSignedOut);
|
|
17
18
|
signedOut.current = markSignedOut;
|
|
@@ -20,6 +21,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
20
21
|
setState({ kind: "loading" });
|
|
21
22
|
session.status().then(async (status) => {
|
|
22
23
|
setPasswordReset(status.passwordReset);
|
|
24
|
+
setSocialProviders(status.socialProviders);
|
|
23
25
|
if (status.needsAdmin) return setState({ kind: "needs-admin" });
|
|
24
26
|
const user = await session.session();
|
|
25
27
|
setState(user ? {
|
|
@@ -36,6 +38,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
36
38
|
state,
|
|
37
39
|
session,
|
|
38
40
|
passwordReset,
|
|
41
|
+
socialProviders,
|
|
39
42
|
markSignedOut,
|
|
40
43
|
recheck,
|
|
41
44
|
signIn: async (email, password) => {
|
|
@@ -44,6 +47,9 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
44
47
|
user: await session.signIn(email, password)
|
|
45
48
|
});
|
|
46
49
|
},
|
|
50
|
+
signInSocial: async (provider) => {
|
|
51
|
+
window.location.assign(await session.signInSocial(provider, window.location.href));
|
|
52
|
+
},
|
|
47
53
|
signOut: async () => {
|
|
48
54
|
await session.signOut();
|
|
49
55
|
setState({ kind: "signed-out" });
|
|
@@ -52,6 +58,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
52
58
|
state,
|
|
53
59
|
session,
|
|
54
60
|
passwordReset,
|
|
61
|
+
socialProviders,
|
|
55
62
|
markSignedOut,
|
|
56
63
|
recheck
|
|
57
64
|
]);
|
|
@@ -2,72 +2,88 @@ import { Input } from "../primitives/input.js";
|
|
|
2
2
|
import { Button } from "../primitives/button.js";
|
|
3
3
|
import { AuthLayout } from "./auth-layout.js";
|
|
4
4
|
import { useState } from "react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region src/auth/sign-in-form.tsx
|
|
7
|
-
const SignInForm = ({ onSubmit, busy, error, workspace, notice, onForgotPassword }) => {
|
|
7
|
+
const SignInForm = ({ onSubmit, busy, error, workspace, notice, onForgotPassword, onContinueWithGitHub }) => {
|
|
8
8
|
const [email, setEmail] = useState("");
|
|
9
9
|
const [password, setPassword] = useState("");
|
|
10
10
|
return /* @__PURE__ */ jsxs(AuthLayout, {
|
|
11
11
|
title: "Sign in to Protobase",
|
|
12
12
|
description: workspace,
|
|
13
|
-
children: [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
13
|
+
children: [
|
|
14
|
+
notice && /* @__PURE__ */ jsx("p", {
|
|
15
|
+
role: "status",
|
|
16
|
+
className: "mb-4 rounded-md bg-success-soft px-3 py-2 text-xs font-medium text-success-text",
|
|
17
|
+
children: notice
|
|
18
|
+
}),
|
|
19
|
+
onContinueWithGitHub && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
20
|
+
onClick: onContinueWithGitHub,
|
|
21
|
+
disabled: busy,
|
|
22
|
+
className: "min-h-10 w-full",
|
|
23
|
+
children: "Continue with GitHub"
|
|
24
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
25
|
+
className: "my-4 flex items-center gap-3 text-xs text-muted-foreground",
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" }),
|
|
28
|
+
"or",
|
|
29
|
+
/* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
|
|
30
|
+
]
|
|
31
|
+
})] }),
|
|
32
|
+
/* @__PURE__ */ jsxs("form", {
|
|
33
|
+
className: "flex flex-col gap-4",
|
|
34
|
+
onSubmit: (event) => {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
onSubmit(email.trim(), password);
|
|
37
|
+
},
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("label", {
|
|
40
|
+
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
41
|
+
children: ["Email", /* @__PURE__ */ jsx(Input, {
|
|
42
|
+
type: "email",
|
|
43
|
+
name: "email",
|
|
44
|
+
autoComplete: "username",
|
|
45
|
+
required: true,
|
|
46
|
+
autoFocus: true,
|
|
47
|
+
value: email,
|
|
48
|
+
invalid: Boolean(error),
|
|
49
|
+
onChange: (event) => setEmail(event.target.value),
|
|
50
|
+
className: "min-h-6"
|
|
51
|
+
})]
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ jsxs("label", {
|
|
54
|
+
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
55
|
+
children: ["Password", /* @__PURE__ */ jsx(Input, {
|
|
56
|
+
type: "password",
|
|
57
|
+
name: "password",
|
|
58
|
+
autoComplete: "current-password",
|
|
59
|
+
required: true,
|
|
60
|
+
value: password,
|
|
61
|
+
invalid: Boolean(error),
|
|
62
|
+
onChange: (event) => setPassword(event.target.value),
|
|
63
|
+
className: "min-h-6"
|
|
64
|
+
})]
|
|
65
|
+
}),
|
|
66
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
67
|
+
role: "alert",
|
|
68
|
+
className: "text-xs font-medium text-danger-text",
|
|
69
|
+
children: error
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ jsx(Button, {
|
|
72
|
+
variant: "primary",
|
|
73
|
+
type: "submit",
|
|
74
|
+
loading: busy,
|
|
75
|
+
className: "min-h-10",
|
|
76
|
+
children: "Sign in"
|
|
77
|
+
}),
|
|
78
|
+
onForgotPassword && /* @__PURE__ */ jsx("button", {
|
|
79
|
+
type: "button",
|
|
80
|
+
onClick: onForgotPassword,
|
|
81
|
+
className: "self-center text-[13px] font-medium text-primary-text hover:underline",
|
|
82
|
+
children: "Forgot password?"
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
})
|
|
86
|
+
]
|
|
71
87
|
});
|
|
72
88
|
};
|
|
73
89
|
//#endregion
|
|
@@ -11,14 +11,14 @@ const signInMessage = (error) => {
|
|
|
11
11
|
return error.message;
|
|
12
12
|
};
|
|
13
13
|
const SignInPage = ({ workspace, notice, onForgotPassword }) => {
|
|
14
|
-
const { signIn } = useAuth();
|
|
14
|
+
const { signIn, signInSocial, socialProviders } = useAuth();
|
|
15
15
|
const [busy, setBusy] = useState(false);
|
|
16
16
|
const [error, setError] = useState();
|
|
17
|
-
const
|
|
17
|
+
const attempt = async (start) => {
|
|
18
18
|
setBusy(true);
|
|
19
19
|
setError(void 0);
|
|
20
20
|
try {
|
|
21
|
-
await
|
|
21
|
+
await start();
|
|
22
22
|
} catch (failure) {
|
|
23
23
|
if (!(failure instanceof AuthError)) throw failure;
|
|
24
24
|
setError(signInMessage(failure));
|
|
@@ -31,7 +31,8 @@ const SignInPage = ({ workspace, notice, onForgotPassword }) => {
|
|
|
31
31
|
workspace,
|
|
32
32
|
notice,
|
|
33
33
|
onForgotPassword,
|
|
34
|
-
|
|
34
|
+
onContinueWithGitHub: socialProviders.includes("github") ? () => void attempt(() => signInSocial("github")) : void 0,
|
|
35
|
+
onSubmit: (email, password) => void attempt(() => signIn(email, password))
|
|
35
36
|
});
|
|
36
37
|
};
|
|
37
38
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,6 @@ import { useRecord } from "./data/use-record.js";
|
|
|
9
9
|
import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
|
|
10
10
|
import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
|
|
11
11
|
import { LayoutRecord, useLayoutRecord } from "./app/pages/record-scope.js";
|
|
12
|
-
|
|
12
|
+
import { DockItem, JobResult, Plan, PlanSize, QueryResult } from "./assistant-dock/model.js";
|
|
13
|
+
import { AssistantDock, AssistantDockProps } from "./assistant-dock/assistant-dock.js";
|
|
14
|
+
export { type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, type AppProps, AssistantDock, type AssistantDockProps, type DockItem, type JobResult, type LayoutRecord, type MetaData, type Plan, type PlanSize, type ProjectUi, type QueryResult, defineUi, isConflict, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
|
package/dist/index.js
CHANGED
|
@@ -7,4 +7,5 @@ import { defineUi } from "./app/pages/project-ui.js";
|
|
|
7
7
|
import { useList } from "./data/use-list.js";
|
|
8
8
|
import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
|
|
9
9
|
import { App } from "./app/app.js";
|
|
10
|
-
|
|
10
|
+
import { AssistantDock } from "./assistant-dock/assistant-dock.js";
|
|
11
|
+
export { ApiProvider, App, AssistantDock, defineUi, isConflict, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The Protobase admin app and the React hooks and components for custom UI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"react-dom": "^19.3.0",
|
|
39
39
|
"recharts": "^3.10.1",
|
|
40
40
|
"tailwindcss": "^4.3.3",
|
|
41
|
-
"@protobase/client": "0.1.
|
|
42
|
-
"@protobase/
|
|
43
|
-
"@protobase/
|
|
41
|
+
"@protobase/client": "0.1.1",
|
|
42
|
+
"@protobase/layout": "0.1.1",
|
|
43
|
+
"@protobase/schema": "0.1.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsdown"
|