machinaos 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/.env.template +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TriggerNode - Visual component for trigger nodes (no input connections)
|
|
3
|
+
*
|
|
4
|
+
* Trigger nodes start workflows and have only output handles.
|
|
5
|
+
* Based on SquareNode design but without input handles.
|
|
6
|
+
*
|
|
7
|
+
* Used for: cronScheduler, webhookTrigger, whatsappReceive, start
|
|
8
|
+
*/
|
|
9
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
10
|
+
import { Handle, Position, NodeProps } from 'reactflow';
|
|
11
|
+
import { NodeData } from '../types/NodeTypes';
|
|
12
|
+
import { useAppStore } from '../store/useAppStore';
|
|
13
|
+
import { nodeDefinitions } from '../nodeDefinitions';
|
|
14
|
+
import { useAppTheme } from '../hooks/useAppTheme';
|
|
15
|
+
import { useWebSocket, useWhatsAppStatus } from '../contexts/WebSocketContext';
|
|
16
|
+
import { PlayCircleFilled, ScheduleOutlined } from '@ant-design/icons';
|
|
17
|
+
|
|
18
|
+
const TriggerNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
|
|
19
|
+
const theme = useAppTheme();
|
|
20
|
+
const { setSelectedNode, renamingNodeId, setRenamingNodeId, updateNodeData } = useAppStore();
|
|
21
|
+
const [isConfigured, setIsConfigured] = useState(false);
|
|
22
|
+
const isDisabled = data?.disabled === true;
|
|
23
|
+
|
|
24
|
+
// Inline rename state
|
|
25
|
+
const [isRenaming, setIsRenaming] = useState(false);
|
|
26
|
+
const [editLabel, setEditLabel] = useState('');
|
|
27
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
28
|
+
|
|
29
|
+
// Get node status from WebSocket context
|
|
30
|
+
const { getNodeStatus } = useWebSocket();
|
|
31
|
+
const nodeStatus = getNodeStatus(id);
|
|
32
|
+
const executionStatus = nodeStatus?.status || 'idle';
|
|
33
|
+
|
|
34
|
+
// Check if this is a WhatsApp trigger
|
|
35
|
+
const isWhatsAppTrigger = type === 'whatsappReceive';
|
|
36
|
+
const whatsappStatus = useWhatsAppStatus();
|
|
37
|
+
|
|
38
|
+
// Combine waiting and executing states for glow animation (matching SquareNode pattern)
|
|
39
|
+
// - waiting: Trigger is listening for events (cron scheduled, webhook listening)
|
|
40
|
+
// - executing: Trigger is actively running
|
|
41
|
+
// Both states show the glow animation to indicate active state
|
|
42
|
+
const isExecuting = executionStatus === 'executing' || executionStatus === 'waiting';
|
|
43
|
+
|
|
44
|
+
const definition = nodeDefinitions[type as keyof typeof nodeDefinitions];
|
|
45
|
+
|
|
46
|
+
// Check configuration status
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const hasRequiredParams = data && Object.keys(data).length > 0;
|
|
49
|
+
setIsConfigured(hasRequiredParams);
|
|
50
|
+
}, [data]);
|
|
51
|
+
|
|
52
|
+
// Sync with global renaming state
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (renamingNodeId === id) {
|
|
55
|
+
setIsRenaming(true);
|
|
56
|
+
setEditLabel(data?.label || definition?.displayName || type || '');
|
|
57
|
+
} else {
|
|
58
|
+
setIsRenaming(false);
|
|
59
|
+
}
|
|
60
|
+
}, [renamingNodeId, id, data?.label, definition?.displayName, type]);
|
|
61
|
+
|
|
62
|
+
// Focus and select input when entering rename mode
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (isRenaming && inputRef.current) {
|
|
65
|
+
inputRef.current.focus();
|
|
66
|
+
inputRef.current.select();
|
|
67
|
+
}
|
|
68
|
+
}, [isRenaming]);
|
|
69
|
+
|
|
70
|
+
// Handle save rename
|
|
71
|
+
const handleSaveRename = useCallback(() => {
|
|
72
|
+
const newLabel = editLabel.trim();
|
|
73
|
+
const originalLabel = data?.label || definition?.displayName || type || '';
|
|
74
|
+
|
|
75
|
+
// Only save if label changed and is not empty
|
|
76
|
+
if (newLabel && newLabel !== originalLabel) {
|
|
77
|
+
updateNodeData(id, { ...data, label: newLabel });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setIsRenaming(false);
|
|
81
|
+
setRenamingNodeId(null);
|
|
82
|
+
}, [editLabel, data, definition?.displayName, type, id, updateNodeData, setRenamingNodeId]);
|
|
83
|
+
|
|
84
|
+
// Handle cancel rename
|
|
85
|
+
const handleCancelRename = useCallback(() => {
|
|
86
|
+
setIsRenaming(false);
|
|
87
|
+
setRenamingNodeId(null);
|
|
88
|
+
}, [setRenamingNodeId]);
|
|
89
|
+
|
|
90
|
+
// Handle double-click to rename
|
|
91
|
+
const handleLabelDoubleClick = useCallback(() => {
|
|
92
|
+
setRenamingNodeId(id);
|
|
93
|
+
}, [id, setRenamingNodeId]);
|
|
94
|
+
|
|
95
|
+
const handleParametersClick = (e: React.MouseEvent) => {
|
|
96
|
+
e.stopPropagation();
|
|
97
|
+
setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Get status indicator color based on execution state
|
|
101
|
+
const getStatusIndicatorColor = () => {
|
|
102
|
+
// Combined executing/waiting state - show purple with glow
|
|
103
|
+
if (isExecuting) {
|
|
104
|
+
return theme.dracula.purple;
|
|
105
|
+
}
|
|
106
|
+
if (executionStatus === 'success') {
|
|
107
|
+
return theme.dracula.green;
|
|
108
|
+
}
|
|
109
|
+
if (executionStatus === 'error') {
|
|
110
|
+
return theme.dracula.red;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// WhatsApp trigger - use connection status when idle
|
|
114
|
+
if (isWhatsAppTrigger) {
|
|
115
|
+
if (whatsappStatus.connected) return theme.dracula.green;
|
|
116
|
+
if (whatsappStatus.pairing) return theme.dracula.orange;
|
|
117
|
+
return theme.dracula.red;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Idle state - show configured status
|
|
121
|
+
return isConfigured ? theme.dracula.green : theme.dracula.orange;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const getStatusTitle = () => {
|
|
125
|
+
switch (executionStatus) {
|
|
126
|
+
case 'executing':
|
|
127
|
+
return 'Executing...';
|
|
128
|
+
case 'waiting':
|
|
129
|
+
return nodeStatus?.message || 'Waiting for trigger event...';
|
|
130
|
+
case 'success':
|
|
131
|
+
return 'Trigger fired successfully';
|
|
132
|
+
case 'error':
|
|
133
|
+
return `Error: ${nodeStatus?.data?.error || 'Unknown error'}`;
|
|
134
|
+
default:
|
|
135
|
+
// WhatsApp trigger status
|
|
136
|
+
if (isWhatsAppTrigger) {
|
|
137
|
+
if (whatsappStatus.connected) return 'WhatsApp connected - ready to receive';
|
|
138
|
+
if (whatsappStatus.pairing) return 'Pairing in progress...';
|
|
139
|
+
return 'WhatsApp not connected';
|
|
140
|
+
}
|
|
141
|
+
return isConfigured ? 'Trigger configured and ready' : 'Click to configure trigger';
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Check if string is likely an emoji
|
|
146
|
+
const isEmoji = (str: string): boolean => {
|
|
147
|
+
const emojiRegex = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2300}-\u{23FF}]|[\u{2B50}]|[\u{231A}-\u{231B}]|[\u{25AA}-\u{25AB}]|[\u{25B6}]|[\u{25C0}]|[\u{25FB}-\u{25FE}]|[\u{2614}-\u{2615}]|[\u{2648}-\u{2653}]|[\u{267F}]|[\u{2693}]|[\u{26A1}]|[\u{26AA}-\u{26AB}]|[\u{26BD}-\u{26BE}]|[\u{26C4}-\u{26C5}]|[\u{26CE}]|[\u{26D4}]|[\u{26EA}]|[\u{26F2}-\u{26F3}]|[\u{26F5}]|[\u{26FA}]|[\u{26FD}]|[\u{2702}]|[\u{2705}]|[\u{2708}-\u{270D}]|[\u{270F}]|[\u{2712}]|[\u{2714}]|[\u{2716}]|[\u{271D}]|[\u{2721}]|[\u{2728}]|[\u{2733}-\u{2734}]|[\u{2744}]|[\u{2747}]|[\u{274C}]|[\u{274E}]|[\u{2753}-\u{2755}]|[\u{2757}]|[\u{2763}-\u{2764}]|[\u{2795}-\u{2797}]|[\u{27A1}]|[\u{27B0}]|[\u{27BF}]|[\u{E000}-\u{F8FF}]/u;
|
|
148
|
+
return emojiRegex.test(str);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Helper to render icon
|
|
152
|
+
const renderIcon = (icon: string) => {
|
|
153
|
+
if (icon.startsWith('http') || icon.startsWith('data:') || icon.startsWith('/')) {
|
|
154
|
+
return (
|
|
155
|
+
<img
|
|
156
|
+
src={icon}
|
|
157
|
+
alt="icon"
|
|
158
|
+
style={{
|
|
159
|
+
width: '28px',
|
|
160
|
+
height: '28px',
|
|
161
|
+
objectFit: 'contain',
|
|
162
|
+
borderRadius: '4px'
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (isEmoji(icon)) {
|
|
169
|
+
return icon;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Fallback
|
|
173
|
+
return '⚡';
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Get trigger icon for display
|
|
177
|
+
const getTriggerIcon = () => {
|
|
178
|
+
// Start node - use PlayCircleFilled icon
|
|
179
|
+
if (type === 'start') {
|
|
180
|
+
return <PlayCircleFilled style={{ fontSize: 28, color: theme.dracula.cyan }} />;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Cron Scheduler - use ScheduleOutlined
|
|
184
|
+
if (type === 'cronScheduler') {
|
|
185
|
+
return <ScheduleOutlined style={{ fontSize: 28, color: definition?.defaults?.color || nodeColor }} />;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Use the icon from the node definition if available
|
|
189
|
+
if (definition?.icon && typeof definition.icon === 'string') {
|
|
190
|
+
return renderIcon(definition.icon);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Fallback to lightning bolt for triggers
|
|
194
|
+
return '⚡';
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Get the node color from definition or use default trigger color
|
|
198
|
+
const nodeColor = definition?.defaults?.color || '#f59e0b';
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div
|
|
202
|
+
style={{
|
|
203
|
+
position: 'relative',
|
|
204
|
+
display: 'flex',
|
|
205
|
+
flexDirection: 'column',
|
|
206
|
+
alignItems: 'center',
|
|
207
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
208
|
+
fontSize: '11px',
|
|
209
|
+
cursor: 'pointer',
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{/* Main Trigger Node */}
|
|
213
|
+
{/* Show glow animation for both executing and waiting states */}
|
|
214
|
+
<div
|
|
215
|
+
style={{
|
|
216
|
+
position: 'relative',
|
|
217
|
+
width: theme.nodeSize.square,
|
|
218
|
+
height: theme.nodeSize.square,
|
|
219
|
+
borderRadius: theme.borderRadius.lg,
|
|
220
|
+
background: theme.isDarkMode
|
|
221
|
+
? `linear-gradient(135deg, ${nodeColor}25 0%, ${theme.colors.background} 100%)`
|
|
222
|
+
: `linear-gradient(145deg, #ffffff 0%, ${nodeColor}08 100%)`,
|
|
223
|
+
border: `2px solid ${
|
|
224
|
+
isExecuting
|
|
225
|
+
? (theme.isDarkMode ? theme.dracula.purple : '#2563eb')
|
|
226
|
+
: selected
|
|
227
|
+
? theme.colors.focus
|
|
228
|
+
: theme.isDarkMode ? nodeColor + '80' : `${nodeColor}40`
|
|
229
|
+
}`,
|
|
230
|
+
display: 'flex',
|
|
231
|
+
alignItems: 'center',
|
|
232
|
+
justifyContent: 'center',
|
|
233
|
+
color: theme.colors.text,
|
|
234
|
+
fontSize: theme.nodeSize.squareIcon,
|
|
235
|
+
fontWeight: '600',
|
|
236
|
+
transition: 'all 0.2s ease',
|
|
237
|
+
boxShadow: isExecuting
|
|
238
|
+
? theme.isDarkMode
|
|
239
|
+
? `0 4px 12px ${theme.dracula.purple}66, 0 0 0 3px ${theme.dracula.purple}4D`
|
|
240
|
+
: `0 0 0 3px rgba(37, 99, 235, 0.5), 0 4px 16px rgba(37, 99, 235, 0.35)`
|
|
241
|
+
: selected
|
|
242
|
+
? `0 4px 12px ${theme.colors.focusRing}, 0 0 0 1px ${theme.colors.focusRing}`
|
|
243
|
+
: theme.isDarkMode
|
|
244
|
+
? `0 2px 8px ${nodeColor}40`
|
|
245
|
+
: `0 2px 8px ${nodeColor}20, 0 4px 12px rgba(0,0,0,0.06)`,
|
|
246
|
+
// Subtle animation for both modes
|
|
247
|
+
animation: isExecuting ? 'pulse 1.5s ease-in-out infinite' : 'none',
|
|
248
|
+
opacity: isDisabled ? 0.5 : 1,
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
{/* Disabled Overlay */}
|
|
252
|
+
{isDisabled && (
|
|
253
|
+
<div style={{
|
|
254
|
+
position: 'absolute',
|
|
255
|
+
top: 0,
|
|
256
|
+
left: 0,
|
|
257
|
+
right: 0,
|
|
258
|
+
bottom: 0,
|
|
259
|
+
backgroundColor: 'rgba(128, 128, 128, 0.4)',
|
|
260
|
+
borderRadius: 'inherit',
|
|
261
|
+
zIndex: 35,
|
|
262
|
+
display: 'flex',
|
|
263
|
+
alignItems: 'center',
|
|
264
|
+
justifyContent: 'center',
|
|
265
|
+
pointerEvents: 'none',
|
|
266
|
+
}}>
|
|
267
|
+
<span style={{ fontSize: '20px', opacity: 0.8, color: theme.colors.textSecondary }}>||</span>
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
|
|
271
|
+
{/* Trigger Icon */}
|
|
272
|
+
{getTriggerIcon()}
|
|
273
|
+
|
|
274
|
+
{/* Parameters Button */}
|
|
275
|
+
<button
|
|
276
|
+
onClick={handleParametersClick}
|
|
277
|
+
style={{
|
|
278
|
+
position: 'absolute',
|
|
279
|
+
top: '-8px',
|
|
280
|
+
right: '-8px',
|
|
281
|
+
width: theme.nodeSize.paramButton,
|
|
282
|
+
height: theme.nodeSize.paramButton,
|
|
283
|
+
borderRadius: theme.borderRadius.sm,
|
|
284
|
+
backgroundColor: theme.isDarkMode ? theme.colors.backgroundAlt : '#ffffff',
|
|
285
|
+
border: `1px solid ${theme.isDarkMode ? theme.colors.border : '#d1d5db'}`,
|
|
286
|
+
cursor: 'pointer',
|
|
287
|
+
display: 'flex',
|
|
288
|
+
alignItems: 'center',
|
|
289
|
+
justifyContent: 'center',
|
|
290
|
+
fontSize: theme.fontSize.xs,
|
|
291
|
+
color: theme.colors.textSecondary,
|
|
292
|
+
fontWeight: '400',
|
|
293
|
+
transition: theme.transitions.fast,
|
|
294
|
+
zIndex: 30,
|
|
295
|
+
boxShadow: theme.isDarkMode
|
|
296
|
+
? `0 1px 3px ${theme.colors.shadow}`
|
|
297
|
+
: '0 1px 4px rgba(0,0,0,0.1)'
|
|
298
|
+
}}
|
|
299
|
+
title="Configure Trigger"
|
|
300
|
+
>
|
|
301
|
+
⚙️
|
|
302
|
+
</button>
|
|
303
|
+
|
|
304
|
+
{/* Execution Status Indicator */}
|
|
305
|
+
{/* Status Indicator - glows for both waiting and executing states */}
|
|
306
|
+
<div
|
|
307
|
+
style={{
|
|
308
|
+
position: 'absolute',
|
|
309
|
+
top: '-4px',
|
|
310
|
+
left: '-4px',
|
|
311
|
+
width: theme.nodeSize.statusIndicator,
|
|
312
|
+
height: theme.nodeSize.statusIndicator,
|
|
313
|
+
borderRadius: '50%',
|
|
314
|
+
backgroundColor: getStatusIndicatorColor(),
|
|
315
|
+
border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
316
|
+
boxShadow: isExecuting
|
|
317
|
+
? theme.isDarkMode
|
|
318
|
+
? `0 0 6px ${theme.dracula.purple}80`
|
|
319
|
+
: '0 0 4px rgba(37, 99, 235, 0.5)'
|
|
320
|
+
: theme.isDarkMode
|
|
321
|
+
? `0 1px 2px ${theme.colors.shadow}`
|
|
322
|
+
: '0 1px 3px rgba(0,0,0,0.15)',
|
|
323
|
+
zIndex: 30,
|
|
324
|
+
// Subtle pulse animation for both modes
|
|
325
|
+
animation: isExecuting ? 'pulse 1s ease-in-out infinite' : 'none',
|
|
326
|
+
}}
|
|
327
|
+
title={getStatusTitle()}
|
|
328
|
+
/>
|
|
329
|
+
|
|
330
|
+
{/* NO INPUT HANDLE - Trigger nodes don't have inputs */}
|
|
331
|
+
|
|
332
|
+
{/* Trigger Badge - Lightning bolt indicator on bottom-left */}
|
|
333
|
+
<div
|
|
334
|
+
style={{
|
|
335
|
+
position: 'absolute',
|
|
336
|
+
bottom: '-4px',
|
|
337
|
+
left: '-4px',
|
|
338
|
+
width: theme.nodeSize.outputBadge,
|
|
339
|
+
height: theme.nodeSize.outputBadge,
|
|
340
|
+
borderRadius: theme.borderRadius.sm,
|
|
341
|
+
backgroundColor: theme.dracula.yellow,
|
|
342
|
+
border: `1px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
343
|
+
display: 'flex',
|
|
344
|
+
alignItems: 'center',
|
|
345
|
+
justifyContent: 'center',
|
|
346
|
+
fontSize: theme.fontSize.xs,
|
|
347
|
+
zIndex: 30,
|
|
348
|
+
boxShadow: theme.isDarkMode
|
|
349
|
+
? `0 1px 3px ${theme.colors.shadow}`
|
|
350
|
+
: '0 1px 3px rgba(0,0,0,0.15)',
|
|
351
|
+
}}
|
|
352
|
+
title="Trigger Node - Starts workflow execution"
|
|
353
|
+
>
|
|
354
|
+
<span style={{ lineHeight: 1, color: theme.isDarkMode ? theme.colors.background : '#1a1d21' }}>⚡</span>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
{/* Output Handle (right side) */}
|
|
358
|
+
<Handle
|
|
359
|
+
id="output-main"
|
|
360
|
+
type="source"
|
|
361
|
+
position={Position.Right}
|
|
362
|
+
isConnectable={isConnectable}
|
|
363
|
+
style={{
|
|
364
|
+
position: 'absolute',
|
|
365
|
+
right: '-6px',
|
|
366
|
+
top: '50%',
|
|
367
|
+
transform: 'translateY(-50%)',
|
|
368
|
+
width: theme.nodeSize.handle,
|
|
369
|
+
height: theme.nodeSize.handle,
|
|
370
|
+
backgroundColor: isConfigured ? nodeColor : theme.colors.textSecondary,
|
|
371
|
+
border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
372
|
+
borderRadius: '50%',
|
|
373
|
+
zIndex: 20
|
|
374
|
+
}}
|
|
375
|
+
title="Trigger Output"
|
|
376
|
+
/>
|
|
377
|
+
|
|
378
|
+
{/* Output Data Indicator */}
|
|
379
|
+
{executionStatus === 'success' && nodeStatus?.data && (
|
|
380
|
+
<div
|
|
381
|
+
style={{
|
|
382
|
+
position: 'absolute',
|
|
383
|
+
bottom: '-4px',
|
|
384
|
+
right: '-4px',
|
|
385
|
+
width: theme.nodeSize.outputBadge,
|
|
386
|
+
height: theme.nodeSize.outputBadge,
|
|
387
|
+
borderRadius: theme.borderRadius.sm,
|
|
388
|
+
backgroundColor: theme.dracula.green,
|
|
389
|
+
border: `1px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
390
|
+
display: 'flex',
|
|
391
|
+
alignItems: 'center',
|
|
392
|
+
justifyContent: 'center',
|
|
393
|
+
fontSize: theme.fontSize.xs,
|
|
394
|
+
color: 'white',
|
|
395
|
+
fontWeight: 'bold',
|
|
396
|
+
zIndex: 30,
|
|
397
|
+
boxShadow: theme.isDarkMode
|
|
398
|
+
? '0 1px 3px rgba(0,0,0,0.2)'
|
|
399
|
+
: '0 1px 3px rgba(0,0,0,0.15)',
|
|
400
|
+
}}
|
|
401
|
+
title="Output data available - click node to view"
|
|
402
|
+
>
|
|
403
|
+
<span style={{ lineHeight: 1 }}>D</span>
|
|
404
|
+
</div>
|
|
405
|
+
)}
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
{/* Trigger Name Below Node */}
|
|
409
|
+
{isRenaming ? (
|
|
410
|
+
<input
|
|
411
|
+
ref={inputRef}
|
|
412
|
+
type="text"
|
|
413
|
+
value={editLabel}
|
|
414
|
+
onChange={(e) => setEditLabel(e.target.value)}
|
|
415
|
+
onKeyDown={(e) => {
|
|
416
|
+
if (e.key === 'Enter') {
|
|
417
|
+
handleSaveRename();
|
|
418
|
+
} else if (e.key === 'Escape') {
|
|
419
|
+
handleCancelRename();
|
|
420
|
+
}
|
|
421
|
+
e.stopPropagation();
|
|
422
|
+
}}
|
|
423
|
+
onBlur={handleSaveRename}
|
|
424
|
+
onClick={(e) => e.stopPropagation()}
|
|
425
|
+
style={{
|
|
426
|
+
marginTop: theme.spacing.sm,
|
|
427
|
+
width: '100%',
|
|
428
|
+
maxWidth: '120px',
|
|
429
|
+
padding: '2px 4px',
|
|
430
|
+
fontSize: theme.fontSize.sm,
|
|
431
|
+
fontWeight: theme.fontWeight.medium,
|
|
432
|
+
color: theme.colors.text,
|
|
433
|
+
backgroundColor: theme.colors.backgroundElevated,
|
|
434
|
+
border: `1px solid ${theme.dracula.purple}`,
|
|
435
|
+
borderRadius: theme.borderRadius.sm,
|
|
436
|
+
outline: 'none',
|
|
437
|
+
textAlign: 'center',
|
|
438
|
+
}}
|
|
439
|
+
/>
|
|
440
|
+
) : (
|
|
441
|
+
<div
|
|
442
|
+
onDoubleClick={handleLabelDoubleClick}
|
|
443
|
+
style={{
|
|
444
|
+
marginTop: theme.spacing.sm,
|
|
445
|
+
fontSize: theme.fontSize.sm,
|
|
446
|
+
fontWeight: theme.fontWeight.medium,
|
|
447
|
+
color: theme.colors.text,
|
|
448
|
+
lineHeight: '1.2',
|
|
449
|
+
textAlign: 'center',
|
|
450
|
+
maxWidth: '120px',
|
|
451
|
+
cursor: 'text',
|
|
452
|
+
}}
|
|
453
|
+
title="Double-click to rename"
|
|
454
|
+
>
|
|
455
|
+
{data?.label || definition?.displayName}
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
458
|
+
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export default TriggerNode;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login/Register Page with Dracula theme.
|
|
3
|
+
* Shows login form, or register form if registration is available.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
|
+
import { useAuth } from '../../contexts/AuthContext';
|
|
8
|
+
import { dracula } from '../../styles/theme';
|
|
9
|
+
|
|
10
|
+
const LoginPage: React.FC = () => {
|
|
11
|
+
const { login, register, canRegister, error, isLoading } = useAuth();
|
|
12
|
+
|
|
13
|
+
const [isRegistering, setIsRegistering] = useState(false);
|
|
14
|
+
const [email, setEmail] = useState('');
|
|
15
|
+
const [password, setPassword] = useState('');
|
|
16
|
+
const [displayName, setDisplayName] = useState('');
|
|
17
|
+
const [localError, setLocalError] = useState<string | null>(null);
|
|
18
|
+
|
|
19
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
setLocalError(null);
|
|
22
|
+
|
|
23
|
+
if (!email || !password) {
|
|
24
|
+
setLocalError('Email and password are required');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isRegistering) {
|
|
29
|
+
if (!displayName) {
|
|
30
|
+
setLocalError('Display name is required');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (password.length < 8) {
|
|
34
|
+
setLocalError('Password must be at least 8 characters');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await register(email, password, displayName);
|
|
38
|
+
} else {
|
|
39
|
+
await login(email, password);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const toggleMode = () => {
|
|
44
|
+
setIsRegistering(!isRegistering);
|
|
45
|
+
setLocalError(null);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const displayError = localError || error;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div style={styles.container}>
|
|
52
|
+
<div style={styles.card}>
|
|
53
|
+
{/* Logo/Title */}
|
|
54
|
+
<div style={styles.header}>
|
|
55
|
+
<h1 style={styles.title}>MachinaOs</h1>
|
|
56
|
+
<p style={styles.subtitle}>
|
|
57
|
+
{isRegistering ? 'Create your account' : 'Sign in to continue'}
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{/* Error Message */}
|
|
62
|
+
{displayError && (
|
|
63
|
+
<div style={styles.errorBox}>
|
|
64
|
+
{displayError}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{/* Form */}
|
|
69
|
+
<form onSubmit={handleSubmit} style={styles.form}>
|
|
70
|
+
{isRegistering && (
|
|
71
|
+
<div style={styles.inputGroup}>
|
|
72
|
+
<label style={styles.label}>Display Name</label>
|
|
73
|
+
<input
|
|
74
|
+
type="text"
|
|
75
|
+
value={displayName}
|
|
76
|
+
onChange={(e) => setDisplayName(e.target.value)}
|
|
77
|
+
style={styles.input}
|
|
78
|
+
placeholder="Your name"
|
|
79
|
+
disabled={isLoading}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<div style={styles.inputGroup}>
|
|
85
|
+
<label style={styles.label}>Email</label>
|
|
86
|
+
<input
|
|
87
|
+
type="email"
|
|
88
|
+
value={email}
|
|
89
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
90
|
+
style={styles.input}
|
|
91
|
+
placeholder="you@example.com"
|
|
92
|
+
disabled={isLoading}
|
|
93
|
+
autoComplete="email"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div style={styles.inputGroup}>
|
|
98
|
+
<label style={styles.label}>Password</label>
|
|
99
|
+
<input
|
|
100
|
+
type="password"
|
|
101
|
+
value={password}
|
|
102
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
103
|
+
style={styles.input}
|
|
104
|
+
placeholder={isRegistering ? 'At least 8 characters' : 'Your password'}
|
|
105
|
+
disabled={isLoading}
|
|
106
|
+
autoComplete={isRegistering ? 'new-password' : 'current-password'}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<button
|
|
111
|
+
type="submit"
|
|
112
|
+
style={{
|
|
113
|
+
...styles.submitButton,
|
|
114
|
+
opacity: isLoading ? 0.7 : 1,
|
|
115
|
+
cursor: isLoading ? 'not-allowed' : 'pointer'
|
|
116
|
+
}}
|
|
117
|
+
disabled={isLoading}
|
|
118
|
+
>
|
|
119
|
+
{isLoading
|
|
120
|
+
? 'Please wait...'
|
|
121
|
+
: isRegistering
|
|
122
|
+
? 'Create Account'
|
|
123
|
+
: 'Sign In'}
|
|
124
|
+
</button>
|
|
125
|
+
</form>
|
|
126
|
+
|
|
127
|
+
{/* Toggle Login/Register */}
|
|
128
|
+
{canRegister && (
|
|
129
|
+
<div style={styles.toggleSection}>
|
|
130
|
+
<span style={styles.toggleText}>
|
|
131
|
+
{isRegistering ? 'Already have an account?' : "Don't have an account?"}
|
|
132
|
+
</span>
|
|
133
|
+
<button
|
|
134
|
+
onClick={toggleMode}
|
|
135
|
+
style={styles.toggleButton}
|
|
136
|
+
disabled={isLoading}
|
|
137
|
+
>
|
|
138
|
+
{isRegistering ? 'Sign In' : 'Register'}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
148
|
+
container: {
|
|
149
|
+
display: 'flex',
|
|
150
|
+
justifyContent: 'center',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
minHeight: '100vh',
|
|
153
|
+
backgroundColor: dracula.background,
|
|
154
|
+
padding: 20,
|
|
155
|
+
},
|
|
156
|
+
card: {
|
|
157
|
+
backgroundColor: dracula.currentLine,
|
|
158
|
+
borderRadius: 12,
|
|
159
|
+
padding: 40,
|
|
160
|
+
width: '100%',
|
|
161
|
+
maxWidth: 400,
|
|
162
|
+
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
|
|
163
|
+
},
|
|
164
|
+
header: {
|
|
165
|
+
textAlign: 'center',
|
|
166
|
+
marginBottom: 24,
|
|
167
|
+
},
|
|
168
|
+
title: {
|
|
169
|
+
color: dracula.purple,
|
|
170
|
+
fontSize: 32,
|
|
171
|
+
fontWeight: 700,
|
|
172
|
+
margin: 0,
|
|
173
|
+
marginBottom: 8,
|
|
174
|
+
},
|
|
175
|
+
subtitle: {
|
|
176
|
+
color: dracula.comment,
|
|
177
|
+
fontSize: 14,
|
|
178
|
+
margin: 0,
|
|
179
|
+
},
|
|
180
|
+
errorBox: {
|
|
181
|
+
backgroundColor: `${dracula.red}20`,
|
|
182
|
+
border: `1px solid ${dracula.red}`,
|
|
183
|
+
borderRadius: 6,
|
|
184
|
+
padding: '12px 16px',
|
|
185
|
+
marginBottom: 20,
|
|
186
|
+
color: dracula.red,
|
|
187
|
+
fontSize: 13,
|
|
188
|
+
},
|
|
189
|
+
form: {
|
|
190
|
+
display: 'flex',
|
|
191
|
+
flexDirection: 'column',
|
|
192
|
+
gap: 16,
|
|
193
|
+
},
|
|
194
|
+
inputGroup: {
|
|
195
|
+
display: 'flex',
|
|
196
|
+
flexDirection: 'column',
|
|
197
|
+
gap: 6,
|
|
198
|
+
},
|
|
199
|
+
label: {
|
|
200
|
+
color: dracula.foreground,
|
|
201
|
+
fontSize: 13,
|
|
202
|
+
fontWeight: 500,
|
|
203
|
+
},
|
|
204
|
+
input: {
|
|
205
|
+
backgroundColor: dracula.background,
|
|
206
|
+
border: `1px solid ${dracula.comment}50`,
|
|
207
|
+
borderRadius: 6,
|
|
208
|
+
padding: '12px 14px',
|
|
209
|
+
fontSize: 14,
|
|
210
|
+
color: dracula.foreground,
|
|
211
|
+
outline: 'none',
|
|
212
|
+
transition: 'border-color 0.2s, box-shadow 0.2s',
|
|
213
|
+
},
|
|
214
|
+
submitButton: {
|
|
215
|
+
backgroundColor: dracula.purple,
|
|
216
|
+
color: dracula.background,
|
|
217
|
+
border: 'none',
|
|
218
|
+
borderRadius: 6,
|
|
219
|
+
padding: '14px 20px',
|
|
220
|
+
fontSize: 15,
|
|
221
|
+
fontWeight: 600,
|
|
222
|
+
marginTop: 8,
|
|
223
|
+
transition: 'opacity 0.2s, transform 0.1s',
|
|
224
|
+
},
|
|
225
|
+
toggleSection: {
|
|
226
|
+
textAlign: 'center',
|
|
227
|
+
marginTop: 24,
|
|
228
|
+
paddingTop: 20,
|
|
229
|
+
borderTop: `1px solid ${dracula.comment}30`,
|
|
230
|
+
},
|
|
231
|
+
toggleText: {
|
|
232
|
+
color: dracula.comment,
|
|
233
|
+
fontSize: 13,
|
|
234
|
+
marginRight: 8,
|
|
235
|
+
},
|
|
236
|
+
toggleButton: {
|
|
237
|
+
background: 'none',
|
|
238
|
+
border: 'none',
|
|
239
|
+
color: dracula.cyan,
|
|
240
|
+
fontSize: 13,
|
|
241
|
+
fontWeight: 500,
|
|
242
|
+
cursor: 'pointer',
|
|
243
|
+
padding: 0,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export default LoginPage;
|