From 462f891d9e35e34ac407c9d28beb8a64bbe246e9 Mon Sep 17 00:00:00 2001 From: Mael-RABOT Date: Mon, 2 Dec 2024 14:59:10 +0100 Subject: [PATCH] feat(dashboard): add workflow creation page Signed-off-by: Mael-RABOT # Conflicts: # client_web/src/App.tsx # client_web/src/Pages/Auth/Forms/Login.tsx # client_web/src/Pages/Auth/Forms/Register.tsx # client_web/src/Pages/Dashboard/Dashboard.tsx # client_web/tsconfig.json --- client_web/package-lock.json | 579 +++++++++++++++- client_web/package.json | 7 +- client_web/public/google-icon.png | Bin 0 -> 20865 bytes client_web/src/App.tsx | 170 ++++- .../Auth}/Buttons/DiscordAuth.tsx | 0 .../Components/Auth/Buttons/GoogleAuth.tsx | 54 ++ .../Auth}/Buttons/LinkedinAuth.tsx | 0 .../Auth}/Buttons/MicrosoftAuth.tsx | 0 .../Auth}/Buttons/SpotifyAuth.tsx | 0 .../auth => Components/Auth}/OAuthButtons.tsx | 2 +- .../Layout/Footer.tsx | 0 .../Layout/Header.tsx | 0 .../Layout/Layout.tsx | 0 client_web/src/Components/LinkButton.tsx | 33 + .../{components => Components}/Security.tsx | 0 client_web/src/Pages/Auth/Forms/Login.tsx | 7 +- client_web/src/Pages/Auth/Forms/Register.tsx | 8 +- client_web/src/Pages/Dashboard/Dashboard.tsx | 40 +- client_web/src/Pages/Home.tsx | 246 ++++--- client_web/src/Pages/NotFound.tsx | 9 +- .../src/Pages/Workflows/CreateWorkflow.tsx | 619 ++++++++++++++++++ .../Pages/Workflows/CreateWorkflow.utils.ts | 3 + .../src/Pages/Workflows/WorkflowsTable.tsx | 51 ++ .../components/auth/Buttons/GoogleAuth.tsx | 33 - client_web/src/types.ts | 71 ++ client_web/tsconfig.json | 7 + docs/source/client_web/index.rst | 7 +- docs/source/client_web/workflowCreation.rst | 52 ++ 28 files changed, 1802 insertions(+), 196 deletions(-) create mode 100644 client_web/public/google-icon.png rename client_web/src/{components/auth => Components/Auth}/Buttons/DiscordAuth.tsx (100%) create mode 100644 client_web/src/Components/Auth/Buttons/GoogleAuth.tsx rename client_web/src/{components/auth => Components/Auth}/Buttons/LinkedinAuth.tsx (100%) rename client_web/src/{components/auth => Components/Auth}/Buttons/MicrosoftAuth.tsx (100%) rename client_web/src/{components/auth => Components/Auth}/Buttons/SpotifyAuth.tsx (100%) rename client_web/src/{components/auth => Components/Auth}/OAuthButtons.tsx (95%) rename client_web/src/{components => Components}/Layout/Footer.tsx (100%) rename client_web/src/{components => Components}/Layout/Header.tsx (100%) rename client_web/src/{components => Components}/Layout/Layout.tsx (100%) create mode 100644 client_web/src/Components/LinkButton.tsx rename client_web/src/{components => Components}/Security.tsx (100%) create mode 100644 client_web/src/Pages/Workflows/CreateWorkflow.tsx create mode 100644 client_web/src/Pages/Workflows/CreateWorkflow.utils.ts create mode 100644 client_web/src/Pages/Workflows/WorkflowsTable.tsx delete mode 100644 client_web/src/components/auth/Buttons/GoogleAuth.tsx create mode 100644 client_web/src/types.ts create mode 100644 docs/source/client_web/workflowCreation.rst diff --git a/client_web/package-lock.json b/client_web/package-lock.json index 374542b..3b8bfca 100644 --- a/client_web/package-lock.json +++ b/client_web/package-lock.json @@ -10,6 +10,9 @@ "dependencies": { "@azure/msal-browser": "^3.27.0", "@react-oauth/google": "^0.12.1", + "@tsparticles/engine": "^3.7.1", + "@tsparticles/react": "^3.0.0", + "@tsparticles/slim": "^3.7.1", "@types/js-cookie": "^3.0.6", "antd": "^5.22.1", "axios": "^1.7.7", @@ -17,8 +20,10 @@ "framer-motion": "^11.11.17", "js-cookie": "^3.0.5", "react": "^18.3.1", + "react-color": "^2.19.3", "react-dom": "^18.3.1", - "react-router-dom": "^6.28.0" + "react-router-dom": "^6.28.0", + "react-toastify": "^10.0.6" }, "devDependencies": { "@eslint/js": "^9.13.0", @@ -1099,6 +1104,15 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -1636,6 +1650,470 @@ "node": ">=10" } }, + "node_modules/@tsparticles/basic": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-3.7.1.tgz", + "integrity": "sha512-oJMJ3qzYUROYaOEsaFXkVynxT2OTWBXbQ9MNc1bJi/bVc1VOU44VN7X/KmiZjD+w1U+Qalk6BeVvDRwpFshblw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1", + "@tsparticles/move-base": "3.7.1", + "@tsparticles/plugin-hex-color": "3.7.1", + "@tsparticles/plugin-hsl-color": "3.7.1", + "@tsparticles/plugin-rgb-color": "3.7.1", + "@tsparticles/shape-circle": "3.7.1", + "@tsparticles/updater-color": "3.7.1", + "@tsparticles/updater-opacity": "3.7.1", + "@tsparticles/updater-out-modes": "3.7.1", + "@tsparticles/updater-size": "3.7.1" + } + }, + "node_modules/@tsparticles/engine": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/engine/-/engine-3.7.1.tgz", + "integrity": "sha512-GYzBgq/oOE9YJdOL1++MoawWmYg4AvVct6CIrJGx84ZRb3U2owYmLsRGabYl0qX1CWWOvUG569043RJmyp/vQA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@tsparticles/interaction-external-attract": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-attract/-/interaction-external-attract-3.7.1.tgz", + "integrity": "sha512-cpnMsFJ7ZJNKccpQvskKvSs1ofknByHE6FGqbEb17ij7HqvbECQOCOVKHPFnYipHe14cXor/Cd+nVisRcTASoQ==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-bounce": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-bounce/-/interaction-external-bounce-3.7.1.tgz", + "integrity": "sha512-npvU9Qt6WDonjezHqi+hWM44ga2Oh5yXdr8eSoJpvuHZrCP7rIdRSz5XseHouO1bMS9DbXk86sx4qwrhB5w58w==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-bubble": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-bubble/-/interaction-external-bubble-3.7.1.tgz", + "integrity": "sha512-WdbYL46lMfuf2g5kfVB1hhhxRBtEXDvnwz8PJwLKurSThL/27bqsqysyXsMzXtLByXUneGhtJTj4D5I5RYdgjA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-connect": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-connect/-/interaction-external-connect-3.7.1.tgz", + "integrity": "sha512-hqx0ANIbjLIz/nxmk0LvqANBiNLLmVybbCA7N+xDHtEORvpKjNlKEvMz6Razocl6vRjoHZ/olSwcxIG84dh/cg==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-grab": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-grab/-/interaction-external-grab-3.7.1.tgz", + "integrity": "sha512-JMYpFW+7YvkpK5MYlt4Ec3Gwb5ZxS7RLVL8IRUSd5yJOw25husPTYg+FQywxrt5WhKe+tPsCAYo+uGIbTTHi9w==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-pause": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-pause/-/interaction-external-pause-3.7.1.tgz", + "integrity": "sha512-Kkp+7sCe24hawH0XvS1V6UCCuHfMvpLK7oseqSam9Gt4SyGrFvaqIXxkjXhRhn9MysJyKFPBV4/dtBM1HR9p6A==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-push": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-push/-/interaction-external-push-3.7.1.tgz", + "integrity": "sha512-4VoaR5jvXgQdB7irtq4uSZYr5c+D6TBTVEnLVpBfJhUs6jhw6mgN5g7yp5izIYkK0AlcO431MHn8dvJacvRLDw==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-remove": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-remove/-/interaction-external-remove-3.7.1.tgz", + "integrity": "sha512-FRBW7U7zD5MkO6/b7e8iSMk/UTtRLY2XiIVFZNsKri3Re3yPpvZzzd5tl2YlYGQlg1Xc+K8SJYMQQA3PtgQ/Tg==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-repulse": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-repulse/-/interaction-external-repulse-3.7.1.tgz", + "integrity": "sha512-mwM06dVmg2FEvHMQsPOfRBQWACbjf3qnelODkqI9DSVxQ0B8DESP4BYNXyraFGYv00YiPzRv5Xy/uejHdbsQUA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-external-slow": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-slow/-/interaction-external-slow-3.7.1.tgz", + "integrity": "sha512-CfCAs3kUQC3pLOj0dbzn5AolQyBHgjxORLdfnYBhepvFV1BXB+4ytChRfXBzjykBPI6U+rCnw5Fk/vVjAroSFA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-particles-attract": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-attract/-/interaction-particles-attract-3.7.1.tgz", + "integrity": "sha512-UABbBORKaiItAT8vR0t4ye2H3VE6/Ah4zcylBlnq0Jd5yDkyP4rnkwhicaY6y4Zlfwyx+0PWdAC4f/ziFAyObg==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-particles-collisions": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-collisions/-/interaction-particles-collisions-3.7.1.tgz", + "integrity": "sha512-0GY9++Gn2KXViyeifXWkH7a2UO5+uRwyS1rDeTN8eleyiq2j9zQf4xztZEIft8T0hTetq2rkWxQ92j2kev6NVA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/interaction-particles-links": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-links/-/interaction-particles-links-3.7.1.tgz", + "integrity": "sha512-BxCXAAOBNmEvlyOQzwprryW8YdtMIop2v4kgSCff5MCtDwYWoQIfzaQlWbBAkD9ey6BoF8iMjhBUaY1MnDecTA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/move-base": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/move-base/-/move-base-3.7.1.tgz", + "integrity": "sha512-LPtMHwJHhzwfRIcSAk814fY9NcRiICwaEbapaJSYyP1DwscSXqOWoyAEWwzV9hMgAcPdsED6nGeg8RCXGm58lw==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/move-parallax": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/move-parallax/-/move-parallax-3.7.1.tgz", + "integrity": "sha512-B40azo6EJyMdI+kmIxpqWDaObPwODTYLDCikzkZ73n5tS6OhFUlkz81Scfo+g1iGTdryKFygUKhVGcG1EFuA5g==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/plugin-easing-quad": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-easing-quad/-/plugin-easing-quad-3.7.1.tgz", + "integrity": "sha512-nSwKCRe6C/noCi3dyZlm1GiQGask0aXdWDuS36b82iwzwQ01cBTXeXR25mLr4fsfMLFfYAZXyBxEMMpw3rkSiw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/plugin-hex-color": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-hex-color/-/plugin-hex-color-3.7.1.tgz", + "integrity": "sha512-7xu3MV8EdNNejjYyEmrq5fCDdYAcqz/9VatLpnwtwR5Q5t2qI0tD4CrcGaFfC/rTAVJacfiJe02UV/hlj03KKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/plugin-hsl-color": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-hsl-color/-/plugin-hsl-color-3.7.1.tgz", + "integrity": "sha512-zzAI1CuoCMBJhgeYZ5Rq42nGbPg35ZzIs11eQegjsWG5Msm5QKSj60qPzERnoUcCc4HCKtIWP7rYMz6h3xpoEg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/plugin-rgb-color": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-rgb-color/-/plugin-rgb-color-3.7.1.tgz", + "integrity": "sha512-taEraTpCYR6jpjflqBL95tN0zFU8JrAChXTt8mxVn7gddxoNMHI/LGymEPRCweLukwV6GQyAGOkeGEdWDPtYTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/react": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tsparticles/react/-/react-3.0.0.tgz", + "integrity": "sha512-hjGEtTT1cwv6BcjL+GcVgH++KYs52bIuQGW3PWv7z3tMa8g0bd6RI/vWSLj7p//NZ3uTjEIeilYIUPBh7Jfq/Q==", + "peerDependencies": { + "@tsparticles/engine": "^3.0.2", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@tsparticles/shape-circle": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-circle/-/shape-circle-3.7.1.tgz", + "integrity": "sha512-kmOWaUuFwuTtcCFYjuyJbdA5qDqWdGsharLalYnIczkLu2c1I8jJo/OmGePKhWn62ocu7mqKMomfElkIcw2AsA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/shape-emoji": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-emoji/-/shape-emoji-3.7.1.tgz", + "integrity": "sha512-mX18c/xhYVljS/r5Xbowzclw+1YwhtWoQFOOfkmjjZppA+RjgcwSKLvH6E20PaH1yVTjBOfSF+3POKpwsULzTg==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/shape-image": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-image/-/shape-image-3.7.1.tgz", + "integrity": "sha512-eDzfkQhqLY6fb9QH2Vo9TGfdJBFFpYnojhxQxc7IdzIwOFMD3JK4B52RVl9oowR+rNE8dNp6P2L+eMAF4yld0g==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/shape-line": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-line/-/shape-line-3.7.1.tgz", + "integrity": "sha512-lMPYApUpg7avxmYPfHHr4dQepZSNn/g0Q1/g2+lnTi8ZtUBiCZ2WMVy9R3GOzyozbnzigLQ6AJRnOpsUZV7H4g==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/shape-polygon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-polygon/-/shape-polygon-3.7.1.tgz", + "integrity": "sha512-5FrRfpYC3qnvV2nXBLE4Q0v+SMNWJO8xgzh6MBFwfptvqH4EOrqc/58eS5x0jlf+evwf9LjPgeGkOTcwaHHcYQ==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/shape-square": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-square/-/shape-square-3.7.1.tgz", + "integrity": "sha512-7VCqbRwinjBZ+Ryme27rOtl+jKrET8qDthqZLrAoj3WONBqyt+R9q6SXAJ9WodqEX68IBvcluqbFY5qDZm8iAQ==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/shape-star": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-star/-/shape-star-3.7.1.tgz", + "integrity": "sha512-3G4oipioyWKLEQYT11Sx3k6AObu3dbv/A5LRqGGTQm5IR6UACa+INwykZYI0a+MdJJMb83E0e4Fn3hlZbi0/8w==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/slim": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/slim/-/slim-3.7.1.tgz", + "integrity": "sha512-OtJEhud2KleX7OxiG2r/VYriHNIwTpFm3sPFy4EOJzAD0EW7KZoKXGpGn5gwGI1NWeB0jso92yNTrTC2ZTW0qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/basic": "3.7.1", + "@tsparticles/engine": "3.7.1", + "@tsparticles/interaction-external-attract": "3.7.1", + "@tsparticles/interaction-external-bounce": "3.7.1", + "@tsparticles/interaction-external-bubble": "3.7.1", + "@tsparticles/interaction-external-connect": "3.7.1", + "@tsparticles/interaction-external-grab": "3.7.1", + "@tsparticles/interaction-external-pause": "3.7.1", + "@tsparticles/interaction-external-push": "3.7.1", + "@tsparticles/interaction-external-remove": "3.7.1", + "@tsparticles/interaction-external-repulse": "3.7.1", + "@tsparticles/interaction-external-slow": "3.7.1", + "@tsparticles/interaction-particles-attract": "3.7.1", + "@tsparticles/interaction-particles-collisions": "3.7.1", + "@tsparticles/interaction-particles-links": "3.7.1", + "@tsparticles/move-parallax": "3.7.1", + "@tsparticles/plugin-easing-quad": "3.7.1", + "@tsparticles/shape-emoji": "3.7.1", + "@tsparticles/shape-image": "3.7.1", + "@tsparticles/shape-line": "3.7.1", + "@tsparticles/shape-polygon": "3.7.1", + "@tsparticles/shape-square": "3.7.1", + "@tsparticles/shape-star": "3.7.1", + "@tsparticles/updater-life": "3.7.1", + "@tsparticles/updater-rotate": "3.7.1", + "@tsparticles/updater-stroke-color": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-color": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-color/-/updater-color-3.7.1.tgz", + "integrity": "sha512-QimV3yn17dcdJx7PpTwLtw9BhkQ0q8qFF035OdcZpnynBPAO/hg0zvSMpMGoeuDVFH02wWBy4h2/BYCv6wh6Sw==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-life": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-life/-/updater-life-3.7.1.tgz", + "integrity": "sha512-NY5gUrgO5AsARNC0usP9PKahXf7JCxbP/H1vzTfA0SJw4veANfWTldOvhIlcm2CHVP5P1b827p0hWsBHECwz7A==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-opacity": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-opacity/-/updater-opacity-3.7.1.tgz", + "integrity": "sha512-YcyviCooTv7SAKw7sxd84CfJqZ7dYPSdYZzCpedV6TKIObRiwLqXlyLXQGJ3YltghKQSCSolmVy8woWBCDm1qA==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-out-modes": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-out-modes/-/updater-out-modes-3.7.1.tgz", + "integrity": "sha512-Cb5sWquRtUYLSiFpmBjjYKRdpNV52diCo9+qMtK1oVlldDBhUwqO+1TQjdlaA2yl5DURlY9ZfOHXvY+IT7CHCw==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-rotate": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-rotate/-/updater-rotate-3.7.1.tgz", + "integrity": "sha512-toVHwl+h6SvtA8dyxSA2kMH2QdDA71vehuAa+HoRqf1y06h5kxyYiMKZFHCqDJ6lFfRPs47MjrC9dD2bDz14MQ==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-size": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-size/-/updater-size-3.7.1.tgz", + "integrity": "sha512-+Y0H0PnDJVIsJ+zHTyubYu1jtRFmVnY1dAv3VCjScIDw6bcpL/ol+HrtHTGIX0WbMyUfjCyALfAoaXi/Wm8VcQ==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, + "node_modules/@tsparticles/updater-stroke-color": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-stroke-color/-/updater-stroke-color-3.7.1.tgz", + "integrity": "sha512-VHhQkCNuxjx/Hy7A+g0Yijb24T0+wQ3jNsF/yfrR9dEdZWSBiimZLvV1bilPdAeEtieAJTAZo2VNhcD1snF0iQ==", + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "3.7.1" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2376,6 +2854,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3664,6 +4151,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3717,6 +4216,12 @@ "node": ">=10" } }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", + "license": "ISC" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3838,6 +4343,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -4028,6 +4542,23 @@ "node": ">=0.4.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4706,6 +5237,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "license": "MIT", + "dependencies": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -4767,6 +5316,28 @@ "react-dom": ">=16.8" } }, + "node_modules/react-toastify": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", + "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.0.1" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -5062,6 +5633,12 @@ "node": ">=12.22" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/client_web/package.json b/client_web/package.json index 80fa366..8013ac0 100644 --- a/client_web/package.json +++ b/client_web/package.json @@ -16,6 +16,9 @@ "dependencies": { "@azure/msal-browser": "^3.27.0", "@react-oauth/google": "^0.12.1", + "@tsparticles/engine": "^3.7.1", + "@tsparticles/react": "^3.0.0", + "@tsparticles/slim": "^3.7.1", "@types/js-cookie": "^3.0.6", "antd": "^5.22.1", "axios": "^1.7.7", @@ -23,8 +26,10 @@ "framer-motion": "^11.11.17", "js-cookie": "^3.0.5", "react": "^18.3.1", + "react-color": "^2.19.3", "react-dom": "^18.3.1", - "react-router-dom": "^6.28.0" + "react-router-dom": "^6.28.0", + "react-toastify": "^10.0.6" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/client_web/public/google-icon.png b/client_web/public/google-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..494acede0c520f847f75982ca1f671fc6eaa889e GIT binary patch literal 20865 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 G$o~QRsPyvy literal 0 HcmV?d00001 diff --git a/client_web/src/App.tsx b/client_web/src/App.tsx index a0133ef..c4bd551 100644 --- a/client_web/src/App.tsx +++ b/client_web/src/App.tsx @@ -1,10 +1,22 @@ import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { ContextManager } from "./Context/ContextManager"; -import { uri } from './Config/uri'; +// @ts-ignore +import { uri } from '@Config/uri'; + +import { useEffect, useMemo, useState } from "react"; +import Particles, { initParticlesEngine } from "@tsparticles/react"; +import { + type Container, + type ISourceOptions, + MoveDirection, + OutMode, +} from "@tsparticles/engine"; +import { loadSlim } from "@tsparticles/slim"; import Home from './Pages/Home'; import NotFound from './Pages/NotFound'; -import Layout from './components/Layout/Layout'; +// @ts-ignore +import Layout from '@/Components/Layout/Layout'; import Login from './Pages/Auth/Forms/Login'; import Register from './Pages/Auth/Forms/Register'; @@ -13,27 +25,147 @@ import SpotifyCallback from './Pages/Auth/Callback/SpotifyCallback'; import MicrosoftCallback from './Pages/Auth/Callback/MicrosoftCallback'; import DiscordCallback from './Pages/Auth/Callback/DiscordCallback'; +import CreateWorkflow from "./Pages/Workflows/CreateWorkflow"; +import WorkflowsTable from "./Pages/Workflows/WorkflowsTable"; + import Dashboard from './Pages/Dashboard/Dashboard'; +import { ToastContainer } from 'react-toastify'; +// @ts-ignore +import 'react-toastify/dist/ReactToastify.css'; + const App = () => { + const [init, setInit] = useState(false); + const [backgroundColor, setBackgroundColor] = useState("#FFA500"); + + useEffect(() => { + initParticlesEngine(async (engine) => { + await loadSlim(engine); + }).then(() => { + setInit(true); + }); + setBackgroundColor(sessionStorage.getItem("backgroundColor") || "#FFA500"); + }, []); + + const particlesLoaded = async (container?: Container): Promise => { + console.log(container); + }; + + const options: ISourceOptions = useMemo( + () => ({ + background: { + color: { + value: backgroundColor, + }, + }, + fpsLimit: 60, + interactivity: { + events: { + onClick: { + enable: true, + mode: "push", + }, + onHover: { + enable: true, + mode: "repulse", + }, + }, + modes: { + push: { + quantity: 1, + }, + repulse: { + distance: 200, + duration: 0.4, + }, + }, + }, + particles: { + color: { + value: "#ffffff", + }, + links: { + color: "#ffffff", + distance: 150, + enable: true, + opacity: 0.5, + width: 1, + }, + move: { + direction: MoveDirection.none, + enable: true, + outModes: { + default: OutMode.out, + }, + random: false, + speed: 6, + straight: false, + }, + number: { + density: { + enable: true, + }, + value: 80, + }, + opacity: { + value: 0.5, + }, + shape: { + type: "circle", + }, + size: { + value: { min: 1, max: 5 }, + }, + }, + detectRetina: true, + }), + [backgroundColor], + ); + return ( - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - + <> + + {init && ( + + )} + + + + + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + + + + + ); }; diff --git a/client_web/src/components/auth/Buttons/DiscordAuth.tsx b/client_web/src/Components/Auth/Buttons/DiscordAuth.tsx similarity index 100% rename from client_web/src/components/auth/Buttons/DiscordAuth.tsx rename to client_web/src/Components/Auth/Buttons/DiscordAuth.tsx diff --git a/client_web/src/Components/Auth/Buttons/GoogleAuth.tsx b/client_web/src/Components/Auth/Buttons/GoogleAuth.tsx new file mode 100644 index 0000000..ae8b42e --- /dev/null +++ b/client_web/src/Components/Auth/Buttons/GoogleAuth.tsx @@ -0,0 +1,54 @@ +import { Form, Button } from 'antd'; +import { GoogleOAuthProvider } from '@react-oauth/google'; +// @ts-ignore +import { uri } from '@Config/uri'; + +interface GoogleAuthProps { + onSuccess: (response: unknown) => void; + onError: () => void; + buttonText: string; +} + +const GoogleAuth = ({ onSuccess, onError, buttonText }: GoogleAuthProps) => { + if (!uri.google.auth.clientId || uri.google.auth.clientId === '') { + return null; + } + + const handleGoogleLogin = () => { + // Initialize the Google Sign-In client + let google: any; + const client = google.accounts.oauth2.initCodeClient({ + client_id: uri.google.auth.clientId, + scope: 'email profile', // TODO: Add other scopes as needed + callback: (response: unknown) => { + // @ts-ignore + if (response?.code) { + onSuccess(response); + } else { + onError(); + } + }, + }); + client.requestCode(); + }; + + return ( + + + + + + ); +}; + +export default GoogleAuth; diff --git a/client_web/src/components/auth/Buttons/LinkedinAuth.tsx b/client_web/src/Components/Auth/Buttons/LinkedinAuth.tsx similarity index 100% rename from client_web/src/components/auth/Buttons/LinkedinAuth.tsx rename to client_web/src/Components/Auth/Buttons/LinkedinAuth.tsx diff --git a/client_web/src/components/auth/Buttons/MicrosoftAuth.tsx b/client_web/src/Components/Auth/Buttons/MicrosoftAuth.tsx similarity index 100% rename from client_web/src/components/auth/Buttons/MicrosoftAuth.tsx rename to client_web/src/Components/Auth/Buttons/MicrosoftAuth.tsx diff --git a/client_web/src/components/auth/Buttons/SpotifyAuth.tsx b/client_web/src/Components/Auth/Buttons/SpotifyAuth.tsx similarity index 100% rename from client_web/src/components/auth/Buttons/SpotifyAuth.tsx rename to client_web/src/Components/Auth/Buttons/SpotifyAuth.tsx diff --git a/client_web/src/components/auth/OAuthButtons.tsx b/client_web/src/Components/Auth/OAuthButtons.tsx similarity index 95% rename from client_web/src/components/auth/OAuthButtons.tsx rename to client_web/src/Components/Auth/OAuthButtons.tsx index 7c92f22..b6379e4 100644 --- a/client_web/src/components/auth/OAuthButtons.tsx +++ b/client_web/src/Components/Auth/OAuthButtons.tsx @@ -31,7 +31,7 @@ const OAuthButtons = ({ = ({ text, url, type = "primary", style = {}, goBack = false }) => { + const navigate = useNavigate(); + + const handleClick = () => { + if (goBack) { + navigate(-1); + } else if (url) { + navigate(url); + } + }; + + const buttonStyle = type === "danger" ? { ...style, backgroundColor: 'red', borderColor: 'red', color: 'white' } : style; + + return ( + + ); +}; + +export default LinkButton; diff --git a/client_web/src/components/Security.tsx b/client_web/src/Components/Security.tsx similarity index 100% rename from client_web/src/components/Security.tsx rename to client_web/src/Components/Security.tsx diff --git a/client_web/src/Pages/Auth/Forms/Login.tsx b/client_web/src/Pages/Auth/Forms/Login.tsx index 3f152ec..b7508c6 100644 --- a/client_web/src/Pages/Auth/Forms/Login.tsx +++ b/client_web/src/Pages/Auth/Forms/Login.tsx @@ -1,6 +1,6 @@ import {Form, Input, Button, Card } from 'antd'; import { Link } from 'react-router-dom'; -import OAuthButtons from '../../../components/auth/OAuthButtons'; +import OAuthButtons from '../../../Components/Auth/OAuthButtons'; const Login = () => { const onFinish = (values: { email: string, password: string }) => { @@ -41,9 +41,6 @@ const Login = () => { return (
{ width: '100%', top: 0, left: 0 - }}> + }} role="main">
{ const onFinish = (values: unknown) => { @@ -41,9 +42,6 @@ const Register = () => { return (
{ width: '100%', top: 0, left: 0 - }}> + }} role="main"> { return ( -
- {/* Header */} - + <div style={{padding: 24, position: 'relative', zIndex: 1}} role="main"> + <Title level={3} style={{marginBottom: 16}}> Dashboard {/* Stats Cards */} - + - - - 12 + + + {/* TODO: Replace X with the number of active automations */} + X Active Automations - - - 1,234 + + + {/* TODO: Replace X with the number of tasks completed */} + X Tasks Completed - - - 5 + + + {/* TODO: Replace X with the number of pending updates */} + X Pending Updates @@ -48,13 +51,16 @@ const Dashboard: FC = () => { {/* Main Content Area */} - + {/* Activity content would go here */} - - {/* Quick actions content would go here */} + + + + + diff --git a/client_web/src/Pages/Home.tsx b/client_web/src/Pages/Home.tsx index b39954c..2426eb8 100644 --- a/client_web/src/Pages/Home.tsx +++ b/client_web/src/Pages/Home.tsx @@ -1,120 +1,154 @@ -import { Layout, Typography, Row, Col, Button, Card } from 'antd'; +import React, { useState } from 'react'; +import { Layout, Typography, Row, Col, Button, Card, Space, Modal } from 'antd'; import { ThunderboltOutlined, ApiOutlined, SafetyCertificateOutlined } from '@ant-design/icons'; import { motion } from 'framer-motion'; -import { Link } from 'react-router-dom'; +// @ts-ignore +import { BlockPicker } from 'react-color'; +import LinkButton from "@/Components/LinkButton"; const { Content } = Layout; const { Title, Paragraph } = Typography; -const Home = () => { - return ( - - {/* Hero Section */} - - - - - Connect Your Digital World - - - Automate your life by connecting your favorite services. Create powerful automation flows with just a few clicks. - - - - - - - Automation Illustration - - - +interface HomeProps { + backgroundColor: string; + setBackgroundColor: (color: string) => void; +} + +const Home: React.FC = ({ backgroundColor, setBackgroundColor }) => { + const [isModalVisible, setIsModalVisible] = useState(false); + const [tempColor, setTempColor] = useState(backgroundColor); + + const handleColorChange = (color: { hex: any; }) => { + setTempColor(color.hex); + }; + + const showModal = () => { + setTempColor(backgroundColor); + setIsModalVisible(true); + }; + + const handleOk = () => { + setBackgroundColor(tempColor); + sessionStorage.setItem('backgroundColor', tempColor); + setIsModalVisible(false); + }; - {/* Features Section */} - - - - - - - Easy Automation - - Create powerful automation workflows with our intuitive drag-and-drop interface. - No coding required! - - - - - - - - - Connect Services - - Integrate with popular services and apps. Make them work together seamlessly. - - - - - - - - - Secure & Reliable - - Your data is protected with enterprise-grade security. Run your automations with confidence. - - - - - - + const handleCancel = () => { + setIsModalVisible(false); + }; - {/* How It Works Section */} -
-
- - How It Works - + return ( + + + + + + Connect Your Digital World + + + Automate your life by connecting your favorite services. Create powerful automation flows with just a few clicks. + + + + + + + Automation Illustration + + + + + - {[ - { step: '1', title: 'Choose a Trigger', description: 'Select an event that starts your automation' }, - { step: '2', title: 'Add Actions', description: 'Define what happens when the trigger fires' }, - { step: '3', title: 'Watch It Work', description: 'Sit back and let Area handle the rest' }, - ].map((item) => ( - - - - {item.step} - - {item.title} - {item.description} - - - ))} + + + + + Easy Automation + + Create powerful automation workflows with our intuitive drag-and-drop interface. + No coding required! + + + + + + + + + Connect Services + + Integrate with popular services and apps. Make them work together seamlessly. + + + + + + + + + Secure & Reliable + + Your data is protected with enterprise-grade security. Run your automations with confidence. + + + + + + +
+
+ + How It Works + + + {[ + { step: '1', title: 'Choose a Trigger', description: 'Select an event that starts your automation' }, + { step: '2', title: 'Add Actions', description: 'Define what happens when the trigger fires' }, + { step: '3', title: 'Watch It Work', description: 'Sit back and let Area handle the rest' }, + ].map((item) => ( + + + + + {item.step} + + {item.title} + {item.description} + + + + ))} + +
+
+ +
-
- + + + + ); }; diff --git a/client_web/src/Pages/NotFound.tsx b/client_web/src/Pages/NotFound.tsx index 9cf1700..a66e131 100644 --- a/client_web/src/Pages/NotFound.tsx +++ b/client_web/src/Pages/NotFound.tsx @@ -1,5 +1,6 @@ -import { Result, Button } from 'antd'; -import { Link } from 'react-router-dom'; +import { Result } from 'antd'; +// @ts-ignore +import LinkButton from "@/Components/LinkButton"; import React from 'react'; const NotFound: React.FC = () => { @@ -9,9 +10,7 @@ const NotFound: React.FC = () => { title="404" subTitle="Sorry, the page you visited does not exist." extra={ - + } /> ); diff --git a/client_web/src/Pages/Workflows/CreateWorkflow.tsx b/client_web/src/Pages/Workflows/CreateWorkflow.tsx new file mode 100644 index 0000000..7ac45a2 --- /dev/null +++ b/client_web/src/Pages/Workflows/CreateWorkflow.tsx @@ -0,0 +1,619 @@ +import React, { useState } from "react"; +import { Button, Row, Typography, Spin, Space, Card, List, Form, Input, Col, Collapse } from "antd"; +import Security from "@/Components/Security"; +import LinkButton from "@/Components/LinkButton"; +import { normalizeName } from "@/Pages/Workflows/CreateWorkflow.utils"; + +// Import types correctly +import { About, Service, Action, Reaction, Workflow, Parameter } from "@/types"; +import {toast} from "react-toastify"; + +const { Title, Text } = Typography; +const { Panel } = Collapse; + +interface SelectedAction extends Action { + id: string; +} + +interface SelectedReaction extends Reaction { + id: string; +} + +const _data: About = { // Fixtures + client: { + host: "10.101.53.35" + }, + server: { + current_time: 1531680780, + services: [ + { + name: "facebook", + actions: [ + { + name: "new_message_in_group_w/o", + description: "A new message is posted in the group", + parameters: [] + }, + { + name: "new_message_inbox", + description: "A new private message is received by the user", + parameters: [ + { name: "message_id", description: "ID of the message", type: "number" } + ] + }, + { + name: "new_like_w/o", + description: "The user gains a like from one of their messages", + parameters: [] + } + ], + reactions: [ + { + name: "like_message", + description: "The user likes a message", + parameters: [ + { name: "message_id", description: "ID of the message to like", type: "string" } + ] + } + ] + }, + { + name: "twitter", + actions: [ + { + name: "new_tweet", + description: "A new tweet is posted", + parameters: [ + { name: "tweet_id", description: "ID of the tweet", type: "string" } + ] + }, + { + name: "new_follower_w/o", + description: "The user gains a new follower", + parameters: [] + } + ], + reactions: [ + { + name: "retweet", + description: "The user retweets a tweet", + parameters: [ + { name: "tweet_id", description: "ID of the tweet to retweet", type: "string" } + ] + }, + { + name: "like_tweet_w/o", + description: "The user likes a tweet", + parameters: [] + } + ] + }, + { + name: "github", + actions: [ + { + name: "new_issue", + description: "A new issue is created in a repository", + parameters: [ + { name: "issue_id", description: "ID of the issue", type: "string" } + ] + }, + { + name: "new_pull_request_w/o", + description: "A new pull request is created in a repository", + parameters: [] + } + ], + reactions: [ + { + name: "create_issue", + description: "The user creates a new issue", + parameters: [ + { name: "repository", description: "Name of the repository", type: "string" }, + { name: "title", description: "Title of the issue", type: "string" } + ] + }, + { + name: "merge_pull_request_w/o", + description: "The user merges a pull request", + parameters: [] + } + ] + }, + { + name: "slack", + actions: [ + { + name: "new_message", + description: "A new message is posted in a channel", + parameters: [ + { name: "channel_id", description: "ID of the channel", type: "string" }, + { name: "message_text", description: "Text of the message", type: "string" } + ] + }, + { + name: "new_reaction_w/o", + description: "A new reaction is added to a message", + parameters: [] + } + ], + reactions: [ + { + name: "send_message", + description: "The user sends a message to a channel", + parameters: [ + { name: "channel_id", description: "ID of the channel", type: "string" }, + { name: "message_text", description: "Text of the message", type: "string" } + ] + }, + { + name: "add_reaction_w/o", + description: "The user adds a reaction to a message", + parameters: [] + } + ] + } + ] + } +}; + +const CreateWorkflow: React.FC = () => { + const [loading, setLoading] = React.useState(false); + const [about, setAbout] = React.useState(null); + const [selectedActions, setSelectedActions] = React.useState([]); + const [selectedReactions, setSelectedReactions] = React.useState([]); + const [workflowName, setWorkflowName] = React.useState(''); + const [workflowDescription, setWorkflowDescription] = React.useState(''); + const [workflowNameTouched, setWorkflowNameTouched] = React.useState(false); + const [activeActionKeys, setActiveActionKeys] = useState([]); + const [activeReactionKeys, setActiveReactionKeys] = useState([]); + + React.useEffect(() => { + setLoading(true); + setAbout(_data); // TODO: Fetch workflow from the server + setLoading(false); + }, []); + + const toggleAction = (action: Action) => { + const parameters = action.parameters?.length + ? action.parameters.reduce((acc: Record, param: Parameter) => ({...acc, [param.name]: ''}), {}) + : undefined; + + // @ts-ignore + setSelectedActions(prev => [ + ...prev, + { + id: `${action.name}-${Date.now()}`, + name: action.name, + description: action.description, + parameters + } + ]); + }; + + const toggleReaction = (reaction: Reaction) => { + const parameters = reaction.parameters?.length + ? reaction.parameters.reduce((acc: Record, param: Parameter) => ({...acc, [param.name]: ''}), {}) + : undefined; + + // @ts-ignore + setSelectedReactions(prev => [ + ...prev, + { + id: `${reaction.name}-${Date.now()}`, + name: reaction.name, + description: reaction.description, + parameters + } + ]); + }; + + const areAllParametersFilled = () => { + const actionsComplete = selectedActions.every(action => { + if (!action.parameters) return true; + // @ts-ignore + return Object.values(action.parameters).every(value => value !== ''); + }); + + const reactionsComplete = selectedReactions.every(reaction => { + if (!reaction.parameters) return true; + // @ts-ignore + return Object.values(reaction.parameters).every(value => value !== ''); + }); + + return actionsComplete && reactionsComplete; + }; + + const handleCreateWorkflow = () => { + const workflow: Workflow = { + name: workflowName, + description: workflowDescription, + actions: selectedActions.map(action => { + const actionDef = about?.server.services + .flatMap((s: Service) => s.actions) + .find((a: Action) => a.name === action.name); + + return { + name: action.name, + parameters: Object.entries(action.parameters || {}).map(([name, value]) => { + const paramDef = actionDef?.parameters.find((p: Parameter) => p.name === name); + return { + name, + type: paramDef?.type || 'string', + value + }; + }) + }; + }), + reactions: selectedReactions.map(reaction => { + const reactionDef = about?.server.services + .flatMap((s: Service) => s.reactions) + .find((r: Reaction) => r.name === reaction.name); + + return { + name: reaction.name, + parameters: Object.entries(reaction.parameters || {}).map(([name, value]) => { + const paramDef = reactionDef?.parameters.find((p: Parameter) => p.name === name); + return { + name, + type: paramDef?.type || 'string', + value + }; + }) + }; + }) + }; + + console.log('Create Workflow', workflow); + toast.error("API not connected yet"); + }; + + const handleFoldAllActions = () => { + setActiveActionKeys([]); + }; + + const handleUnfoldAllActions = () => { + if (about) { + setActiveActionKeys(about.server.services.map((service: Service) => service.name)); + } + }; + + const handleFoldAllReactions = () => { + setActiveReactionKeys([]); + }; + + const handleUnfoldAllReactions = () => { + if (about) { + setActiveReactionKeys(about.server.services.map((service: Service) => service.name)); + } + }; + + return ( + +
+ + Create Workflow + + + {loading ? ( + + ) : ( + <> + + + + + + setWorkflowName(e.target.value)} + onBlur={() => setWorkflowNameTouched(true)} + aria-required="true" + /> + + + setWorkflowDescription(e.target.value)} + /> + + + + + + + + + + + + + + + {about?.server?.services.map((service: Service) => ( + + ( + toggleAction(action)} + style={{ + cursor: 'pointer', + backgroundColor: selectedActions.some(a => a.name === action.name) + ? '#e6f7ff' + : 'transparent' + }} + > + + {service.name} +
+ {action.description} + + } + /> +
+ )} + /> +
+ ))} +
+
+ + + + +
+ {selectedActions.length > 0 && ( + + When: + + {selectedActions.map(action => ( + { + e.stopPropagation(); + setSelectedActions(prev => prev.filter(a => a.id !== action.id)); + }} + > + × + + } + > + + {normalizeName(action.name)} + {action.parameters && Object.entries(action.parameters).map(([key, value]) => { + const paramDef = about?.server.services + .flatMap((s: any) => s.actions) + .find((a: any) => a.name === action.name) + ?.parameters + .find((p: any) => p.name === key); + + return ( + + {paramDef?.type === 'number' ? ( + { + setSelectedActions(prev => prev.map(a => + a.id === action.id + ? {...a, parameters: {...a.parameters, [key]: e.target.value}} + : a + )); + }} + /> + ) : ( + { + setSelectedActions(prev => prev.map(a => + a.id === action.id + ? {...a, parameters: {...a.parameters, [key]: e.target.value}} + : a + )); + }} + /> + )} + + ); + })} + + + ))} + + + )} + + {selectedReactions.length > 0 && ( + + Then: + + {selectedReactions.map(reaction => ( + { + e.stopPropagation(); + setSelectedReactions(prev => prev.filter(r => r.id !== reaction.id)); + }} + > + × + + } + > + + {normalizeName(reaction.name)} + {reaction.parameters && Object.entries(reaction.parameters).map(([key, value]) => { + const paramDef = about?.server.services + .flatMap((s: any) => s.reactions) + .find((r: any) => r.name === reaction.name) + ?.parameters + .find((p: any) => p.name === key); + + return ( + + {paramDef?.type === 'number' ? ( + { + setSelectedReactions(prev => prev.map(r => + r.id === reaction.id + ? {...r, parameters: {...r.parameters, [key]: e.target.value}} + : r + )); + }} + /> + ) : ( + { + setSelectedReactions(prev => prev.map(r => + r.id === reaction.id + ? {...r, parameters: {...r.parameters, [key]: e.target.value}} + : r + )); + }} + /> + )} + + ); + })} + + + ))} + + + )} + + + + + + +
+
+ + + + + + + + + + {about?.server?.services.map((service: Service) => ( + + ( + toggleReaction(reaction)} + style={{ + cursor: 'pointer', + backgroundColor: selectedReactions.some(r => r.name === reaction.name) + ? '#e6f7ff' + : 'transparent' + }} + > + + {service.name} +
+ {reaction.description} + + } + /> +
+ )} + /> +
+ ))} +
+
+ +
+ + )} +
+
+ ); +}; + +export default CreateWorkflow; diff --git a/client_web/src/Pages/Workflows/CreateWorkflow.utils.ts b/client_web/src/Pages/Workflows/CreateWorkflow.utils.ts new file mode 100644 index 0000000..424b810 --- /dev/null +++ b/client_web/src/Pages/Workflows/CreateWorkflow.utils.ts @@ -0,0 +1,3 @@ +export const normalizeName = (name: string): string => { + return name.replace(/_/g, " ").replace(/\b\w/g, (c: string): string => c.toUpperCase()); +}; diff --git a/client_web/src/Pages/Workflows/WorkflowsTable.tsx b/client_web/src/Pages/Workflows/WorkflowsTable.tsx new file mode 100644 index 0000000..c372afb --- /dev/null +++ b/client_web/src/Pages/Workflows/WorkflowsTable.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { Row, Typography, Spin, Space } from "antd"; +// @ts-ignore +import Security from "@/Components/Security.tsx"; +// @ts-ignore +import LinkButton from "@/Components/LinkButton.tsx"; + +// @ts-ignore +import { Workflow } from "@/types"; + +const { Title } = Typography; + +const WorkflowTable: React.FC = () => { + const [loading, setLoading] = React.useState(false); + const [_, setWorkflows] = React.useState([]); + + React.useEffect(() => { + setLoading(true); + setWorkflows([]); // TODO: Fetch workflows from the server + setLoading(false); + }, []); + + return ( + +
+ + Create Workflow + + + {loading ? ( + + ) : ( + <> + + + This is where you see all your workflows. + + + + + + + + + )} +
+
+ ); +}; + +export default WorkflowTable; diff --git a/client_web/src/components/auth/Buttons/GoogleAuth.tsx b/client_web/src/components/auth/Buttons/GoogleAuth.tsx deleted file mode 100644 index da9b668..0000000 --- a/client_web/src/components/auth/Buttons/GoogleAuth.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Form } from 'antd'; -import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google'; -// @ts-ignore -import { uri } from '@Config/uri'; - -interface GoogleAuthProps { - onSuccess: (response: unknown) => void; - onError: () => void; - buttonText?: string; -} - -const GoogleAuth = ({ onSuccess, onError, buttonText = "signin_with"}: GoogleAuthProps) => { - if (!uri.google.auth.clientId || uri.google.auth.clientId === '') { - return null; - } - - return ( - - - - - - ); -}; - -export default GoogleAuth; diff --git a/client_web/src/types.ts b/client_web/src/types.ts new file mode 100644 index 0000000..c550305 --- /dev/null +++ b/client_web/src/types.ts @@ -0,0 +1,71 @@ +export type Parameter = { + name: string; + description: string; + type: "string" | "number" | "datetime"; +}; + +export type Action = { + name: string; + description: string; + parameters: Parameter[]; +}; + +export type Reaction = { + name: string; + description: string; + parameters: Parameter[]; +}; + +export type Service = { + name: string; + actions: Action[]; + reactions: Reaction[]; +}; + +export type Server = { + current_time: number; + services: Service[]; +}; + +export type Client = { + host: string; +}; + +export type About = { + client: Client; + server: Server; +}; + +/* --------------------------------- */ + +export interface WorkflowParameter { + name: string; + type: "string" | "number" | "datetime"; + value: unknown; +} + +export interface WorkflowDefinition { + name: string; + parameters: WorkflowParameter[]; +} + +export interface WorkflowAction extends WorkflowDefinition { + +} + +export interface WorkflowReaction extends WorkflowDefinition { + +} + +export type Workflow = { + name: string; + description: string; + actions: WorkflowAction[]; + reactions: WorkflowReaction[]; +}; + +export interface WorkflowItem { + id: string; + name: string; + parameters?: Record; +} diff --git a/client_web/tsconfig.json b/client_web/tsconfig.json index 66eae90..3391afc 100644 --- a/client_web/tsconfig.json +++ b/client_web/tsconfig.json @@ -1,5 +1,12 @@ { "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/*"], + "@Config/*": ["src/Config/*"] + }, + "skipLibCheck": true, + "strict": true, "forceConsistentCasingInFileNames": true, "module": "commonjs", "target": "es2015", diff --git a/docs/source/client_web/index.rst b/docs/source/client_web/index.rst index 0f4fcca..e357c70 100644 --- a/docs/source/client_web/index.rst +++ b/docs/source/client_web/index.rst @@ -32,7 +32,8 @@ Contents -------- .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - installation - context + installation + context + workflowCreation diff --git a/docs/source/client_web/workflowCreation.rst b/docs/source/client_web/workflowCreation.rst new file mode 100644 index 0000000..4a32989 --- /dev/null +++ b/docs/source/client_web/workflowCreation.rst @@ -0,0 +1,52 @@ +Create Workflow +=============== + +The "Create Workflow" feature allows users to create custom workflows by selecting actions and reactions from various services. Follow the steps below to create a workflow. + +Steps to Create a Workflow +-------------------------- + +1. **Enter Workflow Name**: + - Provide a name for your workflow in the "Enter workflow name" field. + - This field is required. + +2. **Enter Workflow Description**: + - Optionally, provide a description for your workflow in the "Enter workflow description" field. + +3. **Select Actions**: + - Browse through the available actions listed under "Available Actions". + - Click on an action to add it to your workflow. + - Use the "Fold All" and "Unfold All" buttons to manage the visibility of action categories. + +4. **Select Reactions**: + - Browse through the available reactions listed under "Available Reactions". + - Click on a reaction to add it to your workflow. + - Use the "Fold All" and "Unfold All" buttons to manage the visibility of reaction categories. + +5. **Review Selected Items**: + - The selected actions and reactions will be displayed in the "Selected Items" section. + - You can remove any selected action or reaction by clicking the "×" button next to it. + +6. **Clear Selections**: + - Use the "Clear Actions" button to remove all selected actions. + - Use the "Clear Reactions" button to remove all selected reactions. + +7. **Create Workflow**: + - Click the "Create Workflow" button to create your workflow. + - Ensure that you have provided a workflow name and selected at least one action and one reaction. + - If the workflow creation is successful, a confirmation message will be displayed. + +8. **Cancel**: + - Click the "Cancel" button to discard the workflow creation process and go back to the previous page. + +Accessibility Features +---------------------- + +- The "Create Workflow" page is designed to be accessible with keyboard navigation and screen readers. +- The "Fold All" and "Unfold All" buttons help manage the visibility of action and reaction categories for easier navigation. + +Error Handling +-------------- + +- If the workflow name is not provided, an error message will be displayed indicating that the workflow name is required. +- If the workflow creation fails, an error message will be displayed.