@codemation/next-host 0.0.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/README.md +25 -0
- package/app/(shell)/credentials/page.tsx +5 -0
- package/app/(shell)/dashboard/page.tsx +14 -0
- package/app/(shell)/layout.tsx +11 -0
- package/app/(shell)/page.tsx +5 -0
- package/app/(shell)/users/page.tsx +5 -0
- package/app/(shell)/workflows/[workflowId]/page.tsx +19 -0
- package/app/(shell)/workflows/page.tsx +5 -0
- package/app/api/[[...path]]/route.ts +40 -0
- package/app/api/auth/[...nextauth]/route.ts +3 -0
- package/app/globals.css +997 -0
- package/app/invite/[token]/page.tsx +10 -0
- package/app/layout.tsx +65 -0
- package/app/login/layout.tsx +25 -0
- package/app/login/page.tsx +22 -0
- package/components.json +21 -0
- package/docs/FORMS.md +46 -0
- package/docs/TAILWIND_SHADCN_MIGRATION.md +89 -0
- package/eslint.config.mjs +56 -0
- package/middleware.ts +29 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +34 -0
- package/package.json +76 -0
- package/postcss.config.mjs +7 -0
- package/public/canvas-icons/builtin/openai.svg +5 -0
- package/src/api/CodemationApiClient.ts +107 -0
- package/src/api/CodemationApiHttpError.ts +17 -0
- package/src/auth/CodemationNextAuthConfigResolver.ts +14 -0
- package/src/auth/CodemationNextAuthOAuthProviderDescriptorMapper.ts +30 -0
- package/src/auth/CodemationNextAuthOAuthProviderSnapshotResolver.ts +17 -0
- package/src/auth/CodemationNextAuthProviderCatalog.ts +107 -0
- package/src/auth/codemationEdgeAuth.ts +25 -0
- package/src/auth/codemationNextAuth.ts +32 -0
- package/src/components/Codemation.tsx +6 -0
- package/src/components/CodemationDataTable.tsx +37 -0
- package/src/components/CodemationDialog.tsx +137 -0
- package/src/components/CodemationFormattedDateTime.tsx +46 -0
- package/src/components/GoogleColorGIcon.tsx +39 -0
- package/src/components/OauthProviderIcon.tsx +33 -0
- package/src/components/PasswordStrengthMeter.tsx +59 -0
- package/src/components/forms/index.ts +28 -0
- package/src/components/json/JsonMonacoEditor.tsx +75 -0
- package/src/components/oauthProviderIconData.ts +17 -0
- package/src/components/ui/alert.tsx +56 -0
- package/src/components/ui/badge.tsx +40 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +70 -0
- package/src/components/ui/collapsible.tsx +26 -0
- package/src/components/ui/dialog.tsx +137 -0
- package/src/components/ui/dropdown-menu.tsx +238 -0
- package/src/components/ui/form.tsx +147 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/scroll-area.tsx +47 -0
- package/src/components/ui/select.tsx +169 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/switch.tsx +28 -0
- package/src/components/ui/table.tsx +72 -0
- package/src/components/ui/tabs.tsx +76 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle.tsx +41 -0
- package/src/features/credentials/components/CredentialConfirmDialog.tsx +58 -0
- package/src/features/credentials/components/CredentialDialog.tsx +252 -0
- package/src/features/credentials/components/CredentialDialogFeedback.tsx +36 -0
- package/src/features/credentials/components/CredentialDialogFieldRows.tsx +257 -0
- package/src/features/credentials/components/CredentialDialogFormSections.tsx +230 -0
- package/src/features/credentials/components/CredentialEnvFieldStatusRow.tsx +64 -0
- package/src/features/credentials/components/CredentialFieldCopyButton.tsx +48 -0
- package/src/features/credentials/components/CredentialsScreenHealthBadge.tsx +21 -0
- package/src/features/credentials/components/CredentialsScreenInstancesTable.tsx +108 -0
- package/src/features/credentials/components/CredentialsScreenTestFailureAlert.tsx +33 -0
- package/src/features/credentials/hooks/useCredentialCreateDialog.ts +33 -0
- package/src/features/credentials/hooks/useCredentialDialogSession.ts +616 -0
- package/src/features/credentials/hooks/useCredentialsScreen.ts +213 -0
- package/src/features/credentials/lib/credentialFieldHelpers.ts +35 -0
- package/src/features/credentials/lib/credentialFormTypes.ts +1 -0
- package/src/features/credentials/lib/credentialInstanceTestPayloadParser.ts +10 -0
- package/src/features/credentials/screens/CredentialsScreen.tsx +187 -0
- package/src/features/invite/screens/InviteAcceptScreen.tsx +190 -0
- package/src/features/users/components/UsersInviteDialog.tsx +121 -0
- package/src/features/users/components/UsersRegenerateDialog.tsx +81 -0
- package/src/features/users/components/UsersScreenUserStatusBadge.tsx +19 -0
- package/src/features/users/schemas/usersInviteFormSchema.ts +7 -0
- package/src/features/users/screens/UsersScreen.tsx +240 -0
- package/src/features/workflows/components/WorkflowListFolderSection.tsx +91 -0
- package/src/features/workflows/components/WorkflowListItemCard.tsx +67 -0
- package/src/features/workflows/components/WorkflowListRoot.tsx +39 -0
- package/src/features/workflows/components/WorkflowsListTree.tsx +28 -0
- package/src/features/workflows/components/canvas/CanvasNodeChromeTooltip.tsx +96 -0
- package/src/features/workflows/components/canvas/CanvasNodeIconSlot.tsx +25 -0
- package/src/features/workflows/components/canvas/VisibleNodeStatusResolver.tsx +84 -0
- package/src/features/workflows/components/canvas/WorkflowCanvas.tsx +248 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNode.tsx +182 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeAccents.tsx +73 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeAgentBottomSourceHandles.tsx +43 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeAgentLabels.tsx +47 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeCard.tsx +202 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeHandles.tsx +77 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeLabelBelow.tsx +51 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeMainGlyph.tsx +64 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeToolbar.tsx +95 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasLoadingPlaceholder.tsx +69 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasNodeIcon.tsx +102 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasSimpleIconGlyph.tsx +21 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasStraightCountEdge.tsx +33 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasStructureSignature.tsx +7 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasSymmetricForkEdge.tsx +32 -0
- package/src/features/workflows/components/canvas/WorkflowCanvasToolbarIconButton.tsx +95 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasBuiltinIconRegistry.ts +26 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasEdgeCountResolver.ts +51 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasEdgeStyleResolver.ts +35 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasLabelLayoutEstimator.ts +42 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasOverlapResolver.ts +78 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasPortOrderResolver.ts +25 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasRoundedOrthogonalPathPlanner.ts +56 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasSiIconRegistry.ts +18 -0
- package/src/features/workflows/components/canvas/lib/WorkflowCanvasSymmetricForkPathPlanner.ts +43 -0
- package/src/features/workflows/components/canvas/lib/layoutWorkflow.ts +315 -0
- package/src/features/workflows/components/canvas/lib/workflowCanvasEdgeGeometry.ts +3 -0
- package/src/features/workflows/components/canvas/lib/workflowCanvasEmbeddedStyles.ts +62 -0
- package/src/features/workflows/components/canvas/lib/workflowCanvasFlowTypes.ts +10 -0
- package/src/features/workflows/components/canvas/lib/workflowCanvasNodeData.ts +41 -0
- package/src/features/workflows/components/canvas/lib/workflowCanvasNodeGeometry.ts +99 -0
- package/src/features/workflows/components/canvas/workflowCanvasNodeChrome.tsx +46 -0
- package/src/features/workflows/components/realtime/RealtimeContext.tsx +14 -0
- package/src/features/workflows/components/realtime/WorkflowRealtimeProvider.tsx +15 -0
- package/src/features/workflows/components/workflowDetail/NodeCredentialBindingRow.tsx +209 -0
- package/src/features/workflows/components/workflowDetail/NodeCredentialBindingsSection.tsx +227 -0
- package/src/features/workflows/components/workflowDetail/NodePropertiesConfigSection.tsx +51 -0
- package/src/features/workflows/components/workflowDetail/NodePropertiesPanelHeader.tsx +50 -0
- package/src/features/workflows/components/workflowDetail/NodePropertiesSlidePanel.tsx +134 -0
- package/src/features/workflows/components/workflowDetail/WorkflowActivationErrorDialog.tsx +71 -0
- package/src/features/workflows/components/workflowDetail/WorkflowActivationHeaderControl.tsx +64 -0
- package/src/features/workflows/components/workflowDetail/WorkflowDetailIcons.tsx +52 -0
- package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspector.tsx +110 -0
- package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorDetailBody.tsx +213 -0
- package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorPanes.tsx +239 -0
- package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorSidebarResizer.tsx +31 -0
- package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorTreePanel.tsx +133 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorAttachmentGroupingPresenter.tsx +31 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorAttachmentList.tsx +118 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorBinaryView.tsx +15 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorErrorView.tsx +107 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorJsonView.tsx +114 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorPrettyTreePresenter.tsx +132 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorPrettyTreeViewRenderer.tsx +147 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorPrettyView.tsx +65 -0
- package/src/features/workflows/components/workflowDetail/WorkflowInspectorViews.tsx +5 -0
- package/src/features/workflows/components/workflowDetail/WorkflowJsonEditorBinaryAttachmentRow.tsx +74 -0
- package/src/features/workflows/components/workflowDetail/WorkflowJsonEditorBinaryUploadRow.tsx +69 -0
- package/src/features/workflows/components/workflowDetail/WorkflowJsonEditorDialog.tsx +254 -0
- package/src/features/workflows/components/workflowDetail/WorkflowRunsList.tsx +89 -0
- package/src/features/workflows/components/workflowDetail/WorkflowRunsSidebar.tsx +50 -0
- package/src/features/workflows/hooks/canvas/useWorkflowCanvasVisibleNodeStatuses.ts +14 -0
- package/src/features/workflows/hooks/realtime/realtime.tsx +271 -0
- package/src/features/workflows/hooks/realtime/runQueryPolling.ts +34 -0
- package/src/features/workflows/hooks/realtime/useWorkflowRealtimeInfrastructure.ts +541 -0
- package/src/features/workflows/hooks/realtime/useWorkflowRealtimeShowDisconnectedBadge.ts +9 -0
- package/src/features/workflows/hooks/workflowDetail/useWorkflowDetailController.tsx +1300 -0
- package/src/features/workflows/lib/realtime/realtimeApi.ts +78 -0
- package/src/features/workflows/lib/realtime/realtimeClientBridge.ts +52 -0
- package/src/features/workflows/lib/realtime/realtimeDomainTypes.ts +191 -0
- package/src/features/workflows/lib/realtime/realtimeQueryKeys.ts +15 -0
- package/src/features/workflows/lib/realtime/realtimeRunMutations.ts +167 -0
- package/src/features/workflows/lib/realtime/workflowTypes.ts +5 -0
- package/src/features/workflows/lib/workflowDetail/PersistedWorkflowSnapshotMapper.ts +205 -0
- package/src/features/workflows/lib/workflowDetail/WorkflowActivationHttpErrorFormat.ts +32 -0
- package/src/features/workflows/lib/workflowDetail/WorkflowDetailPresenter.ts +1017 -0
- package/src/features/workflows/lib/workflowDetail/WorkflowDetailUrlCodec.ts +70 -0
- package/src/features/workflows/lib/workflowDetail/workflowDetailTypes.ts +152 -0
- package/src/features/workflows/lib/workflowDetailTreeStyles.ts +65 -0
- package/src/features/workflows/screens/WorkflowDetailScreen.tsx +236 -0
- package/src/features/workflows/screens/WorkflowDetailScreenInspectorPanel.tsx +55 -0
- package/src/features/workflows/screens/WorkflowsList.tsx +35 -0
- package/src/features/workflows/screens/WorkflowsScreen.tsx +31 -0
- package/src/index.ts +1 -0
- package/src/lib/utils.ts +6 -0
- package/src/middleware/CodemationNextHostMiddlewarePathRules.ts +31 -0
- package/src/providers/CodemationSessionProvider.tsx +23 -0
- package/src/providers/Providers.tsx +36 -0
- package/src/providers/RealtimeBoundary.tsx +17 -0
- package/src/providers/WhitelabelProvider.tsx +22 -0
- package/src/server/CodemationAuthPrismaClient.ts +21 -0
- package/src/server/CodemationNextHost.ts +379 -0
- package/src/shell/AppLayout.tsx +141 -0
- package/src/shell/AppLayoutNavItems.tsx +129 -0
- package/src/shell/AppLayoutPageHeader.tsx +79 -0
- package/src/shell/AppLayoutSidebarBrand.tsx +33 -0
- package/src/shell/AppMainContent.tsx +17 -0
- package/src/shell/AppShellHeaderActions.tsx +12 -0
- package/src/shell/AppShellHeaderActionsAuthenticated.tsx +51 -0
- package/src/shell/CodemationNextClientShell.tsx +17 -0
- package/src/shell/CredentialsSignInRedirectResolver.ts +21 -0
- package/src/shell/LoginPageClient.tsx +231 -0
- package/src/shell/WorkflowDetailChromeContext.tsx +42 -0
- package/src/shell/WorkflowFolderTreeBuilder.ts +62 -0
- package/src/shell/WorkflowFolderUi.ts +42 -0
- package/src/shell/WorkflowSidebarNavFolder.tsx +112 -0
- package/src/shell/WorkflowSidebarNavTree.tsx +68 -0
- package/src/shell/appLayoutPageTitle.ts +16 -0
- package/src/shell/appLayoutSidebarIcons.tsx +108 -0
- package/src/whitelabel/CodemationWhitelabelSnapshot.ts +4 -0
- package/src/whitelabel/CodemationWhitelabelSnapshotFactory.ts +18 -0
- package/tsconfig.json +40 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +34 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
|
|
5
|
+
import { usePathname } from "next/navigation";
|
|
6
|
+
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
|
|
11
|
+
import { IconCredentials, IconDashboard, IconUsers, IconWorkflow } from "./appLayoutSidebarIcons";
|
|
12
|
+
import { WorkflowSidebarNavTree } from "./WorkflowSidebarNavTree";
|
|
13
|
+
import { useWorkflowsQuery } from "../features/workflows/hooks/realtime/realtime";
|
|
14
|
+
|
|
15
|
+
export interface AppLayoutNavItemsProps {
|
|
16
|
+
readonly collapsed: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const navLinkClass = (isActive: boolean) =>
|
|
20
|
+
cn(
|
|
21
|
+
"flex items-center gap-3 rounded-sm px-4 py-3 text-sm no-underline transition-colors",
|
|
22
|
+
"text-sidebar-foreground hover:bg-sidebar-accent/80",
|
|
23
|
+
isActive && "bg-sidebar-accent font-medium text-sidebar-primary",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const workflowLinkClass = (isActive: boolean) =>
|
|
27
|
+
cn(
|
|
28
|
+
"flex items-center gap-3 rounded-sm px-4 py-2 text-sm no-underline transition-colors",
|
|
29
|
+
"text-sidebar-foreground hover:bg-sidebar-accent/80",
|
|
30
|
+
isActive && "bg-sidebar-accent font-medium text-sidebar-primary",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const workflowLinkClassCollapsed = (isActive: boolean) =>
|
|
34
|
+
cn(
|
|
35
|
+
"flex items-center justify-center rounded-sm p-3 text-sidebar-foreground no-underline transition-colors hover:bg-sidebar-accent/80",
|
|
36
|
+
isActive && "bg-sidebar-accent font-medium text-sidebar-primary",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export function AppLayoutNavItems({ collapsed }: AppLayoutNavItemsProps): ReactNode {
|
|
40
|
+
const pathname = usePathname();
|
|
41
|
+
const workflowsQuery = useWorkflowsQuery();
|
|
42
|
+
const workflows = workflowsQuery.data ?? [];
|
|
43
|
+
|
|
44
|
+
const navItem = (href: string, label: string, icon: ReactNode, exact?: boolean) => {
|
|
45
|
+
const isActive = exact ? pathname === href : pathname.startsWith(href);
|
|
46
|
+
const content = (
|
|
47
|
+
<Link
|
|
48
|
+
key={href}
|
|
49
|
+
href={href}
|
|
50
|
+
className={navLinkClass(isActive)}
|
|
51
|
+
data-testid={`nav-${label.toLowerCase().replace(/\s+/g, "-")}`}
|
|
52
|
+
aria-label={collapsed ? label : undefined}
|
|
53
|
+
>
|
|
54
|
+
<span className="flex shrink-0 items-center justify-center" aria-hidden>
|
|
55
|
+
{icon}
|
|
56
|
+
</span>
|
|
57
|
+
{!collapsed && <span className="truncate">{label}</span>}
|
|
58
|
+
</Link>
|
|
59
|
+
);
|
|
60
|
+
return collapsed ? (
|
|
61
|
+
<span key={href} className="relative flex overflow-visible">
|
|
62
|
+
{content}
|
|
63
|
+
</span>
|
|
64
|
+
) : (
|
|
65
|
+
content
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
{navItem("/dashboard", "Dashboard", <IconDashboard />, true)}
|
|
72
|
+
{navItem("/credentials", "Credentials", <IconCredentials />)}
|
|
73
|
+
{navItem("/users", "Users", <IconUsers />)}
|
|
74
|
+
{collapsed ? (
|
|
75
|
+
<div className="mt-3 flex flex-col gap-1">
|
|
76
|
+
<span className="relative flex overflow-visible">
|
|
77
|
+
<Link
|
|
78
|
+
href="/workflows"
|
|
79
|
+
className={cn(
|
|
80
|
+
"flex items-center justify-center rounded-sm p-3 text-sidebar-foreground no-underline transition-colors hover:bg-sidebar-accent/80",
|
|
81
|
+
pathname === "/workflows" && "bg-sidebar-accent font-medium text-sidebar-primary",
|
|
82
|
+
)}
|
|
83
|
+
data-testid="nav-workflows"
|
|
84
|
+
aria-label="All workflows"
|
|
85
|
+
>
|
|
86
|
+
<span className="flex shrink-0 opacity-70" aria-hidden>
|
|
87
|
+
<IconWorkflow />
|
|
88
|
+
</span>
|
|
89
|
+
</Link>
|
|
90
|
+
</span>
|
|
91
|
+
{workflowsQuery.isLoading && <span className="px-4 py-2 text-xs text-muted-foreground">…</span>}
|
|
92
|
+
{!workflowsQuery.isLoading && workflows.length > 0 && (
|
|
93
|
+
<WorkflowSidebarNavTree
|
|
94
|
+
workflows={workflows}
|
|
95
|
+
pathname={pathname}
|
|
96
|
+
workflowLinkClass={workflowLinkClassCollapsed}
|
|
97
|
+
collapsed
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="mt-4 flex flex-col gap-1">
|
|
103
|
+
<span className="px-4 py-2 text-[0.6875rem] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
104
|
+
Workflows
|
|
105
|
+
</span>
|
|
106
|
+
<div className="flex flex-col gap-1">
|
|
107
|
+
<Link
|
|
108
|
+
href="/workflows"
|
|
109
|
+
className={workflowLinkClass(pathname === "/workflows")}
|
|
110
|
+
data-testid="nav-workflows"
|
|
111
|
+
>
|
|
112
|
+
<span className="flex shrink-0 opacity-70" aria-hidden>
|
|
113
|
+
<IconWorkflow />
|
|
114
|
+
</span>
|
|
115
|
+
<span className="truncate text-sm">All workflows</span>
|
|
116
|
+
</Link>
|
|
117
|
+
{workflowsQuery.isLoading && <span className="px-4 py-2 text-xs text-muted-foreground">Loading…</span>}
|
|
118
|
+
{!workflowsQuery.isLoading && workflows.length === 0 && (
|
|
119
|
+
<span className="px-4 py-2 text-xs text-muted-foreground">No workflows</span>
|
|
120
|
+
)}
|
|
121
|
+
{!workflowsQuery.isLoading && workflows.length > 0 && (
|
|
122
|
+
<WorkflowSidebarNavTree workflows={workflows} pathname={pathname} workflowLinkClass={workflowLinkClass} />
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePathname } from "next/navigation";
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
import { AlertCircle } from "lucide-react";
|
|
8
|
+
|
|
9
|
+
import { CanvasNodeChromeTooltip } from "../features/workflows/components/canvas/CanvasNodeChromeTooltip";
|
|
10
|
+
import { WorkflowActivationErrorDialog } from "../features/workflows/components/workflowDetail/WorkflowActivationErrorDialog";
|
|
11
|
+
import { WorkflowActivationHeaderControl } from "../features/workflows/components/workflowDetail/WorkflowActivationHeaderControl";
|
|
12
|
+
import { useWhitelabel } from "../providers/WhitelabelProvider";
|
|
13
|
+
import { getPageTitle } from "./appLayoutPageTitle";
|
|
14
|
+
import { AppShellHeaderActions } from "./AppShellHeaderActions";
|
|
15
|
+
import { useWorkflowDetailChrome } from "./WorkflowDetailChromeContext";
|
|
16
|
+
import { useWorkflowsQuery } from "../features/workflows/hooks/realtime/realtime";
|
|
17
|
+
|
|
18
|
+
export function AppLayoutPageHeader(): ReactNode {
|
|
19
|
+
const pathname = usePathname();
|
|
20
|
+
const { productName } = useWhitelabel();
|
|
21
|
+
const workflowsQuery = useWorkflowsQuery();
|
|
22
|
+
const workflows = workflowsQuery.data ?? [];
|
|
23
|
+
const title = getPageTitle(pathname, workflows, productName);
|
|
24
|
+
const chrome = useWorkflowDetailChrome();
|
|
25
|
+
const isWorkflowDetail = /^\/workflows\/[^/]+$/.test(pathname);
|
|
26
|
+
const showChromeRow = isWorkflowDetail && chrome !== null;
|
|
27
|
+
const credentialLines = chrome?.credentialAttentionSummaryLines ?? [];
|
|
28
|
+
const activationAlertLines = chrome?.workflowActivationAlertLines ?? null;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<header className="flex shrink-0 flex-col border-b border-border bg-card">
|
|
32
|
+
<div className="flex h-14 items-center justify-between gap-6 px-8">
|
|
33
|
+
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
34
|
+
<h1
|
|
35
|
+
className="m-0 min-w-0 truncate text-xl font-semibold leading-none text-foreground"
|
|
36
|
+
data-testid={isWorkflowDetail ? "workflow-detail-workflow-title" : undefined}
|
|
37
|
+
>
|
|
38
|
+
{title}
|
|
39
|
+
</h1>
|
|
40
|
+
{showChromeRow && credentialLines.length > 0 ? (
|
|
41
|
+
<CanvasNodeChromeTooltip
|
|
42
|
+
testId="workflow-credential-attention-indicator"
|
|
43
|
+
ariaLabel="Workflow credential issues"
|
|
44
|
+
tooltip={credentialLines.join("\n")}
|
|
45
|
+
>
|
|
46
|
+
<span
|
|
47
|
+
data-testid="workflow-credential-attention-icon"
|
|
48
|
+
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-amber-300 bg-amber-50 text-amber-900 shadow-sm"
|
|
49
|
+
>
|
|
50
|
+
<AlertCircle size={16} strokeWidth={2.2} />
|
|
51
|
+
</span>
|
|
52
|
+
</CanvasNodeChromeTooltip>
|
|
53
|
+
) : null}
|
|
54
|
+
{showChromeRow && chrome?.isLiveWorkflowView ? (
|
|
55
|
+
<WorkflowActivationHeaderControl
|
|
56
|
+
variant="shell"
|
|
57
|
+
showErrorAlert={false}
|
|
58
|
+
active={chrome.workflowIsActive}
|
|
59
|
+
pending={chrome.isWorkflowActivationPending}
|
|
60
|
+
onActiveChange={chrome.setWorkflowActive}
|
|
61
|
+
alertLines={chrome.workflowActivationAlertLines}
|
|
62
|
+
onDismissAlert={chrome.dismissWorkflowActivationAlert}
|
|
63
|
+
/>
|
|
64
|
+
) : null}
|
|
65
|
+
</div>
|
|
66
|
+
<AppShellHeaderActions />
|
|
67
|
+
</div>
|
|
68
|
+
{showChromeRow && chrome && activationAlertLines && activationAlertLines.length > 0 ? (
|
|
69
|
+
<div data-testid="workflow-activation-shell-error">
|
|
70
|
+
<WorkflowActivationErrorDialog
|
|
71
|
+
open
|
|
72
|
+
alertLines={activationAlertLines}
|
|
73
|
+
onDismiss={chrome.dismissWorkflowActivationAlert}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
) : null}
|
|
77
|
+
</header>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { useWhitelabel } from "../providers/WhitelabelProvider";
|
|
7
|
+
|
|
8
|
+
export function AppLayoutSidebarBrand(args: Readonly<{ collapsed: boolean }>): ReactNode {
|
|
9
|
+
const { productName, logoUrl } = useWhitelabel();
|
|
10
|
+
return (
|
|
11
|
+
<Link
|
|
12
|
+
href="/"
|
|
13
|
+
className="flex min-w-0 max-w-full items-center gap-2 text-lg font-semibold text-sidebar-foreground no-underline hover:text-primary"
|
|
14
|
+
data-testid="sidebar-brand"
|
|
15
|
+
>
|
|
16
|
+
{logoUrl !== null ? (
|
|
17
|
+
<img
|
|
18
|
+
src={logoUrl}
|
|
19
|
+
alt=""
|
|
20
|
+
width={32}
|
|
21
|
+
height={32}
|
|
22
|
+
className={args.collapsed ? "size-8 shrink-0 object-contain" : "size-8 shrink-0 object-contain"}
|
|
23
|
+
data-testid="sidebar-whitelabel-logo"
|
|
24
|
+
/>
|
|
25
|
+
) : null}
|
|
26
|
+
{!args.collapsed ? (
|
|
27
|
+
<span className="min-w-0 truncate" data-testid="sidebar-whitelabel-product-name">
|
|
28
|
+
{productName}
|
|
29
|
+
</span>
|
|
30
|
+
) : null}
|
|
31
|
+
</Link>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePathname } from "next/navigation";
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
export function AppMainContent(args: Readonly<{ children: ReactNode }>): ReactNode {
|
|
10
|
+
const pathname = usePathname();
|
|
11
|
+
const isWorkflowDetail = /^\/workflows\/[^/]+$/.test(pathname);
|
|
12
|
+
return (
|
|
13
|
+
<div className={cn("min-h-0 flex-1 overflow-auto p-8", isWorkflowDetail && "overflow-hidden p-0")}>
|
|
14
|
+
{args.children}
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { AppShellHeaderActionsAuthenticated } from "./AppShellHeaderActionsAuthenticated";
|
|
6
|
+
|
|
7
|
+
export function AppShellHeaderActions(): ReactNode {
|
|
8
|
+
if (process.env.NEXT_PUBLIC_CODEMATION_SKIP_UI_AUTH === "true") {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return <AppShellHeaderActionsAuthenticated />;
|
|
12
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { signOut, useSession } from "next-auth/react";
|
|
4
|
+
|
|
5
|
+
import { useState, type ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
|
|
9
|
+
export function AppShellHeaderActionsAuthenticated(): ReactNode {
|
|
10
|
+
const { data: session, status } = useSession();
|
|
11
|
+
const [isSigningOut, setIsSigningOut] = useState(false);
|
|
12
|
+
|
|
13
|
+
if (status === "loading") {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className="flex shrink-0 items-center gap-4"
|
|
17
|
+
data-testid="header-session-loading"
|
|
18
|
+
aria-busy="true"
|
|
19
|
+
aria-label="Loading session"
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const email = session?.user?.email;
|
|
25
|
+
if (!email) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleSignOut = (): void => {
|
|
30
|
+
setIsSigningOut(true);
|
|
31
|
+
void signOut({ callbackUrl: "/login" });
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="flex shrink-0 items-center gap-4">
|
|
36
|
+
<span className="max-w-56 truncate text-xs text-muted-foreground" data-testid="header-user-email">
|
|
37
|
+
{email}
|
|
38
|
+
</span>
|
|
39
|
+
<Button
|
|
40
|
+
type="button"
|
|
41
|
+
variant="outline"
|
|
42
|
+
size="sm"
|
|
43
|
+
data-testid="header-logout"
|
|
44
|
+
disabled={isSigningOut}
|
|
45
|
+
onClick={handleSignOut}
|
|
46
|
+
>
|
|
47
|
+
{isSigningOut ? "Signing out…" : "Log out"}
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import "@xyflow/react/dist/style.css";
|
|
4
|
+
import "rc-tree/assets/index.css";
|
|
5
|
+
|
|
6
|
+
import { Component, type ReactNode } from "react";
|
|
7
|
+
import { Providers } from "../providers/Providers";
|
|
8
|
+
|
|
9
|
+
export interface CodemationNextClientShellProps {
|
|
10
|
+
readonly children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CodemationNextClientShell extends Component<CodemationNextClientShellProps> {
|
|
14
|
+
render(): ReactNode {
|
|
15
|
+
return <Providers websocketPort={process.env.NEXT_PUBLIC_CODEMATION_WS_PORT}>{this.props.children}</Providers>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SignInResponse } from "next-auth/react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps NextAuth `signIn(..., { redirect: false })` outcomes to a browser navigation target.
|
|
5
|
+
* Auth.js may return `ok: true` with an empty or missing `url` on success; callers must fall back to `callbackUrl`.
|
|
6
|
+
*/
|
|
7
|
+
export class CredentialsSignInRedirectResolver {
|
|
8
|
+
static resolveRedirectUrl(result: SignInResponse, callbackUrl: string): string | null {
|
|
9
|
+
if (result.error) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const trimmed = result.url?.trim();
|
|
13
|
+
if (trimmed && trimmed.length > 0) {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
if (result.ok) {
|
|
17
|
+
return callbackUrl;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { signIn } from "next-auth/react";
|
|
4
|
+
import { Component, type FormEvent, type ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import { Label } from "@/components/ui/label";
|
|
10
|
+
import { Separator } from "@/components/ui/separator";
|
|
11
|
+
|
|
12
|
+
import { OauthProviderIcon } from "../components/OauthProviderIcon";
|
|
13
|
+
import { CredentialsSignInRedirectResolver } from "./CredentialsSignInRedirectResolver";
|
|
14
|
+
|
|
15
|
+
type LoginPageClientProps = Readonly<{
|
|
16
|
+
callbackUrl: string;
|
|
17
|
+
productName: string;
|
|
18
|
+
logoUrl: string | null;
|
|
19
|
+
oauthProviders: ReadonlyArray<{ id: string; name: string }>;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
type LoginPageClientState = Readonly<{
|
|
23
|
+
email: string;
|
|
24
|
+
password: string;
|
|
25
|
+
error: string | null;
|
|
26
|
+
isSubmitting: boolean;
|
|
27
|
+
oauthSubmittingId: string | null;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export class LoginPageClient extends Component<LoginPageClientProps, LoginPageClientState> {
|
|
31
|
+
constructor(props: LoginPageClientProps) {
|
|
32
|
+
super(props);
|
|
33
|
+
this.state = {
|
|
34
|
+
email: "",
|
|
35
|
+
password: "",
|
|
36
|
+
error: null,
|
|
37
|
+
isSubmitting: false,
|
|
38
|
+
oauthSubmittingId: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override render(): ReactNode {
|
|
43
|
+
const { isSubmitting, oauthSubmittingId } = this.state;
|
|
44
|
+
const formBusy = isSubmitting || oauthSubmittingId !== null;
|
|
45
|
+
const { productName, logoUrl } = this.props;
|
|
46
|
+
const titleInitial = productName.trim().length > 0 ? productName.trim().charAt(0).toUpperCase() : "C";
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className="relative flex min-h-screen items-center justify-center bg-gradient-to-br from-muted/40 to-background p-4"
|
|
51
|
+
data-testid="login-page"
|
|
52
|
+
>
|
|
53
|
+
<div
|
|
54
|
+
className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/5 via-transparent to-transparent"
|
|
55
|
+
aria-hidden
|
|
56
|
+
/>
|
|
57
|
+
<Card className="relative z-10 w-full max-w-md shadow-lg">
|
|
58
|
+
<CardHeader className="gap-3">
|
|
59
|
+
<div className="flex items-start gap-3">
|
|
60
|
+
{logoUrl !== null ? (
|
|
61
|
+
<img
|
|
62
|
+
src={logoUrl}
|
|
63
|
+
alt=""
|
|
64
|
+
width={40}
|
|
65
|
+
height={40}
|
|
66
|
+
className="size-10 shrink-0 rounded-lg object-contain"
|
|
67
|
+
data-testid="login-whitelabel-logo"
|
|
68
|
+
/>
|
|
69
|
+
) : (
|
|
70
|
+
<span
|
|
71
|
+
className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary text-lg font-bold text-primary-foreground"
|
|
72
|
+
aria-hidden
|
|
73
|
+
data-testid="login-whitelabel-initial"
|
|
74
|
+
>
|
|
75
|
+
{titleInitial}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
<div className="min-w-0 space-y-1">
|
|
79
|
+
<CardTitle className="text-xl">Welcome back</CardTitle>
|
|
80
|
+
<p className="text-base font-semibold text-foreground" data-testid="login-whitelabel-product-name">
|
|
81
|
+
{productName}
|
|
82
|
+
</p>
|
|
83
|
+
<CardDescription>Sign in to run and manage your workflows.</CardDescription>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</CardHeader>
|
|
87
|
+
<CardContent>
|
|
88
|
+
<form
|
|
89
|
+
className="flex flex-col gap-4"
|
|
90
|
+
suppressHydrationWarning
|
|
91
|
+
aria-busy={isSubmitting}
|
|
92
|
+
onSubmit={(event: FormEvent) => {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
void this.submitCredentials();
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<div className="space-y-2" suppressHydrationWarning>
|
|
98
|
+
<Label htmlFor="codemation-login-email">Email</Label>
|
|
99
|
+
<Input
|
|
100
|
+
id="codemation-login-email"
|
|
101
|
+
type="email"
|
|
102
|
+
name="email"
|
|
103
|
+
autoComplete="username"
|
|
104
|
+
placeholder="you@company.com"
|
|
105
|
+
value={this.state.email}
|
|
106
|
+
onChange={(e) => this.setState({ email: e.target.value })}
|
|
107
|
+
required
|
|
108
|
+
disabled={formBusy}
|
|
109
|
+
suppressHydrationWarning
|
|
110
|
+
data-testid="login-email"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="space-y-2" suppressHydrationWarning>
|
|
114
|
+
<Label htmlFor="codemation-login-password">Password</Label>
|
|
115
|
+
<Input
|
|
116
|
+
id="codemation-login-password"
|
|
117
|
+
type="password"
|
|
118
|
+
name="password"
|
|
119
|
+
autoComplete="current-password"
|
|
120
|
+
placeholder="••••••••"
|
|
121
|
+
value={this.state.password}
|
|
122
|
+
onChange={(e) => this.setState({ password: e.target.value })}
|
|
123
|
+
required
|
|
124
|
+
disabled={formBusy}
|
|
125
|
+
suppressHydrationWarning
|
|
126
|
+
data-testid="login-password"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
{this.state.error ? (
|
|
130
|
+
<p className="text-sm text-destructive" data-testid="login-error" role="alert">
|
|
131
|
+
{this.state.error}
|
|
132
|
+
</p>
|
|
133
|
+
) : null}
|
|
134
|
+
<Button
|
|
135
|
+
type="button"
|
|
136
|
+
className="w-full"
|
|
137
|
+
data-testid="login-submit"
|
|
138
|
+
disabled={formBusy}
|
|
139
|
+
onClick={() => {
|
|
140
|
+
void this.submitCredentials();
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{isSubmitting ? (
|
|
144
|
+
<span
|
|
145
|
+
className="mr-2 inline-block size-4 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent"
|
|
146
|
+
aria-hidden
|
|
147
|
+
data-testid="login-submit-spinner"
|
|
148
|
+
/>
|
|
149
|
+
) : null}
|
|
150
|
+
{isSubmitting ? "Signing in…" : "Sign in"}
|
|
151
|
+
</Button>
|
|
152
|
+
</form>
|
|
153
|
+
{this.props.oauthProviders.length > 0 ? (
|
|
154
|
+
<div className="mt-6 space-y-4">
|
|
155
|
+
<div className="flex items-center gap-3">
|
|
156
|
+
<Separator className="flex-1" />
|
|
157
|
+
<span className="text-xs text-muted-foreground">Or</span>
|
|
158
|
+
<Separator className="flex-1" />
|
|
159
|
+
</div>
|
|
160
|
+
<section className="space-y-3" aria-label="OAuth sign-in">
|
|
161
|
+
<p className="text-center text-xs text-muted-foreground">Continue with a connected account</p>
|
|
162
|
+
<div className="flex flex-col gap-2">
|
|
163
|
+
{this.props.oauthProviders.map((provider) => {
|
|
164
|
+
const busy = oauthSubmittingId === provider.id;
|
|
165
|
+
return (
|
|
166
|
+
<Button
|
|
167
|
+
key={provider.id}
|
|
168
|
+
type="button"
|
|
169
|
+
variant="outline"
|
|
170
|
+
className="w-full justify-center gap-2"
|
|
171
|
+
data-testid={`login-oauth-${provider.id}`}
|
|
172
|
+
disabled={formBusy}
|
|
173
|
+
onClick={() => this.handleOAuthSignIn(provider.id)}
|
|
174
|
+
>
|
|
175
|
+
<OauthProviderIcon
|
|
176
|
+
providerId={provider.id}
|
|
177
|
+
className="size-4 shrink-0"
|
|
178
|
+
testId={`login-oauth-${provider.id}-icon`}
|
|
179
|
+
/>
|
|
180
|
+
{busy ? "Connecting…" : provider.name}
|
|
181
|
+
</Button>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
</div>
|
|
185
|
+
</section>
|
|
186
|
+
</div>
|
|
187
|
+
) : null}
|
|
188
|
+
</CardContent>
|
|
189
|
+
<CardFooter className="justify-center border-t bg-transparent pt-0">
|
|
190
|
+
<p className="text-center text-xs text-muted-foreground" data-testid="login-whitelabel-tagline">
|
|
191
|
+
{productName} — workflow automation you own.
|
|
192
|
+
</p>
|
|
193
|
+
</CardFooter>
|
|
194
|
+
</Card>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private handleOAuthSignIn(providerId: string): void {
|
|
200
|
+
this.setState({ oauthSubmittingId: providerId, error: null });
|
|
201
|
+
void signIn(providerId, { callbackUrl: this.props.callbackUrl });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private async submitCredentials(): Promise<void> {
|
|
205
|
+
this.setState({ error: null, isSubmitting: true });
|
|
206
|
+
try {
|
|
207
|
+
const result = await signIn("credentials", {
|
|
208
|
+
redirect: false,
|
|
209
|
+
email: this.state.email,
|
|
210
|
+
password: this.state.password,
|
|
211
|
+
callbackUrl: this.props.callbackUrl,
|
|
212
|
+
});
|
|
213
|
+
if (!result) {
|
|
214
|
+
this.setState({ error: "Something went wrong. Try again.", isSubmitting: false });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (result.error) {
|
|
218
|
+
this.setState({ error: "Invalid email or password.", isSubmitting: false });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const target = CredentialsSignInRedirectResolver.resolveRedirectUrl(result, this.props.callbackUrl);
|
|
222
|
+
if (target) {
|
|
223
|
+
window.location.assign(target);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.setState({ isSubmitting: false });
|
|
227
|
+
} catch {
|
|
228
|
+
this.setState({ error: "Something went wrong. Try again.", isSubmitting: false });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
type Dispatch,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
type SetStateAction,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
export type WorkflowDetailChromeState = Readonly<{
|
|
14
|
+
isLiveWorkflowView: boolean;
|
|
15
|
+
workflowIsActive: boolean;
|
|
16
|
+
isWorkflowActivationPending: boolean;
|
|
17
|
+
setWorkflowActive: (active: boolean) => void;
|
|
18
|
+
workflowActivationAlertLines: ReadonlyArray<string> | null;
|
|
19
|
+
dismissWorkflowActivationAlert: () => void;
|
|
20
|
+
credentialAttentionSummaryLines: ReadonlyArray<string>;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
type WorkflowDetailChromeContextValue = Readonly<{
|
|
24
|
+
chrome: WorkflowDetailChromeState | null;
|
|
25
|
+
setChrome: Dispatch<SetStateAction<WorkflowDetailChromeState | null>>;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
const WorkflowDetailChromeContext = createContext<WorkflowDetailChromeContextValue | null>(null);
|
|
29
|
+
|
|
30
|
+
export function WorkflowDetailChromeProvider(args: Readonly<{ children: ReactNode }>): React.JSX.Element {
|
|
31
|
+
const [chrome, setChrome] = useState<WorkflowDetailChromeState | null>(null);
|
|
32
|
+
const value = useMemo(() => ({ chrome, setChrome }), [chrome]);
|
|
33
|
+
return <WorkflowDetailChromeContext.Provider value={value}>{args.children}</WorkflowDetailChromeContext.Provider>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useWorkflowDetailChrome(): WorkflowDetailChromeState | null {
|
|
37
|
+
return useContext(WorkflowDetailChromeContext)?.chrome ?? null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useWorkflowDetailChromeDispatch(): Dispatch<SetStateAction<WorkflowDetailChromeState | null>> | null {
|
|
41
|
+
return useContext(WorkflowDetailChromeContext)?.setChrome ?? null;
|
|
42
|
+
}
|