diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..654f09ac --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "tabWidth": 2 +} diff --git a/benefit-decision-toolkit.code-workspace b/benefit-decision-toolkit.code-workspace index 722c4973..5b68da14 100644 --- a/benefit-decision-toolkit.code-workspace +++ b/benefit-decision-toolkit.code-workspace @@ -22,7 +22,17 @@ "java.import.maven.enabled": true, "java.compile.nullAnalysis.mode": "automatic", + // Prettier + "prettier.requireConfig": true, + // Node.js settings for frontend modules + "[javascript, typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "typescript.preferences.importModuleSpecifier": "non-relative", + "javascript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.includePackageJsonAutoImports": "auto", "typescript.suggest.autoImports": true, @@ -108,4 +118,4 @@ } ] } -} \ No newline at end of file +} diff --git a/builder-frontend/.env.example b/builder-frontend/.env.example index 78624a74..1bf42416 100644 --- a/builder-frontend/.env.example +++ b/builder-frontend/.env.example @@ -1,5 +1,5 @@ VITE_API_KEY=AIzaSyDummyKeyForEmulator -VITE_API_URL=http://localhost:8081/api +VITE_API_URL=http://localhost:8081 VITE_APP_ID=1:123456789:web:abcdef VITE_AUTH_DOMAIN=localhost:9099 VITE_MEASUREMENT_ID=G-XXXXXXXXXX diff --git a/builder-frontend/index.html b/builder-frontend/index.html index 7ca8c16e..a9fdfc70 100644 --- a/builder-frontend/index.html +++ b/builder-frontend/index.html @@ -1,4 +1,4 @@ - + @@ -10,7 +10,6 @@ integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous" /> - Benefit Decision Toolkit
diff --git a/builder-frontend/package-lock.json b/builder-frontend/package-lock.json index 1fa01e45..52ffae73 100644 --- a/builder-frontend/package-lock.json +++ b/builder-frontend/package-lock.json @@ -14,11 +14,13 @@ "@kogito-tooling/kie-editors-standalone": "^0.16.0", "@solid-primitives/deep": "^0.3.3", "@solid-primitives/resource": "^0.4.2", + "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.15.3", "dmn-js": "^17.2.1", "fast-xml-parser": "^5.2.5", "firebase": "^11.9.1", "lodash.debounce": "^4.0.8", + "lucide-solid": "^0.561.0", "solid-bootstrap": "^1.0.21", "solid-js": "^1.9.5", "solid-toast": "^0.5.0", @@ -27,10 +29,14 @@ "devDependencies": { "@tailwindcss/forms": "^0.5.10", "@tailwindcss/postcss": "^4.1.8", + "@tailwindcss/vite": "^4.1.18", + "@types/lodash.debounce": "^4.0.9", "autoprefixer": "^10.4.21", "postcss": "^8.5.4", + "prettier": "^3.7.4", "sass-embedded": "^1.89.2", "tailwindcss": "^4.1.8", + "typescript": "^5.9.3", "vite": "^6.3.5", "vite-plugin-solid": "^2.11.6", "vite-tsconfig-paths": "^5.1.4" @@ -1658,9 +1664,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -2363,6 +2370,15 @@ "solid-js": "^1.6.12" } }, + "node_modules/@solidjs/meta": { + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/@solidjs/meta/-/meta-0.29.4.tgz", + "integrity": "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==", + "license": "MIT", + "peerDependencies": { + "solid-js": ">=1.8.4" + } + }, "node_modules/@solidjs/router": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.15.3.tgz", @@ -2660,6 +2676,525 @@ "tailwindcss": "4.1.8" } }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2706,6 +3241,23 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" }, + "node_modules/@types/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "24.0.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", @@ -2798,7 +3350,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/atoa": { "version": "1.0.0", @@ -2854,12 +3407,13 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -2981,6 +3535,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -3095,6 +3650,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3155,9 +3711,10 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, "node_modules/debug": { "version": "4.4.1", @@ -3179,6 +3736,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -3400,6 +3958,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -3421,10 +3980,11 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -3449,6 +4009,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3457,6 +4018,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3465,6 +4027,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -3476,6 +4039,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -3776,9 +4340,10 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3821,6 +4386,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3846,6 +4412,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3869,6 +4436,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3897,6 +4465,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3922,6 +4491,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3933,6 +4503,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -3947,6 +4518,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4048,10 +4620,11 @@ } }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, + "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -4202,6 +4775,27 @@ "lightningcss-win32-x64-msvc": "1.30.1" } }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-darwin-arm64": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", @@ -4461,6 +5055,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-solid": { + "version": "0.561.0", + "resolved": "https://registry.npmjs.org/lucide-solid/-/lucide-solid-0.561.0.tgz", + "integrity": "sha512-9pi76feeX4PCYBvbJQ5eKPE8iVeo+yqcHXUxzmrfFNt5bKabIEc/RJMyKhYNrQg1CH/FMpC3c7qvShc1JNyQKA==", + "license": "ISC", + "peerDependencies": { + "solid-js": "^1.4.7" + } + }, "node_modules/luxon": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", @@ -4470,11 +5073,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/marked": { @@ -4492,6 +5096,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -4521,6 +5126,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4529,6 +5135,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -4845,6 +5452,22 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5724,6 +6347,20 @@ "node": ">=4" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", @@ -5815,10 +6452,11 @@ "dev": true }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/builder-frontend/package.json b/builder-frontend/package.json index a9db017a..db617c54 100644 --- a/builder-frontend/package.json +++ b/builder-frontend/package.json @@ -15,11 +15,13 @@ "@kogito-tooling/kie-editors-standalone": "^0.16.0", "@solid-primitives/deep": "^0.3.3", "@solid-primitives/resource": "^0.4.2", + "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.15.3", "dmn-js": "^17.2.1", "fast-xml-parser": "^5.2.5", "firebase": "^11.9.1", "lodash.debounce": "^4.0.8", + "lucide-solid": "^0.561.0", "solid-bootstrap": "^1.0.21", "solid-js": "^1.9.5", "solid-toast": "^0.5.0", @@ -28,10 +30,14 @@ "devDependencies": { "@tailwindcss/forms": "^0.5.10", "@tailwindcss/postcss": "^4.1.8", + "@tailwindcss/vite": "^4.1.18", + "@types/lodash.debounce": "^4.0.9", "autoprefixer": "^10.4.21", "postcss": "^8.5.4", + "prettier": "^3.7.4", "sass-embedded": "^1.89.2", "tailwindcss": "^4.1.8", + "typescript": "^5.9.3", "vite": "^6.3.5", "vite-plugin-solid": "^2.11.6", "vite-tsconfig-paths": "^5.1.4" diff --git a/builder-frontend/src/assets/logos/bdt-logo-large-mono-dark.svg b/builder-frontend/public/logos/bdt-logo-large-mono-dark.svg similarity index 100% rename from builder-frontend/src/assets/logos/bdt-logo-large-mono-dark.svg rename to builder-frontend/public/logos/bdt-logo-large-mono-dark.svg diff --git a/builder-frontend/src/assets/logos/bdt-logo-large-mono-light.svg b/builder-frontend/public/logos/bdt-logo-large-mono-light.svg similarity index 100% rename from builder-frontend/src/assets/logos/bdt-logo-large-mono-light.svg rename to builder-frontend/public/logos/bdt-logo-large-mono-light.svg diff --git a/builder-frontend/src/App.tsx b/builder-frontend/src/App.tsx index edd7ffb1..ab0cbee8 100644 --- a/builder-frontend/src/App.tsx +++ b/builder-frontend/src/App.tsx @@ -1,29 +1,40 @@ -import { Navigate, Route } from "@solidjs/router"; +import { Match, ParentProps, Switch } from "solid-js"; +import { Route } from "@solidjs/router"; -import Project from "./components/project/Project"; import AuthForm from "./components/auth/AuthForm"; import { useAuth } from "./context/AuthContext"; import HomeScreen from "./components/homeScreen/HomeScreen"; -import EligibilityCheckDetail from "./components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail"; import Screener from "./components/screener/Screener"; import Loading from "./components/Loading"; -import { Match, Switch } from "solid-js"; - +import EligibilityChecksList from "@/components/homeScreen/eligibilityCheckList/EligibilityChecksList"; +import EligibilityCheckDetail from "./components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail"; +import Header from "@/components/Header"; +import ANavBar from "@/components/shared/ANavBar"; +import ProjectsList from "@/components/Project/ProjectsList"; +import Project from "@/components/Project/ProjectDetails/Project"; +import Projects from "@/components/Project/Projects"; +import { ProjectsLayout } from "@/components/Project/ProjectsLayout"; -const ProtectedRoute = (props) => { +const ProtectedRoute = (props: ParentProps) => { const { user, isAuthLoading } = useAuth(); - - // If user is logged in, render the requested component, otherwise redirect to login + + const navbarItems = [ + { label: "Projects", href: "/projects" }, + { label: "Eligibility checks", href: "/check" }, + ]; + return ( - - + + - - + +
+ + {props.children} ); @@ -32,13 +43,24 @@ const ProtectedRoute = (props) => { function App() { return ( <> - - - } /> - } /> - } /> - -
404 - Page Not Found
} /> + + + + + + + + + + + + + + +
404 - Page Not Found
} + /> ); } diff --git a/builder-frontend/src/api/auth.js b/builder-frontend/src/api/auth.js deleted file mode 100644 index f44572f9..00000000 --- a/builder-frontend/src/api/auth.js +++ /dev/null @@ -1,17 +0,0 @@ -import { auth } from "../firebase/firebase.js"; - - -export async function authFetch(input, init = {}) { - const user = auth.currentUser; - - // If no user is logged in, you can handle it accordingly - if (!user) { - throw new Error("User not authenticated"); - } - - const token = await user.getIdToken(); - const headers = new Headers(init.headers || {}); - - headers.set("Authorization", `Bearer ${token}`); - return fetch(input, { ...init, headers }); -} diff --git a/builder-frontend/src/api/auth.ts b/builder-frontend/src/api/auth.ts new file mode 100644 index 00000000..50a0ea28 --- /dev/null +++ b/builder-frontend/src/api/auth.ts @@ -0,0 +1,29 @@ +import { auth } from "../firebase/firebase.js"; + +type RestMethod = "GET" | "POST" | "PUT" | "DELETE"; +type FetchInit = { + headers: Record; + body: string; +}; + +export const authFetch = + (method: RestMethod) => async (url: string, init?: FetchInit) => { + const user = auth.currentUser; + + // If no user is logged in, you can handle it accordingly + if (!user) { + throw new Error("User not authenticated"); + } + + const token = await user.getIdToken(); + const basicHeaders = { Accept: "application/json" }; + const headers = new Headers({ ...basicHeaders, ...init?.headers }); + headers.set("Authorization", `Bearer ${token}`); + + return fetch(url, { method, ...init, headers }); + }; + +export const authGet = authFetch("GET"); +export const authPost = authFetch("POST"); +export const authPut = authFetch("PUT"); +export const authDelete = authFetch("DELETE"); diff --git a/builder-frontend/src/api/benefit.ts b/builder-frontend/src/api/benefit.ts index 37e33c27..32f7f58c 100644 --- a/builder-frontend/src/api/benefit.ts +++ b/builder-frontend/src/api/benefit.ts @@ -1,22 +1,13 @@ -import { authFetch } from "@/api/auth"; -import BenefitList from "@/components/project/manageBenefits/benefitList/BenefitList"; - import { Benefit } from "@/types"; - -const apiUrl = import.meta.env.VITE_API_URL; +import { authGet, authPut } from "@/api/auth"; export const fetchScreenerBenefit = async ( srceenerId: string, - benefitId: string + benefitId: string, ): Promise => { - const url = apiUrl + "/screener/" + srceenerId + "/benefit/" + benefitId; + const url = `/api/screener/${srceenerId}/benefit/${benefitId}`; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -31,16 +22,12 @@ export const fetchScreenerBenefit = async ( export const updateScreenerBenefit = async ( screenerId: string, - benefitData: Benefit + benefitData: Benefit, ): Promise => { - const url = apiUrl + "/screener/" + screenerId + "/benefit"; + const url = `/api/screener/${screenerId}/benefit`; try { - const response = await authFetch(url, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPut(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(benefitData), }); @@ -56,14 +43,9 @@ export const updateScreenerBenefit = async ( }; export const fetchPublicBenefits = async (): Promise => { - const url = apiUrl + "/benefit"; + const url = "/api/benefit"; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); diff --git a/builder-frontend/src/api/check.ts b/builder-frontend/src/api/check.ts index d9f0d56e..f739a110 100644 --- a/builder-frontend/src/api/check.ts +++ b/builder-frontend/src/api/check.ts @@ -1,18 +1,10 @@ -import { authFetch } from "@/api/auth"; - import type { EligibilityCheck, OptionalBoolean } from "@/types"; - -const apiUrl = import.meta.env.VITE_API_URL; +import { authGet, authPost, authPut } from "@/api/auth"; export const fetchPublicChecks = async (): Promise => { - const url = apiUrl + "/library-checks"; + const url = "/api/library-checks"; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -27,20 +19,13 @@ export const fetchPublicChecks = async (): Promise => { }; export const fetchCheck = async ( - checkId: string + checkId: string, ): Promise => { - let url = apiUrl + `/custom-checks/${checkId}`; - if (checkId.charAt(0) === "L") { - url = apiUrl + `/library-checks/${checkId}`; - } + const checkResource = checkId[0] === "L" ? "library-checks" : "custom-checks"; + const url = `/api/${checkResource}/${checkId}`; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -55,14 +40,10 @@ export const fetchCheck = async ( }; export const addCheck = async (check: EligibilityCheck) => { - const url = apiUrl + "/custom-checks"; + const url = "/api/custom-checks"; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(check), }); @@ -78,14 +59,10 @@ export const addCheck = async (check: EligibilityCheck) => { }; export const updateCheck = async (check: EligibilityCheck) => { - const url = apiUrl + "/custom-checks"; + const url = "/api/custom-checks"; try { - const response = await authFetch(url, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPut(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(check), }); @@ -101,14 +78,10 @@ export const updateCheck = async (check: EligibilityCheck) => { }; export const saveCheckDmn = async (checkId: string, dmnModel: string) => { - const url = apiUrl + "/save-check-dmn"; + const url = "/api/save-check-dmn"; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: checkId, dmnModel: dmnModel }), }); @@ -123,16 +96,12 @@ export const saveCheckDmn = async (checkId: string, dmnModel: string) => { export const validateCheckDmn = async ( checkId: string, - dmnModel: string + dmnModel: string, ): Promise => { - const url = apiUrl + "/validate-check-dmn"; + const url = "/api/validate-check-dmn"; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: checkId, dmnModel: dmnModel }), }); @@ -149,18 +118,12 @@ export const validateCheckDmn = async ( }; export const fetchUserDefinedChecks = async ( - working: boolean + working: boolean, ): Promise => { - const workingQueryParam = working ? "true" : "false"; - let url: string = apiUrl + `/custom-checks?working=${workingQueryParam}`; + const url = `/api/custom-checks?working=${working}`; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -176,17 +139,13 @@ export const fetchUserDefinedChecks = async ( export const evaluateWorkingCheck = async ( checkId: string, checkConfig: any, - inputData: Record + inputData: Record, ): Promise => { - const url = apiUrl + `/decision/working-check?checkId=${checkId}`; + const url = `/api/decision/working-check?checkId=${checkId}`; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify({ checkConfig: checkConfig, inputData: inputData }), + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ checkConfig, inputData }), }); if (!response.ok) { @@ -201,16 +160,11 @@ export const evaluateWorkingCheck = async ( }; export const getRelatedPublishedChecks = async ( - checkId: string + checkId: string, ): Promise => { - const url = apiUrl + `/custom-checks/${checkId}/published-check-versions`; + const url = `/api/custom-checks/${checkId}/published-check-versions`; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -224,15 +178,13 @@ export const getRelatedPublishedChecks = async ( }; export const publishCheck = async ( - checkId: string + checkId: string, ): Promise => { - const url = apiUrl + `/publish-check/${checkId}`; + const url = `/api/publish-check/${checkId}`; try { - const response = await authFetch(url, { - method: "POST", - headers: { - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({}), }); if (!response.ok) { diff --git a/builder-frontend/src/api/publishedScreener.ts b/builder-frontend/src/api/publishedScreener.ts index 0fc97dbe..17982fa1 100644 --- a/builder-frontend/src/api/publishedScreener.ts +++ b/builder-frontend/src/api/publishedScreener.ts @@ -1,15 +1,13 @@ import type { PublishedScreener, ScreenerResult } from "@/types"; -const apiUrl = import.meta.env.VITE_API_URL; - -export const fetchPublishedScreener = async (publishedScreenerId: string): Promise => { - const url = apiUrl + "/published/screener/" + publishedScreenerId; +export const fetchPublishedScreener = async ( + publishedScreenerId: string, +): Promise => { + const url = `/api/published/screener/${publishedScreenerId}`; try { const response = await fetch(url, { method: "GET", - headers: { - Accept: "application/json", - }, + headers: { Accept: "application/json" }, }); if (!response.ok) { @@ -23,9 +21,11 @@ export const fetchPublishedScreener = async (publishedScreenerId: string): Promi } }; - -export const evaluatePublishedScreener = async (publishedScreenerId: string, inputData: any): Promise => { - const url = apiUrl + "/published/" + publishedScreenerId + "/evaluate"; +export const evaluatePublishedScreener = async ( + publishedScreenerId: string, + inputData: any, +): Promise => { + const url = `/api/published/${publishedScreenerId}/evaluate`; try { const response = await fetch(url, { method: "POST", diff --git a/builder-frontend/src/api/screener.ts b/builder-frontend/src/api/screener.ts index 8c3fb8f4..83ee691d 100644 --- a/builder-frontend/src/api/screener.ts +++ b/builder-frontend/src/api/screener.ts @@ -1,18 +1,10 @@ -import { authFetch } from "@/api/auth"; +import type { BenefitDetail, Project, ScreenerResult } from "@/types"; +import { authDelete, authGet, authPost, authPut } from "@/api/auth"; -import type { BenefitDetail, ScreenerResult } from "@/types"; - -const apiUrl = import.meta.env.VITE_API_URL; - -export const fetchProjects = async () => { - const url = apiUrl + "/screeners"; +export const fetchProjects = async (): Promise => { + const url = "/api/screeners"; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -21,19 +13,15 @@ export const fetchProjects = async () => { return data; } catch (error) { console.error("Error fetching projects:", error); + return []; throw error; // rethrow so you can handle it in your component if needed } }; -export const fetchProject = async (screenerId) => { - const url = apiUrl + "/screener/" + screenerId; +export const fetchProject = async (screenerId: string) => { + const url = `/api/screener/${screenerId}`; try { - const response = await authFetch(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); + const response = await authGet(url); if (!response.ok) { throw new Error(`Fetch failed with status: ${response.status}`); @@ -46,15 +34,13 @@ export const fetchProject = async (screenerId) => { } }; -export const createNewScreener = async (screenerData) => { - const url = apiUrl + "/screener"; +export const createNewScreener = async (screenerData: { + screenerName: string; +}): Promise<{ id: string }> => { + const url = "/api/screener"; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(screenerData), }); @@ -69,15 +55,14 @@ export const createNewScreener = async (screenerData) => { } }; -export const updateScreener = async (screenerData) => { - const url = apiUrl + "/screener"; +export const updateScreener = async (screenerData: { + screenerName: string; + id: string; +}) => { + const url = "/api/screener"; try { - const response = await authFetch(url, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPut(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(screenerData), }); @@ -90,16 +75,10 @@ export const updateScreener = async (screenerData) => { } }; -export const deleteScreener = async (screenerData) => { - const url = apiUrl + "/screener/delete?screenerId=" + screenerData.id; +export const deleteScreener = async (screenerId: string) => { + const url = `/api/screener/delete?screenerId=${screenerId}`; try { - const response = await authFetch(url, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - }); + const response = await authDelete(url); if (!response.ok) { throw new Error(`Update failed with status: ${response.status}`); @@ -110,18 +89,15 @@ export const deleteScreener = async (screenerData) => { } }; -export const saveFormSchema = async (screenerId, schema) => { +// What is schema's type? +export const saveFormSchema = async (screenerId: string, schema) => { const requestData: any = {}; requestData.screenerId = screenerId; requestData.schema = schema; - const url = apiUrl + "/save-form-schema"; + const url = "/api/save-form-schema"; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestData), }); @@ -135,14 +111,10 @@ export const saveFormSchema = async (screenerId, schema) => { }; export const publishScreener = async (screenerId: string): Promise => { - const url = apiUrl + "/publish"; + const url = "/api/publish"; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ screenerId: screenerId }), }); @@ -155,15 +127,14 @@ export const publishScreener = async (screenerId: string): Promise => { } }; -export const addCustomBenefit = async (screenerId: string, benefit: BenefitDetail) => { - const url = apiUrl + "/screener/" + screenerId + "/benefit"; +export const addCustomBenefit = async ( + screenerId: string, + benefit: BenefitDetail, +) => { + const url = `/api/screener/${screenerId}/benefit`; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(benefit), }); @@ -176,19 +147,18 @@ export const addCustomBenefit = async (screenerId: string, benefit: BenefitDetai } }; -export const removeCustomBenefit = async (screenerId: string, benefitId: string) => { - const url = apiUrl + "/screener/" + screenerId + "/benefit/" + benefitId; +export const removeCustomBenefit = async ( + screenerId: string, + benefitId: string, +) => { + const url = `/api/screener/${screenerId}/benefit/${benefitId}`; try { - const response = await authFetch(url, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - }); + const response = await authDelete(url); if (!response.ok) { - throw new Error(`Delete of benefit failed with status: ${response.status}`); + throw new Error( + `Delete of benefit failed with status: ${response.status}`, + ); } } catch (error) { console.error("Error deleting custom benefit:", error); @@ -196,22 +166,21 @@ export const removeCustomBenefit = async (screenerId: string, benefitId: string) } }; -export const evaluateScreener = async (screenerId: string, inputData: any): Promise => { - const url = apiUrl + "/decision/v2?screenerId=" + screenerId; +export const evaluateScreener = async ( + screenerId: string, + inputData: any, +): Promise => { + const url = `/api/decision/v2?screenerId=${screenerId}`; try { - const response = await authFetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + const response = await authPost(url, { + headers: { "Content-Type": "application/json" }, body: JSON.stringify(inputData), }); if (!response.ok) { throw new Error(`Evaluation failed with status: ${response.status}`); } - + const data = await response.json(); return data; } catch (error) { diff --git a/builder-frontend/src/components/Header.css b/builder-frontend/src/components/Header.css new file mode 100644 index 00000000..4d789347 --- /dev/null +++ b/builder-frontend/src/components/Header.css @@ -0,0 +1,13 @@ +@import "tailwindcss"; + +.header { + @apply bg-gray-200 min-h-24 h-24 px-4 flex items-center justify-between border-b-2 border-gray-300; +} +.logout-btn, +.back-to-home { + @apply px-4 py-3 font-bold text-gray-700 rounded-md flex items-center cursor-pointer select-none no-underline; +} +.logout-btn:hover, +.back-to-home:hover { + @apply bg-gray-300 text-gray-700; +} diff --git a/builder-frontend/src/components/Header.tsx b/builder-frontend/src/components/Header.tsx index bb42198a..65e4f329 100644 --- a/builder-frontend/src/components/Header.tsx +++ b/builder-frontend/src/components/Header.tsx @@ -1,30 +1,15 @@ -import { useAuth } from "../context/AuthContext"; +import { createMemo } from "solid-js"; import { useLocation, useNavigate } from "@solidjs/router"; -import bdtLogo from "../assets/logos/bdt-logo-large-mono-light.svg"; -import { Show } from "solid-js"; - - -const HeaderButton = ({ buttonText, onClick }: { buttonText: string; onClick: () => void }) => { - return ( -
- {buttonText} -
- ); -} +import { useAuth } from "../context/AuthContext"; +import "./Header.css"; export default function Header() { const { logout } = useAuth(); const navigate = useNavigate(); const location = useLocation(); - const isNotRoot = location.pathname !== "/"; + const isNotRoot = createMemo(() => location.pathname !== "/"); const handleLogout = () => { logout(); @@ -32,15 +17,24 @@ export default function Header() { }; return ( -
-
- -
-
- - navigate("/")} /> - - +
+ + BDT logo + + +
+ {isNotRoot() && ( + + ← Back to home + + )} +
); diff --git a/builder-frontend/src/components/Project/DeleteConfirmation.tsx b/builder-frontend/src/components/Project/DeleteConfirmation.tsx new file mode 100644 index 00000000..0e30ffe2 --- /dev/null +++ b/builder-frontend/src/components/Project/DeleteConfirmation.tsx @@ -0,0 +1,30 @@ +import { Trash2 } from "lucide-solid"; + +import { Button } from "@/components/shared/Button"; + +interface Props { + id: string; + screenerName: string; + onCancel: () => void; + onDelete: () => void; +} +export default function DeleteConfirmation(props: Props) { + return ( +
+

+ Are you sure you would like to delete {props.screenerName}? +

+
+ Once deleted, all associated data will be deleted and cant be recovered. +
+
+ + +
+
+ ); +} diff --git a/builder-frontend/src/components/Project/EditScreenerForm.tsx b/builder-frontend/src/components/Project/EditScreenerForm.tsx new file mode 100644 index 00000000..74c572b1 --- /dev/null +++ b/builder-frontend/src/components/Project/EditScreenerForm.tsx @@ -0,0 +1,95 @@ +import { + createSignal, + JSX, + ParentProps, + ResourceActions, + Show, +} from "solid-js"; +import { Trash2 } from "lucide-solid"; + +import type { Project } from "@/types"; +import { deleteScreener, updateScreener } from "@/api/screener"; +import Form from "@/components/shared/Form"; +import { Button } from "@/components/shared/Button"; +import { Modal } from "@/components/shared/Modal"; +import DeleteConfirmation from "./DeleteConfirmation"; + +interface Props { + id: string; + screenerName: string; + refetchProjects: ResourceActions["refetch"]; +} + +export default function EditScreenerForm(props: ParentProps) { + const [showDelete, setShowDelete] = createSignal(false); + const [error, setError] = createSignal(null); + const [isLoading, setIsLoading] = createSignal(false); + + const handleSubmit: JSX.EventHandler = (e) => { + e.preventDefault(); + setIsLoading(true); + const fd = new FormData(e.currentTarget); + const screenerName = fd.get("screenerName")?.toString() || ""; + + if (screenerName.length > 0) { + setError(null); + updateScreener({ screenerName, id: props.id }) + .then(() => { + props.refetchProjects(); + setIsLoading(false); + }) + .catch((e) => { + setIsLoading(false); + }); + } else { + setError("Please enter a name."); + setIsLoading(false); + } + }; + + const handleDelete = () => { + setIsLoading(true); + deleteScreener(props.id) + .then(() => { + props.refetchProjects(); + setIsLoading(false); + }) + .catch((e) => { + setIsLoading(false); + }); + }; + + return ( +
+

Edit screener

+ +
+ + + + +
{error()}
+
+ +
+ +
+ + + + setShowDelete(false)}> + setShowDelete(false)} + onDelete={handleDelete} + /> + + {isLoading() &&
Loading ...
} +
+ ); +} diff --git a/builder-frontend/src/components/Project/NewProjectCard.tsx b/builder-frontend/src/components/Project/NewProjectCard.tsx new file mode 100644 index 00000000..5c535b02 --- /dev/null +++ b/builder-frontend/src/components/Project/NewProjectCard.tsx @@ -0,0 +1,19 @@ +import { createSignal } from "solid-js"; + +import { Modal } from "@/components/shared/Modal"; +import NewScreenerForm from "@/components/Project/NewScreenerForm"; +import { Button } from "@/components/shared/Button"; + +export const NewProjectCard = () => { + const [showModal, setShowModal] = createSignal(false); + return ( +
setShowModal(true)}> +
+ Create new screener +
+ setShowModal(false)}> + + +
+ ); +}; diff --git a/builder-frontend/src/components/Project/NewScreenerForm.tsx b/builder-frontend/src/components/Project/NewScreenerForm.tsx new file mode 100644 index 00000000..f19623a1 --- /dev/null +++ b/builder-frontend/src/components/Project/NewScreenerForm.tsx @@ -0,0 +1,51 @@ +import { createSignal, type JSX, Show } from "solid-js"; +import { useNavigate } from "@solidjs/router"; + +import { createNewScreener } from "@/api/screener"; +import Form from "@/components/shared/Form"; + +export default function NewScreenerForm() { + const navigate = useNavigate(); + const [isLoading, setIsLoading] = createSignal(false); + const [error, setError] = createSignal(); + + const handleSubmit: JSX.EventHandler = async ( + e, + ) => { + e.preventDefault(); + setIsLoading(true); + const fd = new FormData(e.currentTarget); + const screenerName = fd.get("screenerName")?.toString() || ""; + + if (screenerName.length > 0) { + setError(null); + const newScreener = await createNewScreener({ screenerName }); + navigate(`/projects/${newScreener.id}`); + } else { + setError("Please enter a name."); + setIsLoading(false); + } + }; + + return ( +
+

Create a screener

+
+ + + + +
{error()}
+
+ + {
Loading ...
}
+
+
+ ); +} diff --git a/builder-frontend/src/components/Project/ProjectCard.tsx b/builder-frontend/src/components/Project/ProjectCard.tsx new file mode 100644 index 00000000..6c44a87e --- /dev/null +++ b/builder-frontend/src/components/Project/ProjectCard.tsx @@ -0,0 +1,39 @@ +import { + Component, + createSignal, + ParentProps, + ResourceActions, +} from "solid-js"; +import { Ellipsis } from "lucide-solid"; + +import { Modal } from "@/components/shared/Modal"; +import EditScreenerForm from "@/components/Project/EditScreenerForm"; +import { Project } from "@/types"; + +interface Props { + id: string; + screenerName: string; + refetchProjects: ResourceActions["refetch"]; +} + +export const ProjectCard: Component> = (props) => { + const [showMenu, setShowMenu] = createSignal(false); + + return ( +
+
setShowMenu(true)}> + + setShowMenu(false)}> + + +
+ +
{props.screenerName}
+
+
+ ); +}; diff --git a/builder-frontend/src/components/project/FormEditorView.tsx b/builder-frontend/src/components/Project/ProjectDetails/FormEditorView.tsx similarity index 95% rename from builder-frontend/src/components/project/FormEditorView.tsx rename to builder-frontend/src/components/Project/ProjectDetails/FormEditorView.tsx index 320e7290..52941732 100644 --- a/builder-frontend/src/components/project/FormEditorView.tsx +++ b/builder-frontend/src/components/Project/ProjectDetails/FormEditorView.tsx @@ -6,12 +6,11 @@ import { FormEditor } from "@bpmn-io/form-js-editor"; import FilterFormComponentsModule from "./formJsExtensions/FilterFormComponentsModule"; import CustomFormFieldsModule from "./formJsExtensions/customFormFields"; -import { saveFormSchema } from "../../api/screener"; +import { saveFormSchema } from "@/api/screener"; import "@bpmn-io/form-js/dist/assets/form-js.css"; import "@bpmn-io/form-js-editor/dist/assets/form-js-editor.css"; - function FormEditorView({ formSchema, setFormSchema }) { const [isUnsaved, setIsUnsaved] = createSignal(false); const [isSaving, setIsSaving] = createSignal(false); @@ -31,10 +30,7 @@ function FormEditorView({ formSchema, setFormSchema }) { onMount(() => { formEditor = new FormEditor({ container, - additionalModules: [ - FilterFormComponentsModule, - CustomFormFieldsModule - ], + additionalModules: [FilterFormComponentsModule, CustomFormFieldsModule], }); if (formSchema()) { diff --git a/builder-frontend/src/components/project/Project.tsx b/builder-frontend/src/components/Project/ProjectDetails/Project.tsx similarity index 73% rename from builder-frontend/src/components/project/Project.tsx rename to builder-frontend/src/components/Project/ProjectDetails/Project.tsx index 5fb28868..dada30c8 100644 --- a/builder-frontend/src/components/project/Project.tsx +++ b/builder-frontend/src/components/Project/ProjectDetails/Project.tsx @@ -2,8 +2,7 @@ import { createSignal, createResource, Accessor } from "solid-js"; import { useParams } from "@solidjs/router"; import FormEditorView from "./FormEditorView"; -import Header from "../Header"; -import Loading from "../Loading"; +import Loading from "@/components/Loading"; import ManageBenefits from "./manageBenefits/ManageBenefits"; import Preview from "./preview/Preview"; import Publish from "./Publish"; @@ -11,7 +10,6 @@ import Publish from "./Publish"; import { fetchProject } from "@/api/screener"; import BdtNavbar, { NavbarProps } from "@/components/shared/BdtNavbar"; - type TabOption = "manageBenefits" | "formEditor" | "preview" | "publish"; function Project() { @@ -34,16 +32,32 @@ function Project() { // including a dummy signal 'forceUpdate' that can be unique for // each call to the refetch () => [params.projectId, forceUpdate()], - fetchAndCacheProject + fetchAndCacheProject, ); const navbarDefs: Accessor = () => { return { tabDefs: [ - { key: "manageBenefits", label: "Manage Benefits", onClick: () => setActiveTab("manageBenefits") }, - { key: "formEditor", label: "Form Editor", onClick: () => setActiveTab("formEditor") }, - { key: "preview", label: "Preview", onClick: () => setActiveTab("preview") }, - { key: "publish", label: "Publish", onClick: () => setActiveTab("publish") }, + { + key: "manageBenefits", + label: "Manage Benefits", + onClick: () => setActiveTab("manageBenefits"), + }, + { + key: "formEditor", + label: "Form Editor", + onClick: () => setActiveTab("formEditor"), + }, + { + key: "preview", + label: "Preview", + onClick: () => setActiveTab("preview"), + }, + { + key: "publish", + label: "Publish", + onClick: () => setActiveTab("publish"), + }, ], activeTabKey: () => activeTab(), titleDef: { label: project().screenerName }, @@ -52,9 +66,8 @@ function Project() { return (
-
{project.loading ? ( - + ) : ( <> @@ -64,11 +77,9 @@ function Project() { setFormSchema={setFormSchema} /> )} - {activeTab() == "manageBenefits" && ( - - )} + {activeTab() == "manageBenefits" && } {activeTab() == "preview" && ( - + )} {activeTab() == "publish" && ( { return project()?.screenerName }; - const isPublished = () => { return project()?.publishedScreenerId !== null }; - const lastPublishDate = () => { return project()?.lastPublishDate }; + const screenerName = () => { + return project()?.screenerName; + }; + const isPublished = () => { + return project()?.publishedScreenerId !== null; + }; + const lastPublishDate = () => { + return project()?.lastPublishDate; + }; const screenerUrl = () => { - return window.location.protocol + "//" +window.location.host + "/screener/" + project()?.publishedScreenerId; + return ( + window.location.protocol + + "//" + + window.location.host + + "/screener/" + + project()?.publishedScreenerId + ); }; const { projectId } = useParams(); diff --git a/builder-frontend/src/components/project/formJsExtensions/FilterFormComponentsModule.tsx b/builder-frontend/src/components/Project/ProjectDetails/formJsExtensions/FilterFormComponentsModule.tsx similarity index 100% rename from builder-frontend/src/components/project/formJsExtensions/FilterFormComponentsModule.tsx rename to builder-frontend/src/components/Project/ProjectDetails/formJsExtensions/FilterFormComponentsModule.tsx diff --git a/builder-frontend/src/components/project/formJsExtensions/customFormFields/YesNoQuestion.ts b/builder-frontend/src/components/Project/ProjectDetails/formJsExtensions/customFormFields/YesNoQuestion.ts similarity index 100% rename from builder-frontend/src/components/project/formJsExtensions/customFormFields/YesNoQuestion.ts rename to builder-frontend/src/components/Project/ProjectDetails/formJsExtensions/customFormFields/YesNoQuestion.ts diff --git a/builder-frontend/src/components/project/formJsExtensions/customFormFields/index.js b/builder-frontend/src/components/Project/ProjectDetails/formJsExtensions/customFormFields/index.js similarity index 100% rename from builder-frontend/src/components/project/formJsExtensions/customFormFields/index.js rename to builder-frontend/src/components/Project/ProjectDetails/formJsExtensions/customFormFields/index.js diff --git a/builder-frontend/src/components/project/manageBenefits/ManageBenefits.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/ManageBenefits.tsx similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/ManageBenefits.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/ManageBenefits.tsx diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/BenefitList.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/BenefitList.tsx similarity index 69% rename from builder-frontend/src/components/project/manageBenefits/benefitList/BenefitList.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/BenefitList.tsx index 9b10d104..eca15b1a 100644 --- a/builder-frontend/src/components/project/manageBenefits/benefitList/BenefitList.tsx +++ b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/BenefitList.tsx @@ -1,37 +1,43 @@ import { Accessor, createSignal, For, Setter, Show } from "solid-js"; import AddNewBenefitModal from "./modals/AddNewBenefitModal"; -import ConfirmationModal from "../../../shared/ConfirmationModal"; +import ConfirmationModal from "@/components/shared/ConfirmationModal"; import SelectExistingBenefitModal from "./modals/SelectExistingBenefitModal"; import screenerBenefitResource from "./screenerBenefitsResource"; -import Loading from "../../../Loading"; +import Loading from "@/components/Loading"; import type { BenefitDetail } from "@/types"; - -const BenefitList = ( - { screenerId, setBenefitIdToConfigure }: - { screenerId: Accessor; setBenefitIdToConfigure: Setter } -) => { - const { screenerBenefits, actions, actionInProgress, initialLoadStatus } = screenerBenefitResource(screenerId); +const BenefitList = ({ + screenerId, + setBenefitIdToConfigure, +}: { + screenerId: Accessor; + setBenefitIdToConfigure: Setter; +}) => { + const { screenerBenefits, actions, actionInProgress, initialLoadStatus } = + screenerBenefitResource(screenerId); const [addingNewBenefit, setAddingNewBenefit] = createSignal(false); - const [selectExistingBenefitModal, setSelectExistingBenefitModal] = createSignal(false); - const [benefitIdToRemove, setBenefitIdToRemove] = createSignal(null); + const [selectExistingBenefitModal, setSelectExistingBenefitModal] = + createSignal(false); + const [benefitIdToRemove, setBenefitIdToRemove] = createSignal( + null, + ); return (
-
- Manage Benefits -
+
Manage Benefits
- Define and organize the benefits available in your screener. - Each benefit can have associated eligibility checks. + Define and organize the benefits available in your screener. Each + benefit can have associated eligibility checks.
{setAddingNewBenefit(true)}} + onClick={() => { + setAddingNewBenefit(true); + }} > Create New Benefit
@@ -47,9 +53,14 @@ const BenefitList = ( grid-cols-1 md:grid-cols-2 xl:grid-cols-3" > - + - +
No benefits found. Please add a new benefit.
@@ -66,13 +77,12 @@ const BenefitList = ( }}
- { - addingNewBenefit() && + {addingNewBenefit() && ( setAddingNewBenefit(false)} addNewBenefit={actions.addNewBenefit} /> - } + )} {/* { selectExistingBenefitModal() && } */} - { - benefitIdToRemove() !== null && + {benefitIdToRemove() !== null && ( actions.removeBenefit(benefitIdToRemove()) } - closeModal={() => setBenefitIdToRemove(null) } + callback={() => actions.removeBenefit(benefitIdToRemove())} + closeModal={() => setBenefitIdToRemove(null)} /> - } + )}
- ) + ); }; -const BenefitCard = ( - { benefit, setBenefitIdToConfigure, setBenefitIdToRemove }: - { - benefit: BenefitDetail, - setBenefitIdToConfigure: Setter, - setBenefitIdToRemove: Setter - } -) => { +const BenefitCard = ({ + benefit, + setBenefitIdToConfigure, + setBenefitIdToRemove, +}: { + benefit: BenefitDetail; + setBenefitIdToConfigure: Setter; + setBenefitIdToRemove: Setter; +}) => { return (
-
- {benefit.name} -
+
{benefit.name}
Description: {benefit.description}
@@ -125,13 +133,17 @@ const BenefitCard = ( >
{ setBenefitIdToConfigure(benefit.id); } } + onClick={() => { + setBenefitIdToConfigure(benefit.id); + }} > Edit
{ setBenefitIdToRemove(benefit.id); } } + onClick={() => { + setBenefitIdToRemove(benefit.id); + }} > Remove
@@ -139,6 +151,6 @@ const BenefitCard = (
); -} +}; export default BenefitList; diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/modals/AddNewBenefitModal.tsx diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/modals/SelectExistingBenefitModal.tsx diff --git a/builder-frontend/src/components/project/manageBenefits/benefitList/screenerBenefitsResource.ts b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/screenerBenefitsResource.ts similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/benefitList/screenerBenefitsResource.ts rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/benefitList/screenerBenefitsResource.ts diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/ConfigureBenefit.tsx similarity index 95% rename from builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/ConfigureBenefit.tsx index b3cc645c..77e54149 100644 --- a/builder-frontend/src/components/project/manageBenefits/configureBenefit/ConfigureBenefit.tsx +++ b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/ConfigureBenefit.tsx @@ -2,7 +2,7 @@ import { Accessor, createResource, createSignal, For, Show } from "solid-js"; import SelectedEligibilityCheck from "./SelectedEligibilityCheck"; import EligibilityCheckListView from "./EligibilityCheckListView"; -import Loading from "../../../Loading"; +import Loading from "@/components/Loading"; import BenefitResource from "./benefitResource"; import { fetchPublicChecks, fetchUserDefinedChecks } from "@/api/check"; @@ -26,7 +26,7 @@ const ConfigureBenefit = ({ createSignal("public"); const [publicChecks] = createResource(fetchPublicChecks); const [userDefinedChecks] = createResource(() => - fetchUserDefinedChecks(false) + fetchUserDefinedChecks(false), ); const onRemoveEligibilityCheck = (checkIndexToRemove: number) => { @@ -93,11 +93,11 @@ const ConfigureBenefit = ({ onRemoveEligibilityCheck(checkIndex()) } updateCheckConfigParams={( - newCheckData: ParameterValues + newCheckData: ParameterValues, ) => { actions.updateCheckConfigParams( checkIndex(), - newCheckData + newCheckData, ); }} /> diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/EligibilityCheckListView.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/EligibilityCheckListView.tsx similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/configureBenefit/EligibilityCheckListView.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/EligibilityCheckListView.tsx diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/benefitResource.ts b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/benefitResource.ts similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/configureBenefit/benefitResource.ts rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/benefitResource.ts diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/modals/ConfigureCheckModal.tsx b/builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/modals/ConfigureCheckModal.tsx similarity index 100% rename from builder-frontend/src/components/project/manageBenefits/configureBenefit/modals/ConfigureCheckModal.tsx rename to builder-frontend/src/components/Project/ProjectDetails/manageBenefits/configureBenefit/modals/ConfigureCheckModal.tsx diff --git a/builder-frontend/src/components/project/preview/FormRenderer.tsx b/builder-frontend/src/components/Project/ProjectDetails/preview/FormRenderer.tsx similarity index 100% rename from builder-frontend/src/components/project/preview/FormRenderer.tsx rename to builder-frontend/src/components/Project/ProjectDetails/preview/FormRenderer.tsx diff --git a/builder-frontend/src/components/project/preview/Preview.tsx b/builder-frontend/src/components/Project/ProjectDetails/preview/Preview.tsx similarity index 82% rename from builder-frontend/src/components/project/preview/Preview.tsx rename to builder-frontend/src/components/Project/ProjectDetails/preview/Preview.tsx index e7049585..e0a9ebbd 100644 --- a/builder-frontend/src/components/project/preview/Preview.tsx +++ b/builder-frontend/src/components/Project/ProjectDetails/preview/Preview.tsx @@ -3,13 +3,13 @@ import { Accessor, createSignal } from "solid-js"; import FormRenderer from "./FormRenderer"; import Results from "./Results"; -import { evaluateScreener } from "../../../api/screener"; +import { evaluateScreener } from "@/api/screener"; import { PreviewFormData, ScreenerResult } from "./types"; - const Preview = ({ project, formSchema }) => { - const [lastInputDataSent, setLastInputDataSent] = createSignal({}); + const [lastInputDataSent, setLastInputDataSent] = + createSignal({}); const [results, setResults] = createSignal(); const [resultsLoading, setResultsLoading] = createSignal(false); @@ -24,7 +24,7 @@ const Preview = ({ project, formSchema }) => { schemaVersion: 18, type: "default", }; - } + }; const handleSubmitForm = async (data: PreviewFormData) => { setLastInputDataSent(data); @@ -39,13 +39,17 @@ const Preview = ({ project, formSchema }) => {
Form
- +
Results
- +
); -} +}; export default Preview; diff --git a/builder-frontend/src/components/project/preview/Results.tsx b/builder-frontend/src/components/Project/ProjectDetails/preview/Results.tsx similarity index 65% rename from builder-frontend/src/components/project/preview/Results.tsx rename to builder-frontend/src/components/Project/ProjectDetails/preview/Results.tsx index 26b306c7..9b965bc5 100644 --- a/builder-frontend/src/components/project/preview/Results.tsx +++ b/builder-frontend/src/components/Project/ProjectDetails/preview/Results.tsx @@ -2,19 +2,19 @@ import { Accessor, For, Match, Show, Switch } from "solid-js"; import { PreviewFormData, ScreenerResult } from "./types"; -import checkIcon from "../../../assets/images/checkIcon.svg"; -import questionIcon from "../../../assets/images/questionIcon.svg"; -import xIcon from "../../../assets/images/xIcon.svg"; +import checkIcon from "@/assets/images/checkIcon.svg"; +import questionIcon from "@/assets/images/questionIcon.svg"; +import xIcon from "@/assets/images/xIcon.svg"; - -export default function Results( - { inputData, results, resultsLoading }: - { - inputData: Accessor, - results: Accessor, - resultsLoading: Accessor - } -) { +export default function Results({ + inputData, + results, + resultsLoading, +}: { + inputData: Accessor; + results: Accessor; + resultsLoading: Accessor; +}) { return (
@@ -27,7 +27,9 @@ export default function Results( {([key, value]) => ( {key}: - {value?.toString() || "--"} + + {value?.toString() || "--"} + )} @@ -39,7 +41,13 @@ export default function Results(
Loading results...
- 0}> + 0 + } + >
Benefits
@@ -59,7 +67,9 @@ export default function Results( Ineligible - + Need more information @@ -74,13 +84,29 @@ export default function Results( {check.name}:{" "} - + - + - - + +
diff --git a/builder-frontend/src/components/project/preview/types.ts b/builder-frontend/src/components/Project/ProjectDetails/preview/types.ts similarity index 100% rename from builder-frontend/src/components/project/preview/types.ts rename to builder-frontend/src/components/Project/ProjectDetails/preview/types.ts diff --git a/builder-frontend/src/components/Project/Projects.css b/builder-frontend/src/components/Project/Projects.css new file mode 100644 index 00000000..ace6e612 --- /dev/null +++ b/builder-frontend/src/components/Project/Projects.css @@ -0,0 +1,20 @@ +@reference "tailwindcss"; + +.new-project-card { + @apply p-4 w-80 h-60 flex justify-center cursor-pointer border-4 border-gray-300 rounded-lg shadow-md hover:shadow-lg hover:bg-gray-200; +} +.project-card { + @apply w-80 h-60 relative cursor-pointer border-2 border-gray-300 rounded-lg shadow-md hover:shadow-lg hover:bg-gray-200; + color: var(--text-strong); +} +.project-card-menu { + @apply absolute px-2 top-2 right-2 hover:bg-gray-300 rounded-xl; +} + +.project-card-link { + @apply h-60 p-4 flex flex-col justify-center items-center no-underline; + color: var(--text-strong); +} +.project-card-link:hover { + color: var(--text-strong); +} diff --git a/builder-frontend/src/components/Project/Projects.tsx b/builder-frontend/src/components/Project/Projects.tsx new file mode 100644 index 00000000..ced0b732 --- /dev/null +++ b/builder-frontend/src/components/Project/Projects.tsx @@ -0,0 +1,39 @@ +import { Show, createResource, onMount } from "solid-js"; +import { useNavigate } from "@solidjs/router"; + +import type { Project } from "@/types"; +import { fetchProjects } from "@/api/screener"; +import { useAuth } from "@/context/AuthContext"; +import { NewProjectCard } from "@/components/Project/NewProjectCard"; +import ProjectsList from "@/components/Project/ProjectsList"; + +import "./Projects.css"; + +export default function Projects() { + const [projectsList, { refetch }] = createResource(fetchProjects, { + initialValue: [], + }); + const navigate = useNavigate(); + const { user } = useAuth(); + + onMount(() => { + // Change to and redirect user to /projects after sign in? + if (user() === null) { + navigate("/login", { replace: true }); + } + }); + + return ( +
+ + +
+
Loading screeners...
+
+
+ + + +
+ ); +} diff --git a/builder-frontend/src/components/Project/ProjectsLayout.tsx b/builder-frontend/src/components/Project/ProjectsLayout.tsx new file mode 100644 index 00000000..b3f2cc2e --- /dev/null +++ b/builder-frontend/src/components/Project/ProjectsLayout.tsx @@ -0,0 +1,5 @@ +import { Component, createSignal, JSX, ParentProps } from "solid-js"; + +export const ProjectsLayout: Component = (props) => { + return
{props.children}
; +}; diff --git a/builder-frontend/src/components/Project/ProjectsList.tsx b/builder-frontend/src/components/Project/ProjectsList.tsx new file mode 100644 index 00000000..4650024f --- /dev/null +++ b/builder-frontend/src/components/Project/ProjectsList.tsx @@ -0,0 +1,23 @@ +import { For, Resource, ResourceActions } from "solid-js"; + +import { Project } from "@/types"; +import { ProjectCard } from "@/components/Project/ProjectCard"; + +interface Props { + projects: Resource; + refetchProjects: ResourceActions["refetch"]; +} + +export default function ProjectsList(props: Props) { + return ( + + {(item) => ( + + )} + + ); +} diff --git a/builder-frontend/src/components/auth/AuthForm.jsx b/builder-frontend/src/components/auth/AuthForm.jsx index 744537ad..cf74dd87 100644 --- a/builder-frontend/src/components/auth/AuthForm.jsx +++ b/builder-frontend/src/components/auth/AuthForm.jsx @@ -4,6 +4,7 @@ import { useAuth } from "../../context/AuthContext"; import Login from "./Login"; import Signup from "./Signup"; import { useLocation, useNavigate } from "@solidjs/router"; +import { Title } from "@solidjs/meta"; export default function AuthForm() { const [isSigningIn, setIsSigningIn] = createSignal(false); @@ -12,10 +13,10 @@ export default function AuthForm() { const location = useLocation(); const toggleMode = () => { - if (location.pathname === "/login") { - navigate("/signup"); + if (location.pathname === "/signup") { + navigate("/"); } else { - navigate("/login"); + navigate("/signup"); } }; @@ -38,10 +39,16 @@ export default function AuthForm() {
Benefits Decision Tookit
- {location.pathname === "/login" ? ( - + {location.pathname === "/signup" ? ( + <> + Sign up to Builder Decision Toolkit + + ) : ( - + <> + Log in to Builder Decision Toolkit + + )}

diff --git a/builder-frontend/src/components/homeScreen/DeleteConfirmation.jsx b/builder-frontend/src/components/homeScreen/DeleteConfirmation.jsx deleted file mode 100644 index 9cd2c793..00000000 --- a/builder-frontend/src/components/homeScreen/DeleteConfirmation.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import TrashIcon from "../icon/TrashIcon"; - -export default function DeleteConfirmation({ - screenerData, - setIsConfirmationVisible, - handleDelete, -}) { - return ( -
setIsConfirmationVisible(false)} - className="fixed inset-0 bg-black/10 flex items-center justify-center z-100" - > -
e.stopPropagation()} - className="bg-white px-12 py-8 rounded-xl max-w-100 w-1/2 min-w-80 h-80" - > -
- Are you sure you would like to delete {screenerData.screenerName}? -
-
- Once deleted, all associated data will be deleted and cant be - recovered. -
-
- - -
-
-
- ); -} diff --git a/builder-frontend/src/components/homeScreen/EditScreenerForm.jsx b/builder-frontend/src/components/homeScreen/EditScreenerForm.jsx deleted file mode 100644 index 71256ca3..00000000 --- a/builder-frontend/src/components/homeScreen/EditScreenerForm.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { createSignal, onCleanup, onMount } from "solid-js"; -import TrashIcon from "../icon/TrashIcon"; -import DeleteConfirmation from "./DeleteConfirmation"; - -export default function EditScreenerForm({ - setIsEditModalVisible, - screenerData, - handleEditScreener, - handleDeleteScreener, -}) { - const [isLoading, setIsLoading] = createSignal(false); - const [isConfirmationVisible, setIsConfirmationVisible] = createSignal(false); - const [screenerName, setScreenerName] = createSignal(); - let isActive = true; - - onMount(() => { - if (screenerData?.screenerName) { - setScreenerName(screenerData.screenerName); - } - }); - - onCleanup(() => { - isActive = false; - }); - - const handleSubmit = async (e) => { - e.preventDefault(); - try { - setIsLoading(true); - const data = { - screenerName: screenerName(), - id: screenerData.id, - }; - await handleEditScreener(data); - if (isActive) setIsLoading(false); - } catch (e) { - if (setIsLoading()) { - setIsLoading(false); - } - } - }; - - const handleDelete = async () => { - try { - setIsLoading(true); - const data = { - id: screenerData.id, - }; - await handleDeleteScreener(data); - if (isActive) setIsLoading(false); - } catch (e) { - if (setIsLoading()) { - setIsLoading(false); - } - } - }; - - const handleDeleteClicked = (e) => { - e.preventDefault(); - setIsConfirmationVisible(true); - }; - - return ( - <> -
setIsEditModalVisible(false)} - className="fixed inset-0 bg-black/10 backdrop-blur-sm flex items-center justify-center z-50" - > -
e.stopPropagation()} - className="bg-white px-12 py-8 rounded-xl max-w-140 w-1/2 min-w-80 h-96" - > -
-
Edit screener
-
setIsEditModalVisible(false)} - className="text-2xl hover:font-bold hover:cursor-pointer" - > - X -
-
- -
-
- - setScreenerName(e.currentTarget.value)} - className="p-1 border-1 border-gray-400 w-90" - > -
- - -
-
-
- - - - {isLoading() &&
Loading ...
} -
-
-
- {isConfirmationVisible() && ( - - )} - - ); -} diff --git a/builder-frontend/src/components/homeScreen/HomeScreen.tsx b/builder-frontend/src/components/homeScreen/HomeScreen.tsx index f6e24b35..e6cd9e0d 100644 --- a/builder-frontend/src/components/homeScreen/HomeScreen.tsx +++ b/builder-frontend/src/components/homeScreen/HomeScreen.tsx @@ -1,38 +1,26 @@ -import { Accessor, createSignal, Match, Switch } from "solid-js"; +import { createSignal, Match, Switch } from "solid-js"; +import { Title } from "@solidjs/meta"; import EligibilityChecksList from "./eligibilityCheckList/EligibilityChecksList"; -import ProjectsList from "./ProjectsList" -import Header from "../Header"; - -import BdtNavbar, { NavbarProps } from "@/components/shared/BdtNavbar";0 +import ProjectsList from "@/components/Project/ProjectsList"; const HomeScreen = () => { - const [screenMode, setScreenMode] = createSignal<"screeners" | "checks">("screeners"); - - const navbarDefs: Accessor = () => { - return { - tabDefs: [ - { key: "screeners", label: "Screeners", onClick: () => setScreenMode("screeners") }, - { key: "checks", label: "Eligibility checks", onClick: () => setScreenMode("checks") }, - ], - activeTabKey: () => screenMode(), - titleDef: null, - } - }; + const [screenMode, setScreenMode] = createSignal<"screeners" | "checks">( + "screeners", + ); return (
-
- - + BDT Home + {/* - + */}
- ) -} -export default HomeScreen; \ No newline at end of file + ); +}; +export default HomeScreen; diff --git a/builder-frontend/src/components/homeScreen/NewScreenerForm.jsx b/builder-frontend/src/components/homeScreen/NewScreenerForm.jsx deleted file mode 100644 index f781a3e7..00000000 --- a/builder-frontend/src/components/homeScreen/NewScreenerForm.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import { createSignal, onCleanup } from "solid-js"; - -export default function NewScreenerForm({ - setIsModalVisible, - handleCreateNewScreener, -}) { - const [isLoading, setIsLoading] = createSignal(false); - let nameInput; - let isActive = true; - - onCleanup(() => { - isActive = false; - }); - - const handleSubmit = async (e) => { - e.preventDefault(); - setIsLoading(true); - const data = { - screenerName: nameInput.value, - }; - await handleCreateNewScreener(data); - if (isActive) setIsLoading(false); - }; - - return ( -
setIsModalVisible(false)} - className="fixed inset-0 bg-black/10 backdrop-blur-sm flex items-center justify-center z-50" - > -
e.stopPropagation()} - className="bg-white px-12 py-8 rounded-xl max-w-140 w-1/2 min-w-80 h-96" - > -
-
Create a screener
-
setIsModalVisible(false)} - className="text-2xl hover:font-bold hover:cursor-pointer" - > - X -
-
-
- What is the name of your screener? -
-
-
- - (nameInput = el)} - className="p-1 border-1 border-gray-400 w-90" - > -
- - {isLoading() &&
Loading ...
} -
-
-
- ); -} diff --git a/builder-frontend/src/components/homeScreen/ProjectsList.tsx b/builder-frontend/src/components/homeScreen/ProjectsList.tsx deleted file mode 100644 index af91db72..00000000 --- a/builder-frontend/src/components/homeScreen/ProjectsList.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { For, Show, createResource, createSignal, onMount } from "solid-js"; -import { useNavigate } from "@solidjs/router"; - -import EditScreenerForm from "./EditScreenerForm"; -import NewScreenerForm from "./NewScreenerForm"; -import MenuIcon from "../icon/MenuIcon"; - -import { - fetchProjects, updateScreener, deleteScreener, createNewScreener, -} from "@/api/screener"; -import { useAuth } from "@/context/AuthContext"; - - -export default function ProjectsList() { - const [projectList, { refetch: refetchProjectList }] = createResource(fetchProjects); - const [isNewScreenerModalVisible, setIsNewScreenerModalVisible] = createSignal(false); - const [isEditModalVisible, setIsEditgModalVisible] = createSignal(false); - const [editModelData, setEditModalData] = createSignal(); - const navigate = useNavigate(); - const { user } = useAuth(); - - onMount(() => { - if (user() === null) { - navigate("/login", { replace: true }); - } - }); - - const navigateToProject = (project) => { - navigate("/project/" + project.id); - }; - - const handleCreateNewScreener = async (screenerData) => { - try { - const newScreener = await createNewScreener(screenerData); - navigate(`/project/${newScreener.id}`); - } catch (e) { - console.log("Error creating screener", e); - } - }; - - const handleProjectMenuClicked = (e, screenerData) => { - e.stopPropagation(); - setEditModalData(screenerData); - setIsEditgModalVisible(true); - }; - - const handleUpdateScreener = async (screenerData) => { - try { - await updateScreener(screenerData); - refetchProjectList(); - setIsEditgModalVisible(false); - } catch (e) { - console.log("Error editing screener", e); - } - }; - - const handleDeleteScreener = async (screenerData) => { - try { - await deleteScreener(screenerData); - refetchProjectList(); - setIsEditgModalVisible(false); - } catch (e) { - console.log("Error deleting screener", e); - } - }; - - return ( - <> -
- Loading...
}> -
-
setIsNewScreenerModalVisible(true)} - class=" - p-4 w-80 h-60 flex justify-center cursor-pointer - border-4 border-gray-300 rounded-lg - shadow-md hover:shadow-lg hover:bg-gray-200" - > -
- Create new screener -
-
- -
-
- Loading screeners... -
-
-
- - {(item) => - item && ( -
-
handleProjectMenuClicked(e, item)} - > - -
-
navigateToProject(item)} - class="h-60 p-4 flex flex-col justify-center items-center" - > -
- {item.screenerName} -
-
-
- ) - } -
-
- -
- {isNewScreenerModalVisible() && ( - - )} - {isEditModalVisible() && ( - - )} - - ); -} diff --git a/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail.tsx b/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail.tsx index 786b6405..6bf907cc 100644 --- a/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail.tsx +++ b/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/EligibilityCheckDetail.tsx @@ -16,17 +16,22 @@ import ParametersConfiguration from "./ParametersConfiguration"; import ErrorDisplayModal from "@/components/shared/ErrorModal"; import BdtNavbar, { NavbarProps } from "@/components/shared/BdtNavbar"; - -type CheckDetailScreenMode = "paramConfig" | "dmnDefinition" | "testing" | "publish"; +type CheckDetailScreenMode = + | "paramConfig" + | "dmnDefinition" + | "testing" + | "publish"; const EligibilityCheckDetail = () => { const { checkId } = useParams(); const [currentDmnModel, setCurrentDmnModel] = createSignal(""); - const [screenMode, setScreenMode] = createSignal("paramConfig"); + const [screenMode, setScreenMode] = + createSignal("paramConfig"); const [validationErrors, setValidationErrors] = createSignal([]); - const [showingErrorModal, setShowingErrorModal] = createSignal(false); + const [showingErrorModal, setShowingErrorModal] = + createSignal(false); const { eligibilityCheck, actions, actionInProgress, initialLoadStatus } = eligibilityCheckDetailResource(() => checkId); @@ -43,15 +48,31 @@ const EligibilityCheckDetail = () => { } else { toast.success("No validation errors found in DMN model."); } - } + }; const navbarDefs: Accessor = () => { return { tabDefs: [ - { key: "paramConfig", label: "Parameter Configuration", onClick: () => setScreenMode("paramConfig") }, - { key: "dmnDefinition", label: "DMN Definition", onClick: () => setScreenMode("dmnDefinition") }, - { key: "testing", label: "Testing", onClick: () => setScreenMode("testing") }, - { key: "publish", label: "Publish", onClick: () => setScreenMode("publish") }, + { + key: "paramConfig", + label: "Parameter Configuration", + onClick: () => setScreenMode("paramConfig"), + }, + { + key: "dmnDefinition", + label: "DMN Definition", + onClick: () => setScreenMode("dmnDefinition"), + }, + { + key: "testing", + label: "Testing", + onClick: () => setScreenMode("testing"), + }, + { + key: "publish", + label: "Publish", + onClick: () => setScreenMode("publish"), + }, ], activeTabKey: () => screenMode(), titleDef: { label: eligibilityCheck().name }, @@ -63,10 +84,13 @@ const EligibilityCheckDetail = () => { -
- + { Validate Current DMN
actions.saveDmnModel(currentDmnModel())} > Save Changes diff --git a/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/checkTesting/EligibilityCheckTest.tsx b/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/checkTesting/EligibilityCheckTest.tsx index 5d05733c..0e1704e9 100644 --- a/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/checkTesting/EligibilityCheckTest.tsx +++ b/builder-frontend/src/components/homeScreen/eligibilityCheckList/eligibilityCheckDetail/checkTesting/EligibilityCheckTest.tsx @@ -6,7 +6,7 @@ import { OptionalBoolean, ParameterValues, } from "@/types"; -import SelectedEligibilityCheck from "@/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck"; +import SelectedEligibilityCheck from "@/components/Project/ProjectDetails/manageBenefits/configureBenefit/SelectedEligibilityCheck"; import CheckJsonEditor from "./CheckJsonEditor"; import { JSONContent } from "vanilla-jsoneditor"; @@ -22,7 +22,7 @@ const EligibilityCheckTest = ({ eligibilityCheck: Accessor; testEligibility: ( checkConfg: CheckConfig, - inputData: Record + inputData: Record, ) => Promise; }) => { const [checkConfig, setCheckConfig] = createSignal({ @@ -56,7 +56,7 @@ const EligibilityCheckTest = ({ onClick={async () => { const result = await testEligibility( checkConfig(), - currentJsonContent().json + currentJsonContent().json, ); if (!result) { return; diff --git a/builder-frontend/src/components/screener/FormRenderer.tsx b/builder-frontend/src/components/screener/FormRenderer.tsx index 9bc6944c..f04abb51 100644 --- a/builder-frontend/src/components/screener/FormRenderer.tsx +++ b/builder-frontend/src/components/screener/FormRenderer.tsx @@ -4,18 +4,24 @@ import debounce from "lodash.debounce"; import { Form } from "@bpmn-io/form-js-viewer"; import { State } from "@bpmn-io/form-js-viewer/dist/types/Form"; -import CustomFormFieldsModule from "../project/formJsExtensions/customFormFields"; +import CustomFormFieldsModule from "@/components/Project/ProjectDetails/formJsExtensions/customFormFields"; import "@bpmn-io/form-js/dist/assets/form-js.css"; -function FormRenderer( - { schema, submitForm }: - { schema: { [key: string]: any; }, submitForm: (data: any) => void } -) { +function FormRenderer({ + schema, + submitForm, +}: { + schema: { [key: string]: any }; + submitForm: (data: any) => void; +}) { let container: Element | null = null; onMount(() => { - const form: Form = new Form({ container, additionalModules: [ CustomFormFieldsModule ] }); + const form: Form = new Form({ + container, + additionalModules: [CustomFormFieldsModule], + }); const debouncedSubmit = debounce((data) => { submitForm(data); diff --git a/builder-frontend/src/components/shared/ANavBar.css b/builder-frontend/src/components/shared/ANavBar.css new file mode 100644 index 00000000..17197ec4 --- /dev/null +++ b/builder-frontend/src/components/shared/ANavBar.css @@ -0,0 +1,11 @@ +@reference "tailwindcss"; + +.navbarlink { + @apply px-4 py-2 no-underline border-b-2 hover:bg-gray-200 transition-colors; +} +.navbarlink.inactive { + @apply border-transparent text-gray-500 hover:text-gray-700; +} +.navbarlink.active { + @apply border-b border-gray-700 text-gray-700; +} diff --git a/builder-frontend/src/components/shared/ANavBar.tsx b/builder-frontend/src/components/shared/ANavBar.tsx new file mode 100644 index 00000000..97a3d928 --- /dev/null +++ b/builder-frontend/src/components/shared/ANavBar.tsx @@ -0,0 +1,21 @@ +import type { Component } from "solid-js"; +import { A } from "@solidjs/router"; + +import "./ANavBar.css"; + +interface Props { + items: { label: string; href: string }[]; +} +const ANavBar: Component = (props) => { + return ( +
+ {props.items.map(({ label, href }) => ( + + {label} + + ))} +
+ ); +}; + +export default ANavBar; diff --git a/builder-frontend/src/components/shared/Button.module.css b/builder-frontend/src/components/shared/Button.module.css new file mode 100644 index 00000000..f3bd2191 --- /dev/null +++ b/builder-frontend/src/components/shared/Button.module.css @@ -0,0 +1,45 @@ +@reference "tailwindcss"; + +.button { + @apply inline-flex py-1.5 px-4 rounded border-2 disabled:opacity-50; +} + +.primary { + @apply border-(--brand) bg-(--brand) text-white; +} +.primary:not([disabled]):hover { + @apply brightness-110; +} +.outline-primary { + @apply border-(--brand) bg-white text-(--brand); +} +.outline-primary:not([disabled]):hover { + @apply border-(--brand)/70 bg-(--brand)/30; +} + +.secondary { + @apply border-(--text-strong) bg-(--text-strong) text-white; +} +.secondary:not([disabled]):hover { + @apply brightness-130; +} +.outline-secondary { + @apply border-(--text-strong) bg-white text-(--text-strong); +} +.outline-secondary:not([disabled]):hover { + @apply border-(--text-strong)/70 bg-(--text-strong)/30; +} + +.tertiary { + @apply border-(--text-strong) bg-(--text-strong) text-white; +} + +.danger { + @apply border-red-400 bg-red-400 text-white; +} +.outline-danger { + @apply border-red-400 bg-white text-red-400; +} +.outline-danger:not([disabled]):hover { + @apply bg-red-100; +} diff --git a/builder-frontend/src/components/shared/Button.tsx b/builder-frontend/src/components/shared/Button.tsx new file mode 100644 index 00000000..654f1a87 --- /dev/null +++ b/builder-frontend/src/components/shared/Button.tsx @@ -0,0 +1,28 @@ +import { Component, JSX, ParentProps } from "solid-js"; + +import styles from "./Button.module.css"; + +interface Props extends JSX.ButtonHTMLAttributes { + variant?: + | "primary" + | "secondary" + | "tertiary" + | "danger" + | "outline-primary" + | "outline-secondary" + | "outline-tertiary" + | "outline-danger"; +} + +export const Button: Component> = (props) => { + const { type, variant, children, ...rest } = props; + return ( + + ); +}; diff --git a/builder-frontend/src/components/shared/Form.css b/builder-frontend/src/components/shared/Form.css new file mode 100644 index 00000000..dde3f53f --- /dev/null +++ b/builder-frontend/src/components/shared/Form.css @@ -0,0 +1,35 @@ +@reference "tailwindcss"; + +.textfield { + @apply relative; +} +.textfield .textfield-input, +.textfield-above { + @apply border-2 border-slate-400 rounded-sm; +} +.textfield:hover .textfield-input, +.textfield-above:hover { + @apply border-slate-500; +} +.textfield-above { + @apply relative; + flex-direction: column; + margin-bottom: 1rem; +} +.textfield-label { + @apply py-2; +} +.textfield-above > .textfield-label { + @apply absolute top-0 left-0 p-4 text-slate-500 pointer-events-none; +} +.textfield-above:has(.textfield-input:focus) { + @apply border-slate-800; +} +.textfield-above > .textfield-input:focus ~ .textfield-label, +.textfield-above > .textfield-input:not(:placeholder-shown) ~ .textfield-label { + transform: scale(0.8) translateY(-1.5rem); + transform-origin: 1rem; +} +.textfield-input { + @apply w-full p-4 focus:outline-0 text-(--text-strong); +} diff --git a/builder-frontend/src/components/shared/Form.tsx b/builder-frontend/src/components/shared/Form.tsx new file mode 100644 index 00000000..452470a5 --- /dev/null +++ b/builder-frontend/src/components/shared/Form.tsx @@ -0,0 +1,94 @@ +import { + Component, + createContext, + createSignal, + JSX, + splitProps, + useContext, +} from "solid-js"; + +import "./Form.css"; + +interface FormProps extends JSX.FormHTMLAttributes {} +interface FormWrapperProps extends JSX.HTMLAttributes { + htmlFor: string; +} +interface LabelWrapperProps extends JSX.HTMLAttributes { + htmlFor: string; + placeholder: string; +} +interface TextInputProps extends JSX.InputHTMLAttributes { + value?: string; + placeholder?: string; +} +interface LabelProps extends JSX.LabelHTMLAttributes {} + +const mergeClasses = (...classes: (string | undefined)[]): string => { + return classes.filter((c) => c !== undefined && c.length > 0).join(" "); +}; +const FormContext = createContext<{ htmlFor?: string }>({}); + +const TextInput = (props: TextInputProps) => { + const [value, setValue] = createSignal(props.value || ""); + const [local, rest] = splitProps(props, ["class", "placeholder"]); + const ctx = useContext(FormContext); + const showPlaceholder = + value().length < 1 && (local.placeholder || "").length > 0; + + return ( + setValue(e.target.value)} + placeholder={showPlaceholder ? local.placeholder : ""} + {...rest} + /> + ); +}; +const Label = (props: LabelProps) => { + const [local, rest] = splitProps(props, ["class"]); + const ctx = useContext(FormContext); + + return ( +