@gntik-ai/templates 0.0.0-stage → 0.5.0
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/index.d.ts +2420 -0
- package/dist/index.js +14520 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -3
- package/src/accept-invitation/Page.test.tsx +40 -0
- package/src/accept-invitation/Page.tsx +153 -0
- package/src/accept-invitation/data.ts +22 -0
- package/src/accept-invitation/template.meta.ts +12 -0
- package/src/assistant-chat/Page.test.tsx +51 -0
- package/src/assistant-chat/Page.tsx +273 -0
- package/src/assistant-chat/data.ts +71 -0
- package/src/assistant-chat/template.meta.ts +12 -0
- package/src/audit-log/Page.test.tsx +58 -0
- package/src/audit-log/Page.tsx +284 -0
- package/src/audit-log/data.ts +133 -0
- package/src/audit-log/template.meta.ts +12 -0
- package/src/board/Lane.tsx +117 -0
- package/src/board/Page.test.tsx +68 -0
- package/src/board/Page.tsx +178 -0
- package/src/board/data.ts +127 -0
- package/src/board/template.meta.ts +12 -0
- package/src/board/useBoard.ts +146 -0
- package/src/changelog/Page.test.tsx +31 -0
- package/src/changelog/Page.tsx +174 -0
- package/src/changelog/data.ts +105 -0
- package/src/changelog/template.meta.ts +12 -0
- package/src/code-editor/Page.test.tsx +83 -0
- package/src/code-editor/Page.tsx +207 -0
- package/src/code-editor/data.ts +130 -0
- package/src/code-editor/template.meta.ts +12 -0
- package/src/contact/Page.test.tsx +33 -0
- package/src/contact/Page.tsx +272 -0
- package/src/contact/PublicFrame.tsx +84 -0
- package/src/contact/data.ts +129 -0
- package/src/contact/template.meta.ts +12 -0
- package/src/create-wizard/Overlay.test.tsx +68 -0
- package/src/create-wizard/Page.test.tsx +57 -0
- package/src/create-wizard/Page.tsx +246 -0
- package/src/create-wizard/data.ts +139 -0
- package/src/create-wizard/examples/Overlay.tsx +60 -0
- package/src/create-wizard/template.meta.ts +12 -0
- package/src/create-workspace/Page.test.tsx +41 -0
- package/src/create-workspace/Page.tsx +227 -0
- package/src/create-workspace/data.ts +61 -0
- package/src/create-workspace/template.meta.ts +12 -0
- package/src/data-explorer/Page.test.tsx +45 -0
- package/src/data-explorer/Page.tsx +247 -0
- package/src/data-explorer/data.ts +94 -0
- package/src/data-explorer/template.meta.ts +12 -0
- package/src/docs-article/ArticleFeedback.tsx +75 -0
- package/src/docs-article/Page.test.tsx +34 -0
- package/src/docs-article/Page.tsx +200 -0
- package/src/docs-article/data.ts +65 -0
- package/src/docs-article/template.meta.ts +12 -0
- package/src/evaluations/Page.test.tsx +40 -0
- package/src/evaluations/Page.tsx +238 -0
- package/src/evaluations/data.ts +194 -0
- package/src/evaluations/template.meta.ts +12 -0
- package/src/forgot-password/Page.test.tsx +199 -0
- package/src/forgot-password/Page.tsx +66 -0
- package/src/forgot-password/data.ts +5 -0
- package/src/forgot-password/examples/UsernameOrEmail.tsx +25 -0
- package/src/forgot-password/template.meta.ts +12 -0
- package/src/getting-started/Page.test.tsx +27 -0
- package/src/getting-started/Page.tsx +125 -0
- package/src/getting-started/data.ts +67 -0
- package/src/getting-started/template.meta.ts +12 -0
- package/src/help-home/Page.test.tsx +33 -0
- package/src/help-home/Page.tsx +159 -0
- package/src/help-home/data.ts +151 -0
- package/src/help-home/template.meta.ts +12 -0
- package/src/home-dashboard/Page.test.tsx +57 -0
- package/src/home-dashboard/Page.tsx +152 -0
- package/src/home-dashboard/data.ts +66 -0
- package/src/home-dashboard/template.meta.ts +12 -0
- package/src/incident-board/Page.test.tsx +77 -0
- package/src/incident-board/Page.tsx +263 -0
- package/src/incident-board/data.ts +220 -0
- package/src/incident-board/template.meta.ts +12 -0
- package/src/incident-console/Page.test.tsx +49 -0
- package/src/incident-console/Page.tsx +274 -0
- package/src/incident-console/data.ts +176 -0
- package/src/incident-console/template.meta.ts +12 -0
- package/src/index.ts +250 -0
- package/src/keyboard-shortcuts/Page.test.tsx +45 -0
- package/src/keyboard-shortcuts/Page.tsx +94 -0
- package/src/keyboard-shortcuts/ShortcutSheet.tsx +80 -0
- package/src/keyboard-shortcuts/data.ts +84 -0
- package/src/keyboard-shortcuts/template.meta.ts +12 -0
- package/src/landing/Page.test.tsx +27 -0
- package/src/landing/Page.tsx +110 -0
- package/src/landing/data.ts +17 -0
- package/src/landing/template.meta.ts +11 -0
- package/src/live-event-stream/Page.test.tsx +251 -0
- package/src/live-event-stream/Page.tsx +307 -0
- package/src/live-event-stream/data.ts +44 -0
- package/src/live-event-stream/examples/Connecting.tsx +5 -0
- package/src/live-event-stream/examples/Error.tsx +5 -0
- package/src/live-event-stream/examples/LiveEmpty.tsx +5 -0
- package/src/live-event-stream/examples/LiveWithEvents.tsx +6 -0
- package/src/live-event-stream/examples/Stopped.tsx +6 -0
- package/src/live-event-stream/template.meta.ts +74 -0
- package/src/messaging/Page.test.tsx +34 -0
- package/src/messaging/Page.tsx +236 -0
- package/src/messaging/data.ts +118 -0
- package/src/messaging/template.meta.ts +12 -0
- package/src/meta.ts +24 -0
- package/src/mfa/Page.test.tsx +22 -0
- package/src/mfa/Page.tsx +45 -0
- package/src/mfa/data.ts +6 -0
- package/src/mfa/template.meta.ts +12 -0
- package/src/model-providers/Page.test.tsx +58 -0
- package/src/model-providers/Page.tsx +198 -0
- package/src/model-providers/data.ts +101 -0
- package/src/model-providers/template.meta.ts +12 -0
- package/src/notification-inbox/Page.test.tsx +49 -0
- package/src/notification-inbox/Page.tsx +237 -0
- package/src/notification-inbox/data.ts +129 -0
- package/src/notification-inbox/template.meta.ts +12 -0
- package/src/pending-activation/Page.test.tsx +390 -0
- package/src/pending-activation/Page.tsx +145 -0
- package/src/pending-activation/data.ts +25 -0
- package/src/pending-activation/template.meta.ts +12 -0
- package/src/pricing/ComparisonTable.tsx +75 -0
- package/src/pricing/Page.test.tsx +29 -0
- package/src/pricing/Page.tsx +83 -0
- package/src/pricing/PublicFrame.tsx +84 -0
- package/src/pricing/data.ts +104 -0
- package/src/pricing/template.meta.ts +12 -0
- package/src/prompt-playground/Page.test.tsx +72 -0
- package/src/prompt-playground/Page.tsx +254 -0
- package/src/prompt-playground/data.ts +68 -0
- package/src/prompt-playground/template.meta.ts +12 -0
- package/src/public-hub/Page.test.tsx +176 -0
- package/src/public-hub/Page.tsx +125 -0
- package/src/public-hub/Source.test.ts +54 -0
- package/src/public-hub/data.ts +24 -0
- package/src/public-hub/examples/Dark.tsx +11 -0
- package/src/public-hub/examples/Full.tsx +6 -0
- package/src/public-hub/examples/HighContrast.tsx +11 -0
- package/src/public-hub/examples/Light.tsx +11 -0
- package/src/public-hub/examples/Loading.tsx +6 -0
- package/src/public-hub/examples/Minimal.tsx +5 -0
- package/src/public-hub/examples/MinimalLoading.tsx +5 -0
- package/src/public-hub/template.meta.ts +60 -0
- package/src/reset-password/Page.test.tsx +27 -0
- package/src/reset-password/Page.tsx +46 -0
- package/src/reset-password/data.ts +5 -0
- package/src/reset-password/template.meta.ts +12 -0
- package/src/resource-detail/Page.test.tsx +260 -0
- package/src/resource-detail/Page.tsx +262 -0
- package/src/resource-detail/data.ts +55 -0
- package/src/resource-detail/examples/postgres-table-detail.tsx +28 -0
- package/src/resource-detail/examples/realtime-configuration.tsx +26 -0
- package/src/resource-detail/examples/runtime-with-transition.tsx +30 -0
- package/src/resource-detail/examples/runtime-without-transition.tsx +33 -0
- package/src/resource-detail/examples/shell.ts +12 -0
- package/src/resource-detail/template.meta.ts +21 -0
- package/src/resource-form/Page.test.tsx +40 -0
- package/src/resource-form/Page.tsx +186 -0
- package/src/resource-form/data.ts +63 -0
- package/src/resource-form/template.meta.ts +12 -0
- package/src/resource-gallery/Page.test.tsx +42 -0
- package/src/resource-gallery/Page.tsx +125 -0
- package/src/resource-gallery/data.ts +44 -0
- package/src/resource-gallery/template.meta.ts +20 -0
- package/src/resource-index/Page.test.tsx +120 -0
- package/src/resource-index/Page.tsx +216 -0
- package/src/resource-index/data.ts +84 -0
- package/src/resource-index/template.meta.ts +21 -0
- package/src/resource-tree/Page.test.tsx +31 -0
- package/src/resource-tree/Page.tsx +232 -0
- package/src/resource-tree/data.ts +180 -0
- package/src/resource-tree/template.meta.ts +12 -0
- package/src/run-trace/Page.test.tsx +56 -0
- package/src/run-trace/Page.tsx +242 -0
- package/src/run-trace/data.ts +171 -0
- package/src/run-trace/template.meta.ts +12 -0
- package/src/scorecard/Page.test.tsx +38 -0
- package/src/scorecard/Page.tsx +160 -0
- package/src/scorecard/TargetsTable.tsx +92 -0
- package/src/scorecard/data.ts +118 -0
- package/src/scorecard/template.meta.ts +12 -0
- package/src/search-results/Page.test.tsx +43 -0
- package/src/search-results/Page.tsx +243 -0
- package/src/search-results/data.ts +130 -0
- package/src/search-results/template.meta.ts +12 -0
- package/src/settings-api-keys/Page.test.tsx +39 -0
- package/src/settings-api-keys/Page.tsx +171 -0
- package/src/settings-api-keys/data.ts +58 -0
- package/src/settings-api-keys/template.meta.ts +12 -0
- package/src/settings-appearance/Page.test.tsx +44 -0
- package/src/settings-appearance/Page.tsx +92 -0
- package/src/settings-appearance/data.ts +21 -0
- package/src/settings-appearance/template.meta.ts +11 -0
- package/src/settings-billing/Page.test.tsx +31 -0
- package/src/settings-billing/Page.tsx +71 -0
- package/src/settings-billing/data.ts +15 -0
- package/src/settings-billing/template.meta.ts +19 -0
- package/src/settings-integrations/Page.test.tsx +31 -0
- package/src/settings-integrations/Page.tsx +185 -0
- package/src/settings-integrations/data.ts +116 -0
- package/src/settings-integrations/template.meta.ts +11 -0
- package/src/settings-members/Page.test.tsx +32 -0
- package/src/settings-members/Page.tsx +140 -0
- package/src/settings-members/data.ts +28 -0
- package/src/settings-members/template.meta.ts +12 -0
- package/src/settings-notifications/Page.test.tsx +31 -0
- package/src/settings-notifications/Page.tsx +71 -0
- package/src/settings-notifications/data.ts +12 -0
- package/src/settings-notifications/template.meta.ts +11 -0
- package/src/settings-profile/Baseline.test.tsx +9 -0
- package/src/settings-profile/Editable.test.tsx +21 -0
- package/src/settings-profile/Page.test.tsx +41 -0
- package/src/settings-profile/Page.tsx +133 -0
- package/src/settings-profile/ReadOnly.test.tsx +143 -0
- package/src/settings-profile/ReadOnlyProfile.tsx +85 -0
- package/src/settings-profile/SettingsFrame.test.tsx +55 -0
- package/src/settings-profile/SettingsFrame.tsx +88 -0
- package/src/settings-profile/__tests__/dom.ts +8 -0
- package/src/settings-profile/data.ts +41 -0
- package/src/settings-profile/examples/ReadOnly.tsx +31 -0
- package/src/settings-profile/fixtures/editable.snap +1 -0
- package/src/settings-profile/settings-nav.ts +98 -0
- package/src/settings-profile/template.meta.ts +21 -0
- package/src/settings-security/MfaSetupDialog.tsx +96 -0
- package/src/settings-security/Page.test.tsx +44 -0
- package/src/settings-security/Page.tsx +158 -0
- package/src/settings-security/data.ts +11 -0
- package/src/settings-security/template.meta.ts +11 -0
- package/src/settings-workspace/Page.test.tsx +44 -0
- package/src/settings-workspace/Page.tsx +120 -0
- package/src/settings-workspace/data.ts +44 -0
- package/src/settings-workspace/template.meta.ts +19 -0
- package/src/shared/ConsoleShell.test.tsx +81 -0
- package/src/shared/ConsoleShell.tsx +98 -0
- package/src/shell-sidebar/Page.test.tsx +21 -0
- package/src/shell-sidebar/Page.tsx +85 -0
- package/src/shell-sidebar/data.ts +24 -0
- package/src/shell-sidebar/template.meta.ts +12 -0
- package/src/shell-topnav/Page.test.tsx +25 -0
- package/src/shell-topnav/Page.tsx +94 -0
- package/src/shell-topnav/data.ts +25 -0
- package/src/shell-topnav/template.meta.ts +12 -0
- package/src/sign-in/BrandPanel.tsx +34 -0
- package/src/sign-in/Page.test.tsx +41 -0
- package/src/sign-in/Page.tsx +73 -0
- package/src/sign-in/data.ts +11 -0
- package/src/sign-in/template.meta.ts +12 -0
- package/src/sign-in-card/Page.test.tsx +182 -0
- package/src/sign-in-card/Page.tsx +82 -0
- package/src/sign-in-card/data.ts +8 -0
- package/src/sign-in-card/examples/Username.tsx +28 -0
- package/src/sign-in-card/template.meta.ts +12 -0
- package/src/sign-up/Page.test.tsx +33 -0
- package/src/sign-up/Page.tsx +70 -0
- package/src/sign-up/data.ts +15 -0
- package/src/sign-up/template.meta.ts +12 -0
- package/src/sso-login/Page.test.tsx +41 -0
- package/src/sso-login/Page.tsx +151 -0
- package/src/sso-login/data.ts +10 -0
- package/src/sso-login/template.meta.ts +12 -0
- package/src/status-403/Page.test.tsx +23 -0
- package/src/status-403/Page.tsx +92 -0
- package/src/status-403/data.ts +13 -0
- package/src/status-403/template.meta.ts +12 -0
- package/src/status-404/Page.test.tsx +23 -0
- package/src/status-404/Page.tsx +60 -0
- package/src/status-404/PageHeading.test.tsx +52 -0
- package/src/status-404/data.ts +12 -0
- package/src/status-404/examples/PageHeader.tsx +28 -0
- package/src/status-404/template.meta.ts +12 -0
- package/src/status-500/Page.test.tsx +24 -0
- package/src/status-500/Page.tsx +70 -0
- package/src/status-500/data.ts +20 -0
- package/src/status-500/template.meta.ts +12 -0
- package/src/status-maintenance/Page.test.tsx +24 -0
- package/src/status-maintenance/Page.tsx +84 -0
- package/src/status-maintenance/data.ts +14 -0
- package/src/status-maintenance/template.meta.ts +12 -0
- package/src/styles.css +11 -0
- package/src/test/a11y.ts +18 -0
- package/src/test/setup.ts +36 -0
- package/src/upgrade-required/Page.test.tsx +28 -0
- package/src/upgrade-required/Page.tsx +79 -0
- package/src/upgrade-required/data.ts +19 -0
- package/src/upgrade-required/template.meta.ts +12 -0
- package/src/usage-analytics/Page.test.tsx +83 -0
- package/src/usage-analytics/Page.tsx +184 -0
- package/src/usage-analytics/data.ts +161 -0
- package/src/usage-analytics/template.meta.ts +21 -0
- package/src/verify-email/Page.test.tsx +32 -0
- package/src/verify-email/Page.tsx +118 -0
- package/src/verify-email/data.ts +7 -0
- package/src/verify-email/template.meta.ts +12 -0
- package/src/workflow-builder/Page.test.tsx +97 -0
- package/src/workflow-builder/Page.tsx +163 -0
- package/src/workflow-builder/data.ts +13 -0
- package/src/workflow-builder/template.meta.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { AuthHeader } from '@gntik-ai/blocks';
|
|
2
|
+
import { Alert, AuthLayout, Button, Link, Stack } from '@gntik-ai/ui';
|
|
3
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
4
|
+
import { verifyEmailSample } from './data';
|
|
5
|
+
|
|
6
|
+
export interface VerifyEmailPageProps {
|
|
7
|
+
/** Address the verification link was sent to. */
|
|
8
|
+
email: string;
|
|
9
|
+
/** Sends the link again; the button then waits `resendCooldown` seconds. A rejection shows in the alert. */
|
|
10
|
+
onResend: () => void | Promise<void>;
|
|
11
|
+
/** "Use a different email" (back to sign-up). */
|
|
12
|
+
onChangeEmail: () => void;
|
|
13
|
+
resendCooldown: number;
|
|
14
|
+
/** Seconds left before the first resend. Defaults to `resendCooldown` (a link was just sent). */
|
|
15
|
+
initialCooldown: number;
|
|
16
|
+
signInHref: string;
|
|
17
|
+
supportHref: string;
|
|
18
|
+
logo: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const clock = (s: number) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
|
|
22
|
+
|
|
23
|
+
/** Card check-your-inbox step with a resend countdown. */
|
|
24
|
+
export default function VerifyEmailPage({
|
|
25
|
+
email = verifyEmailSample.email,
|
|
26
|
+
onResend,
|
|
27
|
+
onChangeEmail,
|
|
28
|
+
resendCooldown = verifyEmailSample.resendCooldown,
|
|
29
|
+
initialCooldown,
|
|
30
|
+
signInHref = verifyEmailSample.signInHref,
|
|
31
|
+
supportHref = verifyEmailSample.supportHref,
|
|
32
|
+
logo,
|
|
33
|
+
}: Partial<VerifyEmailPageProps>) {
|
|
34
|
+
// One timestamp for both, so the first render reads exactly the full cooldown.
|
|
35
|
+
const [startedAt] = useState(() => Date.now());
|
|
36
|
+
const [now, setNow] = useState(startedAt);
|
|
37
|
+
const [resendAt, setResendAt] = useState(startedAt + (initialCooldown ?? resendCooldown) * 1000);
|
|
38
|
+
const [sending, setSending] = useState(false);
|
|
39
|
+
const [resent, setResent] = useState(false);
|
|
40
|
+
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
const remaining = Math.max(0, Math.ceil((resendAt - now) / 1000));
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (remaining <= 0) return;
|
|
45
|
+
const id = window.setInterval(() => setNow(Date.now()), 1000);
|
|
46
|
+
return () => window.clearInterval(id);
|
|
47
|
+
}, [remaining]);
|
|
48
|
+
|
|
49
|
+
const resend = async () => {
|
|
50
|
+
setError(null);
|
|
51
|
+
setSending(true);
|
|
52
|
+
try {
|
|
53
|
+
await onResend?.();
|
|
54
|
+
const t = Date.now();
|
|
55
|
+
setNow(t);
|
|
56
|
+
setResendAt(t + resendCooldown * 1000);
|
|
57
|
+
setResent(true);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
setError(e instanceof Error ? e.message : 'The email could not be sent. Try again.');
|
|
60
|
+
}
|
|
61
|
+
setSending(false);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<AuthLayout
|
|
66
|
+
fullScreen
|
|
67
|
+
variant="card"
|
|
68
|
+
logo={logo}
|
|
69
|
+
skipLinkLabel="Skip to verification"
|
|
70
|
+
footer={
|
|
71
|
+
<>
|
|
72
|
+
Already verified? <Link href={signInHref}>Sign in</Link>
|
|
73
|
+
</>
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
<AuthHeader
|
|
77
|
+
eyebrow="Verify your email"
|
|
78
|
+
title="Check your inbox"
|
|
79
|
+
description={
|
|
80
|
+
<>
|
|
81
|
+
We sent a verification link to <span className="font-medium text-foreground">{email}</span>. Open it on this
|
|
82
|
+
device to finish setting up your account. The link expires in 24 hours.
|
|
83
|
+
</>
|
|
84
|
+
}
|
|
85
|
+
/>
|
|
86
|
+
<Stack gap={4}>
|
|
87
|
+
{error && <Alert tone="destructive" description={error} />}
|
|
88
|
+
<Stack gap={2}>
|
|
89
|
+
<Button
|
|
90
|
+
variant="secondary"
|
|
91
|
+
size="lg"
|
|
92
|
+
className="w-full"
|
|
93
|
+
loading={sending}
|
|
94
|
+
disabled={remaining > 0}
|
|
95
|
+
onClick={() => void resend()}
|
|
96
|
+
>
|
|
97
|
+
{remaining > 0 ? `Resend email in ${clock(remaining)}` : 'Resend email'}
|
|
98
|
+
</Button>
|
|
99
|
+
<Button variant="ghost" size="lg" className="w-full" onClick={onChangeEmail}>
|
|
100
|
+
Use a different email
|
|
101
|
+
</Button>
|
|
102
|
+
</Stack>
|
|
103
|
+
<Alert
|
|
104
|
+
role="note"
|
|
105
|
+
tone="info"
|
|
106
|
+
description={
|
|
107
|
+
<>
|
|
108
|
+
Can't find it? Check your spam folder, or <Link href={supportHref}>contact support</Link>.
|
|
109
|
+
</>
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
</Stack>
|
|
113
|
+
<span role="status" className="sr-only">
|
|
114
|
+
{resent ? `A new verification email was sent to ${email}.` : ''}
|
|
115
|
+
</span>
|
|
116
|
+
</AuthLayout>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: TemplateMeta = {
|
|
4
|
+
name: 'Verify email',
|
|
5
|
+
family: 'Auth',
|
|
6
|
+
priority: 'P2',
|
|
7
|
+
status: 'beta',
|
|
8
|
+
description:
|
|
9
|
+
'Centred card check-your-inbox step after sign-up: the address the link went to, resend with a countdown, change email and a support link.',
|
|
10
|
+
layout: 'AuthLayout',
|
|
11
|
+
blocks: [],
|
|
12
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import type { ConsoleEntry } from '@gntik-ai/blocks';
|
|
6
|
+
import FlowBuilderPage from './Page';
|
|
7
|
+
|
|
8
|
+
// React Flow in jsdom reads the zoom from DOMMatrixReadOnly and node sizes from offset*.
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
if (!('DOMMatrixReadOnly' in window)) {
|
|
11
|
+
(window as unknown as { DOMMatrixReadOnly: unknown }).DOMMatrixReadOnly = class {
|
|
12
|
+
m22 = 1;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperties(HTMLElement.prototype, {
|
|
16
|
+
offsetHeight: { configurable: true, get: () => 90 },
|
|
17
|
+
offsetWidth: { configurable: true, get: () => 208 },
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('FlowBuilderPage', () => {
|
|
22
|
+
it('renders the editor chrome and the flow builder', { timeout: 15000 }, async () => {
|
|
23
|
+
render(<FlowBuilderPage />);
|
|
24
|
+
expect(screen.getByRole('main', { name: 'Order sync editor' })).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Order sync' })).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByRole('region', { name: 'Order sync canvas' })).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByRole('log', { name: 'Run console' })).toBeInTheDocument();
|
|
28
|
+
await expectNoAxeViolations();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('runs the flow and saves', { timeout: 15000 }, async () => {
|
|
32
|
+
const onRun = vi.fn();
|
|
33
|
+
const onSave = vi.fn(() => Promise.resolve());
|
|
34
|
+
render(<FlowBuilderPage onRun={onRun} onSave={onSave} />);
|
|
35
|
+
await userEvent.click(screen.getByRole('button', { name: 'Run' }));
|
|
36
|
+
expect(onRun).toHaveBeenCalledOnce();
|
|
37
|
+
expect(within(screen.getByRole('log', { name: 'Run console' })).getByText(/Run #1 started/)).toBeInTheDocument();
|
|
38
|
+
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
|
39
|
+
expect(onSave).toHaveBeenCalledOnce();
|
|
40
|
+
expect(await screen.findByText('All changes saved')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('passes a controlled console through to the builder', { timeout: 15000 }, async () => {
|
|
44
|
+
function Host() {
|
|
45
|
+
const [entries, setEntries] = useState<ConsoleEntry[]>([
|
|
46
|
+
{ id: 'h', time: '09:00:00', level: 'info', message: 'Connected to runner' },
|
|
47
|
+
]);
|
|
48
|
+
return (
|
|
49
|
+
<FlowBuilderPage
|
|
50
|
+
consoleEntries={entries}
|
|
51
|
+
onConsoleChange={setEntries}
|
|
52
|
+
onRun={() => Promise.resolve([{ id: 'r', time: '09:00:01', level: 'success', message: 'Remote run ok' }])}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
render(<Host />);
|
|
57
|
+
const log = screen.getByRole('log', { name: 'Run console' });
|
|
58
|
+
expect(log).toHaveTextContent('Connected to runner');
|
|
59
|
+
await userEvent.click(screen.getByRole('button', { name: 'Run' }));
|
|
60
|
+
expect(await within(log).findByText('Remote run ok')).toBeInTheDocument();
|
|
61
|
+
expect(log).toHaveTextContent('Connected to runner');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('passes run mode, status, validation and selection through and hides save and publish', async () => {
|
|
66
|
+
const selected = vi.fn();
|
|
67
|
+
render(
|
|
68
|
+
<FlowBuilderPage
|
|
69
|
+
mode="run"
|
|
70
|
+
nodeStatus={{ webhook: 'running' }}
|
|
71
|
+
onNodeSelect={selected}
|
|
72
|
+
validationIssues={[{ code: 'test', nodeId: 'webhook', message: 'Input needs review' }]}
|
|
73
|
+
codePanel={{
|
|
74
|
+
files: [],
|
|
75
|
+
problems: [
|
|
76
|
+
{
|
|
77
|
+
id: 'semantic',
|
|
78
|
+
file: 'flow.yaml',
|
|
79
|
+
line: 1,
|
|
80
|
+
severity: 'error',
|
|
81
|
+
message: 'Invalid document',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
}}
|
|
85
|
+
/>,
|
|
86
|
+
);
|
|
87
|
+
expect(screen.queryByRole('button', { name: 'Save' })).toBeNull();
|
|
88
|
+
expect(screen.queryByRole('button', { name: 'Publish' })).toBeNull();
|
|
89
|
+
const node = screen.getByRole('group', { name: 'Webhook' });
|
|
90
|
+
expect(within(node).getByText('Running')).toBeInTheDocument();
|
|
91
|
+
expect(within(node).getByText('Input needs review')).toBeInTheDocument();
|
|
92
|
+
// Test node selection at the click seam; jsdom cannot supply d3's native Window for a pan gesture.
|
|
93
|
+
fireEvent.click(node);
|
|
94
|
+
expect(selected).toHaveBeenCalledWith('webhook');
|
|
95
|
+
expect(screen.getByRole('region', { name: 'Code panel' })).toHaveTextContent('Invalid document');
|
|
96
|
+
await expectNoAxeViolations();
|
|
97
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CodePanel,
|
|
3
|
+
type CodePanelProps,
|
|
4
|
+
FlowBuilder,
|
|
5
|
+
type ConsoleEntry,
|
|
6
|
+
type FlowBuilderProps,
|
|
7
|
+
type FlowEdge,
|
|
8
|
+
type FlowNode,
|
|
9
|
+
type FlowNodeKind,
|
|
10
|
+
type PaletteItem,
|
|
11
|
+
} from '@gntik-ai/blocks';
|
|
12
|
+
import { Save, Upload } from '@gntik-ai/icons';
|
|
13
|
+
import { Breadcrumbs, Button, CanvasLayout, StatusTag, type BreadcrumbItem } from '@gntik-ai/ui';
|
|
14
|
+
import { useState } from 'react';
|
|
15
|
+
import { flowBuilderBreadcrumbs, flowBuilderContent } from './data';
|
|
16
|
+
|
|
17
|
+
export interface FlowBuilderPageProps<Kind extends string = FlowNodeKind> extends Pick<
|
|
18
|
+
FlowBuilderProps<Kind>,
|
|
19
|
+
| 'nodeTypes'
|
|
20
|
+
| 'onConnectEdge'
|
|
21
|
+
| 'onConnect'
|
|
22
|
+
| 'isValidConnection'
|
|
23
|
+
| 'onConnectionRejected'
|
|
24
|
+
| 'validationIssues'
|
|
25
|
+
| 'mode'
|
|
26
|
+
| 'nodeStatus'
|
|
27
|
+
| 'onNodeSelect'
|
|
28
|
+
> {
|
|
29
|
+
/** Optional companion code panel; run mode forces readOnly. */
|
|
30
|
+
codePanel?: CodePanelProps;
|
|
31
|
+
title: string;
|
|
32
|
+
/** StatusTag key next to the title (draft, active…). */
|
|
33
|
+
status: string;
|
|
34
|
+
/** Initial graph and palette; omitted values use the FlowBuilder block samples. */
|
|
35
|
+
nodes: FlowNode<Kind | FlowNodeKind>[];
|
|
36
|
+
edges: FlowEdge[];
|
|
37
|
+
palette: PaletteItem[];
|
|
38
|
+
/** Return console entries (or a promise of them) to log the real run; nothing logs a simulated run. */
|
|
39
|
+
onRun: NonNullable<FlowBuilderProps<Kind>['onRun']>;
|
|
40
|
+
/** Run console output (controlled); report changes with `onConsoleChange`. */
|
|
41
|
+
consoleEntries: ConsoleEntry[];
|
|
42
|
+
/** Initial run console output (uncontrolled). */
|
|
43
|
+
defaultConsoleEntries: ConsoleEntry[];
|
|
44
|
+
onConsoleChange: (entries: ConsoleEntry[]) => void;
|
|
45
|
+
onConsoleClear: () => void;
|
|
46
|
+
onSave: () => void | Promise<void>;
|
|
47
|
+
onPublish: () => void;
|
|
48
|
+
breadcrumbs: BreadcrumbItem[];
|
|
49
|
+
/** Builder height in px on wide screens. */
|
|
50
|
+
height: number;
|
|
51
|
+
fullScreen: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Flow builder page: CanvasLayout + the FlowBuilder block. The app imports
|
|
56
|
+
* '@gntik-ai/flow/styles.css' once for the React Flow canvas.
|
|
57
|
+
*/
|
|
58
|
+
export default function FlowBuilderPage<Kind extends string = FlowNodeKind>(
|
|
59
|
+
props: Partial<FlowBuilderPageProps<Kind>>,
|
|
60
|
+
) {
|
|
61
|
+
const {
|
|
62
|
+
title = flowBuilderContent.title,
|
|
63
|
+
status = flowBuilderContent.status,
|
|
64
|
+
nodes,
|
|
65
|
+
edges,
|
|
66
|
+
palette,
|
|
67
|
+
onRun,
|
|
68
|
+
consoleEntries,
|
|
69
|
+
defaultConsoleEntries,
|
|
70
|
+
onConsoleChange,
|
|
71
|
+
onConsoleClear,
|
|
72
|
+
onSave,
|
|
73
|
+
onPublish,
|
|
74
|
+
nodeTypes,
|
|
75
|
+
onConnectEdge,
|
|
76
|
+
onConnect,
|
|
77
|
+
isValidConnection,
|
|
78
|
+
onConnectionRejected,
|
|
79
|
+
validationIssues,
|
|
80
|
+
mode,
|
|
81
|
+
nodeStatus,
|
|
82
|
+
onNodeSelect,
|
|
83
|
+
codePanel,
|
|
84
|
+
breadcrumbs = flowBuilderBreadcrumbs,
|
|
85
|
+
height = 640,
|
|
86
|
+
fullScreen = true,
|
|
87
|
+
} = props;
|
|
88
|
+
const [saving, setSaving] = useState(false);
|
|
89
|
+
const [savedNote, setSavedNote] = useState(flowBuilderContent.lastSaved);
|
|
90
|
+
|
|
91
|
+
const save = async () => {
|
|
92
|
+
setSaving(true);
|
|
93
|
+
try {
|
|
94
|
+
await onSave?.();
|
|
95
|
+
setSavedNote('All changes saved');
|
|
96
|
+
} finally {
|
|
97
|
+
setSaving(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<CanvasLayout
|
|
103
|
+
fullScreen={fullScreen}
|
|
104
|
+
mainLabel={`${title} editor`}
|
|
105
|
+
smallScreenNotice={null}
|
|
106
|
+
header={
|
|
107
|
+
<>
|
|
108
|
+
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
109
|
+
<Breadcrumbs items={breadcrumbs} className="hidden min-w-0 md:flex" />
|
|
110
|
+
<h1 className="truncate text-[14px] font-semibold text-foreground">{title}</h1>
|
|
111
|
+
<StatusTag status={status} />
|
|
112
|
+
{mode !== 'run' && (
|
|
113
|
+
<p aria-live="polite" className="hidden text-[12px] text-muted-foreground sm:block">
|
|
114
|
+
{savedNote}
|
|
115
|
+
</p>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
{mode !== 'run' && (
|
|
119
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
120
|
+
<Button size="sm" variant="secondary" icon={Save} loading={saving} onClick={() => void save()}>
|
|
121
|
+
Save
|
|
122
|
+
</Button>
|
|
123
|
+
<Button size="sm" icon={Upload} onClick={onPublish}>
|
|
124
|
+
Publish
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
</>
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
<div className="h-full overflow-auto p-3 sm:p-4">
|
|
132
|
+
<FlowBuilder
|
|
133
|
+
title={title}
|
|
134
|
+
nodes={nodes}
|
|
135
|
+
edges={edges}
|
|
136
|
+
palette={palette}
|
|
137
|
+
onRun={onRun}
|
|
138
|
+
consoleEntries={consoleEntries}
|
|
139
|
+
defaultConsoleEntries={defaultConsoleEntries}
|
|
140
|
+
onConsoleChange={onConsoleChange}
|
|
141
|
+
onConsoleClear={onConsoleClear}
|
|
142
|
+
height={height}
|
|
143
|
+
nodeTypes={nodeTypes}
|
|
144
|
+
onConnectEdge={onConnectEdge}
|
|
145
|
+
onConnect={onConnect}
|
|
146
|
+
isValidConnection={isValidConnection}
|
|
147
|
+
onConnectionRejected={onConnectionRejected}
|
|
148
|
+
validationIssues={validationIssues}
|
|
149
|
+
mode={mode}
|
|
150
|
+
nodeStatus={nodeStatus}
|
|
151
|
+
onNodeSelect={onNodeSelect}
|
|
152
|
+
/>
|
|
153
|
+
{codePanel && (
|
|
154
|
+
<CodePanel
|
|
155
|
+
{...codePanel}
|
|
156
|
+
headingLevel={codePanel.headingLevel ?? 2}
|
|
157
|
+
readOnly={mode === 'run' ? true : codePanel.readOnly}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
</CanvasLayout>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BreadcrumbItem } from '@gntik-ai/ui';
|
|
2
|
+
|
|
3
|
+
/** Neutral fixtures for the flow builder page; the graph itself uses the FlowBuilder block fixtures. */
|
|
4
|
+
export const flowBuilderContent = {
|
|
5
|
+
title: 'Order sync',
|
|
6
|
+
status: 'draft',
|
|
7
|
+
lastSaved: 'Saved 2 min ago',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const flowBuilderBreadcrumbs: BreadcrumbItem[] = [
|
|
11
|
+
{ label: 'Automations', href: '/automations' },
|
|
12
|
+
{ label: 'order-sync', mono: true },
|
|
13
|
+
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: TemplateMeta = {
|
|
4
|
+
name: 'Flow builder',
|
|
5
|
+
family: 'Builders',
|
|
6
|
+
priority: 'P3',
|
|
7
|
+
status: 'experimental',
|
|
8
|
+
description:
|
|
9
|
+
'Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode="run", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import \'@gntik-ai/flow/styles.css\' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode="run" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode="run" nodeStatus={{step:"running"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.',
|
|
10
|
+
layout: 'CanvasLayout',
|
|
11
|
+
blocks: ['flow-builder', 'code-panel'],
|
|
12
|
+
};
|
package/README.md
DELETED