diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md
new file mode 100644
index 0000000..dfb2f02
--- /dev/null
+++ b/.github/pull-request-template.md
@@ -0,0 +1,53 @@
+## Type of Change
+
+
+
+- [ ] โจ New feature (non-breaking change which adds functionality)
+- [ ] ๐ ๏ธ Bug fix (non-breaking change which fixes an issue)
+- [ ] โ Breaking change (fix or feature that would cause existing functionality to change)
+- [ ] ๐งน Code refactor
+- [ ] โ
Build configuration change
+- [ ] ๐ Documentation
+- [ ] ๐๏ธ Chore
+
+# What does this PR do?
+
+### Description
+
+Put description here
+
+### Screenshot
+
+Put screenshot following the template `before` => `after` here or explain why no screenshots are provided
+
+---
+
+## PR Checklist
+
+### Global
+
+- [ ] I have performed a self-review of my code
+
+### Clean Code
+
+- [ ] I made sure the code is type safe (no any)
+- [ ] I have commented my code, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+
+## Type of Change (commit types)
+
+The appropriate commit types in your PR are required. See a table of them below
+
+| Commit Type | Title | Description | Emoji |
+| ----------- | ------------------------ | ----------------------------------------------------------------------------------------------------------- | :---: |
+| `feat` | Features | A new feature (something new that impacts the end-user) | โจ |
+| `fix` | Bug Fixes | A bug Fix | ๐ |
+| `docs` | Documentation | Documentation only changes | ๐ |
+| `style` | Styles | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) | ๐ |
+| `refactor` | Code Refactoring | A code change that neither fixes a bug nor adds a feature | ๐ฆ |
+| `perf` | Performance Improvements | A code change that improves performance | ๐ |
+| `test` | Tests | Adding missing tests or correcting existing tests | ๐จ |
+| `build` | Builds | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) | ๐ |
+| `ci` | Continuous Integrations | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) | โ๏ธ |
+| `chore` | Chores | Other changes that don't modify src or test files | โป๏ธ |
+| `revert` | Reverts | Reverts a previous commit | ๐ |
diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs
index 198aa75..26cc943 100644
--- a/frontend/.eslintrc.cjs
+++ b/frontend/.eslintrc.cjs
@@ -1,18 +1,19 @@
module.exports = {
root: true,
- env: {browser: true, es2020: true},
+ env: { browser: true, es2020: true },
extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:react-hooks/recommended',
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:react-hooks/recommended",
+ "plugin:tailwindcss/recommended",
],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
+ ignorePatterns: ["dist", ".eslintrc.cjs"],
+ parser: "@typescript-eslint/parser",
+ plugins: ["react-refresh"],
rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- {allowConstantExport: true},
+ "react-refresh/only-export-components": [
+ "warn",
+ { allowConstantExport: true },
],
},
-}
+};
diff --git a/frontend/index.html b/frontend/index.html
index 8bdee71..93ed292 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,13 +1,13 @@
-
+
-
-
-
-
+
+
+
+
Torii โฉ๏ธ - Internal Developer Portal
-
-
-
-
-
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index d29eb08..80fe672 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,23 +10,31 @@
"dependencies": {
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
+ "@hookform/resolvers": "^3.3.4",
"@radix-ui/react-slot": "^1.0.2",
"@tailwindcss/forms": "^0.5.7",
+ "@tanstack/query-core": "^5.29.0",
"@tanstack/react-query": "^5.13.4",
+ "@tanstack/react-query-devtools": "^5.29.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
+ "jotai-devtools": "^0.8.0",
+ "jotai-tanstack-query": "^0.8.5",
"localforage": "^1.10.0",
"lucide-react": "^0.294.0",
"match-sorter": "^6.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-error-boundary": "^4.0.13",
+ "react-hook-form": "^7.51.2",
"react-router-dom": "^6.20.1",
"sort-by": "^1.2.0",
"tailwind-merge": "^2.1.0",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "wonka": "^6.3.4",
+ "yup": "^1.4.0"
},
"devDependencies": {
- "@tanstack/react-query": "^5.13.4",
"@types/node": "^20.10.4",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
@@ -38,7 +46,10 @@
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
+ "eslint-plugin-tailwindcss": "^3.15.1",
"postcss": "^8.4.32",
+ "prettier": "^3.2.5",
+ "prettier-plugin-tailwindcss": "^0.5.13",
"tailwindcss": "^3.3.6",
"typescript": "^5.2.2",
"vite": "^5.0.0"
@@ -81,7 +92,6 @@
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
- "dev": true,
"dependencies": {
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
@@ -216,7 +226,6 @@
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
- "dev": true,
"dependencies": {
"@babel/types": "^7.22.15"
},
@@ -280,7 +289,6 @@
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
- "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -289,7 +297,6 @@
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
- "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -321,7 +328,6 @@
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
- "dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
@@ -423,7 +429,6 @@
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz",
"integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==",
- "dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
@@ -433,6 +438,138 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
+ "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/serialize": "^1.1.2",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "peer": true
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
+ "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
+ "peer": true,
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/sheet": "^1.2.2",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
+ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==",
+ "peer": true
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "peer": true
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.11.4",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz",
+ "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.11.0",
+ "@emotion/cache": "^11.11.0",
+ "@emotion/serialize": "^1.1.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz",
+ "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==",
+ "peer": true,
+ "dependencies": {
+ "@emotion/hash": "^0.9.1",
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/unitless": "^0.8.1",
+ "@emotion/utils": "^1.2.1",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
+ "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==",
+ "peer": true
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "peer": true
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
+ "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
+ "peer": true,
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
+ "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==",
+ "peer": true
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
+ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==",
+ "peer": true
+ },
"node_modules/@esbuild/android-arm": {
"version": "0.19.9",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz",
@@ -856,6 +993,54 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
+ "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.1"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
+ "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
+ "dependencies": {
+ "@floating-ui/core": "^1.0.0",
+ "@floating-ui/utils": "^0.2.0"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.2.tgz",
+ "integrity": "sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==",
+ "dependencies": {
+ "@floating-ui/react-dom": "^1.3.0",
+ "aria-hidden": "^1.1.3",
+ "tabbable": "^6.0.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz",
+ "integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==",
+ "dependencies": {
+ "@floating-ui/dom": "^1.2.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
+ "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
+ },
"node_modules/@headlessui/react": {
"version": "1.7.17",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz",
@@ -879,6 +1064,14 @@
"react": ">= 16"
}
},
+ "node_modules/@hookform/resolvers": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz",
+ "integrity": "sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==",
+ "peerDependencies": {
+ "react-hook-form": "^7.0.0"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -955,6 +1148,82 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@mantine/core": {
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.21.tgz",
+ "integrity": "sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg==",
+ "dependencies": {
+ "@floating-ui/react": "^0.19.1",
+ "@mantine/styles": "6.0.21",
+ "@mantine/utils": "6.0.21",
+ "@radix-ui/react-scroll-area": "1.0.2",
+ "react-remove-scroll": "^2.5.5",
+ "react-textarea-autosize": "8.3.4"
+ },
+ "peerDependencies": {
+ "@mantine/hooks": "6.0.21",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@mantine/hooks": {
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz",
+ "integrity": "sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@mantine/prism": {
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/prism/-/prism-6.0.21.tgz",
+ "integrity": "sha512-M9hDUAuuxiINI7f07V0qlX532UXlOTpBqNcG1WWm80t6C0fHjzkTvFj77QpnGS73+MI88mV8ru458y10bQjTBA==",
+ "dependencies": {
+ "@mantine/utils": "6.0.21",
+ "prism-react-renderer": "^1.2.1"
+ },
+ "peerDependencies": {
+ "@mantine/core": "6.0.21",
+ "@mantine/hooks": "6.0.21",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@mantine/styles": {
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.21.tgz",
+ "integrity": "sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg==",
+ "dependencies": {
+ "clsx": "1.1.1",
+ "csstype": "3.0.9"
+ },
+ "peerDependencies": {
+ "@emotion/react": ">=11.9.0",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@mantine/styles/node_modules/clsx": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+ "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@mantine/styles/node_modules/csstype": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
+ "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
+ },
+ "node_modules/@mantine/utils": {
+ "version": "6.0.21",
+ "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.21.tgz",
+ "integrity": "sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -987,6 +1256,22 @@
"node": ">= 8"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz",
+ "integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
+ "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
@@ -1004,6 +1289,121 @@
}
}
},
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
+ "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
+ "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
+ "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
+ "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz",
+ "integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-slot": "1.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
+ "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
+ "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-compose-refs": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz",
+ "integrity": "sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/number": "1.0.0",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-direction": "1.0.0",
+ "@radix-ui/react-presence": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.1",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
+ "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
@@ -1022,6 +1422,40 @@
}
}
},
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
+ "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
+ "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@redux-devtools/extension": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@redux-devtools/extension/-/extension-3.3.0.tgz",
+ "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "immutable": "^4.3.4"
+ },
+ "peerDependencies": {
+ "redux": "^3.1.0 || ^4.0.0 || ^5.0.0"
+ }
+ },
"node_modules/@remix-run/router": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
@@ -1211,28 +1645,51 @@
}
},
"node_modules/@tanstack/query-core": {
- "version": "5.13.4",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.13.4.tgz",
- "integrity": "sha512-8+rJucXvC/xlr4OrxHhEIob/cTlbT4fgmz1VsvB0D12FRStKaXeLORNGcOhSAynRd2NL74SV/Qq0IIb4DedLcA==",
- "dev": true,
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.29.0.tgz",
+ "integrity": "sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/query-devtools": {
+ "version": "5.28.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.28.10.tgz",
+ "integrity": "sha512-5UN629fKa5/1K/2Pd26gaU7epxRrYiT1gy+V+pW5K6hnf1DeUKK3pANSb2eHKlecjIKIhTwyF7k9XdyE2gREvQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.13.4",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.13.4.tgz",
- "integrity": "sha512-3HjvkFFriEQwffUXtKHPiwkfFXUGbs46YATTzzyK1+Pw6Ekd3kwzS50e45qdamWuEXmXxyo5S1zp534LdFG0Rw==",
- "dev": true,
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.29.0.tgz",
+ "integrity": "sha512-yxlhHB73jaBla6h5B6zPaGmQjokkzAhMHN4veotkPNiQ3Ac/mCxgABRZPsJJrgCTvhpcncBZcDBFxaR2B37vug==",
+ "dependencies": {
+ "@tanstack/query-core": "5.29.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-query-devtools": {
+ "version": "5.29.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.29.0.tgz",
+ "integrity": "sha512-WLuaU6yM4KdvBimEP1Km5lM4/p1J40cMp5I5z0Mc6a8QbBUYLK8qJcGIKelfLfDp7KmEcr59tzbRTmdH/GWvzQ==",
"dependencies": {
- "@tanstack/query-core": "5.13.4"
+ "@tanstack/query-devtools": "5.28.10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
+ "@tanstack/react-query": "^5.29.0",
"react": "^18.0.0"
}
},
@@ -1277,12 +1734,22 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/base16": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz",
+ "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A=="
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.0",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
+ "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA=="
+ },
"node_modules/@types/node": {
"version": "20.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz",
@@ -1292,17 +1759,21 @@
"undici-types": "~5.26.4"
}
},
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "peer": true
+ },
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
- "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
- "devOptional": true
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
},
"node_modules/@types/react": {
"version": "18.2.43",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.43.tgz",
"integrity": "sha512-nvOV01ZdBdd/KW6FahSbcNplt2jCJfyWdTos61RYHV+FVv5L/g9AOX1bmbVcWcLFL8+KHQfh1zVIQrud6ihyQA==",
- "devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -1321,8 +1792,7 @@
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
- "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
- "devOptional": true
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
},
"node_modules/@types/semver": {
"version": "7.5.6",
@@ -1594,7 +2064,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@@ -1630,6 +2099,17 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -1693,11 +2173,31 @@
"proxy-from-env": "^1.1.0"
}
},
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "node_modules/base16": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
+ "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ=="
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -1762,7 +2262,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -1799,7 +2298,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -1870,11 +2368,19 @@
"node": ">=6"
}
},
+ "node_modules/color": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
+ "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+ "dependencies": {
+ "color-convert": "^1.9.3",
+ "color-string": "^1.6.0"
+ }
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@@ -1882,8 +2388,16 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -1916,6 +2430,31 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "peer": true,
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cosmiconfig/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1944,8 +2483,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/debug": {
"version": "4.3.4",
@@ -1979,11 +2517,21 @@
"node": ">=0.4.0"
}
},
- "node_modules/didyoumean": {
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
+ },
+ "node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
+ "node_modules/diff-match-patch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -2019,6 +2567,15 @@
"integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==",
"dev": true
},
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "peer": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/esbuild": {
"version": "0.19.9",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz",
@@ -2069,7 +2626,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
"engines": {
"node": ">=0.8.0"
}
@@ -2150,6 +2706,22 @@
"eslint": ">=7"
}
},
+ "node_modules/eslint-plugin-tailwindcss": {
+ "version": "3.15.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.15.1.tgz",
+ "integrity": "sha512-4RXRMIaMG07C2TBEW1k0VM4+dDazz1kxcZhkK4zirvmHGZTA4jnlSO2kq5mamuSPi+Wo17dh2SlC8IyFBuCd7Q==",
+ "dev": true,
+ "dependencies": {
+ "fast-glob": "^3.2.5",
+ "postcss": "^8.4.4"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "tailwindcss": "^3.4.0"
+ }
+ },
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -2409,6 +2981,12 @@
"node": ">=8"
}
},
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "peer": true
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -2527,6 +3105,14 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -2597,7 +3183,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -2613,6 +3198,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "peer": true,
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
@@ -2627,11 +3221,15 @@
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
+ "node_modules/immutable": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
+ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw=="
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dev": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -2666,6 +3264,20 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "peer": true
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -2730,6 +3342,11 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/javascript-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
+ "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="
+ },
"node_modules/jiti": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
@@ -2738,6 +3355,60 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/jotai": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.8.0.tgz",
+ "integrity": "sha512-yZNMC36FdLOksOr8qga0yLf14miCJlEThlp5DeFJNnqzm2+ZG7wLcJzoOyij5K6U6Xlc5ljQqPDlJRgqW0Y18g==",
+ "peer": true,
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=17.0.0",
+ "react": ">=17.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jotai-devtools": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/jotai-devtools/-/jotai-devtools-0.8.0.tgz",
+ "integrity": "sha512-dSxiDuEz/fvgxC5hCWJfRHTSaDz8RBaQ2DUO3SJqvskIP44BUxx5EJSQHN9WdbPqp9VQ2Db8yIR9jLGbpnuLtA==",
+ "dependencies": {
+ "@mantine/core": "^6.0.21",
+ "@mantine/hooks": "^6.0.21",
+ "@mantine/prism": "^6.0.21",
+ "@redux-devtools/extension": "^3.3.0",
+ "javascript-stringify": "^2.1.0",
+ "jsondiffpatch": "^0.5.0",
+ "react-error-boundary": "^4.0.12",
+ "react-json-tree": "^0.18.0",
+ "react-resizable-panels": "^0.0.54"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@emotion/react": ">=11.0.0",
+ "react": ">=17.0.0"
+ }
+ },
+ "node_modules/jotai-tanstack-query": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/jotai-tanstack-query/-/jotai-tanstack-query-0.8.5.tgz",
+ "integrity": "sha512-cFq+1sE7Qkt7Kh9Db2KE8LXdbPiGwCiy8S5YSEnjUDxF59A4XhoXTDJBuPiMIA1dD1/yMsNKr1ADfN5CvscYZw==",
+ "peerDependencies": {
+ "@tanstack/query-core": "*",
+ "jotai": ">=2.0.0",
+ "wonka": "^6.3.4"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2773,6 +3444,12 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "peer": true
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -2797,6 +3474,82 @@
"node": ">=6"
}
},
+ "node_modules/jsondiffpatch": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.5.0.tgz",
+ "integrity": "sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw==",
+ "dependencies": {
+ "chalk": "^3.0.0",
+ "diff-match-patch": "^1.0.0"
+ },
+ "bin": {
+ "jsondiffpatch": "bin/jsondiffpatch"
+ },
+ "engines": {
+ "node": ">=8.17.0"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/jsondiffpatch/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -2863,6 +3616,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash.curry": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
+ "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3111,7 +3869,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
"dependencies": {
"callsites": "^3.0.0"
},
@@ -3119,6 +3876,24 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -3154,7 +3929,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -3338,6 +4112,108 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.13.tgz",
+ "integrity": "sha512-2tPWHCFNC+WRjAC4SIWQNSOdcL1NNkydXim8w7TDqlZi+/ulZYz2OouAI6qMtkggnPt7lGamboj6LcTMwcCvoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig-melody": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig-melody": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/prism-react-renderer": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz",
+ "integrity": "sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==",
+ "peerDependencies": {
+ "react": ">=0.14.9"
+ }
+ },
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -3383,6 +4259,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-base16-styling": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
+ "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.7",
+ "@types/base16": "^1.0.2",
+ "@types/lodash": "^4.14.178",
+ "base16": "^1.0.0",
+ "color": "^3.2.1",
+ "csstype": "^3.0.10",
+ "lodash.curry": "^4.1.1"
+ }
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -3395,6 +4285,52 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-error-boundary": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz",
+ "integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.51.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.2.tgz",
+ "integrity": "sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==",
+ "engines": {
+ "node": ">=12.22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18"
+ }
+ },
+ "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==",
+ "peer": true
+ },
+ "node_modules/react-json-tree": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.18.0.tgz",
+ "integrity": "sha512-Qe6HKSXrr++n9Y31nkRJ3XvQMATISpqigH1vEKhLwB56+nk5thTP0ITThpjxY6ZG/ubpVq/aEHIcyLP/OPHxeA==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.6",
+ "@types/lodash": "^4.14.191",
+ "react-base16-styling": "^0.9.1"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
@@ -3404,6 +4340,60 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.5.9",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz",
+ "integrity": "sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.6",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
+ "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
+ "dependencies": {
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-resizable-panels": {
+ "version": "0.0.54",
+ "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.54.tgz",
+ "integrity": "sha512-f8hHdQrqvXoiZGdRNuoOi/C2cdYT2nEpaOb1KIWVWorSTPZmnE+ZQiamGeu+AMx3iZ/tqBtlAkBOpKXzTnDCoA==",
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-router": {
"version": "6.20.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz",
@@ -3434,6 +4424,44 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-style-singleton": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
+ "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "invariant": "^2.2.4",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-textarea-autosize": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz",
+ "integrity": "sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.2",
+ "use-composed-ref": "^1.3.0",
+ "use-latest": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -3453,6 +4481,12 @@
"node": ">=8.10.0"
}
},
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "peer": true
+ },
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
@@ -3483,7 +4517,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -3625,6 +4658,19 @@
"node": ">=8"
}
},
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/simple-swizzle/node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -3642,6 +4688,15 @@
"object-path": "0.6.0"
}
},
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -3674,6 +4729,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "peer": true
+ },
"node_modules/sucrase": {
"version": "3.34.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
@@ -3718,7 +4779,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -3737,6 +4797,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
+ },
"node_modules/tailwind-merge": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.1.0.tgz",
@@ -3750,9 +4815,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz",
- "integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
+ "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -3762,7 +4827,7 @@
"fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "jiti": "^1.19.1",
+ "jiti": "^1.21.0",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
@@ -3818,11 +4883,15 @@
"node": ">=0.8"
}
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -3838,6 +4907,11 @@
"node": ">=8.0"
}
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@@ -3855,6 +4929,11 @@
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -3937,6 +5016,84 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
+ "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-composed-ref": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
+ "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-latest": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz",
+ "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==",
+ "dependencies": {
+ "use-isomorphic-layout-effect": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
+ "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -4012,6 +5169,11 @@
"node": ">= 8"
}
},
+ "node_modules/wonka": {
+ "version": "6.3.4",
+ "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.4.tgz",
+ "integrity": "sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg=="
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -4042,6 +5204,28 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/yup": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",
+ "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ },
+ "node_modules/yup/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index c84368a..1296eb8 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,23 +12,31 @@
"dependencies": {
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
+ "@hookform/resolvers": "^3.3.4",
"@radix-ui/react-slot": "^1.0.2",
"@tailwindcss/forms": "^0.5.7",
+ "@tanstack/query-core": "^5.29.0",
"@tanstack/react-query": "^5.13.4",
+ "@tanstack/react-query-devtools": "^5.29.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
+ "jotai-devtools": "^0.8.0",
+ "jotai-tanstack-query": "^0.8.5",
"localforage": "^1.10.0",
"lucide-react": "^0.294.0",
"match-sorter": "^6.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-error-boundary": "^4.0.13",
+ "react-hook-form": "^7.51.2",
"react-router-dom": "^6.20.1",
"sort-by": "^1.2.0",
"tailwind-merge": "^2.1.0",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "wonka": "^6.3.4",
+ "yup": "^1.4.0"
},
"devDependencies": {
- "@tanstack/react-query": "^5.13.4",
"@types/node": "^20.10.4",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
@@ -40,9 +48,17 @@
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
+ "eslint-plugin-tailwindcss": "^3.15.1",
"postcss": "^8.4.32",
+ "prettier": "^3.2.5",
+ "prettier-plugin-tailwindcss": "^0.5.13",
"tailwindcss": "^3.3.6",
"typescript": "^5.2.2",
"vite": "^5.0.0"
+ },
+ "prettier": {
+ "plugins": [
+ "prettier-plugin-tailwindcss"
+ ]
}
}
diff --git a/frontend/prettier.config.js b/frontend/prettier.config.js
new file mode 100644
index 0000000..522dd81
--- /dev/null
+++ b/frontend/prettier.config.js
@@ -0,0 +1,4 @@
+module.exports = {
+ tailwindConfig: "tailwind.config.js",
+ plugins: ["prettier-plugin-tailwindcss"],
+};
diff --git a/frontend/src/atoms/catalog.atoms.ts b/frontend/src/atoms/catalog.atoms.ts
new file mode 100644
index 0000000..cc419fb
--- /dev/null
+++ b/frontend/src/atoms/catalog.atoms.ts
@@ -0,0 +1,24 @@
+import { API_URL } from "@/config";
+import { atomWithSwr } from "@/lib/atom-with-swr";
+import { makeQueryAtoms } from "@/lib/make-query-atoms";
+import { Catalog } from "@/types/catalog.type";
+import { atom } from "jotai";
+
+export const selectedServiceSlugAtom = atom(null);
+
+export const selectedCatalogSlugAtom = atom(null);
+
+export const [catalogsAtom, catalogsStatusAtom] = makeQueryAtoms<
+ string,
+ Catalog[]
+>(
+ "catalogs",
+ () => `${API_URL}/catalogs`,
+ (params) => () => {
+ return fetch(params)
+ .then((res) => res.json())
+ .then((data) => data.results);
+ }
+);
+
+export const catalogsSwrAtom = atomWithSwr(catalogsAtom);
diff --git a/frontend/src/atoms/dialog.atoms.ts b/frontend/src/atoms/dialog.atoms.ts
new file mode 100644
index 0000000..f66deb2
--- /dev/null
+++ b/frontend/src/atoms/dialog.atoms.ts
@@ -0,0 +1,4 @@
+import { atom } from "jotai";
+import { atomFamily } from "jotai/utils";
+
+export const dialogOpenedAtomFamily = atomFamily(() => atom(false));
diff --git a/frontend/src/atoms/navigation.atoms.ts b/frontend/src/atoms/navigation.atoms.ts
new file mode 100644
index 0000000..53daf33
--- /dev/null
+++ b/frontend/src/atoms/navigation.atoms.ts
@@ -0,0 +1,3 @@
+import { atom } from "jotai";
+
+export const pageTitleAtom = atom("");
diff --git a/frontend/src/atoms/run.atoms.ts b/frontend/src/atoms/run.atoms.ts
new file mode 100644
index 0000000..5794644
--- /dev/null
+++ b/frontend/src/atoms/run.atoms.ts
@@ -0,0 +1,17 @@
+import { API_URL } from "@/config";
+import { makeQueryAtoms } from "@/lib/make-query-atoms";
+import { selectedCatalogSlugAtom } from "./catalog.atoms";
+
+export const [runsAtom, runsStatusAtom] = makeQueryAtoms(
+ `catalogs-${1}-runs`,
+ (get) => {
+ const selectedCatalogSlug = get(selectedCatalogSlugAtom);
+
+ return `${API_URL}/runs/${selectedCatalogSlug}/runs`;
+ },
+ (params) => () => {
+ return fetch(params as string)
+ .then((res) => res.json())
+ .then((data) => data.results);
+ }
+);
diff --git a/frontend/src/atoms/service.atoms.ts b/frontend/src/atoms/service.atoms.ts
new file mode 100644
index 0000000..51f020c
--- /dev/null
+++ b/frontend/src/atoms/service.atoms.ts
@@ -0,0 +1,59 @@
+import { API_URL } from "@/config";
+import { Catalog, Service } from "@/types/catalog.type";
+import { atom } from "jotai";
+import { MutationOptions, atomWithMutation } from "jotai-tanstack-query";
+import {
+ catalogsSwrAtom,
+ selectedCatalogSlugAtom,
+ selectedServiceSlugAtom,
+} from "./catalog.atoms";
+import { ExecuteServicePayload } from "@/components/self-service/SelfServiceCreateDialog";
+
+export const selectedServiceAtom = atom((get) => {
+ const selectedServiceSlug = get(selectedServiceSlugAtom);
+ const selectedCatalogSlug = get(selectedCatalogSlugAtom);
+ const catalogs = get(catalogsSwrAtom) as any;
+
+ if (
+ !selectedServiceSlug ||
+ !selectedCatalogSlug ||
+ catalogs.status !== "success"
+ ) {
+ return null;
+ }
+
+ const catalog = catalogs.data.find(
+ (catalog: Catalog) => catalog.slug === selectedCatalogSlug
+ );
+
+ if (!catalog) {
+ return null;
+ }
+
+ return (
+ catalog.services.find(
+ (service: Service) => service.slug === selectedServiceSlug
+ ) || null
+ );
+});
+
+export const executeServiceMutation = atomWithMutation((get) => {
+ const selectedServiceSlug = get(selectedServiceSlugAtom);
+ const selectedCatalogSlug = get(selectedCatalogSlugAtom);
+
+ return {
+ mutationKey: ["executeService"],
+ mutationFn: async (payload: ExecuteServicePayload) => {
+ return fetch(
+ `${API_URL}/catalogs/${selectedCatalogSlug}/services/${selectedServiceSlug}/execute`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ payload }),
+ }
+ );
+ },
+ } as MutationOptions;
+});
diff --git a/frontend/src/atoms/user.atoms.ts b/frontend/src/atoms/user.atoms.ts
new file mode 100644
index 0000000..27a951d
--- /dev/null
+++ b/frontend/src/atoms/user.atoms.ts
@@ -0,0 +1,18 @@
+import { atom } from "jotai";
+
+export interface User {
+ id: string;
+ name: string;
+ email: string;
+ imageUrl: string;
+}
+
+const user = {
+ id: "1",
+ name: "Tom Cook",
+ email: "tom@example.com",
+ imageUrl:
+ "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
+};
+
+export const userAtom = atom(user);
diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx
index 318966d..bc9df7e 100644
--- a/frontend/src/components/AppShell.tsx
+++ b/frontend/src/components/AppShell.tsx
@@ -1,287 +1,30 @@
-import {Fragment, useEffect, useState} from 'react'
-import {Dialog, Disclosure, Transition} from '@headlessui/react'
-import {Bars3Icon, BeakerIcon, ChevronRightIcon, ClipboardIcon, HomeIcon, ShieldCheckIcon, XMarkIcon,} from '@heroicons/react/24/outline'
-import {classNames} from "@/lib/utils.ts";
-import {useQuery} from "@tanstack/react-query";
-import {API_URL} from "@/config.ts";
-import SelfService from "@/components/self-service/SelfService.tsx";
-
-class NavigationItem {
- name: string
- href: string
- icon: any
- current: boolean
- disabled: boolean
- children?: NavigationChildrenItem[]
-
- constructor(name: string, href: string, icon: any, current: boolean, disabled: boolean, children?: NavigationChildrenItem[]) {
- this.name = name
- this.href = href
- this.icon = icon
- this.current = current
- this.disabled = disabled
- this.children = children
- }
-}
-
-class NavigationChildrenItem {
- name: string
- href: string
- current: boolean
- children: JSX.Element
-
- constructor(name: string, href: string, current: boolean, children: JSX.Element) {
- this.name = name
- this.href = href
- this.current = current
- this.children = children
- }
-
-}
+import { BeakerIcon, HomeIcon } from "@heroicons/react/24/outline";
+import { Outlet } from "react-router-dom";
+import { Header } from "./common/Header";
+import { RouteItem } from "./common/NavigationItem";
+import Subheader from "./common/Subheader";
export default function AppShell() {
- const [sidebarOpen, setSidebarOpen] = useState(false)
- const [currentTabTitle, setCurrentTabTitle] = useState('Dashboard')
- const [navigationItems, setNavigationItems] = useState([])
- const [content, setContent] = useState(undefined)
-
- function setCurrentTabHelper(navItem: NavigationItem, subItem?: NavigationChildrenItem) {
- if (subItem) {
- setCurrentTabTitle(`${navItem.name} > ${subItem.name}`)
- } else {
- setCurrentTabTitle(navItem.name)
- }
-
- setContent(subItem?.children)
- }
-
- const {status, data} = useQuery({
- queryKey: ['catalogs'],
- queryFn: () =>
- fetch(`${API_URL}/catalogs`).then(
- (res) => res.json(),
- ),
- })
-
- useEffect(() => {
- if (status === 'success') {
- const ni = [
- new NavigationItem('Dashboard', '#', HomeIcon, false, true),
- new NavigationItem('Self Service', '#', BeakerIcon, true, false, data.results.map((catalog: any): NavigationChildrenItem => {
- // populate catalogs in navigation
- return new NavigationChildrenItem(catalog.name, `/catalogs/${catalog.slug}/services`, false,
- );
- })),
- new NavigationItem('Scorecard', '#', ClipboardIcon, false, true),
- new NavigationItem('Audit', '#', ShieldCheckIcon, false, true),
- ]
-
- setCurrentTabTitle(ni.find((item) => item.current)?.name || 'Dashboard')
- setNavigationItems(ni)
- }
- }, [status, data]);
+ const routes = [
+ new RouteItem("Dashboard", "/dashboard", HomeIcon, false, false),
+ new RouteItem("Self Service", "/self-service", BeakerIcon, true, false),
+ ];
return (
<>
- {/*
- This example requires updating your template:
-
- ```
-
-
- ```
- */}
-
-
-
-
-
- {/* Static sidebar for desktop */}
-
- {/* Sidebar component, swap this element with another sidebar if you like */}
-
-
- {/*

*/}
-
Torii โฉ๏ธ
-
-
+
+
+
-
-
-
-
- {content}
-
>
- )
-}
-
-function getNavigationJsx(
- item: NavigationItem,
- onItemClicked: (item: NavigationItem, subItem?: NavigationChildrenItem) => void,
-): JSX.Element {
- return
- {!item.children ? (
- onItemClicked(item, undefined)}
- >
-
- {item.name}
-
- ) : (
-
- {({open}) => (
- <>
-
-
- {item.name}
-
-
-
- {item.children?.map((subItem) => (
-
- {/* 44px */}
- onItemClicked(item, subItem)}
- >
- {subItem.name}
-
-
- ))}
-
- >
- )}
-
- )}
-
+ );
}
diff --git a/frontend/src/components/common/Button.tsx b/frontend/src/components/common/Button.tsx
new file mode 100644
index 0000000..a53ec70
--- /dev/null
+++ b/frontend/src/components/common/Button.tsx
@@ -0,0 +1,50 @@
+import { ThemeColors } from "@/enums/theme-colors.enum";
+import clsx from "clsx";
+import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from "react";
+
+export interface ButtonProps
+ extends DetailedHTMLProps<
+ ButtonHTMLAttributes
,
+ HTMLButtonElement
+ > {
+ color?: ThemeColors;
+ loading?: boolean;
+ flat?: boolean;
+ disabled?: boolean;
+ children?: ReactNode;
+}
+
+export function Button({
+ color = ThemeColors.PRIMARY,
+ loading,
+ flat,
+ disabled,
+ children,
+ ...props
+}: ButtonProps) {
+ const config: {
+ [key in ThemeColors]: string;
+ } = {
+ [ThemeColors.PRIMARY]: flat
+ ? "bg-transparent text-indigo-500"
+ : "bg-indigo-500 hover:bg-indigo-700 text-white",
+ [ThemeColors.SECONDARY]: flat
+ ? "bg-transparent text-violet-500"
+ : "bg-violet-500 hover:bg-violet-700 text-white",
+ };
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/common/Dialog.tsx b/frontend/src/components/common/Dialog.tsx
new file mode 100644
index 0000000..89b9bd9
--- /dev/null
+++ b/frontend/src/components/common/Dialog.tsx
@@ -0,0 +1,115 @@
+import { dialogOpenedAtomFamily } from "@/atoms/dialog.atoms";
+import { DialogIds } from "@/enums/dialog-ids.enum";
+import { ThemeColors } from "@/enums/theme-colors.enum";
+import { Dialog as HeadlessDialog, Transition } from "@headlessui/react";
+import { XMarkIcon } from "@heroicons/react/24/outline";
+import { useAtom } from "jotai";
+import { Fragment, ReactNode } from "react";
+import { Button } from "./Button";
+
+export interface DialogProps {
+ id: DialogIds;
+ title: string;
+ initialFocus?: React.MutableRefObject;
+ children: ReactNode;
+ customFooter?: boolean;
+}
+
+export interface DialogFooterProps {
+ onClose?: () => void;
+ onValidate?: () => void;
+}
+
+export function DialogFooter({ onClose, onValidate }: DialogFooterProps) {
+ return (
+
+
+
+
+ );
+}
+
+export default function Dialog({
+ id,
+ title,
+ initialFocus,
+ customFooter,
+ children,
+}: DialogProps) {
+ const [isOpen, setIsOpen] = useAtom(dialogOpenedAtomFamily(id));
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {title}
+
+
{children}
+
+
+ {!customFooter && (
+ setIsOpen(false)}
+ onValidate={() => setIsOpen(false)}
+ />
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/common/DynamicField.tsx b/frontend/src/components/common/DynamicField.tsx
new file mode 100644
index 0000000..2134891
--- /dev/null
+++ b/frontend/src/components/common/DynamicField.tsx
@@ -0,0 +1,47 @@
+import { Field } from "@/types/catalog.type";
+import SwitchField from "./SwitchField";
+import TextField from "./TextField";
+import TextareaField from "./TextareaField";
+
+export interface DynamicFieldProps {
+ field: Field;
+ initialFocus?: {
+ name: string;
+ ref: React.MutableRefObject;
+ };
+}
+
+export function DynamicField({ field, initialFocus }: DynamicFieldProps) {
+ const ref = initialFocus?.name === field.slug ? initialFocus.ref : undefined;
+
+ if (field.type === "text") {
+ return (
+ }
+ />
+ );
+ } else if (field.type === "number") {
+ return (
+ }
+ />
+ );
+ } else if (field.type === "textarea") {
+ return (
+ }
+ />
+ );
+ } else if (field.type === "boolean") {
+ return ;
+ }
+
+ return '{field.type}' is not a supported field
;
+}
diff --git a/frontend/src/components/common/DynamicFields.tsx b/frontend/src/components/common/DynamicFields.tsx
new file mode 100644
index 0000000..19aa07d
--- /dev/null
+++ b/frontend/src/components/common/DynamicFields.tsx
@@ -0,0 +1,18 @@
+import { Field } from "@/types/catalog.type";
+import { DynamicField } from "./DynamicField";
+
+export interface DynamicFieldsProps {
+ fields: Field[];
+ initialFocus?: {
+ name: string;
+ ref: React.MutableRefObject;
+ };
+}
+
+export function DynamicFields({ fields, initialFocus }: DynamicFieldsProps) {
+ return fields.map((field) => (
+
+ ));
+}
+
+export default DynamicFields;
diff --git a/frontend/src/components/common/EmptyState.tsx b/frontend/src/components/common/EmptyState.tsx
index 2ebe986..cdb5af2 100644
--- a/frontend/src/components/common/EmptyState.tsx
+++ b/frontend/src/components/common/EmptyState.tsx
@@ -3,7 +3,7 @@ interface Props {
subText: string;
}
-export default function EmptyState({text, subText}: Props) {
+export default function EmptyState({ text, subText }: Props) {
return (
- )
+ );
}
diff --git a/frontend/src/components/common/Form.tsx b/frontend/src/components/common/Form.tsx
new file mode 100644
index 0000000..286d53b
--- /dev/null
+++ b/frontend/src/components/common/Form.tsx
@@ -0,0 +1,31 @@
+import {
+ FieldValues,
+ FormProvider,
+ SubmitHandler,
+ UseFormReturn,
+} from "react-hook-form";
+
+export interface FormProps {
+ formRef: UseFormReturn;
+ onSubmit?: SubmitHandler;
+ className?: string;
+ children: JSX.Element | JSX.Element[];
+}
+
+export function Form({
+ onSubmit,
+ children,
+ className,
+ formRef,
+}: FormProps) {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/components/common/FormButtons.tsx b/frontend/src/components/common/FormButtons.tsx
new file mode 100644
index 0000000..8dae75f
--- /dev/null
+++ b/frontend/src/components/common/FormButtons.tsx
@@ -0,0 +1,27 @@
+import { ThemeColors } from "@/enums/theme-colors.enum";
+import { Button } from "./Button";
+
+export interface FormButtonsProps {
+ valid: boolean;
+ onCancel?: () => void;
+}
+
+export function FormButtons({ valid, onCancel }: FormButtonsProps) {
+ return (
+
+
+ {onCancel && (
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/common/Header.tsx b/frontend/src/components/common/Header.tsx
new file mode 100644
index 0000000..d0df3fe
--- /dev/null
+++ b/frontend/src/components/common/Header.tsx
@@ -0,0 +1,116 @@
+import { userAtom } from "@/atoms/user.atoms";
+import { Disclosure, Menu, Transition } from "@headlessui/react";
+import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
+import clsx from "clsx";
+import { useAtomValue } from "jotai";
+import { BellIcon } from "lucide-react";
+import { Fragment } from "react";
+import { MobileNav, Nav } from "./Nav";
+import { RouteItem } from "./NavigationItem";
+
+export interface HeaderProps {
+ routes: RouteItem[];
+ userMenu: RouteItem[];
+}
+
+export function Header({ routes, userMenu }: HeaderProps) {
+ const user = useAtomValue(userAtom);
+
+ return (
+
+ {({ open }) => (
+ <>
+
+
+
+
+ {/*

+

*/}
+
+ Torii โฉ๏ธ
+
+
+
+
+
+
+
+
+
+ {/* Profile dropdown */}
+
+
+
+ {/* Mobile menu button */}
+
+
+ Open main menu
+ {open ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/frontend/src/components/common/Nav.tsx b/frontend/src/components/common/Nav.tsx
new file mode 100644
index 0000000..8ab13c0
--- /dev/null
+++ b/frontend/src/components/common/Nav.tsx
@@ -0,0 +1,86 @@
+import { userAtom } from "@/atoms/user.atoms";
+import { Disclosure } from "@headlessui/react";
+import clsx from "clsx";
+import { useAtomValue } from "jotai";
+import { BellIcon } from "lucide-react";
+import NavigationItem, { RouteItem } from "./NavigationItem";
+
+export interface NavProps {
+ routes: RouteItem[];
+}
+
+export interface MobileNavProps extends NavProps {
+ userMenu: RouteItem[];
+}
+
+export function MobileNav({ routes, userMenu }: MobileNavProps) {
+ const user = useAtomValue(userAtom);
+
+ return (
+
+
+ {routes.map((item) => (
+
+ {item.name}
+
+ ))}
+
+
+
+
+

+
+
+
+ {user.name}
+
+
+ {user.email}
+
+
+
+
+
+ {userMenu.map((item) => (
+
+ {item.name}
+
+ ))}
+
+
+
+ );
+}
+
+export function Nav({ routes }: NavProps) {
+ return routes.map((item) => );
+}
+
+export default Nav;
diff --git a/frontend/src/components/common/NavigationItem.tsx b/frontend/src/components/common/NavigationItem.tsx
new file mode 100644
index 0000000..4d42b71
--- /dev/null
+++ b/frontend/src/components/common/NavigationItem.tsx
@@ -0,0 +1,70 @@
+import clsx from "clsx";
+import { ElementType } from "react";
+
+export class RouteItem {
+ name: string;
+ href: string;
+ icon: ElementType;
+ current: boolean;
+ disabled: boolean;
+ children?: ChildRouteItem[];
+
+ constructor(
+ name: string,
+ href: string,
+ icon: ElementType,
+ current: boolean,
+ disabled: boolean,
+ children?: ChildRouteItem[],
+ ) {
+ this.name = name;
+ this.href = href;
+ this.icon = icon;
+ this.current = current;
+ this.disabled = disabled;
+ this.children = children;
+ }
+}
+
+export class ChildRouteItem {
+ name: string;
+ href: string;
+ current: boolean;
+ children: JSX.Element;
+
+ constructor(
+ name: string,
+ href: string,
+ current: boolean,
+ children: JSX.Element,
+ ) {
+ this.name = name;
+ this.href = href;
+ this.current = current;
+ this.children = children;
+ }
+}
+
+export interface NavigationItemProps {
+ item: RouteItem;
+}
+
+export function NavigationItem({ item }: NavigationItemProps): JSX.Element {
+ return (
+
+ {item.name}
+
+ );
+}
+
+export default NavigationItem;
diff --git a/frontend/src/components/common/ServerError.tsx b/frontend/src/components/common/ServerError.tsx
new file mode 100644
index 0000000..23ba599
--- /dev/null
+++ b/frontend/src/components/common/ServerError.tsx
@@ -0,0 +1,16 @@
+export interface ServerErrorProps {
+ error: Error;
+}
+
+export function ServerError({ error }: ServerErrorProps) {
+ return (
+
+
Oops!
+
Sorry, an unexpected error has occurred.
+
{error.name}
+
+ {error.message}
+
+
+ );
+}
diff --git a/frontend/src/components/common/Subheader.tsx b/frontend/src/components/common/Subheader.tsx
new file mode 100644
index 0000000..0645827
--- /dev/null
+++ b/frontend/src/components/common/Subheader.tsx
@@ -0,0 +1,18 @@
+import { pageTitleAtom } from "@/atoms/navigation.atoms";
+import { useAtomValue } from "jotai";
+
+export function Subheader() {
+ const pageTitle = useAtomValue(pageTitleAtom);
+
+ return (
+
+ );
+}
+
+export default Subheader;
diff --git a/frontend/src/components/common/SwitchField.tsx b/frontend/src/components/common/SwitchField.tsx
new file mode 100644
index 0000000..00d64a7
--- /dev/null
+++ b/frontend/src/components/common/SwitchField.tsx
@@ -0,0 +1,75 @@
+import { Field } from "@/types/catalog.type";
+import { Switch } from "@headlessui/react";
+import clsx from "clsx";
+import { Controller, useFormContext } from "react-hook-form";
+
+interface SwitchFieldProps {
+ field: Field;
+}
+
+export default function SwitchField({ field }: SwitchFieldProps) {
+ const { control } = useFormContext();
+
+ let defaultValue = false;
+
+ if (field.default.toLowerCase() === "true") {
+ defaultValue = true;
+ }
+
+ return (
+
+
+
+
+ {field.required && "Required"}
+
+
+
+ (
+
+
+
+ {field.description}
+
+
+ {
+ if (onChange) {
+ onChange(v);
+ }
+ }}
+ className={clsx(
+ value ? "bg-indigo-600" : "bg-gray-200",
+ "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2",
+ )}
+ >
+
+
+
+ )}
+ />
+
+
+ );
+}
diff --git a/frontend/src/components/common/TextField.tsx b/frontend/src/components/common/TextField.tsx
new file mode 100644
index 0000000..1849b60
--- /dev/null
+++ b/frontend/src/components/common/TextField.tsx
@@ -0,0 +1,63 @@
+import { Field } from "@/types/catalog.type";
+import {
+ InputHTMLAttributes,
+ useEffect,
+ forwardRef,
+ ForwardedRef,
+} from "react";
+import { useFormContext } from "react-hook-form";
+
+export interface TextFieldProps extends InputHTMLAttributes {
+ field: Field;
+}
+
+const TextField = forwardRef(
+ (
+ { field, ...props }: TextFieldProps,
+ ref: ForwardedRef,
+ ) => {
+ const { register, unregister } = useFormContext();
+
+ useEffect(
+ () => () => {
+ unregister(field.slug);
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [],
+ );
+
+ return (
+
+
+
+
+ {field.required && "Required"}
+
+
+
+
+
+
+ );
+ },
+);
+
+export default TextField;
diff --git a/frontend/src/components/common/TextareaField.tsx b/frontend/src/components/common/TextareaField.tsx
new file mode 100644
index 0000000..59ed64e
--- /dev/null
+++ b/frontend/src/components/common/TextareaField.tsx
@@ -0,0 +1,63 @@
+import { Field } from "@/types/catalog.type";
+import {
+ TextareaHTMLAttributes,
+ useEffect,
+ forwardRef,
+ ForwardedRef,
+} from "react";
+import { useFormContext } from "react-hook-form";
+
+interface TextareaFieldProps
+ extends TextareaHTMLAttributes {
+ field: Field;
+}
+
+const TextareaField = forwardRef(
+ (
+ { field, ...props }: TextareaFieldProps,
+ ref: ForwardedRef,
+ ) => {
+ const { register, unregister } = useFormContext();
+
+ useEffect(
+ () => () => {
+ unregister(field.slug);
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [],
+ );
+
+ return (
+
+
+
+
+ {field.required && "Required"}
+
+
+
+
+
+ {field.description}
+
+
+
+ );
+ },
+);
+
+export default TextareaField;
diff --git a/frontend/src/components/self-service/SelfService.tsx b/frontend/src/components/self-service/SelfService.tsx
index d641345..2c0998d 100644
--- a/frontend/src/components/self-service/SelfService.tsx
+++ b/frontend/src/components/self-service/SelfService.tsx
@@ -1,59 +1,18 @@
-import {useState} from "react";
-import {classNames} from "@/lib/utils.ts";
-import SelfServiceTabService from "@/components/self-service/SelfServiceTabService.tsx";
-import SelfServiceTabRun from "@/components/self-service/SelfServiceTabRun.tsx";
-
-const tabs = [
- {name: 'Services', href: '#', current: true},
- {name: 'Runs', href: '#', current: false},
-]
-
-interface Props {
- catalogSlug: string
- services: any[]
+import { withPageTitle } from "@/hoc/with-page-title";
+import { Provider } from "jotai";
+import { Suspense } from "react";
+import SelfServiceCatalog from "./SelfServiceCatalog";
+
+export function SelfService() {
+ return (
+
+ Loading...}>
+
+
+
+ );
}
+const SelfServiceWithTitle = withPageTitle(SelfService, "Self Service");
-export default function SelfService({catalogSlug, services}: Props) {
- const [activeTab, setActiveTab] = useState(tabs[0])
-
- return <>
-
-
-
- {
- activeTab.name === 'Services' &&
- }
-
- {
- activeTab.name === 'Runs' &&
- }
-
- >
-}
+export default SelfServiceWithTitle;
diff --git a/frontend/src/components/self-service/SelfServiceCard.tsx b/frontend/src/components/self-service/SelfServiceCard.tsx
index 4229714..40d7610 100644
--- a/frontend/src/components/self-service/SelfServiceCard.tsx
+++ b/frontend/src/components/self-service/SelfServiceCard.tsx
@@ -1,84 +1,111 @@
-import {classNames} from "@/lib/utils.ts";
-import {TargetIcon} from "lucide-react";
-import {TrashIcon} from "@heroicons/react/24/outline";
+import { ThemeColors } from "@/enums/theme-colors.enum";
+import { Service } from "@/types/catalog.type";
+import { Menu, Transition } from "@headlessui/react";
+import {
+ EllipsisHorizontalIcon,
+ PlusIcon,
+ TrashIcon,
+} from "@heroicons/react/24/outline";
+import clsx from "clsx";
+import { Fragment, useCallback } from "react";
+import { Button } from "../common/Button";
-function getIcon(icon?: string): JSX.Element {
- switch (icon?.toLowerCase()) {
- case 'target':
- return
- case 'trash':
- return
- default:
- return
- }
+export interface SelfServiceCardProps {
+ service: Service;
+ onCreateClicked: (slug: string) => void;
+ onEditClicked: (slug: string) => void;
+ onViewRunsClicked: (slug: string) => void;
}
-interface Props {
- title: string;
- description: string;
- icon: string | undefined;
- iconColor: string | undefined;
- index: number;
- totalCards: number;
- onClick: () => void;
-}
-
-export default function SelfServiceCard({title, description, icon, iconColor, index, totalCards, onClick}: Props) {
- let iconBackground = 'bg-indigo-50'
- if (iconColor) {
- iconBackground = `bg-${iconColor}-50`
- }
-
- let iconForeground = 'text-indigo-700'
- if (iconColor) {
- iconForeground = `text-${iconColor}-700`
- }
-
- const iconElement = getIcon(icon)
+export default function SelfServiceCard({
+ service,
+ onCreateClicked,
+ onEditClicked,
+ onViewRunsClicked,
+}: SelfServiceCardProps) {
+ const getIcon = useCallback((icon: string) => {
+ switch (icon?.toLowerCase()) {
+ case "target":
+ return ;
+ case "trash":
+ return ;
+ default:
+ return ;
+ }
+ }, []);
return (
-
-
-
-
- {iconElement}
-
+
+
+
+ {getIcon(service.icon)}
-
-
-
- {description}
-
+
+ {service.name}
-
+
+ Open options
+
+
+
+
+
+ {({ active }) => (
+ onViewRunsClicked(service.slug)}
+ >
+ View, {service.name}
+
+ )}
+
+
+ {({ active }) => (
+ onEditClicked(service.slug)}
+ >
+ Edit, {service.name}
+
+ )}
+
+
+
+
+
+
+
+
- Fields
+ - {service.fields.length || "---"}
+
+
+
+
{service.description}
+
+
+
- )
+ );
}
diff --git a/frontend/src/components/self-service/SelfServiceCatalog.tsx b/frontend/src/components/self-service/SelfServiceCatalog.tsx
new file mode 100644
index 0000000..548ded0
--- /dev/null
+++ b/frontend/src/components/self-service/SelfServiceCatalog.tsx
@@ -0,0 +1,82 @@
+import {
+ catalogsAtom,
+ selectedCatalogSlugAtom,
+ selectedServiceSlugAtom,
+} from "@/atoms/catalog.atoms";
+import { dialogOpenedAtomFamily } from "@/atoms/dialog.atoms";
+import EmptyState from "@/components/common/EmptyState.tsx";
+import SelfServiceCard from "@/components/self-service/SelfServiceCard.tsx";
+import { DialogIds } from "@/enums/dialog-ids.enum";
+import { useAtom, useSetAtom } from "jotai";
+import { SelfServiceCreateDialog } from "./SelfServiceCreateDialog";
+import { SelfServiceEditDialog } from "./SelfServiceEditDialog";
+
+export default function SelfServiceCatalog() {
+ const [{ data }] = useAtom(catalogsAtom);
+ const setSelectedCatalogSlug = useSetAtom(selectedCatalogSlugAtom);
+ const setSelectedServiceSlug = useSetAtom(selectedServiceSlugAtom);
+
+ const [createDialogOpened, setCreateDialogOpened] = useAtom(
+ dialogOpenedAtomFamily(DialogIds.CreateService),
+ );
+
+ const [editDialogOpened, setEditDialogOpened] = useAtom(
+ dialogOpenedAtomFamily(DialogIds.EditService),
+ );
+
+ if (data.length === 0) {
+ return (
+
+
+
+ );
+ }
+
+ const handleOnCreateClicked = (serviceSlug: string, catalogSlug: string) => {
+ setCreateDialogOpened(true);
+ setSelectedCatalogSlug(catalogSlug);
+ setSelectedServiceSlug(serviceSlug);
+ };
+
+ const handleOnEditClicked = (serviceSlug: string, catalogSlug: string) => {
+ setEditDialogOpened(true);
+ setSelectedCatalogSlug(catalogSlug);
+ setSelectedServiceSlug(serviceSlug);
+ };
+
+ return (
+ <>
+
+ {data.map((catalog) => (
+ -
+
{catalog.name}
+
+ {catalog.services.map((service) => (
+ -
+
+ handleOnCreateClicked(service.slug, catalog.slug)
+ }
+ onEditClicked={() =>
+ handleOnEditClicked(service.slug, catalog.slug)
+ }
+ onViewRunsClicked={() => {}}
+ />
+
+ ))}
+
+
+ ))}
+
+ {createDialogOpened &&
}
+ {editDialogOpened &&
}
+ >
+ );
+}
diff --git a/frontend/src/components/self-service/SelfServiceCreateDialog.tsx b/frontend/src/components/self-service/SelfServiceCreateDialog.tsx
new file mode 100644
index 0000000..e9d9a8a
--- /dev/null
+++ b/frontend/src/components/self-service/SelfServiceCreateDialog.tsx
@@ -0,0 +1,78 @@
+import { dialogOpenedAtomFamily } from "@/atoms/dialog.atoms";
+import {
+ executeServiceMutation,
+ selectedServiceAtom,
+} from "@/atoms/service.atoms";
+import { DialogIds } from "@/enums/dialog-ids.enum";
+import { createDynamicSchema } from "@/lib/create-dynamic-schema";
+import { Field } from "@/types/catalog.type";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { useAtomValue, useSetAtom } from "jotai";
+import { useForm } from "react-hook-form";
+import Dialog from "../common/Dialog";
+import DyanmicFields from "../common/DynamicFields";
+import { Form } from "../common/Form";
+import { FormButtons } from "../common/FormButtons";
+import { useRef } from "react";
+
+export type ExecuteServicePayload = {
+ name: string;
+ description: string;
+ ttl: number;
+ seed: boolean;
+};
+
+export function SelfServiceCreateDialog() {
+ const selectedService = useAtomValue(selectedServiceAtom);
+ const setCreateDialogOpened = useSetAtom(
+ dialogOpenedAtomFamily(DialogIds.CreateService),
+ );
+
+ const initialFocus = {
+ name: "name",
+ ref: useRef(null),
+ };
+
+ const form = useForm({
+ resolver: yupResolver(
+ createDynamicSchema
(
+ selectedService?.fields as Field[],
+ ),
+ ),
+ mode: "onChange",
+ });
+
+ const {
+ mutateAsync: executeService,
+ status,
+ error,
+ } = useAtomValue(executeServiceMutation);
+
+ const handleSubmit = async (payload: ExecuteServicePayload) => {
+ await executeService(payload);
+ setCreateDialogOpened(false);
+ };
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/self-service/SelfServiceEditDialog.tsx b/frontend/src/components/self-service/SelfServiceEditDialog.tsx
new file mode 100644
index 0000000..bd1037c
--- /dev/null
+++ b/frontend/src/components/self-service/SelfServiceEditDialog.tsx
@@ -0,0 +1,16 @@
+import { DialogIds } from "@/enums/dialog-ids.enum";
+import { useAtomValue } from "jotai";
+import Dialog from "../common/Dialog";
+import { selectedServiceAtom } from "@/atoms/service.atoms";
+
+export interface SelfServiceEditProps {}
+
+export function SelfServiceEditDialog() {
+ const selectedService = useAtomValue(selectedServiceAtom);
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/self-service/SelfServiceRunSidebar.tsx b/frontend/src/components/self-service/SelfServiceRunSidebar.tsx
new file mode 100644
index 0000000..7eb04ac
--- /dev/null
+++ b/frontend/src/components/self-service/SelfServiceRunSidebar.tsx
@@ -0,0 +1,18 @@
+import { runsAtom } from "@/atoms/run.atoms";
+import EmptyState from "@/components/common/EmptyState.tsx";
+import SelfServiceRunTable from "@/components/self-service/SelfServiceRunTable.tsx";
+import { useAtom } from "jotai";
+
+export default function SelfServiceRunSidebar() {
+ const [{ data, status, error }] = useAtom(runsAtom);
+
+ if (data.length === 0) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+}
diff --git a/frontend/src/components/self-service/SelfServiceRunTable.tsx b/frontend/src/components/self-service/SelfServiceRunTable.tsx
index 8befd02..172ccb0 100644
--- a/frontend/src/components/self-service/SelfServiceRunTable.tsx
+++ b/frontend/src/components/self-service/SelfServiceRunTable.tsx
@@ -1,58 +1,61 @@
-import {classNames, millisToHumanTime} from "@/lib/utils.ts";
+import { classNames, millisToHumanTime } from "@/lib/utils.ts";
function getStatusStyle(status: string): string {
- if (status === 'QUEUED') {
- return 'text-orange-400 bg-orange-400/10'
+ if (status === "QUEUED") {
+ return "text-orange-400 bg-orange-400/10";
}
- if (status === 'RUNNING') {
- return 'text-cyan-400 bg-cyan-400/10'
+ if (status === "RUNNING") {
+ return "text-cyan-400 bg-cyan-400/10";
}
- if (status === 'SUCCESS') {
- return 'text-green-400 bg-green-400/10'
+ if (status === "SUCCESS") {
+ return "text-green-400 bg-green-400/10";
}
- if (status === 'FAILURE') {
- return 'text-rose-400 bg-rose-400/10'
+ if (status === "FAILURE") {
+ return "text-rose-400 bg-rose-400/10";
}
- return 'text-gray-400 bg-gray-400/10'
+ return "text-gray-400 bg-gray-400/10";
}
function getTotalExecutionTime(tasks: any[]): number {
if (tasks === undefined || tasks.length === 0) {
- return 0
+ return 0;
}
return tasks.reduce((acc, task) => {
- if (task.post_validate_output && task.post_validate_output.execution_time_in_millis) {
- return acc + task.post_validate_output.execution_time_in_millis
+ if (
+ task.post_validate_output &&
+ task.post_validate_output.execution_time_in_millis
+ ) {
+ return acc + task.post_validate_output.execution_time_in_millis;
}
- return acc
- }, 0)
+ return acc;
+ }, 0);
}
function totalSuccessTasks(tasks: any[]): number {
if (tasks === undefined || tasks.length === 0) {
- return 0
+ return 0;
}
return tasks.reduce((acc, task) => {
- if (task.status === 'SUCCESS') {
- return acc + 1
+ if (task.status === "SUCCESS") {
+ return acc + 1;
}
- return acc
- }, 0)
+ return acc;
+ }, 0);
}
interface Props {
- runs: any[]
+ runs: any[];
}
-export default function SelfServiceRunTable({runs}: Props): JSX.Element {
+export default function SelfServiceRunTable({ runs }: Props): JSX.Element {
return (
@@ -60,64 +63,96 @@ export default function SelfServiceRunTable({runs}: Props): JSX.Element {
-
- |
- Date
- |
-
- Service
- |
-
- Status
- |
-
- Tasks
- |
-
- Execution Time
- |
-
- Edit
- |
-
+
+ |
+ Date
+ |
+
+ Service
+ |
+
+ Status
+ |
+
+ Tasks
+ |
+
+ Execution Time
+ |
+
+ Edit
+ |
+
- {runs.map((run) => (
-
- |
- {run.created_at}
- |
-
- {run.service_slug}
- |
-
-
-
-
-
+ {runs.map((run) => (
+
+ |
+ {run.created_at}
+ |
+
+ {run.service_slug}
+ |
+
+
+
+
+
+ {run.status}
+
- {run.status}
-
- |
- {totalSuccessTasks(run.tasks)}/{run.tasks.length} |
- {millisToHumanTime(getTotalExecutionTime(run.tasks))} |
-
-
- Details, {run.name}
-
- |
-
- ))}
+ |
+
+ {totalSuccessTasks(run.tasks)}/{run.tasks.length}
+ |
+
+ {millisToHumanTime(getTotalExecutionTime(run.tasks))}
+ |
+
+
+ Details, {run.name}
+
+ |
+
+ ))}
- )
+ );
}
diff --git a/frontend/src/components/self-service/SelfServiceSlideOver.tsx b/frontend/src/components/self-service/SelfServiceSlideOver.tsx
deleted file mode 100644
index 0ce6b41..0000000
--- a/frontend/src/components/self-service/SelfServiceSlideOver.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import {Fragment} from 'react'
-import {Dialog, Transition} from '@headlessui/react'
-import {XMarkIcon} from '@heroicons/react/24/outline'
-import TextField from "@/components/self-service/fields/TextField.tsx";
-import TextareaField from "@/components/self-service/fields/TextareaField.tsx";
-import SwitchField from "@/components/self-service/fields/SwitchField.tsx";
-import {API_URL} from "@/config.ts";
-
-interface Props {
- catalogSlug: string;
- service: any;
- onClose: () => void;
-}
-
-function getField(field: any, onChange: (value: any) => void): JSX.Element {
- switch (field.type) {
- case 'text':
- return
- case 'number':
- return
- case 'textarea':
- return
- case 'boolean':
- return onChange(v)}/>
- default:
- return '{field.type}' is not a supported field
- }
-}
-
-export default function SelfServiceSlideOver({catalogSlug, service, onClose}: Props): JSX.Element {
-
- const handleSubmit = (event: any) => {
- event.preventDefault()
-
- const fields = []
- for (const [id, value] of new FormData(event.target)) {
- fields.push([id, value])
- }
-
- const payload = Object.fromEntries(fields)
-
- console.log({payload: payload})
-
- fetch(`${API_URL}/catalogs/${catalogSlug}/services/${service.slug}/execute`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({payload: payload}),
- }).then(
- (res) => res.json(),
- ).then((data) => {
- console.log(data)
- })
- }
-
- return (
-
- )
-}
diff --git a/frontend/src/components/self-service/SelfServiceTabRun.tsx b/frontend/src/components/self-service/SelfServiceTabRun.tsx
deleted file mode 100644
index 85458ca..0000000
--- a/frontend/src/components/self-service/SelfServiceTabRun.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import EmptyState from "@/components/common/EmptyState.tsx";
-import {useQuery} from "@tanstack/react-query";
-import {API_URL} from "@/config.ts";
-import {useEffect, useState} from "react";
-import SelfServiceRunTable from "@/components/self-service/SelfServiceRunTable.tsx";
-
-interface Props {
- catalogSlug: string
-}
-
-export default function SelfServiceTabRun({catalogSlug}: Props) {
- const [runs, setRuns] = useState([])
-
- const {status, data} = useQuery({
- queryKey: [`catalogs-${catalogSlug}-runs`],
- queryFn: () =>
- fetch(`${API_URL}/catalogs/${catalogSlug}/runs`).then(
- (res) => res.json(),
- ),
- })
-
- useEffect(() => {
- if (status === 'success') {
- setRuns(data.results)
- }
- }, [status, data]);
-
- if (runs.length === 0) {
- return
-
-
- }
-
- return
-}
diff --git a/frontend/src/components/self-service/SelfServiceTabService.tsx b/frontend/src/components/self-service/SelfServiceTabService.tsx
deleted file mode 100644
index ea122b9..0000000
--- a/frontend/src/components/self-service/SelfServiceTabService.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import {useState} from "react";
-import EmptyState from "@/components/common/EmptyState.tsx";
-import SelfServiceCard from "@/components/self-service/SelfServiceCard.tsx";
-import {Transition} from "@headlessui/react";
-import SelfServiceSlideOver from "@/components/self-service/SelfServiceSlideOver.tsx";
-
-interface Props {
- catalogSlug: string
- services: any[]
-}
-
-export default function SelfServiceTabService({catalogSlug, services}: Props) {
- const [showSideOver, setShowSideOver] = useState({show: false, service: {}}) // pass service
-
- if (services.length === 0) {
- return
-
-
- }
-
- return <>
-
- {
- services.map((service, idx) => {
- return {
- setShowSideOver({show: true, service: service})
- }}/>
- })
- }
-
-
- setShowSideOver({
- show: false,
- service: showSideOver.service
- })} catalogSlug={catalogSlug}/>
-
- >
-}
diff --git a/frontend/src/components/self-service/fields/SwitchField.tsx b/frontend/src/components/self-service/fields/SwitchField.tsx
deleted file mode 100644
index 2f553f8..0000000
--- a/frontend/src/components/self-service/fields/SwitchField.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import {Switch} from "@headlessui/react";
-import {classNames} from "@/lib/utils.ts";
-import {useState} from "react";
-
-interface Props {
- field: any;
- onChange?: (value: boolean) => void;
-}
-
-export default function SwitchField({field, onChange}: Props) {
- let defaultValue = false;
- if (field.default.toLowerCase() === 'true') {
- defaultValue = true;
- }
-
- const [enabled, setEnabled] = useState(defaultValue)
-
- return (
-
-
-
-
- {field.required && 'Required'}
-
-
-
-
-
-
- {field.description}
-
-
- {
- if (onChange) {
- onChange(v)
- }
- setEnabled(v)
- }}
- className={classNames(
- enabled ? 'bg-indigo-600' : 'bg-gray-200',
- 'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2'
- )}
- >
-
-
-
-
-
- )
-}
diff --git a/frontend/src/components/self-service/fields/TextField.tsx b/frontend/src/components/self-service/fields/TextField.tsx
deleted file mode 100644
index eaaa70f..0000000
--- a/frontend/src/components/self-service/fields/TextField.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import {InputHTMLAttributes} from "react";
-
-interface Props {
- field: any;
- inputMode?: InputHTMLAttributes['inputMode'];
-}
-
-export default function TextField({field, inputMode}: Props) {
-
- return (
-
-
-
-
- {field.required && 'Required'}
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/components/self-service/fields/TextareaField.tsx b/frontend/src/components/self-service/fields/TextareaField.tsx
deleted file mode 100644
index 60a460d..0000000
--- a/frontend/src/components/self-service/fields/TextareaField.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-interface Props {
- field: any;
-}
-
-export default function TextareaField({field}: Props) {
-
- return (
-
-
-
-
- {field.required && 'Required'}
-
-
-
-
-
{field.description}
-
-
- )
-}
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
index bfeb16b..eddf36c 100644
--- a/frontend/src/config.ts
+++ b/frontend/src/config.ts
@@ -1 +1,2 @@
-export const API_URL: string = import.meta.env.VITE_API_URL || "http://localhost:9999";
+export const API_URL: string =
+ import.meta.env.VITE_API_URL || "http://localhost:9999";
diff --git a/frontend/src/enums/dialog-ids.enum.ts b/frontend/src/enums/dialog-ids.enum.ts
new file mode 100644
index 0000000..a46a3f0
--- /dev/null
+++ b/frontend/src/enums/dialog-ids.enum.ts
@@ -0,0 +1,4 @@
+export enum DialogIds {
+ CreateService = "CreateService",
+ EditService = "EditService",
+}
diff --git a/frontend/src/enums/theme-colors.enum.ts b/frontend/src/enums/theme-colors.enum.ts
new file mode 100644
index 0000000..020d577
--- /dev/null
+++ b/frontend/src/enums/theme-colors.enum.ts
@@ -0,0 +1,4 @@
+export enum ThemeColors {
+ PRIMARY = "primary",
+ SECONDARY = "secondary",
+}
diff --git a/frontend/src/error-page.jsx b/frontend/src/error-page.jsx
deleted file mode 100644
index 28c22ce..0000000
--- a/frontend/src/error-page.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import {useRouteError} from 'react-router-dom'
-
-export default function ErrorPage() {
- const error = useRouteError()
- console.error(error)
-
- return (
-
Oops!
-
Sorry, an unexpected error has occurred.
-
- {error.statusText || error.message}
-
-
)
-}
diff --git a/frontend/src/hoc/with-page-title.tsx b/frontend/src/hoc/with-page-title.tsx
new file mode 100644
index 0000000..8fe1db0
--- /dev/null
+++ b/frontend/src/hoc/with-page-title.tsx
@@ -0,0 +1,18 @@
+import { pageTitleAtom } from "@/atoms/navigation.atoms";
+import { useSetAtom } from "jotai";
+import React, { useEffect } from "react";
+
+export function withPageTitle(
+ WrappedComponent: React.ComponentType,
+ title: string
+) {
+ return function WithPageTitle(props: T) {
+ const setPageTitle = useSetAtom(pageTitleAtom);
+
+ useEffect(() => {
+ setPageTitle(title);
+ }, [setPageTitle]);
+
+ return ;
+ };
+}
diff --git a/frontend/src/lib/atom-with-swr.ts b/frontend/src/lib/atom-with-swr.ts
new file mode 100644
index 0000000..e307706
--- /dev/null
+++ b/frontend/src/lib/atom-with-swr.ts
@@ -0,0 +1,9 @@
+import { Atom, atom } from "jotai";
+import { unwrap } from "jotai/utils";
+
+export const atomWithSwr = (baseAtom: Atom): Atom> => {
+ const unwrappedAtom = unwrap(baseAtom, (prev) => prev);
+ return atom((get) => {
+ return get(unwrappedAtom) ?? get(baseAtom);
+ });
+};
diff --git a/frontend/src/lib/create-dynamic-schema.ts b/frontend/src/lib/create-dynamic-schema.ts
new file mode 100644
index 0000000..2a8bb21
--- /dev/null
+++ b/frontend/src/lib/create-dynamic-schema.ts
@@ -0,0 +1,52 @@
+import { Field } from "@/types/catalog.type";
+import * as yup from "yup";
+
+export type FieldSchema =
+ | yup.BooleanSchema
+ | yup.StringSchema
+ | yup.NumberSchema;
+
+/**
+ * Creates a dynamic schema from an array of field
+ * to be used inside of the execute service flow
+ * @param {Field[]} fields
+ * @returns {yup.AnyObject}
+ */
+export const createDynamicSchema = (
+ fields: Field[]
+): yup.AnyObjectSchema => {
+ const schema: Record = {} as Record<
+ keyof T,
+ FieldSchema
+ >;
+
+ for (const field of fields as Field[]) {
+ if (field.required) {
+ switch (field.type) {
+ case "boolean":
+ schema[field.slug as keyof T] = yup.boolean().required();
+ break;
+ case "number":
+ schema[field.slug as keyof T] = yup.number().required();
+ break;
+ default:
+ schema[field.slug as keyof T] = yup.string().required();
+ break;
+ }
+ } else {
+ switch (field.type) {
+ case "boolean":
+ schema[field.slug as keyof T] = yup.boolean();
+ break;
+ case "number":
+ schema[field.slug as keyof T] = yup.number();
+ break;
+ default:
+ schema[field.slug as keyof T] = yup.string();
+ break;
+ }
+ }
+ }
+
+ return yup.object(schema);
+};
diff --git a/frontend/src/lib/make-query-atoms.ts b/frontend/src/lib/make-query-atoms.ts
new file mode 100644
index 0000000..2ed5df7
--- /dev/null
+++ b/frontend/src/lib/make-query-atoms.ts
@@ -0,0 +1,78 @@
+import { FetchStatus, QueryFunction } from "@tanstack/react-query";
+import { Atom, Getter, atom } from "jotai";
+import {
+ AtomWithSuspenseQueryResult,
+ atomWithSuspenseQuery,
+ queryClientAtom,
+} from "jotai-tanstack-query";
+
+type QueryFn = QueryFunction<
+ Awaited,
+ (string | TParams)[],
+ never
+>;
+/**
+ * Generates an array of atoms for a query
+ * Under the hood, this uses jotai/tanstack-query to create the relevant atoms
+ *
+ * It returns an array composed of three atoms :
+ *
+ * - The Stale-while-revalidate data atom, which is async until first fetch
+ * - The status atom (fetchStatus)
+ * - The query atom (which is async)
+ *
+ * Example :
+ *
+ * ```typescript
+ * export const [productsAtom, productsStatusAtom] = makeQueryAtoms(
+ * 'inventoryProducts',
+ * (get) => get(fetchProductsParamsAtom),
+ * (params) => () => {
+ * return fetch(params)
+ * }
+ * )
+ * ```
+ * @param key React-Query query key
+ * @param getParameters Function to get parameters for the query
+ * @param queryFn Function to call to generate the query
+ * @returns
+ */
+export function makeQueryAtoms(
+ key: string,
+ getParameters: (get: Getter) => TParams,
+ queryFn: (params: TParams) => QueryFn, TParams>
+): [
+ Atom>,
+ Atom<{ fetchStatus: FetchStatus }>
+] {
+ const queryAtom = atomWithSuspenseQuery((get) => {
+ const params = getParameters(get);
+
+ const qParams = {
+ queryKey: [key, params],
+ queryFn: queryFn(params),
+ };
+
+ return qParams;
+ });
+ queryAtom.debugLabel = key;
+
+ const statusAtom = atom((get) => {
+ get(queryAtom);
+ const params = getParameters ? getParameters(get) : undefined;
+ const queryClient = get(queryClientAtom);
+ const state = queryClient.getQueryState>([
+ key,
+ params,
+ ]);
+ return state
+ ? {
+ fetchStatus: state.fetchStatus,
+ }
+ : {
+ fetchStatus: "idle" as FetchStatus,
+ };
+ });
+
+ return [queryAtom, statusAtom];
+}
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index 2455bb1..3cf64a8 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -1,23 +1,23 @@
-import {type ClassValue, clsx} from "clsx"
-import {twMerge} from "tailwind-merge"
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
export function classNames(...classes: any[]): string {
- return classes.filter(Boolean).join(' ')
+ return classes.filter(Boolean).join(" ");
}
export function millisToHumanTime(duration: number): string {
- let milliseconds = Math.floor((duration % 1000) / 100)
- let seconds = Math.floor((duration / 1000) % 60)
- let minutes = Math.floor((duration / (1000 * 60)) % 60)
- let hours = Math.floor((duration / (1000 * 60 * 60)) % 24)
+ let milliseconds = Math.floor((duration % 1000) / 100);
+ let seconds = Math.floor((duration / 1000) % 60);
+ let minutes = Math.floor((duration / (1000 * 60)) % 60);
+ let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
- let s_hours = (hours < 10) ? "0" + hours : hours;
- let s_minutes = (minutes < 10) ? "0" + minutes : minutes;
- let s_seconds = (seconds < 10) ? "0" + seconds : seconds;
+ let s_hours = hours < 10 ? "0" + hours : hours;
+ let s_minutes = minutes < 10 ? "0" + minutes : minutes;
+ let s_seconds = seconds < 10 ? "0" + seconds : seconds;
return s_hours + ":" + s_minutes + ":" + s_seconds + "." + milliseconds;
}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index a06eb7a..b843d88 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,38 +1,29 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import './main.css'
-import {QueryClient, QueryClientProvider,} from '@tanstack/react-query'
-import {createBrowserRouter, RouterProvider,} from "react-router-dom";
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { RouterProvider, createBrowserRouter } from "react-router-dom";
+import "./main.css";
-import Root from "./routes/root";
-// @ts-ignore
-import ErrorPage from "@/error-page.jsx";
import AppShell from "@/components/AppShell.tsx";
+import SelfServiceWithTitle from "./components/self-service/SelfService";
+import { Providers } from "./providers";
const router = createBrowserRouter([
{
path: "/",
- element: ,
- errorElement: ,
+ element: ,
children: [
{
- index: true,
- element: ,
+ path: "/self-service",
+ element: ,
},
- // {
- // path: "/catalogs/:catalogSlug/services",
- // Component: AppShell,
- // }
],
},
]);
-const queryClient = new QueryClient()
-
-ReactDOM.createRoot(document.getElementById('root')!).render(
+ReactDOM.createRoot(document.getElementById("root")!).render(
-
-
-
- ,
-)
+
+
+
+
+);
diff --git a/frontend/src/providers.tsx b/frontend/src/providers.tsx
new file mode 100644
index 0000000..83b942c
--- /dev/null
+++ b/frontend/src/providers.tsx
@@ -0,0 +1,36 @@
+import { QueryClient } from "@tanstack/query-core";
+import { QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { Provider as JotaiProvider, createStore } from "jotai";
+import { DevTools } from "jotai-devtools";
+
+function getQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000,
+ },
+ },
+ });
+}
+
+export const Providers: React.FC<{
+ children: React.ReactNode;
+}> = ({ children }) => {
+ const queryClient = getQueryClient();
+ const jotaiStore = createStore();
+
+ return (
+
+
+ {process.env.NODE_ENV === "development" && (
+ <>
+
+
+ >
+ )}
+ {children}
+
+
+ );
+};
diff --git a/frontend/src/routes/root.tsx b/frontend/src/routes/root.tsx
deleted file mode 100644
index 2b73e6d..0000000
--- a/frontend/src/routes/root.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import AppShell from "@/components/AppShell.tsx";
-
-
-export default function Root() {
- return
-}
diff --git a/frontend/src/types/Catalog.ts b/frontend/src/types/Catalog.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/src/types/catalog.type.ts b/frontend/src/types/catalog.type.ts
new file mode 100644
index 0000000..592b29d
--- /dev/null
+++ b/frontend/src/types/catalog.type.ts
@@ -0,0 +1,34 @@
+export type Catalog = {
+ slug: string;
+ name: string;
+ description: string;
+ services: Service[];
+};
+
+export type Service = {
+ slug: string;
+ name: string;
+ description: string;
+ icon: string;
+ icon_color: string;
+ fields: Field[];
+ validate: Command[];
+ post_validate: Command[];
+};
+
+export type Field = {
+ slug: string;
+ title: string;
+ description: string;
+ placeholder?: string;
+ type: string;
+ default: string;
+ required: boolean;
+ autocomplete_fetcher: string;
+};
+
+export type Command = {
+ command: string[];
+ timeout: number | null;
+ output_model?: string | null;
+};
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 3e155b5..6252543 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -2,10 +2,10 @@
module.exports = {
darkMode: ["class"],
content: [
- './pages/**/*.{ts,tsx}',
- './components/**/*.{ts,tsx}',
- './app/**/*.{ts,tsx}',
- './src/**/*.{ts,tsx}',
+ "./pages/**/*.{ts,tsx}",
+ "./components/**/*.{ts,tsx}",
+ "./app/**/*.{ts,tsx}",
+ "./src/**/*.{ts,tsx}",
],
theme: {
container: {
@@ -58,12 +58,12 @@ module.exports = {
},
keyframes: {
"accordion-down": {
- from: {height: 0},
- to: {height: "var(--radix-accordion-content-height)"},
+ from: { height: 0 },
+ to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
- from: {height: "var(--radix-accordion-content-height)"},
- to: {height: 0},
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: 0 },
},
},
animation: {
@@ -72,8 +72,5 @@ module.exports = {
},
},
},
- plugins: [
- require("tailwindcss-animate"),
- require('@tailwindcss/forms')
- ],
-}
+ plugins: [require("tailwindcss-animate"), require("@tailwindcss/forms")],
+};