├── config ├── jest │ ├── jest.setup-fetch.js │ ├── jest.setup-jest-dom.js │ ├── index.js │ └── package.json ├── tsconfig │ ├── package.json │ └── tsconfig.common.json ├── tsup │ ├── package.json │ └── index.js └── eslint-config-nrich │ ├── package.json │ └── index.js ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 03_featureRequest.md │ ├── 02_enhancementRequest.md │ └── 01_bugReport.md ├── workflows │ ├── release.yml │ └── build.yml └── pull_request_template.md ├── .yarnrc.yml ├── libs ├── notification │ ├── core │ │ ├── .eslintrc.js │ │ ├── tsup.config.js │ │ ├── jest.config.js │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── api │ │ │ │ ├── index.ts │ │ │ │ ├── notification-type-guards.ts │ │ │ │ └── notification-types.ts │ │ │ ├── hook │ │ │ │ ├── index.ts │ │ │ │ └── use-notifications.ts │ │ │ ├── store │ │ │ │ ├── index.ts │ │ │ │ └── notification-store.ts │ │ │ ├── interceptor │ │ │ │ ├── index.ts │ │ │ │ ├── fetch-notification-interceptor.ts │ │ │ │ └── xhr-notification-interceptor.ts │ │ │ └── index.ts │ │ ├── test │ │ │ ├── testutil │ │ │ │ └── setup-notification-server.ts │ │ │ ├── store │ │ │ │ └── notification-store.test.ts │ │ │ ├── interceptor │ │ │ │ ├── fetch-notification-interceptor.test.ts │ │ │ │ └── xhr-notification-interceptor.test.ts │ │ │ └── hook │ │ │ │ └── use-notification.test.ts │ │ ├── package.json │ │ ├── README.md │ │ └── CHANGELOG.md │ └── mui │ │ ├── .eslintrc.js │ │ ├── tsup.config.js │ │ ├── jest.config.js │ │ ├── tsconfig.json │ │ ├── src │ │ ├── index.ts │ │ └── provider │ │ │ ├── index.ts │ │ │ └── Notifications.tsx │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ └── test │ │ └── provider │ │ └── Notifications.test.tsx └── form-configuration │ └── core │ ├── .eslintrc.js │ ├── tsup.config.js │ ├── tsconfig.json │ ├── jest.config.js │ ├── src │ ├── api │ │ ├── index.ts │ │ └── form-configuration-types.ts │ ├── hook │ │ ├── index.ts │ │ └── use-form-configuration.ts │ ├── store │ │ ├── index.ts │ │ └── form-configuration-store.ts │ ├── component │ │ ├── index.ts │ │ └── FormConfigurationProvider.tsx │ ├── loader │ │ ├── index.ts │ │ └── fetch-form-configurations.ts │ ├── converter │ │ ├── index.ts │ │ └── FormConfigurationValidationConverter.ts │ └── index.ts │ ├── test │ ├── testutil │ │ ├── setup-form-configuration-server.ts │ │ └── form-configuration-generating-util.ts │ ├── store │ │ └── form-configuration-store.test.ts │ ├── loader │ │ └── fetch-form-configurations.test.ts │ ├── component │ │ └── FormConfigurationProvider.test.tsx │ ├── hook │ │ └── use-form-configuration.test.ts │ └── converter │ │ └── FormConfigurationValidationConverter.test.ts │ ├── package.json │ ├── CHANGELOG.md │ └── README.md ├── .editorconfig ├── turbo.json ├── RELEASING.md ├── .changeset ├── README.md └── config.json ├── tsconfig.json ├── CONTRIBUTING.md ├── package.json ├── .gitignore ├── README.md ├── CODE_OF_CONDUCT.md └── LICENSE /config/jest/jest.setup-fetch.js: -------------------------------------------------------------------------------- 1 | require("whatwg-fetch"); 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.2.4.cjs 4 | -------------------------------------------------------------------------------- /config/jest/jest.setup-jest-dom.js: -------------------------------------------------------------------------------- 1 | require("@testing-library/jest-dom/extend-expect"); 2 | -------------------------------------------------------------------------------- /libs/notification/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["nrich"] 4 | }; 5 | -------------------------------------------------------------------------------- /libs/notification/mui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["nrich"] 4 | }; 5 | -------------------------------------------------------------------------------- /libs/form-configuration/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["nrich"] 4 | }; 5 | -------------------------------------------------------------------------------- /config/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@croz/nrich-tsconfig", 3 | "version": "0.0.1", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /libs/notification/core/tsup.config.js: -------------------------------------------------------------------------------- 1 | import sharedConfig from '@croz/nrich-tsup-config'; 2 | 3 | export default sharedConfig; 4 | -------------------------------------------------------------------------------- /libs/notification/mui/tsup.config.js: -------------------------------------------------------------------------------- 1 | import sharedConfig from '@croz/nrich-tsup-config'; 2 | 3 | export default sharedConfig; 4 | -------------------------------------------------------------------------------- /libs/form-configuration/core/tsup.config.js: -------------------------------------------------------------------------------- 1 | import sharedConfig from '@croz/nrich-tsup-config'; 2 | 3 | export default sharedConfig; 4 | -------------------------------------------------------------------------------- /config/tsup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@croz/nrich-tsup-config", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "peerDependencies": { 6 | "tsup": "^6.5.0" 7 | }, 8 | "private": true 9 | } 10 | -------------------------------------------------------------------------------- /config/tsup/index.js: -------------------------------------------------------------------------------- 1 | const tsup = require("tsup"); 2 | 3 | module.exports = tsup.defineConfig({ 4 | entry: ["src/index.ts"], 5 | sourcemap: true, 6 | clean: true, 7 | dts: true, 8 | external: "react", 9 | format: ["cjs", "esm"], 10 | }); 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = 200 11 | 12 | ij_typescript_spaces_within_imports = true 13 | -------------------------------------------------------------------------------- /libs/notification/core/jest.config.js: -------------------------------------------------------------------------------- 1 | const sharedConfig = require('@croz/nrich-jest-config'); 2 | 3 | module.exports = { 4 | ...sharedConfig, 5 | setupFiles: ["@croz/nrich-jest-config/jest.setup-fetch.js"], 6 | coverageDirectory: "../../../coverage/libs/notification/core", 7 | collectCoverageFrom: ["./src/**"] 8 | }; 9 | -------------------------------------------------------------------------------- /libs/notification/mui/jest.config.js: -------------------------------------------------------------------------------- 1 | const sharedConfig = require('@croz/nrich-jest-config'); 2 | 3 | module.exports = { 4 | ...sharedConfig, 5 | setupFilesAfterEnv: ["@croz/nrich-jest-config/jest.setup-jest-dom.js"], 6 | coverageDirectory: "../../../coverage/libs/notification/mui", 7 | collectCoverageFrom: ["./src/**"] 8 | }; 9 | -------------------------------------------------------------------------------- /config/jest/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "jest-environment-jsdom", 3 | testMatch: ["**/__tests__/**/*.ts?(x)", "**/?(*.)+(spec|test).ts?(x)"], 4 | preset: "ts-jest", 5 | collectCoverage: true, 6 | coveragePathIgnorePatterns: ["/dist/", "/test/", "/node_modules/"], 7 | coverageReporters: ["json", "html"], 8 | }; 9 | -------------------------------------------------------------------------------- /config/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@croz/nrich-jest-config", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "jest-environment-jsdom": "^28.1.0", 6 | "ts-jest": "^28.0.4", 7 | "typescript": "^4.7.2" 8 | }, 9 | "main": "index.js", 10 | "peerDependencies": { 11 | "jest": "^28.1.0" 12 | }, 13 | "private": true 14 | } 15 | -------------------------------------------------------------------------------- /libs/notification/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@croz/nrich-tsconfig/tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDirs": ["src", "test"], 5 | "outDir": "dist", 6 | "baseUrl": ".", 7 | "declarationDir": "dist" 8 | }, 9 | "include": [ 10 | "src", 11 | "test" 12 | ], 13 | "exclude": [ 14 | "dist" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /libs/form-configuration/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@croz/nrich-tsconfig/tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDirs": ["src", "test"], 5 | "outDir": "dist", 6 | "baseUrl": ".", 7 | "declarationDir": "dist" 8 | }, 9 | "include": [ 10 | "src", 11 | "test" 12 | ], 13 | "exclude": [ 14 | "dist" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**"] 7 | }, 8 | "test": { 9 | "outputs": ["coverage/**"] 10 | }, 11 | "lint": { 12 | "outputs": [] 13 | }, 14 | "clean": { 15 | "cache": false 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libs/notification/mui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@croz/nrich-tsconfig/tsconfig.common.json", 3 | "compilerOptions": { 4 | "rootDirs": ["src", "test"], 5 | "outDir": "dist", 6 | "baseUrl": ".", 7 | "declarationDir": "dist", 8 | "jsx": "react" 9 | }, 10 | "include": [ 11 | "src", 12 | "test" 13 | ], 14 | "exclude": [ 15 | "dist" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /libs/form-configuration/core/jest.config.js: -------------------------------------------------------------------------------- 1 | const sharedConfig = require('@croz/nrich-jest-config'); 2 | 3 | module.exports = { 4 | ...sharedConfig, 5 | setupFiles: ["@croz/nrich-jest-config/jest.setup-fetch.js"], 6 | setupFilesAfterEnv: ["@croz/nrich-jest-config/jest.setup-jest-dom.js"], 7 | coverageDirectory: "../../../coverage/libs/form-configuration/core", 8 | collectCoverageFrom: ["./src/**"] 9 | }; 10 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing modules 2 | 3 | `nrich-frontend` uses [Changesets](https://github.com/changesets/changesets) to do versioning. This makes releasing really easy and changelogs are automatically generated. 4 | 5 | ## How to do a release 6 | 7 | 1. Run `yarn install` to make sure everything is up-to-date. 8 | 2. Run `yarn version-packages`. 9 | 3. Run `NPM_CONFIG_OTP= yarn release`. `` should be replaced with your NPM OTP code. 10 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { 6 | "repo": "croz-ltd/nrich-frontend" 7 | } 8 | ], 9 | "commit": false, 10 | "fixed": [], 11 | "linked": [], 12 | "access": "public", 13 | "baseBranch": "master", 14 | "updateInternalDependencies": "patch", 15 | "ignore": [], 16 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 17 | "onlyUpdatePeerDependentsWhenOutOfRange": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/tsconfig/tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "node_modules", 4 | "dist", 5 | ], 6 | "compilerOptions": { 7 | "target": "es2015", 8 | "lib": [ 9 | "dom", 10 | "esnext" 11 | ], 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "noEmitOnError": true, 19 | "baseUrl": ".", 20 | "forceConsistentCasingInFileNames": true, 21 | "declaration": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "libs/**/src", 4 | "libs/**/test", 5 | "config" 6 | ], 7 | "exclude": [ 8 | "node_modules", 9 | "libs/**/dist/**/*" 10 | ], 11 | "compilerOptions": { 12 | "target": "es2015", 13 | "lib": [ 14 | "dom", 15 | "esnext" 16 | ], 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "jsx": "react", 20 | "resolveJsonModule": true, 21 | "esModuleInterop": true, 22 | "skipLibCheck": true, 23 | "noEmitOnError": true, 24 | "allowJs": true, 25 | "baseUrl": "." 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/eslint-config-nrich/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-nrich", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "@typescript-eslint/eslint-plugin": "latest", 6 | "@typescript-eslint/parser": "latest", 7 | "eslint-config-airbnb": "^19.0.4", 8 | "eslint-config-airbnb-typescript": "^17.0.0", 9 | "eslint-config-turbo": "^0.0.4", 10 | "eslint-plugin-import": "^2.25.3", 11 | "eslint-plugin-jest": "^26.4.6", 12 | "eslint-plugin-jsx-a11y": "^6.5.1", 13 | "eslint-plugin-react": "^7.28.0", 14 | "eslint-plugin-react-hooks": "^4.3.0" 15 | }, 16 | "main": "index.js", 17 | "peerDependencies": { 18 | "eslint": "^8.2.0" 19 | }, 20 | "private": true 21 | } 22 | -------------------------------------------------------------------------------- /libs/notification/mui/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./provider"; 19 | -------------------------------------------------------------------------------- /libs/notification/mui/src/provider/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./Notifications"; 19 | -------------------------------------------------------------------------------- /libs/notification/core/src/api/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./notification-types"; 19 | -------------------------------------------------------------------------------- /libs/notification/core/src/hook/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./use-notifications"; 19 | -------------------------------------------------------------------------------- /libs/notification/core/src/store/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./notification-store"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/api/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./form-configuration-types"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/hook/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./use-form-configuration"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/store/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./form-configuration-store"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/component/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./FormConfigurationProvider"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/loader/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./fetch-form-configurations"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/converter/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./FormConfigurationValidationConverter"; 19 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./api"; 19 | export * from "./hook"; 20 | export * from "./component"; 21 | -------------------------------------------------------------------------------- /libs/notification/core/src/interceptor/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./fetch-notification-interceptor"; 19 | export * from "./xhr-notification-interceptor"; 20 | -------------------------------------------------------------------------------- /libs/notification/core/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export * from "./api"; 19 | export * from "./interceptor"; 20 | export * from "./hook/use-notifications"; 21 | export { addNotification, removeNotification } from "./store/notification-store"; 22 | -------------------------------------------------------------------------------- /libs/notification/mui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @croz/nrich-notification-mui 2 | 3 | ## 1.1.1 4 | 5 | ### Patch Changes 6 | 7 | - [#48](https://github.com/croz-ltd/nrich-frontend/pull/48) [`7b03ea3`](https://github.com/croz-ltd/nrich-frontend/commit/7b03ea332ee993ffb0df27cb5c5c0dfea37c16f3) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - Update test dependencies to be compatible with React 18 8 | 9 | - [#51](https://github.com/croz-ltd/nrich-frontend/pull/51) [`6a7a5d1`](https://github.com/croz-ltd/nrich-frontend/commit/6a7a5d145ef2c8888c09569bc4c552f65599fca2) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - #50 Fix module field in package.json definitions 10 | 11 | ## 1.1.0 12 | 13 | ### Minor Changes 14 | 15 | - [#42](https://github.com/croz-ltd/nrich-frontend/pull/42) [`620fcdb`](https://github.com/croz-ltd/nrich-frontend/commit/620fcdbe526c8f616547b02785d720e6b0a4f4fd) Thanks [@dmuharemagic](https://github.com/dmuharemagic)! - Initial package publish to NPM 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to nrich-frontend 2 | 3 | Thank you for your interest in contributing to `nrich-frontend`. Anyone is welcome to open [issues](https://github.com/croz-ltd/nrich-frontend/issues) or 4 | [pull requests](https://github.com/croz-ltd/nrich-frontend/pulls) for bug fixes, feature requests, or ideas. If unsure where to start, you can open a 5 | [discussion](https://github.com/croz-ltd/nrich-frontend/discussions) topic first. 6 | 7 | As `nrich-frontend` evolves, the policies described here might change. 8 | 9 | ## Opening a Pull Request 10 | 11 | To contribute to the project, the easiest way would be to fork the repository and open a cross-PR. 12 | 13 | Keep in mind that running the `changeset` script is required before merging. 14 | 15 | When opening the PR, you will get a template to fill out for easier and systematic descriptions. 16 | The issue resolved by the PR will be mentioned, so keep in mind to have an issue ready before opening the PR. 17 | 18 | ## Code of conduct 19 | 20 | Our [Code of conduct](./CODE_OF_CONDUCT.md) governs this project and everyone participating in it. By participating, you are expected to uphold this code. 21 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/testutil/setup-form-configuration-server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { rest } from "msw"; 19 | import { setupServer, SetupServerApi } from "msw/node"; 20 | 21 | import { mockFormConfigurations } from "./form-configuration-generating-util"; 22 | 23 | export const setupFormConfigurationServer = (): SetupServerApi => setupServer( 24 | rest.post("/test-url/fetch-all", (_, response, context) => response(context.json( 25 | mockFormConfigurations, 26 | ))), 27 | ); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nrich-frontend", 3 | "author": "CROZ", 4 | "bugs": "https://github.com/croz-ltd/nrich-frontend/issues", 5 | "devDependencies": { 6 | "@changesets/changelog-github": "^0.4.7", 7 | "@changesets/cli": "^2.25.2", 8 | "syncpack": "^8.3.9", 9 | "turbo": "^1.6.3", 10 | "typedoc": "^0.23.21", 11 | "typescript": "^4.8.4" 12 | }, 13 | "homepage": "https://github.com/croz-ltd/nrich-frontend#readme", 14 | "keywords": [ 15 | "croz", 16 | "nrich", 17 | "react", 18 | "typescript", 19 | "zustand" 20 | ], 21 | "license": "Apache-2.0", 22 | "packageManager": "yarn@3.2.4", 23 | "private": true, 24 | "repository": "croz-ltd/nrich-frontend.git", 25 | "scripts": { 26 | "build": "turbo run build", 27 | "changeset": "changeset", 28 | "clean": "turbo run clean", 29 | "docs:generate": "typedoc --entryPointStrategy packages 'libs/**'", 30 | "lint": "turbo run lint", 31 | "release": "turbo run build && changeset publish", 32 | "test": "turbo run test", 33 | "version-packages": "changeset version && yarn --mode=\"update-lockfile\"" 34 | }, 35 | "workspaces": [ 36 | "config/**/*", 37 | "libs/**/*" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /libs/notification/core/src/api/notification-type-guards.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { NotificationResponse } from "./notification-types"; 19 | 20 | /** 21 | * Checks whether the given response matches the proposed notification format, 22 | * i.e. if it contains 'notification' nested object. 23 | * 24 | * Used internally in {@link fetchNotificationInterceptor} and {@link xhrNotificationInterceptor}. 25 | * 26 | * @param body the response body 27 | */ 28 | export const isNotificationResponse = (body: any): body is NotificationResponse => body && "notification" in body; 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_featureRequest.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for nrich-frontend 4 | title: "" 5 | labels: "feature" 6 | assignees: "" 7 | --- 8 | 9 | 13 | 14 | ## Basic information 15 | 16 | * nrich-frontend module version: 17 | 18 | * Module: 19 | 20 | 21 | ## Additional information 22 | 23 | 24 | 25 | ## Feature description 26 | 27 | 31 | 32 | ### Current behavior 33 | 34 | 35 | 36 | ### Wanted behavior 37 | 38 | 39 | 40 | ### Possible workarounds 41 | 42 | 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_enhancementRequest.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Suggest an enhancement to an existing feature for nrich-frontend 4 | title: "" 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | 13 | 14 | ## Basic information 15 | 16 | * nrich-frontend module version: 17 | 18 | * Module: 19 | 20 | 21 | ## Additional information 22 | 23 | 24 | 25 | ## Enhancement description 26 | 27 | 31 | 32 | ### Current behaviour 33 | 34 | 35 | 36 | ### Wanted behaviour 37 | 38 | 39 | 40 | ### Possible workarounds 41 | 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | strategy: 13 | matrix: 14 | os: [ ubuntu-latest ] 15 | node: [ lts/* ] 16 | registry-url: [ 'https://registry.npmjs.org' ] 17 | registry-scope: [ '@croz' ] 18 | 19 | runs-on: ${{ matrix.os }} 20 | name: Release - ${{ matrix.os }} - Node ${{ matrix.node }} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Setup Node ${{ matrix.node }} 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: ${{ matrix.node }} 31 | registry-url: ${{ matrix.registry-url }} 32 | scope: ${{ matrix.registry-scope }} 33 | cache: 'yarn' 34 | 35 | - name: Install dependencies 36 | run: yarn install --immutable 37 | 38 | - name: Create Release Pull Request or Publish to NPM registry 39 | id: changesets 40 | uses: changesets/action@v1 41 | with: 42 | publish: yarn release 43 | version: yarn version-packages 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 48 | -------------------------------------------------------------------------------- /libs/notification/core/test/testutil/setup-notification-server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { rest } from "msw"; 19 | import { setupServer, SetupServerApi } from "msw/node"; 20 | 21 | export const setupNotificationServer = (): SetupServerApi => setupServer( 22 | rest.get("/with-notification", (_, response, context) => response(context.json( 23 | { 24 | notification: { 25 | title: "Title", 26 | content: "Content", 27 | severity: "WARNING", 28 | }, 29 | data: { 30 | success: true, 31 | }, 32 | }, 33 | ))), 34 | 35 | rest.get("/without-notification", (_, response, context) => response(context.json( 36 | { success: true }, 37 | ))), 38 | 39 | rest.get("/with-plain-text", (_, response, context) => response(context.text( 40 | "plain text", 41 | ))), 42 | ); 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bugReport.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in nrich-frontend 4 | title: "" 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | 16 | 17 | ## Basic information 18 | 19 | * nrich-frontend module version: 20 | 21 | * Module: 22 | 23 | * Link to the complete executable reproducer if available (e.g. GitHub Repo): 24 | 25 | ## Additional information 26 | 27 | 28 | 29 | ## Bug description 30 | 31 | 35 | 36 | ### Steps to reproduce 37 | 38 | 39 | 40 | ### Expected behavior 41 | 42 | 43 | 44 | ### Actual behavior 45 | 46 | 50 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: [ ubuntu-latest ] 14 | node: [ lts/* ] 15 | 16 | runs-on: ${{ matrix.os }} 17 | name: OS ${{ matrix.os }} - Node ${{ matrix.node }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Setup Node ${{ matrix.node }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node }} 28 | cache: 'yarn' 29 | 30 | - name: Install dependencies 31 | run: yarn install --immutable 32 | 33 | - name: Build all modules 34 | run: yarn build 35 | 36 | - name: Lint files 37 | run: yarn lint 38 | 39 | - name: Run tests 40 | run: yarn test 41 | 42 | - name: Generate HTML documentation from TSDoc 43 | if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }} 44 | run: yarn docs:generate 45 | 46 | - name: Publish generated documentation to GitHub Pages 47 | if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }} 48 | uses: JamesIves/github-pages-deploy-action@v4 49 | with: 50 | folder: docs 51 | clean: true 52 | single-commit: true 53 | 54 | - name: Publish test code coverage report 55 | uses: codecov/codecov-action@v3 56 | with: 57 | token: ${{ secrets.CODECOV_TOKEN }} 58 | files: coverage/**/coverage-final.json 59 | -------------------------------------------------------------------------------- /libs/notification/core/src/interceptor/fetch-notification-interceptor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { isNotificationResponse } from "../api/notification-type-guards"; 19 | import { useNotificationStore } from "../store"; 20 | 21 | /** 22 | * Fetch API interceptor which clones the response and checks 23 | * whether the response matches the proposed notification format. 24 | * 25 | * @returns A function which can be called to register the interceptor. 26 | */ 27 | export const fetchNotificationInterceptor = () => { 28 | window.fetch = new Proxy(window.fetch, { 29 | apply(fetch, that, request) { 30 | // @ts-ignore 31 | const result = fetch.apply(that, request); 32 | 33 | result.then((response) => response.clone().json()) 34 | .then((body) => { 35 | if (isNotificationResponse(body)) { 36 | useNotificationStore.getState().add(body.notification); 37 | } 38 | }); 39 | 40 | return result; 41 | }, 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /libs/notification/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@croz/nrich-notification-core", 3 | "description": "Contains core utilities related to the nrich-notification module", 4 | "version": "1.1.4", 5 | "author": "CROZ", 6 | "bugs": "https://github.com/croz-ltd/nrich-frontend/issues", 7 | "dependencies": { 8 | "zustand": "^4.4.7" 9 | }, 10 | "devDependencies": { 11 | "@croz/nrich-jest-config": "*", 12 | "@croz/nrich-tsconfig": "*", 13 | "@croz/nrich-tsup-config": "*", 14 | "@testing-library/react": "^13.4.0", 15 | "@types/jest": "^28.1.0", 16 | "axios": "^1.1.3", 17 | "eslint": "^8.2.0", 18 | "eslint-config-nrich": "*", 19 | "jest": "^28.1.0", 20 | "msw": "^0.48.1", 21 | "react": "^18.1.0", 22 | "react-dom": "^18.1.0", 23 | "tsup": "^6.5.0", 24 | "whatwg-fetch": "^3.6.2" 25 | }, 26 | "files": [ 27 | "dist/*" 28 | ], 29 | "homepage": "https://github.com/croz-ltd/nrich-frontend/tree/master/libs/notification/core#readme", 30 | "keywords": [ 31 | "croz", 32 | "nrich", 33 | "nrich-notification", 34 | "react", 35 | "typescript", 36 | "zustand" 37 | ], 38 | "license": "Apache-2.0", 39 | "main": "dist/index.js", 40 | "module": "dist/index.mjs", 41 | "peerDependencies": { 42 | "react": ">=16.8.0", 43 | "react-dom": ">=16.8.0" 44 | }, 45 | "publishConfig": { 46 | "access": "public" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/croz-ltd/nrich-frontend.git", 51 | "directory": "libs/notification/core" 52 | }, 53 | "scripts": { 54 | "build": "tsup", 55 | "clean": "rm -rf .turbo && rm -rf dist", 56 | "lint": "eslint . --ext .ts,.tsx", 57 | "test": "jest" 58 | }, 59 | "types": "dist/index.d.ts" 60 | } 61 | -------------------------------------------------------------------------------- /libs/notification/core/test/store/notification-store.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { Notification } from "../../src"; 19 | import { useNotificationStore } from "../../src/store"; 20 | 21 | describe("@croz/nrich-notification-core/notification-store", () => { 22 | it("should add and remove notifications from store", () => { 23 | // given 24 | let currentState = useNotificationStore.getState(); 25 | 26 | const notification = { 27 | title: "title", 28 | content: "content", 29 | messageList: [], 30 | severity: "ERROR", 31 | timestamp: new Date(), 32 | } as Notification; 33 | 34 | // when 35 | currentState.add(notification); 36 | 37 | currentState = useNotificationStore.getState(); 38 | 39 | // then 40 | expect(currentState.notifications).toHaveLength(1); 41 | expect(currentState.notifications[0]).toMatchObject(notification); 42 | 43 | // and when 44 | currentState.remove(currentState.notifications[0]); 45 | 46 | currentState = useNotificationStore.getState(); 47 | 48 | // then 49 | expect(currentState.notifications).toHaveLength(0); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /libs/form-configuration/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@croz/nrich-form-configuration-core", 3 | "description": "Contains core utilities related to the nrich-form-configuration module", 4 | "version": "3.1.0", 5 | "author": "CROZ", 6 | "bugs": "https://github.com/croz-ltd/nrich-frontend/issues", 7 | "dependencies": { 8 | "yup": "^1.4.0", 9 | "zustand": "^4.4.7" 10 | }, 11 | "devDependencies": { 12 | "@croz/nrich-jest-config": "*", 13 | "@croz/nrich-tsconfig": "*", 14 | "@croz/nrich-tsup-config": "*", 15 | "@testing-library/jest-dom": "^5.16.5", 16 | "@testing-library/react": "^13.4.0", 17 | "@types/jest": "^28.1.0", 18 | "eslint": "^8.2.0", 19 | "eslint-config-nrich": "*", 20 | "jest": "^28.1.0", 21 | "lodash": "^4.17.21", 22 | "msw": "^0.48.1", 23 | "react": "^18.1.0", 24 | "react-dom": "^18.1.0", 25 | "tsup": "^6.5.0", 26 | "whatwg-fetch": "^3.6.2" 27 | }, 28 | "files": [ 29 | "dist/*" 30 | ], 31 | "homepage": "https://github.com/croz-ltd/nrich-frontend/tree/master/libs/form-configuration/core#readme", 32 | "keywords": [ 33 | "croz", 34 | "nrich", 35 | "nrich-form-configuration", 36 | "react", 37 | "typescript", 38 | "zustand" 39 | ], 40 | "main": "dist/index.js", 41 | "module": "dist/index.mjs", 42 | "peerDependencies": { 43 | "react": ">=16.8.0", 44 | "react-dom": ">=16.8.0" 45 | }, 46 | "publishConfig": { 47 | "access": "public" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "https://github.com/croz-ltd/nrich-frontend.git", 52 | "directory": "libs/form-configuration/core" 53 | }, 54 | "scripts": { 55 | "build": "tsup", 56 | "clean": "rm -rf .turbo && rm -rf dist", 57 | "lint": "eslint . --ext .ts,.tsx", 58 | "test": "jest" 59 | }, 60 | "types": "dist/index.d.ts" 61 | } 62 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/component/FormConfigurationProvider.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import React, { useEffect } from "react"; 19 | 20 | import { FormConfigurationConfiguration } from "../api"; 21 | import { useFormConfiguration } from "../hook"; 22 | import { fetchFormConfigurations } from "../loader"; 23 | 24 | export type Props = { 25 | 26 | /** 27 | * Content to show conditionally 28 | */ 29 | children: React.ReactNode; 30 | 31 | /** 32 | * Custom loader to show until content loads 33 | */ 34 | loader?: React.ReactNode; 35 | } & FormConfigurationConfiguration; 36 | 37 | /** 38 | * Should be used to wrap the whole app that includes forms, so it doesn't render them without loading form configuration from API first. 39 | * @param children content to show conditionally 40 | * @param loader custom loader to show until content loads 41 | */ 42 | export const FormConfigurationProvider = ({ children, loader, ...fetchProps }: Props) => { 43 | useEffect(() => { 44 | fetchFormConfigurations({ ...fetchProps }); 45 | }, []); 46 | 47 | const { formConfigurationLoaded } = useFormConfiguration(); 48 | 49 | return
{formConfigurationLoaded ? children : loader ?? null}
; 50 | }; 51 | -------------------------------------------------------------------------------- /libs/notification/core/src/interceptor/xhr-notification-interceptor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { isNotificationResponse } from "../api/notification-type-guards"; 19 | import { useNotificationStore } from "../store"; 20 | 21 | /** 22 | * XHR interceptor which listens for the response to be acquired 23 | * and checks whether the response matches the proposed notification format. 24 | * 25 | * @returns A function which can be called to register the interceptor. 26 | */ 27 | export const xhrNotificationInterceptor = () => { 28 | const old = XMLHttpRequest.prototype.open; 29 | // eslint-disable-next-line func-names 30 | XMLHttpRequest.prototype.open = function (...args) { 31 | // eslint-disable-next-line func-names 32 | this.addEventListener("readystatechange", function () { 33 | if (this.readyState === 4) { 34 | try { 35 | const body = JSON.parse(this.responseText); 36 | if (isNotificationResponse(body)) { 37 | useNotificationStore.getState().add(body.notification); 38 | } 39 | } 40 | catch (e) { 41 | // ignore non-json responses 42 | } 43 | } 44 | }, false); 45 | // @ts-ignore 46 | old.apply(this, args); 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /libs/notification/mui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@croz/nrich-notification-mui", 3 | "description": "Contains the UI implementation of notifications for the MUI component library", 4 | "version": "1.1.1", 5 | "author": "CROZ", 6 | "bugs": "https://github.com/croz-ltd/nrich-frontend/issues", 7 | "devDependencies": { 8 | "@croz/nrich-jest-config": "*", 9 | "@croz/nrich-tsconfig": "*", 10 | "@croz/nrich-tsup-config": "*", 11 | "@emotion/react": "^11.9.0", 12 | "@emotion/styled": "^11.8.1", 13 | "@mui/material": "^5.0.0", 14 | "@testing-library/dom": "^8.13.0", 15 | "@testing-library/jest-dom": "^5.16.5", 16 | "@testing-library/react": "^13.4.0", 17 | "@testing-library/user-event": "^13.5.0", 18 | "@types/jest": "^28.1.0", 19 | "eslint": "^8.2.0", 20 | "eslint-config-nrich": "*", 21 | "jest": "^28.1.0", 22 | "react": "^18.1.0", 23 | "react-dom": "^18.1.0", 24 | "tsup": "^6.5.0" 25 | }, 26 | "files": [ 27 | "dist/*" 28 | ], 29 | "homepage": "https://github.com/croz-ltd/nrich-frontend/tree/master/libs/notification/mui#readme", 30 | "keywords": [ 31 | "croz", 32 | "nrich", 33 | "nrich-notification", 34 | "react", 35 | "typescript", 36 | "zustand" 37 | ], 38 | "license": "Apache-2.0", 39 | "main": "dist/index.js", 40 | "module": "dist/index.mjs", 41 | "peerDependencies": { 42 | "@croz/nrich-notification-core": "^1.0.0", 43 | "@mui/material": "^5.0.0", 44 | "react": ">=16.8.0", 45 | "react-dom": ">=16.8.0" 46 | }, 47 | "publishConfig": { 48 | "access": "public" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/croz-ltd/nrich-frontend.git", 53 | "directory": "libs/notification/mui" 54 | }, 55 | "scripts": { 56 | "build": "tsup", 57 | "clean": "rm -rf .turbo && rm -rf dist", 58 | "lint": "eslint . --ext .ts,.tsx", 59 | "test": "jest" 60 | }, 61 | "types": "dist/index.d.ts" 62 | } 63 | -------------------------------------------------------------------------------- /libs/notification/core/src/api/notification-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | export type NotificationSeverity = "INFO" | "WARNING" | "ERROR"; 19 | 20 | export interface Notification { 21 | 22 | /** 23 | * Title of the notification. 24 | */ 25 | title: string; 26 | 27 | /** 28 | * Content of the notification (i.e. for exception 29 | * notification this will contain resolved message for exception or 'Error occurred' text). 30 | */ 31 | content: string; 32 | 33 | /** 34 | * List of messages (i.e. for validation failure notification 35 | * this will contain all validation failure messages). 36 | */ 37 | messageList: string[]; 38 | 39 | /** 40 | * Severity indicating importance of notification. 41 | */ 42 | severity: NotificationSeverity; 43 | 44 | /** 45 | * Custom override data sent from the server. Each component library will have its own data here. 46 | * For example current supported options in mui implementation are position and autoClose. 47 | */ 48 | uxNotificationOptions?: Record; 49 | 50 | /** 51 | * Timestamp of notification. 52 | */ 53 | timestamp: Date 54 | } 55 | 56 | export interface NotificationResponse { 57 | 58 | /** 59 | * Notification 60 | */ 61 | notification: Notification 62 | 63 | } 64 | -------------------------------------------------------------------------------- /libs/notification/core/src/hook/use-notifications.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { Notification } from "../api"; 19 | import { useNotificationStore } from "../store"; 20 | 21 | export type NotificationOptions = { 22 | /** 23 | * Array of current state notifications. 24 | */ 25 | notifications: Notification[], 26 | /** 27 | * Adds notification to state. 28 | * @param notification notification to add 29 | */ 30 | add: (notification: Notification) => void, 31 | /** 32 | * Removes notification from state. 33 | * @param notification 34 | */ 35 | remove: (notification: Notification) => void 36 | }; 37 | export type UseNotifications = () => NotificationOptions; 38 | /** 39 | * A hook which simplifies the usage of the intercepted notification state. 40 | * Uses the internal {@link useNotificationStore} hook for managing the notification state. 41 | * 42 | * @returns An array of options to access the notification state and remove a single notification. 43 | */ 44 | export const useNotifications: UseNotifications = () => { 45 | const notifications = useNotificationStore((state) => state.notifications); 46 | const add = useNotificationStore((state) => state.add); 47 | const remove = useNotificationStore((state) => state.remove); 48 | 49 | return { notifications, add, remove }; 50 | }; 51 | -------------------------------------------------------------------------------- /libs/notification/core/README.md: -------------------------------------------------------------------------------- 1 | # @croz/nrich-notification-core 2 | 3 | ## Overview 4 | 5 | `@croz/nrich-notification-core` is a module that is designed for showing automatic messages from the backend on the user interface. 6 | It's the frontend part of [nrich-notification](https://github.com/croz-ltd/nrich/tree/master/nrich-notification) backend module. 7 | 8 | Internally, it intercepts http calls and scans for sign of nrich notification object, and shows the notification if it exists. 9 | 10 | ## Setup 11 | 12 | To use this module in your project run `npm install @croz/nrich-notification-core` or `yarn add @croz/nrich-notification-core` 13 | 14 | ## Usage 15 | 16 | 1. On the top level of your app, register an appropriate interceptor for notifications. 17 | - If you use fetch API or a lib that uses fetch internally, use `fetchNotificationInterceptor()`. 18 | - If you use a lib that uses `XMLHttpRequest`, e.g. `axios`, use `xhrNotificationInterceptor()`. 19 | 20 | 2. Using the `useNotification()` custom hook you get an object containing `notifications` array and `remove` and `add` methods for working with that array. Alternatively, you can use the standalone `removeNotification` and `addNotification` methods if the hook variant is not fit for your use-case. 21 | 22 | Example: 23 | 24 | ```tsx 25 | import { useNotifications } from "@croz/nrich-notification-core"; 26 | 27 | const Notification = ({ title, content, onRemove }) => ( 28 |
29 |

{title}

30 |
{content}
31 | 32 |
33 | ) 34 | 35 | export const Notifications = () => { 36 | const { notifications, remove } = useNotifications(); 37 | 38 | return ( 39 |
40 | {notifications.map(notification => remove(notification)}/>)} 41 |
42 | ) 43 | } 44 | ``` 45 | 46 | If you're using this module alone, you need to provide your own notification UI. For the prepared implementation in MUI, see [@croz/nrich-notification-mui](../mui/README.md) docs 47 | -------------------------------------------------------------------------------- /libs/notification/mui/README.md: -------------------------------------------------------------------------------- 1 | # @croz/nrich-notification-mui 2 | 3 | ## Overview 4 | 5 | `@croz/nrich-notification-mui` is a [MUI](https://mui.com/) wrapper for the [@croz/nrich-notification-core](../core/README.md) module. 6 | For the display of the notifications a `Snackbar` and `Alert` are used. 7 | 8 | ## Setup 9 | 10 | To use this module in your project run `npm install @croz/nrich-notification-mui` or `yarn add @croz/nrich-notification-mui` 11 | 12 | ## Usage 13 | 14 | 1. Add `Notifications` component on a part of your app, usually on `App` or some other higher level component. 15 | 16 | ```tsx 17 | import { Notifications } from "@croz/nrich-notification-mui"; 18 | 19 | const App = () => { 20 | return ( 21 |
22 | 23 | {/* other components... */} 24 |
25 | ) 26 | } 27 | ``` 28 | 29 | Available props for the `Notifications` component are: 30 | 31 | | Prop name | Description | Possible values | Default value | 32 | |-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|----------------| 33 | | autoClose | The duration after which the notification closes
expressed in milliseconds. If left undefined, it doesn't close. | number in milliseconds, or `undefined` | `undefined` | 34 | | position | Specifies the notification position on the page,
derived from a set of predefined positions available in [MUI](https://mui.com/material-ui/react-snackbar/#positioned-snackbars) | `top-left`, `top-center`, `top-right`,
`bottom-left`, `bottom-center`, `bottom-right` | `bottom-right` | 35 | 36 | 37 | -------------------------------------------------------------------------------- /libs/notification/core/src/store/notification-store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { create } from "zustand"; 19 | 20 | import { Notification } from "../api"; 21 | 22 | export interface NotificationState { 23 | 24 | /** 25 | * Array of current state notifications. 26 | */ 27 | notifications: Notification[]; 28 | 29 | /** 30 | * Adds notification to state. 31 | * @param notification notification to add 32 | */ 33 | add: (notification: Notification) => void; 34 | 35 | /** 36 | * Removes notification from state. 37 | * @param notification 38 | */ 39 | remove: (notification: Notification) => void; 40 | 41 | } 42 | 43 | /** 44 | * Creation of the API for managing internal notification state. 45 | * Used internally in the {@link useNotifications} hook. 46 | * 47 | * @returns A hook for managing notification state usable in a React environment. 48 | */ 49 | const store = create((set) => ({ 50 | notifications: [], 51 | add: (notification) => set((state) => ({ 52 | notifications: [...state.notifications, { ...notification, timestamp: new Date(notification.timestamp) || new Date() }], 53 | })), 54 | remove: (notification) => set((state) => ({ 55 | notifications: state.notifications.filter((currentNotification) => currentNotification !== notification), 56 | })), 57 | })); 58 | 59 | export const useNotificationStore = store; 60 | export const addNotification = store.getState().add; 61 | export const removeNotification = store.getState().remove; 62 | -------------------------------------------------------------------------------- /libs/notification/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @croz/nrich-notification-core 2 | 3 | ## 1.1.4 4 | 5 | ### Patch Changes 6 | 7 | - [#65](https://github.com/croz-ltd/nrich-frontend/pull/65) [`b4d9314`](https://github.com/croz-ltd/nrich-frontend/commit/b4d931430cfe6ec54a30fd2f659a57106ddf7953) Thanks [@lmatejic-CROZ](https://github.com/lmatejic-CROZ)! - Added missing export, patched the github templates for clarity 8 | 9 | ## 1.1.3 10 | 11 | ### Patch Changes 12 | 13 | - [#63](https://github.com/croz-ltd/nrich-frontend/pull/63) [`3e3ba59`](https://github.com/croz-ltd/nrich-frontend/commit/3e3ba590a6a45ce62c89b3f5aea3487024c45f68) Thanks [@lmatejic-CROZ](https://github.com/lmatejic-CROZ)! - Exposed notification API 'add' and 'remove' methods as non-hook function variants. 14 | 15 | ## 1.1.2 16 | 17 | ### Patch Changes 18 | 19 | - [#56](https://github.com/croz-ltd/nrich-frontend/pull/56) [`31689c6`](https://github.com/croz-ltd/nrich-frontend/commit/31689c652bde92ef1a6865e5de9aa4977804412c) Thanks [@dmurat](https://github.com/dmurat)! - Upgrade zustand lib and its import statements to non-deprecated sytax 20 | 21 | - [#55](https://github.com/croz-ltd/nrich-frontend/pull/55) [`3298e99`](https://github.com/croz-ltd/nrich-frontend/commit/3298e99e0a9da0e90ab37c3d116e9aaff6c45d83) Thanks [@dmurat](https://github.com/dmurat)! - Update xhrNotificationInterceptor to ignore non-json responses 22 | 23 | ## 1.1.1 24 | 25 | ### Patch Changes 26 | 27 | - [#48](https://github.com/croz-ltd/nrich-frontend/pull/48) [`7b03ea3`](https://github.com/croz-ltd/nrich-frontend/commit/7b03ea332ee993ffb0df27cb5c5c0dfea37c16f3) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - Update test dependencies to be compatible with React 18 28 | 29 | - [#51](https://github.com/croz-ltd/nrich-frontend/pull/51) [`6a7a5d1`](https://github.com/croz-ltd/nrich-frontend/commit/6a7a5d145ef2c8888c09569bc4c552f65599fca2) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - #50 Fix module field in package.json definitions 30 | 31 | ## 1.1.0 32 | 33 | ### Minor Changes 34 | 35 | - [#42](https://github.com/croz-ltd/nrich-frontend/pull/42) [`620fcdb`](https://github.com/croz-ltd/nrich-frontend/commit/620fcdbe526c8f616547b02785d720e6b0a4f4fd) Thanks [@dmuharemagic](https://github.com/dmuharemagic)! - Initial package publish to NPM 36 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/store/form-configuration-store.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { useFormConfigurationStore } from "../../src/store"; 19 | import { mockFormYupConfiguration, mockFormYupConfigurations } from "../testutil/form-configuration-generating-util"; 20 | 21 | describe("@croz/nrich-form-configuration-core/form-configuration-store", () => { 22 | it("should set, add and remove form configurations from store", () => { 23 | // given 24 | let currentState = useFormConfigurationStore.getState(); 25 | 26 | const formConfiguration = mockFormYupConfiguration; 27 | const formConfigurationList = mockFormYupConfigurations; 28 | 29 | // when 30 | currentState.set(formConfigurationList); 31 | 32 | currentState = useFormConfigurationStore.getState(); 33 | 34 | // then 35 | expect(currentState.formYupConfigurations).toHaveLength(2); 36 | expect(currentState.formYupConfigurations[0]).toMatchObject(formConfigurationList[0]); 37 | expect(currentState.formYupConfigurations[1]).toMatchObject(formConfigurationList[1]); 38 | 39 | // and when 40 | currentState.add(formConfiguration); 41 | 42 | currentState = useFormConfigurationStore.getState(); 43 | 44 | // then 45 | expect(currentState.formYupConfigurations).toHaveLength(3); 46 | expect(currentState.formYupConfigurations[2]).toMatchObject(formConfiguration); 47 | 48 | // and when 49 | currentState.remove(currentState.formYupConfigurations[2]); 50 | 51 | currentState = useFormConfigurationStore.getState(); 52 | 53 | // then 54 | expect(currentState.formYupConfigurations).toHaveLength(2); 55 | expect(currentState.formYupConfigurations[0]).toMatchObject(formConfigurationList[0]); 56 | expect(currentState.formYupConfigurations[1]).toMatchObject(formConfigurationList[1]); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pnp.* 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/plugins 5 | !.yarn/releases 6 | !.yarn/sdks 7 | !.yarn/versions 8 | 9 | # Dependency directories 10 | node_modules/ 11 | 12 | # production 13 | build/ 14 | dist/ 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | 28 | # User-specific stuff 29 | .idea/**/workspace.xml 30 | .idea/**/tasks.xml 31 | .idea/**/usage.statistics.xml 32 | .idea/**/dictionaries 33 | .idea/**/shelf 34 | 35 | # AWS User-specific 36 | .idea/**/aws.xml 37 | 38 | # Generated files 39 | .idea/**/contentModel.xml 40 | 41 | # Sensitive or high-churn files 42 | .idea/**/dataSources/ 43 | .idea/**/dataSources.ids 44 | .idea/**/dataSources.local.xml 45 | .idea/**/sqlDataSources.xml 46 | .idea/**/dynamic.xml 47 | .idea/**/uiDesigner.xml 48 | .idea/**/dbnavigator.xml 49 | 50 | # Gradle 51 | .idea/**/gradle.xml 52 | .idea/**/libraries 53 | 54 | # Gradle and Maven with auto-import 55 | # When using Gradle or Maven with auto-import, you should exclude module files, 56 | # since they will be recreated, and may cause churn. Uncomment if using 57 | # auto-import. 58 | # .idea/artifacts 59 | # .idea/compiler.xml 60 | # .idea/jarRepositories.xml 61 | # .idea/modules.xml 62 | # .idea/*.iml 63 | # .idea/modules 64 | *.iml 65 | # *.ipr 66 | 67 | # CMake 68 | cmake-build-*/ 69 | 70 | # Mongo Explorer plugin 71 | .idea/**/mongoSettings.xml 72 | 73 | # File-based project format 74 | *.iws 75 | 76 | # IntelliJ 77 | out/ 78 | 79 | # mpeltonen/sbt-idea plugin 80 | .idea_modules/ 81 | 82 | # JIRA plugin 83 | atlassian-ide-plugin.xml 84 | 85 | # Cursive Clojure plugin 86 | .idea/replstate.xml 87 | 88 | # SonarLint plugin 89 | .idea/sonarlint/ 90 | 91 | # Crashlytics plugin (for Android Studio and IntelliJ) 92 | com_crashlytics_export_strings.xml 93 | crashlytics.properties 94 | crashlytics-build.properties 95 | fabric.properties 96 | 97 | # Editor-based Rest Client 98 | .idea/httpRequests 99 | 100 | # Android studio 3.1+ serialized cache file 101 | .idea/caches/build_file_checksums.ser 102 | 103 | ### Intellij+all Patch ### 104 | # Ignore everything but code style settings and run configurations 105 | # that are supposed to be shared within teams. 106 | 107 | .idea/* 108 | 109 | !.idea/codeStyles 110 | !.idea/runConfigurations 111 | /coverage 112 | /docs 113 | 114 | # Turborepo 115 | .turbo 116 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Basic information 7 | 8 | * nrich-frontend module version: 9 | 10 | * Module: 11 | 12 | 13 | ## Additional information 14 | 15 | 16 | 17 | ## Description 18 | 19 | ### Summary 20 | 21 | 22 | 23 | ### Details 24 | 25 | 26 | 27 | ### Related issue 28 | 29 | 33 | 34 | ## Types of changes 35 | 36 | 37 | 38 | - Bug fix (non-breaking change which fixes an issue) 39 | - Enhancement (non-breaking change which enhances existing functionality) 40 | - New feature (non-breaking change which adds functionality) 41 | - Breaking change (fix, enhancement or feature that would cause existing functionality to change in backward-incompatible way) 42 | - Docs change 43 | - Refactoring 44 | - Dependency upgrade 45 | 46 | ## Checklist 47 | 48 | 57 | 58 | - [ ] I have read the project's **CONTRIBUTING** document 59 | - [ ] I have checked my code with the project's static analysis tooling 60 | - [ ] I have formatted my code with the project's IDEA code-style configuration 61 | - [ ] I have checked my code for misspellings 62 | - [ ] I have organized my changes in easy-to-follow commits 63 | - [ ] My change requires a change to the documentation 64 | - [ ] I have updated the documentation accordingly 65 | - [ ] I have added tests to cover my changes 66 | - [ ] All new and existing tests pass. 67 | -------------------------------------------------------------------------------- /libs/notification/core/test/interceptor/fetch-notification-interceptor.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { fetchNotificationInterceptor } from "../../src"; 19 | import { useNotificationStore } from "../../src/store"; 20 | import { setupNotificationServer } from "../testutil/setup-notification-server"; 21 | 22 | const server = setupNotificationServer(); 23 | 24 | beforeAll(() => { 25 | server.listen(); 26 | fetchNotificationInterceptor(); 27 | }); 28 | 29 | afterEach(() => server.resetHandlers()); 30 | afterAll(() => server.close()); 31 | 32 | describe("@croz/nrich-notification-core/fetch-notification-interceptor", () => { 33 | it("should resolve notification", async () => { 34 | // when 35 | const response = await fetch("/with-notification"); 36 | const parsedResponse = await response.json(); 37 | 38 | // then 39 | expect(parsedResponse).toBeDefined(); 40 | expect(parsedResponse.data).toMatchObject({ success: true }); 41 | 42 | // and when 43 | const notificationState = useNotificationStore.getState(); 44 | 45 | // then 46 | expect(notificationState.notifications).toHaveLength(1); 47 | expect(notificationState.notifications[0]).toMatchObject({ title: "Title", content: "Content", severity: "WARNING" }); 48 | 49 | // cleanup 50 | notificationState.remove(notificationState.notifications[0]); 51 | }); 52 | 53 | it("should ignore responses without notification", async () => { 54 | // when 55 | const response = await fetch("/without-notification"); 56 | const parsedResponse = await response.json(); 57 | 58 | // then 59 | expect(parsedResponse).toBeDefined(); 60 | expect(parsedResponse).toMatchObject({ success: true }); 61 | 62 | // and when 63 | const notificationState = useNotificationStore.getState(); 64 | 65 | // then 66 | expect(notificationState.notifications).toHaveLength(0); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/loader/fetch-form-configurations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import _uniqBy from "lodash/uniqBy"; 19 | 20 | import { FormConfiguration, FormConfigurationConfiguration, FormYupConfiguration } from "../api"; 21 | import { FormConfigurationValidationConverter } from "../converter"; 22 | import { useFormConfigurationStore } from "../store"; 23 | 24 | const mergeFormYupConfigurationsWithoutDuplicates = (oldFormYupConfiguration: FormYupConfiguration[], newFormYupConfiguration: FormYupConfiguration[]) => { 25 | const mergedFormYupConfigurations = [...oldFormYupConfiguration, ...newFormYupConfiguration]; 26 | 27 | return _uniqBy(mergedFormYupConfigurations, "formId"); 28 | }; 29 | 30 | export const fetchFormConfigurations = async ({ url, requestOptionsResolver, additionalValidatorConverters }: FormConfigurationConfiguration): Promise => { 31 | const formConfigurationValidationConverter = new FormConfigurationValidationConverter(additionalValidatorConverters); 32 | const additionalOptions = requestOptionsResolver?.() || {}; 33 | const finalUrl = url || "/nrich/form/configuration"; 34 | 35 | const response = await fetch(`${finalUrl}/fetch-all`, { 36 | method: "POST", 37 | ...additionalOptions, 38 | }); 39 | const body = await response.json() as FormConfiguration[]; 40 | 41 | const formYupConfigurations: FormYupConfiguration[] = []; 42 | 43 | // set the response to the form configuration store 44 | if (response.ok) { 45 | body.forEach((item) => { 46 | formYupConfigurations.push({ 47 | formId: item.formId, 48 | yupSchema: formConfigurationValidationConverter.convertFormConfigurationToYupSchema(item.constrainedPropertyConfigurationList), 49 | }); 50 | }); 51 | useFormConfigurationStore.getState().set(mergeFormYupConfigurationsWithoutDuplicates(useFormConfigurationStore.getState().formYupConfigurations, formYupConfigurations)); 52 | useFormConfigurationStore.getState().setFormConfigurationLoaded(true); 53 | } 54 | 55 | return body; 56 | }; 57 | -------------------------------------------------------------------------------- /libs/notification/core/test/hook/use-notification.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { act, renderHook } from "@testing-library/react"; 19 | 20 | import { Notification, useNotifications } from "../../src"; 21 | import { useNotificationStore } from "../../src/store"; 22 | 23 | const mockNotifications: Notification[] = [ 24 | { 25 | title: "Action with manual resolving", 26 | content: "Action with manual resolving was successful.", 27 | messageList: [], 28 | severity: "INFO", 29 | timestamp: new Date(), 30 | }, 31 | { 32 | title: "Action with notification resolved from request and additional data", 33 | content: "Action with notification resolved from request and additional data was successful with warnings.", 34 | messageList: [], 35 | severity: "WARNING", 36 | uxNotificationOptions: {}, 37 | timestamp: new Date(), 38 | }, 39 | ]; 40 | 41 | describe("@croz/nrich-notification-core/use-notification", () => { 42 | beforeAll(() => { 43 | mockNotifications.forEach((notification) => useNotificationStore.getState().add(notification)); 44 | }); 45 | 46 | it("should resolve notification state", () => { 47 | // when 48 | const { result } = renderHook(() => useNotifications()); 49 | const { notifications } = result.current; 50 | 51 | // then 52 | expect(notifications).toHaveLength(2); 53 | expect(notifications[0]).toEqual({ ...notifications[0], timestamp: expect.any(Date) }); 54 | expect(notifications[1]).toEqual({ ...notifications[1], timestamp: expect.any(Date) }); 55 | }); 56 | 57 | it("should delete notification state", () => { 58 | // when 59 | const { result } = renderHook(() => useNotifications()); 60 | const { notifications, remove } = result.current; 61 | 62 | // then 63 | expect(notifications).toHaveLength(2); 64 | 65 | // and when 66 | act(() => { 67 | remove(notifications[0]); 68 | remove(notifications[1]); 69 | }); 70 | 71 | // then 72 | expect(result.current.notifications).toHaveLength(0); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /config/eslint-config-nrich/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true 6 | }, 7 | extends: [ 8 | "plugin:react/recommended", 9 | "turbo", 10 | "airbnb", 11 | "airbnb-typescript" 12 | ], 13 | parser: "@typescript-eslint/parser", 14 | parserOptions: { 15 | ecmaFeatures: { 16 | jsx: true 17 | }, 18 | ecmaVersion: "latest", 19 | sourceType: "module", 20 | project: "./tsconfig.json", 21 | }, 22 | plugins: [ 23 | "react", 24 | "@typescript-eslint" 25 | ], 26 | rules: { 27 | "import/prefer-default-export": "off", 28 | "react/require-default-props": "off", 29 | "import/no-extraneous-dependencies": [ 30 | "error", 31 | { 32 | devDependencies: true 33 | } 34 | ], 35 | "react/function-component-definition": [ 36 | "error", 37 | { 38 | namedComponents: "arrow-function", 39 | unnamedComponents: "arrow-function" 40 | } 41 | ], 42 | quotes: [ 43 | "error", 44 | "double", 45 | { 46 | avoidEscape: true 47 | } 48 | ], 49 | "@typescript-eslint/quotes": [ 50 | "error", 51 | "double", 52 | { 53 | avoidEscape: true 54 | } 55 | ], 56 | "@typescript-eslint/brace-style": [ 57 | "error", 58 | "stroustrup" 59 | ], 60 | "max-len": [ 61 | "error", 62 | 200 63 | ], 64 | "import/order": [ 65 | "error", 66 | { 67 | alphabetize: { 68 | order: "asc" 69 | }, 70 | "newlines-between": "always", 71 | pathGroups: [ 72 | { 73 | pattern: "react.*", 74 | group: "builtin", 75 | position: "before" 76 | }, 77 | { 78 | pattern: "@croz/**", 79 | group: "internal", 80 | position: "after" 81 | }, 82 | { 83 | pattern: "@mui/**", 84 | group: "external", 85 | position: "after" 86 | } 87 | ], 88 | pathGroupsExcludedImportTypes: [ 89 | "react.*", 90 | "@croz/**", 91 | "@mui/**" 92 | ], 93 | groups: [ 94 | "builtin", 95 | "external", 96 | "internal", 97 | [ 98 | "parent", 99 | "sibling" 100 | ], 101 | "index" 102 | ] 103 | } 104 | ], 105 | "linebreak-style": [ 106 | "error", 107 | "unix" 108 | ] 109 | }, 110 | ignorePatterns: [ 111 | "dist", 112 | "build", 113 | "node_modules", 114 | ".eslintrc.js", 115 | "jest.config.js", 116 | "tsup.config.js", 117 | "index.js" 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/loader/fetch-form-configurations.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { fetchFormConfigurations } from "../../src/loader"; 19 | import { useFormConfigurationStore } from "../../src/store"; 20 | import { mockFormConfigurations, mockFormYupConfigurations, mockValidatorConverters } from "../testutil/form-configuration-generating-util"; 21 | import { setupFormConfigurationServer } from "../testutil/setup-form-configuration-server"; 22 | 23 | const server = setupFormConfigurationServer(); 24 | 25 | beforeAll(() => { 26 | server.listen(); 27 | }); 28 | 29 | afterEach(() => server.resetHandlers()); 30 | afterAll(() => server.close()); 31 | 32 | describe("@croz/nrich-form-configuration-core/fetch-form-configurations", () => { 33 | it("should resolve form configurations", async () => { 34 | // when 35 | const response = await fetchFormConfigurations({ url: "/test-url", additionalValidatorConverters: mockValidatorConverters }); 36 | 37 | // then 38 | expect(response).toBeDefined(); 39 | expect(response).toMatchObject(mockFormConfigurations); 40 | 41 | // and when 42 | const formConfigurationState = useFormConfigurationStore.getState(); 43 | 44 | // then 45 | expect(formConfigurationState.formYupConfigurations).toHaveLength(2); 46 | expect(formConfigurationState.formYupConfigurations[0]).toBeTruthy(); 47 | expect(formConfigurationState.formYupConfigurations[1]).toBeTruthy(); 48 | 49 | // cleanup 50 | formConfigurationState.set([]); 51 | }); 52 | 53 | it("should resolve form configurations and add them to store with ignoring duplicates", async () => { 54 | // given 55 | useFormConfigurationStore.getState().set([mockFormYupConfigurations[0]]); 56 | 57 | // when 58 | const response = await fetchFormConfigurations({ url: "/test-url", additionalValidatorConverters: mockValidatorConverters }); 59 | 60 | // then 61 | expect(response).toHaveLength(2); 62 | 63 | // and when 64 | const formConfigurationState = useFormConfigurationStore.getState(); 65 | 66 | // then 67 | expect(formConfigurationState.formYupConfigurations).toHaveLength(2); 68 | 69 | // cleanup 70 | formConfigurationState.set([]); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /libs/notification/core/test/interceptor/xhr-notification-interceptor.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import axios from "axios"; 19 | 20 | import { xhrNotificationInterceptor } from "../../src"; 21 | import { useNotificationStore } from "../../src/store"; 22 | import { setupNotificationServer } from "../testutil/setup-notification-server"; 23 | 24 | const server = setupNotificationServer(); 25 | 26 | beforeAll(() => { 27 | server.listen(); 28 | xhrNotificationInterceptor(); 29 | }); 30 | 31 | afterEach(() => server.resetHandlers()); 32 | afterAll(() => server.close()); 33 | 34 | describe("@croz/nrich-notification-core/xhr-notification-interceptor", () => { 35 | it("should resolve notification", async () => { 36 | // when 37 | const response = await axios.get("/with-notification"); 38 | 39 | // then 40 | expect(response).toBeDefined(); 41 | expect(response.data.data).toMatchObject({ success: true }); 42 | 43 | // and when 44 | const notificationState = useNotificationStore.getState(); 45 | 46 | // then 47 | expect(notificationState.notifications).toHaveLength(1); 48 | expect(notificationState.notifications[0]).toMatchObject({ title: "Title", content: "Content", severity: "WARNING" }); 49 | 50 | // cleanup 51 | notificationState.remove(notificationState.notifications[0]); 52 | }); 53 | 54 | it("should ignore responses without notification", async () => { 55 | // when 56 | const response = await axios.get("/without-notification"); 57 | 58 | // then 59 | expect(response).toBeDefined(); 60 | expect(response.data).toMatchObject({ success: true }); 61 | 62 | // and when 63 | const notificationState = useNotificationStore.getState(); 64 | 65 | // then 66 | expect(notificationState.notifications).toHaveLength(0); 67 | }); 68 | 69 | it("should ignore responses with plain text", async () => { 70 | // when 71 | const response = await axios.get("/with-plain-text"); 72 | 73 | // then 74 | expect(response).toBeDefined(); 75 | expect(response.data).toMatch("plain text"); 76 | 77 | // and when 78 | const notificationState = useNotificationStore.getState(); 79 | 80 | // then 81 | expect(notificationState.notifications).toHaveLength(0); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/store/form-configuration-store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { create } from "zustand"; 19 | 20 | import { FormYupConfiguration } from "../api"; 21 | 22 | export interface FormConfigurationState { 23 | 24 | /** 25 | * Array of current state form configurations. 26 | */ 27 | formYupConfigurations: FormYupConfiguration[]; 28 | 29 | /** 30 | * Flag that indicates weather form configuration is fetched from API. 31 | */ 32 | formConfigurationLoaded: boolean; 33 | 34 | /** 35 | * Sets form configurations to state. 36 | * Use on initial call to find-all endpoint. 37 | * @param formConfigurations formConfigurations to set 38 | */ 39 | set: (formYupConfigurations: FormYupConfiguration[]) => void; 40 | 41 | /** 42 | * Adds form configuration to state. 43 | * @param formConfiguration formConfiguration to add 44 | */ 45 | add: (formYupConfiguration: FormYupConfiguration) => void; 46 | 47 | /** 48 | * Removes form configuration to state. 49 | * @param formConfiguration formConfiguration to add 50 | */ 51 | remove: (formYupConfiguration: FormYupConfiguration) => void; 52 | 53 | /** 54 | * Sets form configuration loaded to state. 55 | * @param isLoaded loaded flag to set 56 | */ 57 | setFormConfigurationLoaded: (formConfigurationLoaded: boolean) => void; 58 | } 59 | 60 | /** 61 | * Creation of the API for managing internal form configuration state. 62 | * Used internally in the {@link useFormConfiguration} hook. 63 | * 64 | * @returns A hook for managing form configuration state usable in a React environment. 65 | */ 66 | export const useFormConfigurationStore = create((set) => ({ 67 | formYupConfigurations: [], 68 | formConfigurationLoaded: false, 69 | set: ((formYupConfigurations) => set((state) => ({ 70 | ...state, formYupConfigurations, 71 | }))), 72 | add: (formYupConfiguration) => set((state) => ({ 73 | formYupConfigurations: [...state.formYupConfigurations, { ...formYupConfiguration }], 74 | })), 75 | remove: (formYupConfiguration) => set((state) => ({ 76 | formYupConfigurations: state.formYupConfigurations.filter((currentFormConfiguration) => currentFormConfiguration !== formYupConfiguration), 77 | })), 78 | setFormConfigurationLoaded: (formConfigurationLoaded) => set((state) => ({ ...state, formConfigurationLoaded })), 79 | })); 80 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/component/FormConfigurationProvider.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { render, renderHook, screen } from "@testing-library/react"; 19 | import React from "react"; 20 | import { act } from "react-dom/test-utils"; 21 | 22 | import { FormConfigurationProvider } from "../../src"; 23 | import { useFormConfigurationStore } from "../../src/store"; 24 | 25 | describe("@croz/nrich-form-configuration-core/FormConfigurationProvider", () => { 26 | it("should not render children if loader is not defined and fetch is not executed", () => { 27 | // given 28 | useFormConfigurationStore.getState().setFormConfigurationLoaded(false); 29 | const children = "Should not be rendered"; 30 | 31 | // when 32 | const { queryByText } = render({children}); 33 | 34 | // then 35 | expect(queryByText(children)).not.toBeInTheDocument(); 36 | }); 37 | 38 | it("should render loader if loader is defined and fetch is not executed", () => { 39 | // given 40 | useFormConfigurationStore.getState().setFormConfigurationLoaded(false); 41 | const children = "Should not be rendered"; 42 | const loader = "Loading..."; 43 | 44 | // when 45 | const { queryByText, getByText } = render({children}); 46 | 47 | // then 48 | expect(queryByText(children)).not.toBeInTheDocument(); 49 | expect(getByText(loader)).toBeInTheDocument(); 50 | }); 51 | 52 | it("should render children when fetch is executed", () => { 53 | // given 54 | useFormConfigurationStore.getState().setFormConfigurationLoaded(false); 55 | const { result } = renderHook(() => useFormConfigurationStore()); 56 | const { setFormConfigurationLoaded } = result.current; 57 | const children = "Should not be rendered"; 58 | const loader = "Loading..."; 59 | 60 | // when 61 | render({children}); 62 | 63 | // then 64 | expect(screen.queryByText(children)).not.toBeInTheDocument(); 65 | expect(screen.getByText(loader)).toBeInTheDocument(); 66 | 67 | // and when 68 | act(() => { 69 | setFormConfigurationLoaded(true); 70 | }); 71 | 72 | // then 73 | expect(screen.getByText(children)).toBeInTheDocument(); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /libs/form-configuration/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @croz/nrich-form-configuration-core 2 | 3 | ## 3.1.0 4 | 5 | ### Minor Changes 6 | 7 | - [#67](https://github.com/croz-ltd/nrich-frontend/pull/67) [`e1bb4ce`](https://github.com/croz-ltd/nrich-frontend/commit/e1bb4ce191f2341bd0fa39724bda0019f860bb89) Thanks [@ipavic-croz](https://github.com/ipavic-croz)! - Add support for merging yup form configurations instead of overriding them in store 8 | 9 | ### Patch Changes 10 | 11 | - [#70](https://github.com/croz-ltd/nrich-frontend/pull/70) [`7d435fd`](https://github.com/croz-ltd/nrich-frontend/commit/7d435fd30478a0e003aa6930a6ffa5c1f28c17fb) Thanks [@ipavic-croz](https://github.com/ipavic-croz)! - Fix lodash uniqBy import 12 | 13 | ## 3.0.1 14 | 15 | ### Patch Changes 16 | 17 | - [#60](https://github.com/croz-ltd/nrich-frontend/pull/60) [`6ce2e3b`](https://github.com/croz-ltd/nrich-frontend/commit/6ce2e3b0a0a03fa5cfe27948bd6321b911b12af5) Thanks [@vojnovicluka](https://github.com/vojnovicluka)! - An enhancement has been made to the schema merger function to address its previous limitations when using "yup" required() validator function. 18 | 19 | ## 3.0.0 20 | 21 | ### Major Changes 22 | 23 | - [#58](https://github.com/croz-ltd/nrich-frontend/pull/58) [`d2fe264`](https://github.com/croz-ltd/nrich-frontend/commit/d2fe264ea846453bda346d218c215f06fc833e78) Thanks [@vojnovicluka](https://github.com/vojnovicluka)! - Upgrade yup to v1.4.0, add custom deep merge function for merging yup schemas 24 | 25 | ## 2.1.1 26 | 27 | ### Patch Changes 28 | 29 | - [#56](https://github.com/croz-ltd/nrich-frontend/pull/56) [`31689c6`](https://github.com/croz-ltd/nrich-frontend/commit/31689c652bde92ef1a6865e5de9aa4977804412c) Thanks [@dmurat](https://github.com/dmurat)! - Upgrade zustand lib and its import statements to non-deprecated sytax 30 | 31 | ## 2.1.0 32 | 33 | ### Minor Changes 34 | 35 | - [#53](https://github.com/croz-ltd/nrich-frontend/pull/53) [`9ae5c75`](https://github.com/croz-ltd/nrich-frontend/commit/9ae5c75c92cc694c1d368f541c3aa229dc0d8141) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - Added possibility to define generic type when using `useYupFormConfiguration` 36 | 37 | ## 2.0.0 38 | 39 | ### Major Changes 40 | 41 | - [#48](https://github.com/croz-ltd/nrich-frontend/pull/48) [`f6ddbd3`](https://github.com/croz-ltd/nrich-frontend/commit/f6ddbd3fe90340d274ef376b988092e4dc8149d8) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - Changed `useYupFormConfiguration` to return undefined instead of throwing an error 42 | 43 | ### Patch Changes 44 | 45 | - [#48](https://github.com/croz-ltd/nrich-frontend/pull/48) [`7b03ea3`](https://github.com/croz-ltd/nrich-frontend/commit/7b03ea332ee993ffb0df27cb5c5c0dfea37c16f3) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - Update test dependencies to be compatible with React 18 46 | 47 | - [#51](https://github.com/croz-ltd/nrich-frontend/pull/51) [`6a7a5d1`](https://github.com/croz-ltd/nrich-frontend/commit/6a7a5d145ef2c8888c09569bc4c552f65599fca2) Thanks [@jtomic-croz](https://github.com/jtomic-croz)! - #50 Fix module field in package.json definitions 48 | 49 | ## 1.1.0 50 | 51 | ### Minor Changes 52 | 53 | - [#42](https://github.com/croz-ltd/nrich-frontend/pull/42) [`620fcdb`](https://github.com/croz-ltd/nrich-frontend/commit/620fcdbe526c8f616547b02785d720e6b0a4f4fd) Thanks [@dmuharemagic](https://github.com/dmuharemagic)! - Initial package publish to NPM 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/croz-ltd/nrich-frontend/actions/workflows/build.yml/badge.svg)](https://github.com/croz-ltd/nrich-frontend/actions/workflows/build.yml) 2 | [![codecov.io](https://codecov.io/github/croz-ltd/nrich-frontend/coverage.svg?branch=master)](https://codecov.io/github/croz-ltd/nrich-frontend?branch=master) 3 | [![Documentation](https://img.shields.io/badge/API%20Documentation-nrich--frontend-orange)](https://croz-ltd.github.io/nrich-frontend/) 4 | [![License](https://img.shields.io/github/license/croz-ltd/nrich?color=yellow&logo=apache)](https://github.com/croz-ltd/nrich/blob/master/LICENSE) 5 | 6 | ![npm (scoped)](https://img.shields.io/npm/v/@croz/nrich-form-configuration-core?color=yellowgreen&label=@croz/nrich-form-configuration-core) 7 | ![npm (scoped)](https://img.shields.io/npm/v/@croz/nrich-notification-core?color=blueviolet&label=@croz/nrich-notification-core) 8 | ![npm (scoped)](https://img.shields.io/npm/v/@croz/nrich-notification-mui?color=orange&label=@croz/nrich-notification-mui) 9 | 10 | # nrich-frontend 11 | 12 | React monorepo providing bindings to simplify the integration with [nrich](https://github.com/croz-ltd/nrich) libraries. 13 | 14 | The overall project is built on top of the concept of [Yarn workspaces](https://yarnpkg.com/features/workspaces) using [Turborepo](https://turbo.build/repo) as a build management system. 15 | 16 | For the demonstration of the integrations provided here, please visit the [nrich-demo-frontend](https://github.com/croz-ltd/nrich-demo-frontend) 17 | repository. 18 | 19 | ### Workspace/module overview 20 | 21 | Workspace is divided in `config` and `libs` subcategories. 22 | `config` contains common configuration used throughout the `libs` implementations. 23 | 24 | `libs` contains implementation of specific modules separated in two categories. First is a `core` module which contains common logic 25 | and custom hooks for the module. Second are adapters for specific UI component library, currently only `mui`. 26 | 27 | The following workspaces/modules are available: 28 | * [@croz/nrich-form-configuration-core](libs/form-configuration/core/README.md) - contains the core utilities for using nrich form-configuration module 29 | * [@croz/nrich-notification-core](libs/notification/core/README.md) - contains the core utilities for handling common state operations for the nrich notification module 30 | * [@croz/nrich-notification-mui](libs/notification/mui/README.md) - contains the UI implementation of notifications for the MUI component library 31 | 32 | ### Development of new modules 33 | 34 | When developing a new module, the workspaces are to be organized as in the example: 35 | 36 | * `libs/foo-core`, where `foo` is the name of the module matching the naming conventions of the complementary backend module 37 | * `libs/foo-bar`, where `foo` is the name of the module matching the naming conventions of the complementary backend module, and `bar` 38 | is the name of the UI component library 39 | 40 | ### Common commands 41 | 42 | #### Build 43 | 44 | To build all modules, run `yarn build`. 45 | 46 | To build specific module(s), run `yarn build -filter=foo`, where `foo` is the workspace name (e.g. `@croz/nrich-notification-core`). 47 | This specific command receives a variable number of arguments. 48 | 49 | #### Clean 50 | 51 | To clean build artifacts, run `yarn clean`. 52 | 53 | #### Lint 54 | 55 | To execute the linting process in a read-only mode (without actually affecting the files), run `yarn lint`. 56 | 57 | To automatically apply changes, run `yarn lint --fix`. 58 | 59 | #### Test 60 | 61 | To run tests, use `yarn test`. 62 | 63 | 64 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/api/form-configuration-types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import * as yup from "yup"; 19 | 20 | export interface FormConfiguration { 21 | 22 | /** 23 | * Registered form id for this form configuration. 24 | */ 25 | formId: string; 26 | 27 | /** 28 | * List of {@link ConstrainedPropertyConfiguration} instances holding property configuration for each property defined in the class that form id was mapped to. 29 | */ 30 | constrainedPropertyConfigurationList: ConstrainedPropertyConfiguration[]; 31 | 32 | } 33 | 34 | export interface FormYupConfiguration = any> { 35 | 36 | /** 37 | * Registered form id for this form configuration. 38 | */ 39 | formId: string; 40 | 41 | /** 42 | * List of yup ObjectSchema instances holding property configuration for each property defined in the class that form id was mapped to. 43 | */ 44 | yupSchema: yup.ObjectSchema; 45 | 46 | } 47 | 48 | export interface ConstrainedPropertyConfiguration { 49 | 50 | /** 51 | * Path to the property relative to a parent class that is mapped to form id. 52 | */ 53 | path: string; 54 | 55 | /** 56 | * Type of constrained property. 57 | */ 58 | propertyType: string; 59 | 60 | /** 61 | * JavascriptType of constrained property. 62 | */ 63 | javascriptType: string; 64 | 65 | /** 66 | * List of {@link ConstrainedPropertyClientValidatorConfiguration} instances that hold client side validation configuration. 67 | */ 68 | validatorList: ConstrainedPropertyClientValidatorConfiguration[]; 69 | 70 | } 71 | 72 | export interface ConstrainedPropertyClientValidatorConfiguration { 73 | 74 | /** 75 | * Constraint name (i.e. NotNull). 76 | */ 77 | name: string; 78 | 79 | /** 80 | * Constraint arguments as a map. 81 | */ 82 | argumentMap: Record; 83 | 84 | /** 85 | * Error message that should be shown if validation fails. 86 | */ 87 | errorMessage: string; 88 | 89 | } 90 | 91 | export interface ValidatorConverter { 92 | 93 | /** 94 | * Whether this ValidatorConverter supports conversion. 95 | * @param configuration configuration received from the server 96 | */ 97 | supports(configuration: ConstrainedPropertyClientValidatorConfiguration): boolean; 98 | 99 | /** 100 | * Converts validation configuration to yup validator. 101 | * @param configuration configuration received from the server 102 | * @param validator yup validator 103 | */ 104 | convert(configuration: ConstrainedPropertyClientValidatorConfiguration, validator: any): any; 105 | 106 | } 107 | 108 | export interface FormConfigurationConfiguration { 109 | 110 | /** 111 | * Url where to fetch configuration from. It represents the part of the path till /fetch-all. 112 | */ 113 | url?: string; 114 | 115 | /** 116 | * Additional configuration options to send with fetch request i.e. if some authentication information is required. 117 | */ 118 | requestOptionsResolver?: () => RequestInit; 119 | 120 | /** 121 | * Additional converters to use for custom constraints. 122 | */ 123 | additionalValidatorConverters?: ValidatorConverter[]; 124 | 125 | } 126 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/hook/use-form-configuration.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { ObjectSchema } from "yup"; 19 | 20 | import { FormYupConfiguration } from "../api"; 21 | import { useFormConfigurationStore } from "../store"; 22 | 23 | export type FormConfigurationOptions = { 24 | /** 25 | * Array of current state form configurations. 26 | */ 27 | formYupConfigurations: FormYupConfiguration[], 28 | /** 29 | * Flag that indicates weather form configuration is fetched from API. 30 | */ 31 | formConfigurationLoaded: boolean; 32 | /** 33 | * Sets form configurations to state. 34 | * Use on initial call to find-all endpoint. 35 | * @param formConfigurations formConfigurations to set 36 | */ 37 | set: (formConfigurations: FormYupConfiguration[]) => void, 38 | 39 | /** 40 | * Adds form configuration to state. 41 | * @param formConfiguration formConfiguration to add 42 | */ 43 | add: (formConfiguration: FormYupConfiguration) => void, 44 | /** 45 | * Removes form configuration to state. 46 | * @param formConfiguration formConfiguration to add 47 | */ 48 | remove: (formConfiguration: FormYupConfiguration) => void 49 | /** 50 | * Sets form configuration loaded to state. 51 | * @param isLoaded loaded flag to set 52 | */ 53 | setFormConfigurationLoaded: (formConfigurationLoaded: boolean) => void; 54 | }; 55 | 56 | export type UseFormConfiguration = () => FormConfigurationOptions; 57 | 58 | /** 59 | * A hook which simplifies the usage of the form configuration state. 60 | * Uses the internal {@link useFormConfigurationStore} hook for managing the form configuration state. 61 | * 62 | * @returns An array of options to access and set the form configuration state and remove or add a single form configuration. 63 | */ 64 | export const useFormConfiguration: UseFormConfiguration = () => { 65 | const formYupConfigurations = useFormConfigurationStore((state) => state.formYupConfigurations); 66 | const formConfigurationLoaded = useFormConfigurationStore((state) => state.formConfigurationLoaded); 67 | const set = useFormConfigurationStore((state) => state.set); 68 | const add = useFormConfigurationStore((state) => state.add); 69 | const remove = useFormConfigurationStore((state) => state.remove); 70 | const setFormConfigurationLoaded = useFormConfigurationStore((state) => state.setFormConfigurationLoaded); 71 | 72 | return { 73 | formYupConfigurations, formConfigurationLoaded, set, add, remove, setFormConfigurationLoaded, 74 | }; 75 | }; 76 | 77 | /** 78 | * A hook which extracts a specific Yup configuration from the form configuration identified by the form id. 79 | * Uses the internal {@link useFormConfigurationStore} hook for managing the form configuration state. 80 | * 81 | * @param formId Registered form id for a specific form configuration. 82 | * 83 | * @returns Mapped Yup configuration from the form configuration identified by the form id, or undefined if no matching form configuration is found. 84 | */ 85 | export const useYupFormConfiguration = = any>(formId: string) => { 86 | const { formYupConfigurations } = useFormConfiguration(); 87 | 88 | const searchedFormConfiguration = formYupConfigurations.find((formYupConfiguration) => formYupConfiguration.formId === formId); 89 | 90 | if (!searchedFormConfiguration) { 91 | return undefined; 92 | } 93 | 94 | return searchedFormConfiguration.yupSchema as ObjectSchema; 95 | }; 96 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/hook/use-form-configuration.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { act, renderHook } from "@testing-library/react"; 19 | 20 | import { useFormConfiguration, useYupFormConfiguration } from "../../src"; 21 | import { useFormConfigurationStore } from "../../src/store"; 22 | import { mockFormYupConfiguration, mockFormYupConfigurations } from "../testutil/form-configuration-generating-util"; 23 | 24 | describe("@croz/nrich-form-configuration-core/use-form-configuration", () => { 25 | beforeEach(() => { 26 | useFormConfigurationStore.getState().set(mockFormYupConfigurations); 27 | }); 28 | 29 | it("should resolve form configuration state", () => { 30 | // when 31 | const { result } = renderHook(() => useFormConfiguration()); 32 | const { formYupConfigurations, formConfigurationLoaded } = result.current; 33 | 34 | // then 35 | expect(formYupConfigurations).toHaveLength(2); 36 | expect(formConfigurationLoaded).toBeFalsy(); 37 | }); 38 | 39 | it("should add form configuration to store", () => { 40 | // when 41 | const { result } = renderHook(() => useFormConfiguration()); 42 | const { formYupConfigurations, add } = result.current; 43 | 44 | // then 45 | expect(formYupConfigurations).toHaveLength(2); 46 | 47 | // and when 48 | act(() => { 49 | add(mockFormYupConfiguration); 50 | }); 51 | 52 | // then 53 | expect(result.current.formYupConfigurations).toHaveLength(3); 54 | }); 55 | 56 | it("should remove form configuration from store", () => { 57 | // when 58 | const { result } = renderHook(() => useFormConfiguration()); 59 | const { formYupConfigurations, remove } = result.current; 60 | 61 | // then 62 | expect(formYupConfigurations).toHaveLength(2); 63 | 64 | // and when 65 | act(() => { 66 | remove(formYupConfigurations[0]); 67 | }); 68 | 69 | // then 70 | expect(result.current.formYupConfigurations).toHaveLength(1); 71 | }); 72 | 73 | it("should set form configuration loaded flag to store", () => { 74 | // when 75 | const { result } = renderHook(() => useFormConfiguration()); 76 | const { formConfigurationLoaded, setFormConfigurationLoaded } = result.current; 77 | 78 | // then 79 | expect(formConfigurationLoaded).toBeFalsy(); 80 | 81 | // and when 82 | act(() => { 83 | setFormConfigurationLoaded(true); 84 | }); 85 | 86 | // then 87 | expect(result.current.formConfigurationLoaded).toBeTruthy(); 88 | }); 89 | 90 | it("should return yup object for given formId", () => { 91 | // when 92 | const { formId } = mockFormYupConfigurations[0]; 93 | const { result } = renderHook(() => useYupFormConfiguration(formId)); 94 | 95 | // then 96 | expect(result.current).toMatchObject(mockFormYupConfigurations[0].yupSchema); 97 | }); 98 | 99 | it("should throw error for unknown formId", () => { 100 | // when 101 | const formId = "unknown"; 102 | const { result } = renderHook(() => useYupFormConfiguration(formId)); 103 | 104 | // then 105 | expect(result.current).toEqual(undefined); 106 | }); 107 | 108 | it("should compile and return yup object when used with generic", () => { 109 | // given 110 | type RegistrationForm = { 111 | username: string, 112 | password: string, 113 | oib: number, 114 | }; 115 | 116 | // when 117 | const { formId } = mockFormYupConfigurations[0]; 118 | const { result } = renderHook(() => useYupFormConfiguration(formId)); 119 | 120 | // then 121 | expect(result.current).toMatchObject(mockFormYupConfigurations[0].yupSchema); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /libs/form-configuration/core/README.md: -------------------------------------------------------------------------------- 1 | # @croz/nrich-form-configuration-core 2 | 3 | ## Overview 4 | 5 | `@croz/nrich-form-configuration-core` is a module for generating automatic validations for forms in the application. 6 | It's a frontend part of [nrich-form-configuration](https://github.com/croz-ltd/nrich/tree/master/nrich-form-configuration) backend module. 7 | Together, they allow the user to define validations in a single place (backend). 8 | 9 | For validation schemas this lib uses [yup](https://github.com/jquense/yup). 10 | 11 | ## Setup 12 | 13 | To use this module in your project run `npm install @croz/nrich-form-configuration-core` or `yarn add @croz/nrich-form-configuration-core` 14 | 15 | ## Basic usage 16 | 17 | On some upper level of your app, wrap your components in `FormConfigurationProvider`. 18 | 19 | ```tsx 20 | import { FormConfigurationProvider } from "@croz/nrich-form-configuration-core"; 21 | 22 | const App = () => ( 23 | 24 | {/* rest of the app... */} 25 | 26 | ); 27 | ``` 28 | 29 | In your form component, use `useYupFormConfiguration` with your form id defined on backend. 30 | 31 | ```tsx 32 | import React, { useState } from "react"; 33 | import { useYupFormConfiguration } from "@croz/nrich-form-configuration-core"; 34 | import { Form, Formik } from "formik"; 35 | 36 | type CreateForm = { 37 | /* fields of the form */ 38 | } 39 | 40 | export const SomeFormComponent = () => { 41 | const [formValues, setFormValues] = useState({}); 42 | const validationSchema = useYupFormConfiguration('user.create-form'); 43 | 44 | return ( 45 | setFormValues(values)} 48 | > 49 |
50 | { /* Rest of the form */} 51 |
52 |
53 | ); 54 | }; 55 | ``` 56 | 57 | *NOTE: Formik is used just as an example, you can use any form lib compatible with `yup`.* 58 | 59 | ## Details 60 | 61 | `FormConfigurationProvider` component has the following props: 62 | 63 | | Prop name | Description | Required | Default value | 64 | |-------------------------------|-----------------------------------------------------------------------|----------|-----------------------------| 65 | | children | Component tree that will be using nrich form configuration | yes | none | 66 | | loader | Custom component used while waiting for configuration fetch to finish | no | none | 67 | | url | Backend form configuration path | no | `/nrich/form/configuration` | 68 | | requestOptionsResolver | Function that creates options for the initial fetch call to backend | no | none | 69 | | additionalValidatorConverters | List of `ValidatorConverter`s used to allow custom validations | no | none | 70 | 71 | ### Registering and using custom validations 72 | 73 | For custom validations to work, you need to provide a `ValidatorConverter` for it. `ValidatorConverter` contains two fields `supports` and `convert`. 74 | 75 | `supports` is used to check if this is the correct validation for a given form validation configuration, while `convert` serves as an implementation of the 76 | validation. `convert` will usually use the yup's [Schema.test](https://github.com/jquense/yup#schematestname-string-message-string--function--any-test-function-schema) method. 77 | 78 | ```tsx 79 | import oib from "oib.js"; 80 | import { FormConfigurationProvider } from "@croz/nrich-form-configuration-core"; 81 | 82 | const additionalValidatorConverters = [ 83 | { 84 | supports: (configuration) => configuration.name === "ValidOib", 85 | convert: (configuration, validator) => validator.test("validOib", configuration.errorMessage, value => oib.validate(value)) 86 | } 87 | ]; 88 | 89 | const getRequestParams = (): RequestInit => ({ 90 | headers: { 91 | Authorization: "Bearer token", 92 | }, 93 | }); 94 | 95 | const App = () => ( 96 | 99 | {/* rest of the app... */} 100 | 101 | ); 102 | ``` 103 | -------------------------------------------------------------------------------- /libs/notification/mui/src/provider/Notifications.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import React from "react"; 19 | 20 | import { 21 | Alert, AlertColor, AlertTitle, Snackbar, 22 | } from "@mui/material"; 23 | 24 | import { Notification, useNotifications } from "@croz/nrich-notification-core"; 25 | 26 | /** 27 | * Represents an extended set of possibilities of where to place the notification on the screen based on {@link NotificationOrigin}. 28 | */ 29 | export type NotificationPosition = "top-left" | "top-right" | "top-center" | "bottom-left" | "bottom-right" | "bottom-center"; 30 | 31 | /** 32 | * Represents a set of possibilities of where to place the notification on the screen. 33 | */ 34 | export type NotificationOrigin = ["top" | "bottom", "left" | "right" | "center"]; 35 | 36 | export interface NotificationsProviderProps { 37 | 38 | /** 39 | * Number of milliseconds before notification is closed. 40 | */ 41 | autoClose?: number; 42 | 43 | /** 44 | * Position of notification. 45 | */ 46 | position?: NotificationPosition; 47 | 48 | } 49 | 50 | const resolveNotificationOrigin = (notification: Notification, defaultPosition: NotificationPosition): NotificationOrigin => { 51 | const separator = "-"; 52 | const position = notification.uxNotificationOptions?.position; 53 | 54 | let notificationOrigin; 55 | if (typeof position === "string" && position.includes(separator)) { 56 | notificationOrigin = position as NotificationPosition; 57 | } 58 | else { 59 | notificationOrigin = defaultPosition; 60 | } 61 | 62 | return notificationOrigin.split(separator) as NotificationOrigin; 63 | }; 64 | 65 | const resolveNotificationDuration = (notification: Notification, defaultDuration: number): number => { 66 | const autoClose = notification.uxNotificationOptions?.autoClose; 67 | 68 | return (typeof autoClose === "number" && autoClose) || defaultDuration; 69 | }; 70 | 71 | /** 72 | * A provider component used to wrap the container in which the notifications are displayed. 73 | * 74 | * @param __namedParameters notification configuration options 75 | * @param __namedParameters.position specifies the notification position on the page, 76 | * derived from a set of predefined positions available in MUI. 77 | * @param __namedParameters.autoClose the duration after which the notification closes expressed in milliseconds 78 | * (if left undefined, it doesn't close). 79 | */ 80 | export const Notifications = ({ position = "bottom-right", autoClose }: NotificationsProviderProps) => { 81 | const { notifications, remove } = useNotifications(); 82 | 83 | return ( 84 | <> 85 | { 86 | notifications.map((notification) => { 87 | const positioning = resolveNotificationOrigin(notification, position); 88 | const autoHideDuration = resolveNotificationDuration(notification, autoClose); 89 | 90 | return ( 91 | remove(notification)} 96 | anchorOrigin={{ 97 | vertical: positioning[0], 98 | horizontal: positioning[1], 99 | }} 100 | > 101 | 102 | remove(notification)} 104 | severity={notification.severity.toLowerCase() as AlertColor} 105 | > 106 | {notification.title} 107 | {notification.content} 108 | {notification.messageList?.length > 0 && ( 109 |
    110 | {notification.messageList.map((message) =>
  • {message}
  • )} 111 |
112 | )} 113 |
114 |
115 | ); 116 | }) 117 | } 118 | 119 | ); 120 | }; 121 | -------------------------------------------------------------------------------- /libs/notification/mui/test/provider/Notifications.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import { render, screen, waitFor } from "@testing-library/react"; 19 | import userEvent from "@testing-library/user-event"; 20 | import React from "react"; 21 | 22 | import { useNotifications } from "@croz/nrich-notification-core"; 23 | 24 | import { Notifications } from "../../src"; 25 | 26 | jest.mock("@croz/nrich-notification-core", () => ({ 27 | useNotifications: jest.fn(), 28 | })); 29 | 30 | describe("@croz/nrich-notification-mui/provider/Notifications", () => { 31 | it("should render notification without messages", async () => { 32 | // given 33 | const notificationDurationMs = 1000; 34 | let mockNotifications = [{ 35 | title: "Notification title", 36 | content: "Notification content", 37 | messageList: [], 38 | severity: "INFO", 39 | timestamp: new Date(), 40 | }]; 41 | (useNotifications as jest.Mock).mockReturnValue({ 42 | notifications: mockNotifications, 43 | remove: () => { 44 | mockNotifications = []; 45 | }, 46 | }); 47 | 48 | // when 49 | render( 50 | , 51 | ); 52 | 53 | // then 54 | expect(screen.getAllByRole("alert")[0]).toHaveTextContent("Notification title"); 55 | expect(screen.getAllByRole("alert")[0]).toHaveTextContent("Notification content"); 56 | expect(screen.getByRole("button")).toHaveAttribute("title", "Close"); 57 | 58 | // and when 59 | userEvent.click(screen.getByRole("button")); 60 | 61 | // then 62 | expect(mockNotifications).toHaveLength(0); 63 | }); 64 | 65 | it("should render notification with messages", async () => { 66 | // given 67 | let mockNotifications = [{ 68 | title: "Notification title", 69 | content: "Notification content", 70 | messageList: ["First message"], 71 | severity: "WARNING", 72 | timestamp: new Date(), 73 | }]; 74 | (useNotifications as jest.Mock).mockReturnValue({ 75 | notifications: mockNotifications, 76 | remove: () => { 77 | mockNotifications = []; 78 | }, 79 | }); 80 | 81 | // when 82 | render( 83 | , 84 | ); 85 | 86 | // then 87 | expect(screen.getAllByRole("alert")[0]).toHaveTextContent("First message"); 88 | 89 | await waitFor(() => expect(mockNotifications).toHaveLength(0)); 90 | }); 91 | 92 | it("should render notification with overridden configuration", async () => { 93 | // given 94 | const uxNotificationOptions = { autoClose: 5, position: "bottom-center" } as Record; 95 | let mockNotifications = [{ 96 | title: "Notification title", 97 | content: "Notification content", 98 | messageList: [], 99 | severity: "WARNING", 100 | uxNotificationOptions, 101 | timestamp: new Date(), 102 | }]; 103 | (useNotifications as jest.Mock).mockReturnValue({ 104 | notifications: mockNotifications, 105 | remove: () => { 106 | mockNotifications = []; 107 | }, 108 | }); 109 | 110 | // when 111 | render( 112 | , 113 | ); 114 | 115 | // then 116 | expect(screen.getAllByRole("presentation")[0]).toHaveClass("MuiSnackbar-root MuiSnackbar-anchorOriginBottomCenter"); 117 | 118 | await waitFor(() => expect(mockNotifications).toHaveLength(0)); 119 | }); 120 | 121 | it("should ignore invalid overridden configuration", async () => { 122 | // given 123 | const uxNotificationOptions = { autoClose: "invalid", position: "invalid" } as Record; 124 | let mockNotifications = [{ 125 | title: "Notification title", 126 | content: "Notification content", 127 | messageList: [], 128 | severity: "INFO", 129 | uxNotificationOptions, 130 | timestamp: new Date(), 131 | }]; 132 | (useNotifications as jest.Mock).mockReturnValue({ 133 | notifications: mockNotifications, 134 | remove: () => { 135 | mockNotifications = []; 136 | }, 137 | }); 138 | 139 | // when 140 | render( 141 | , 142 | ); 143 | 144 | // then 145 | expect(screen.getAllByRole("presentation")[0]).toHaveClass("MuiSnackbar-root MuiSnackbar-anchorOriginTopLeft"); 146 | 147 | await waitFor(() => expect(mockNotifications).toHaveLength(0)); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, 6 | ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and 7 | orientation. 8 | 9 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 10 | 11 | ## Our Standards 12 | 13 | Examples of behavior that contributes to a positive environment for our community include: 14 | 15 | * Demonstrating empathy and kindness toward other people 16 | * Being respectful of differing opinions, viewpoints, and experiences 17 | * Giving and gracefully accepting constructive feedback 18 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 19 | * Focusing on what is best not just for us as individuals, but for the overall community 20 | 21 | Examples of unacceptable behavior include: 22 | 23 | * The use of sexualized language or imagery, and sexual attention or advances of any kind 24 | * Trolling, insulting or derogatory comments, and personal or political attacks 25 | * Public or private harassment 26 | * Publishing others' private information, such as a physical or email address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a professional setting 28 | 29 | ## Enforcement Responsibilities 30 | 31 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem 32 | inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and 35 | will communicate reasons for moderation decisions when appropriate. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include 40 | using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 41 | 42 | ## Enforcement 43 | 44 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at nrich@croz.net or devops@croz.net. All complaints will be 45 | reviewed and investigated promptly and fairly. 46 | 47 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 48 | 49 | ## Enforcement Guidelines 50 | 51 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 52 | 53 | ### 1. Correction 54 | 55 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 56 | 57 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may 58 | be requested. 59 | 60 | ### 2. Warning 61 | 62 | **Community Impact**: A violation through a single incident or series of actions. 63 | 64 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a 65 | specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 66 | 67 | ### 3. Temporary Ban 68 | 69 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 70 | 71 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, 72 | including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 73 | 74 | ### 4. Permanent Ban 75 | 76 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of 77 | classes of individuals. 78 | 79 | **Consequence**: A permanent ban from any sort of public interaction within the community. 80 | 81 | ## Attribution 82 | 83 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 84 | 85 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 86 | 87 | [homepage]: https://www.contributor-covenant.org 88 | 89 | For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 90 | -------------------------------------------------------------------------------- /libs/form-configuration/core/src/converter/FormConfigurationValidationConverter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import _mergeWith from "lodash/mergeWith"; 19 | import * as yup from "yup"; 20 | 21 | import { ConstrainedPropertyClientValidatorConfiguration, ConstrainedPropertyConfiguration, ValidatorConverter } from "../api"; 22 | 23 | /** 24 | * Converter responsible for conversion between ConstrainedPropertyConfiguration array (that contains validations specified and received from the backend) 25 | * and Yup's ObjectSchema that can be applied on the frontend. The list of supported conversions is in given in {@link FormConfigurationValidationConverter.DEFAULT_CONVERTER_LIST} but 26 | * users can also provide their own by using {@link ValidatorConverter} interface and supplying them in the constructor. 27 | * The last validator converter will match any backend constraint and try to map it directly to Yup. 28 | */ 29 | export class FormConfigurationValidationConverter { 30 | private static PATH_SEPARATOR = "."; 31 | 32 | private static DEFAULT_CONVERTER_LIST: ValidatorConverter[] = [ 33 | { 34 | supports: (configuration) => ["NotNull", "NotBlank", "NotEmpty"].includes(configuration.name), 35 | convert: (configuration, validator) => validator.required(configuration.errorMessage), 36 | }, 37 | { 38 | supports: (configuration) => ["Size", "Length"].includes(configuration.name), 39 | convert: (configuration, validator) => validator.min(configuration.argumentMap.min, configuration.errorMessage).max(configuration.argumentMap.max, configuration.errorMessage), 40 | }, 41 | { 42 | supports: (configuration) => ["Pattern"].includes(configuration.name), 43 | convert: (configuration, validator) => validator.matches(configuration.argumentMap.pattern, configuration.errorMessage), 44 | }, 45 | { 46 | supports: (configuration) => ["Min", "Max"].includes(configuration.name), 47 | convert: (configuration, validator) => validator[configuration.name.toLowerCase()](configuration.argumentMap.value, configuration.errorMessage), 48 | }, 49 | { 50 | supports: (configuration) => ["InList"].includes(configuration.name), 51 | convert: (configuration, validator) => validator.test("inList", configuration.errorMessage, (value) => (configuration.argumentMap.value as string[]).includes(value)), 52 | }, 53 | { 54 | supports: () => true, 55 | convert: (configuration, validator) => validator[configuration.name.toLowerCase()](configuration.errorMessage), 56 | }, 57 | ]; 58 | 59 | /** 60 | * Additional converters that can be registered for unsupported conversion or to change one of the existing conversions (they take precedence to builtin converters). 61 | * @private 62 | */ 63 | private readonly additionalConverters: ValidatorConverter[]; 64 | 65 | constructor(additionalConverters: ValidatorConverter[] = []) { 66 | this.additionalConverters = additionalConverters; 67 | } 68 | 69 | /** 70 | * Converts {@link ConstrainedPropertyConfiguration} array to Yup's schema using builtin and provided converters. 71 | * @param constrainedPropertyConfigurationList array of {@link ConstrainedPropertyConfiguration} to convert 72 | */ 73 | convertFormConfigurationToYupSchema(constrainedPropertyConfigurationList: ConstrainedPropertyConfiguration[]): yup.ObjectSchema { 74 | return this.convertFormConfigurationToYupSchemaInternal(constrainedPropertyConfigurationList); 75 | } 76 | 77 | private convertFormConfigurationToYupSchemaInternal(constrainedPropertyConfigurationList: ConstrainedPropertyConfiguration[]) { 78 | let schema = yup.object().shape({}); 79 | 80 | constrainedPropertyConfigurationList.forEach((property) => { 81 | const yupValidation = yup[property.javascriptType]; 82 | 83 | if (!yupValidation) { 84 | return; 85 | } 86 | 87 | const validator = property.validatorList 88 | .reduce((previousValidator, validatorConfiguration) => this.applyConverter(validatorConfiguration, previousValidator), yupValidation().default(undefined).nullable()); 89 | const [propertyName, restOfPathList] = FormConfigurationValidationConverter.convertPath(property.path); 90 | 91 | if (restOfPathList.length > 0) { 92 | const currentPathSchema = [...restOfPathList].reverse() 93 | .reduce((currentShape, path) => ({ [path]: yup.object().shape(currentShape).default(undefined).nullable() }), { [propertyName]: validator }); 94 | 95 | schema = this.mergeSchemas(schema, yup.object().shape(currentPathSchema)); 96 | } 97 | else { 98 | const currentPropertySchema = yup.object().shape({ [propertyName]: validator }); 99 | 100 | schema = schema.concat(currentPropertySchema); 101 | } 102 | }); 103 | 104 | return schema; 105 | } 106 | 107 | // Function to recursively merge two Yup schemas 108 | mergeSchemas(schema1: yup.ObjectSchema, schema2: yup.ObjectSchema) { 109 | // Recursive helper function to merge two schema objects 110 | const mergeObjects = (obj1, obj2) => { 111 | const merged = { ...obj1 }; 112 | 113 | Object.keys(obj2).forEach((key) => { 114 | if (Object.prototype.hasOwnProperty.call(merged, key)) { 115 | // If both properties are objects, merge recursively 116 | if (obj1[key].type === "object" && obj2[key].type === "object") { 117 | merged[key] = this.mergeSchemas(obj1[key], obj2[key]); 118 | merged[key].spec = _mergeWith(obj1[key].spec, obj2[key].spec, (field1, field2) => (typeof field1 === "boolean" ? field1 && field2 : field1 ?? field2)); 119 | merged[key].internalTests = { ...obj1[key].internalTests, ...obj2[key].internalTests }; 120 | } 121 | else if (obj1[key].type === "array" && obj2[key].type === "array") { 122 | if (obj1[key].innerType.type === "object" && obj2[key].innerType.type === "object") { 123 | merged[key] = yup.array().of(this.mergeSchemas(obj1[key].innerType, obj2[key].innerType)); 124 | } 125 | else { 126 | merged[key] = yup.array().of(obj2[key].innerType); 127 | } 128 | } 129 | else { 130 | merged[key] = obj2[key]; 131 | } 132 | } 133 | else { 134 | // Otherwise, add the property to the merged object 135 | merged[key] = obj2[key]; 136 | } 137 | }); 138 | 139 | return merged; 140 | }; 141 | 142 | // Extract the fields of schema1 143 | const fields1 = schema1.fields; 144 | 145 | // Extract the fields of schema2 146 | const fields2 = schema2.fields; 147 | 148 | // Merge the fields recursively 149 | const mergedFields = mergeObjects(fields1, fields2); 150 | 151 | // Create a new merged schema 152 | return yup.object().shape(mergedFields); 153 | } 154 | 155 | private applyConverter(validatorConfiguration: ConstrainedPropertyClientValidatorConfiguration, validator: any): any { 156 | const converter = this.resolveConverter(validatorConfiguration); 157 | let resolvedValidator = validator; 158 | 159 | if (converter) { 160 | try { 161 | resolvedValidator = converter.convert(validatorConfiguration, validator); 162 | } 163 | catch (ignore) { 164 | // constraint is not registered so skip evaluation 165 | } 166 | } 167 | 168 | return resolvedValidator; 169 | } 170 | 171 | private resolveConverter(validatorConfiguration: ConstrainedPropertyClientValidatorConfiguration) { 172 | const allConverters = this.additionalConverters.concat(FormConfigurationValidationConverter.DEFAULT_CONVERTER_LIST); 173 | 174 | return allConverters.find((additionalConverter) => additionalConverter.supports(validatorConfiguration)); 175 | } 176 | 177 | private static convertPath(path: string): [string, string[]] { 178 | const pathList = path.split(this.PATH_SEPARATOR); 179 | const propertyName = pathList[pathList.length - 1]; 180 | 181 | pathList.pop(); 182 | 183 | return [propertyName, pathList]; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/converter/FormConfigurationValidationConverter.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import * as yup from "yup"; 19 | 20 | import { FormConfigurationValidationConverter } from "../../src/converter"; 21 | import { 22 | createComplexValidationList, createCustomValidationList, createNestedValidationList, createSimpleNullableValidationList, createSimpleValidationList, invalidValidationConfiguration, 23 | } from "../testutil/form-configuration-generating-util"; 24 | 25 | describe("@croz/nrich-form-configuration-core/FormConfigurationValidationConverter", () => { 26 | it("should not throw exception when receiving invalid configuration", () => { 27 | // given 28 | const converter = new FormConfigurationValidationConverter(); 29 | const invalidValidationConfigurationList = invalidValidationConfiguration(); 30 | 31 | // when 32 | const result = converter.convertFormConfigurationToYupSchema(invalidValidationConfigurationList); 33 | 34 | // then 35 | expect(result).toBeDefined(); 36 | expect(result.isValidSync({ username: "username" })).toBe(true); 37 | }); 38 | 39 | it("should convert simple configuration to yup schema", () => { 40 | // given 41 | const converter = new FormConfigurationValidationConverter(); 42 | const simpleValidationList = createSimpleValidationList(); 43 | 44 | // when 45 | const result = converter.convertFormConfigurationToYupSchema(simpleValidationList); 46 | 47 | // then 48 | expect(result).toBeDefined(); 49 | expect(() => result.validateSync({ username: "" })).toThrowError("Username cannot be blank"); 50 | expect(() => result.validateSync({ username: null })).toThrowError("Username cannot be blank"); 51 | expect(result.isValidSync({ username: "username" })).toBe(true); 52 | }); 53 | 54 | it("should use custom converter", () => { 55 | // given 56 | const additionalConverter = { 57 | supports: () => true, 58 | convert: (configuration, validator) => validator.required("Custom validation error"), 59 | }; 60 | const converter = new FormConfigurationValidationConverter([additionalConverter]); 61 | const simpleValidationList = createSimpleValidationList(); 62 | 63 | // when 64 | const result = converter.convertFormConfigurationToYupSchema(simpleValidationList); 65 | 66 | // then 67 | expect(result).toBeDefined(); 68 | expect(() => result.validateSync({ username: "" })).toThrowError("Custom validation error"); 69 | }); 70 | 71 | it("should convert complex configuration to yup schema", () => { 72 | // given 73 | const converter = new FormConfigurationValidationConverter(); 74 | const complexValidationList = createComplexValidationList(); 75 | 76 | // when 77 | const result = converter.convertFormConfigurationToYupSchema(complexValidationList); 78 | 79 | // then 80 | expect(result).toBeDefined(); 81 | expect(() => result.validateSync({ name: "D" })).toThrowError("Name must have minimum 3 and maximum 10 characters"); 82 | expect(() => result.validateSync({ name: "to many characters" })).toThrowError("Name must have minimum 3 and maximum 10 characters"); 83 | expect(() => result.validateSync({ name: "1234" })).toThrowError("Name must contain only letters"); 84 | expect(() => result.validateSync({ age: 10 })).toThrowError("Minimum age is 21"); 85 | expect(() => result.validateSync({ age: 200 })).toThrowError("Maximum age is 110"); 86 | 87 | expect(result.isValidSync({ name: "Name", age: 30 })).toBe(true); 88 | }); 89 | 90 | it.each([ 91 | [{ user: { username: "", email: "email@test.com", address: { street: "street", city: "city" } } }, "Username cannot be blank"], 92 | [{ user: { username: "username", email: "invalid", address: { street: "street", city: "city" } } }, "Not a valid email"], 93 | [{ user: { username: "username", email: "email@test.com", address: { street: "", city: "city" } } }, "Street cannot be blank"], 94 | [{ user: { username: "username", email: "email@test.com", address: { street: "street", city: "" } } }, "City cannot be blank"], 95 | ])("should validate nested data: %p and return error: %p", (validationData: any, expectedMessage: string) => { 96 | // given 97 | const converter = new FormConfigurationValidationConverter(); 98 | const nestedValidationList = createNestedValidationList(); 99 | 100 | // when 101 | const result = converter.convertFormConfigurationToYupSchema(nestedValidationList); 102 | 103 | // then 104 | expect(result).toBeDefined(); 105 | expect(() => result.validateSync(validationData)).toThrowError(expectedMessage); 106 | }); 107 | 108 | it("should support custom constraints", () => { 109 | // given 110 | const converter = new FormConfigurationValidationConverter(); 111 | const customValidationList = createCustomValidationList(); 112 | 113 | // when 114 | const result = converter.convertFormConfigurationToYupSchema(customValidationList); 115 | 116 | // then 117 | expect(result).toBeDefined(); 118 | expect(() => result.validateSync({ title: "other" })).toThrowError("Not in list: mr, mrs, miss"); 119 | expect(result.isValidSync({ title: "mr" })).toBe(true); 120 | }); 121 | 122 | it("should allow null if backend didn't define NotNull, NotBlank, NotEmpty", () => { 123 | // given 124 | const converter = new FormConfigurationValidationConverter(); 125 | const customValidationList = createSimpleNullableValidationList(); 126 | 127 | // when 128 | const result = converter.convertFormConfigurationToYupSchema(customValidationList); 129 | 130 | // then 131 | expect(result).toBeDefined(); 132 | expect(result.isValidSync({ username: null })).toBe(true); 133 | }); 134 | }); 135 | 136 | it.each([ 137 | [ 138 | { 139 | schema1: yup.object().shape({ firstName: yup.string().required() }), 140 | schema2: yup.object().shape({ lastName: yup.string() }), 141 | }, 142 | { 143 | expectedResult: yup.object().shape({ 144 | firstName: yup.string().required(), 145 | lastName: yup.string(), 146 | }), 147 | }, 148 | ], 149 | 150 | [ 151 | { 152 | schema1: yup.object().shape({ firstName: yup.string().required() }), 153 | schema2: yup.object().shape({}), 154 | }, 155 | { 156 | expectedResult: yup.object().shape({ firstName: yup.string().required() }), 157 | }, 158 | ], 159 | 160 | [ 161 | { 162 | schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.string().required() }) }) }), 163 | schema2: yup.object().shape({ id: yup.number() }), 164 | }, 165 | { 166 | expectedResult: yup.object().shape({ id: yup.number(), user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.string().required() }) }) }), 167 | }, 168 | ], 169 | 170 | [ 171 | { 172 | schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.string().required() }) }) }), 173 | schema2: yup.object().shape({ user: yup.object().shape({ address: yup.object().shape({ city: yup.string().required() }) }) }), 174 | }, 175 | { 176 | expectedResult: yup.object().shape({ 177 | user: yup.object().shape({ 178 | username: yup.string(), 179 | address: yup.object().shape({ 180 | street: yup.string().required(), 181 | city: yup.string().required(), 182 | }), 183 | }), 184 | }), 185 | }, 186 | ], 187 | 188 | [ 189 | { 190 | schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string(), address: yup.object().shape({ street: yup.object().shape({ streetName: yup.string() }) }) }) }), 191 | schema2: yup.object().shape({ user: yup.object().shape({ address: yup.object().shape({ city: yup.string().required() }) }) }), 192 | }, 193 | { 194 | expectedResult: yup.object().shape({ 195 | user: yup.object().shape({ 196 | username: yup.string(), 197 | address: yup.object().shape({ 198 | street: yup.object().shape({ 199 | streetName: yup.string(), 200 | }), 201 | city: yup.string().required(), 202 | }), 203 | }), 204 | }), 205 | }, 206 | ], 207 | 208 | [ 209 | { 210 | schema1: yup.object().shape({ user: yup.object().shape({ username: yup.string() }) }), 211 | schema2: yup.object().shape({ user: yup.object().shape({ address: yup.object().shape({ city: yup.string().required() }).default(undefined).nullable() }) }), 212 | }, 213 | { 214 | expectedResult: yup.object().shape({ 215 | user: yup.object().shape({ 216 | username: yup.string(), 217 | address: yup.object().shape({ 218 | city: yup.string().required(), 219 | }).default(undefined).nullable(), 220 | }), 221 | }), 222 | }, 223 | ], 224 | 225 | [ 226 | { 227 | schema1: yup.object().shape({ firstName: yup.string().required() }), 228 | schema2: yup.object().shape({ firstName: yup.string() }), 229 | }, 230 | { expectedResult: yup.object().shape({ firstName: yup.string() }) }, 231 | ], 232 | 233 | [ 234 | { 235 | schema1: yup.object().shape({ todos: yup.array().of(yup.string()) }), 236 | schema2: yup.object().shape({ todos: yup.array().of(yup.number()) }), 237 | }, 238 | { expectedResult: yup.object().shape({ todos: yup.array().of(yup.number()) }) }, 239 | ], 240 | 241 | [ 242 | { 243 | schema1: yup.object().shape({ todos: yup.array().of(yup.string()) }), 244 | schema2: yup.object().shape({ todos: yup.array().of(yup.object().shape({ name: yup.string() })) }), 245 | }, 246 | { expectedResult: yup.object().shape({ todos: yup.array().of(yup.object().shape({ name: yup.string() })) }) }, 247 | ], 248 | 249 | [ 250 | { 251 | schema1: yup.object().shape({ todos: yup.array().of(yup.object().shape({ name: yup.string() })) }), 252 | schema2: yup.object().shape({ todos: yup.array().of(yup.string()) }), 253 | }, 254 | { expectedResult: yup.object().shape({ todos: yup.array().of(yup.string()) }) }, 255 | ], 256 | 257 | [ 258 | { 259 | schema1: yup.object().shape({ test: yup.object().shape({ prop: yup.string().required() }).required() }), 260 | schema2: yup.object().shape({ test: yup.object().shape({ prop2: yup.string() }).optional() }), 261 | }, 262 | { expectedResult: yup.object().shape({ test: yup.object().shape({ prop: yup.string().required(), prop2: yup.string() }).required() }) }, 263 | ], 264 | ])("should merge schemas %p correctly, and get result %p", (schemas, expectedResult) => { 265 | // given 266 | const converter = new FormConfigurationValidationConverter(); 267 | 268 | // when 269 | const mergedSchema = converter.mergeSchemas(schemas.schema1, schemas.schema2); 270 | 271 | // then 272 | expect(mergedSchema.describe()).toEqual(expectedResult.expectedResult.describe()); 273 | }); 274 | 275 | it("merge schemas method should merge internalTests from both schema objects", () => { 276 | // given 277 | const converter = new FormConfigurationValidationConverter(); 278 | const schema1 = yup.object().shape({ obj1: yup.object().required() }); 279 | const schema2 = yup.object().shape({ obj1: yup.object() }); 280 | 281 | // when 282 | const result = converter.mergeSchemas(schema1, schema2); 283 | 284 | // then 285 | expect(result.isValidSync({ obj1: undefined })).toBe(false); 286 | expect(result.isValidSync({ obj1: null })).toBe(false); 287 | expect(result.isValidSync({ obj1: {} })).toBe(true); 288 | }); 289 | -------------------------------------------------------------------------------- /libs/form-configuration/core/test/testutil/form-configuration-generating-util.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 CROZ d.o.o, the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import * as yup from "yup"; 19 | 20 | import { FormConfiguration, ValidatorConverter } from "../../dist"; 21 | import { FormYupConfiguration } from "../../src"; 22 | 23 | export const invalidValidationConfiguration = () => [ 24 | { 25 | path: "type", 26 | propertyType: "net.croz.Type", 27 | javascriptType: "unknown", 28 | validatorList: [ 29 | { 30 | name: "NotEmpty", 31 | argumentMap: {}, 32 | errorMessage: "Type cannot be empty", 33 | }, 34 | ], 35 | }, 36 | { 37 | path: "number", 38 | propertyType: "java.lang.Integer", 39 | javascriptType: "number", 40 | validatorList: [ 41 | { 42 | name: "Pattern", 43 | argumentMap: { pattern: "[0-9]+" }, 44 | errorMessage: "Number must contain only digits", 45 | }, 46 | ], 47 | }, 48 | ]; 49 | 50 | export const createSimpleValidationList = () => [ 51 | { 52 | path: "username", 53 | propertyType: "java.lang.String", 54 | javascriptType: "string", 55 | validatorList: [ 56 | { 57 | name: "NotBlank", 58 | argumentMap: {}, 59 | errorMessage: "Username cannot be blank", 60 | }, 61 | ], 62 | }, 63 | ]; 64 | 65 | export const createSimpleNullableValidationList = () => [ 66 | { 67 | path: "username", 68 | propertyType: "java.lang.String", 69 | javascriptType: "string", 70 | validatorList: [], 71 | }, 72 | ]; 73 | 74 | export const createComplexValidationList = () => [ 75 | { 76 | path: "name", 77 | propertyType: "java.lang.String", 78 | javascriptType: "string", 79 | validatorList: [ 80 | { 81 | name: "Pattern", 82 | argumentMap: { pattern: "[a-zA-Z]+" }, 83 | errorMessage: "Name must contain only letters", 84 | }, 85 | { 86 | name: "Size", 87 | argumentMap: { min: 3, max: 10 }, 88 | errorMessage: "Name must have minimum 3 and maximum 10 characters", 89 | }, 90 | ], 91 | }, 92 | { 93 | path: "age", 94 | propertyType: "java.lang.Integer", 95 | javascriptType: "number", 96 | validatorList: [ 97 | { 98 | name: "Min", 99 | argumentMap: { value: 21 }, 100 | errorMessage: "Minimum age is 21", 101 | }, 102 | { 103 | name: "Max", 104 | argumentMap: { value: 110 }, 105 | errorMessage: "Maximum age is 110", 106 | }, 107 | ], 108 | }, 109 | ]; 110 | 111 | export const createNestedValidationList = () => [ 112 | { 113 | path: "user.username", 114 | propertyType: "java.lang.String", 115 | javascriptType: "string", 116 | validatorList: [ 117 | { 118 | name: "NotBlank", 119 | argumentMap: {}, 120 | errorMessage: "Username cannot be blank", 121 | }, 122 | ], 123 | }, 124 | { 125 | path: "user.address.street", 126 | propertyType: "java.lang.String", 127 | javascriptType: "string", 128 | validatorList: [ 129 | { 130 | name: "NotBlank", 131 | argumentMap: {}, 132 | errorMessage: "Street cannot be blank", 133 | }, 134 | ], 135 | }, 136 | { 137 | path: "user.address.city", 138 | propertyType: "java.lang.String", 139 | javascriptType: "string", 140 | validatorList: [ 141 | { 142 | name: "NotBlank", 143 | argumentMap: {}, 144 | errorMessage: "City cannot be blank", 145 | }, 146 | ], 147 | }, 148 | { 149 | path: "user.email", 150 | propertyType: "java.lang.String", 151 | javascriptType: "string", 152 | validatorList: [ 153 | { 154 | name: "Email", 155 | argumentMap: {}, 156 | errorMessage: "Not a valid email", 157 | }, 158 | ], 159 | }, 160 | ]; 161 | 162 | export const createCustomValidationList = () => [ 163 | { 164 | path: "title", 165 | propertyType: "java.lang.String", 166 | javascriptType: "string", 167 | validatorList: [ 168 | { 169 | name: "InList", 170 | argumentMap: { 171 | value: ["mr", "mrs", "miss"], 172 | }, 173 | errorMessage: "Not in list: mr, mrs, miss", 174 | }, 175 | ], 176 | }, 177 | ]; 178 | 179 | export const mockFormYupConfiguration: FormYupConfiguration & FormConfiguration = { 180 | formId: "form-configuration.demo-request", 181 | yupSchema: yup.object().shape({ 182 | username: yup.string().required(), 183 | password: yup.string().required(), 184 | oib: yup.number().min(10).max(11).required(), 185 | }), 186 | constrainedPropertyConfigurationList: [ 187 | { 188 | path: "firstName", 189 | propertyType: "java.lang.String", 190 | javascriptType: "string", 191 | validatorList: [ 192 | { 193 | name: "NotBlank", 194 | argumentMap: {}, 195 | errorMessage: "Cannot be blank", 196 | }, 197 | ], 198 | }, 199 | { 200 | path: "phone", 201 | propertyType: "java.lang.String", 202 | javascriptType: "string", 203 | validatorList: [ 204 | { 205 | name: "Size", 206 | argumentMap: { 207 | min: 5, 208 | max: 9, 209 | }, 210 | errorMessage: "Size must be between: 9 and 5", 211 | }, 212 | ], 213 | }, 214 | { 215 | path: "startDate", 216 | propertyType: "java.time.Instant", 217 | javascriptType: "date", 218 | validatorList: [ 219 | { 220 | name: "NotNull", 221 | argumentMap: {}, 222 | errorMessage: "Cannot be null", 223 | }, 224 | ], 225 | }, 226 | { 227 | path: "hours", 228 | propertyType: "java.lang.Integer", 229 | javascriptType: "number", 230 | validatorList: [ 231 | { 232 | name: "Min", 233 | argumentMap: { 234 | value: 0, 235 | }, 236 | errorMessage: "Minimum value is: 0", 237 | }, 238 | { 239 | name: "Max", 240 | argumentMap: { 241 | value: 23, 242 | }, 243 | errorMessage: "Maximum value is: 23", 244 | }, 245 | ], 246 | }, 247 | { 248 | path: "income", 249 | propertyType: "java.math.BigDecimal", 250 | javascriptType: "number", 251 | validatorList: [ 252 | { 253 | name: "Digits", 254 | argumentMap: { 255 | integer: 10, 256 | fraction: 2, 257 | }, 258 | errorMessage: "Maximum number of digits is: 10 and scale is: 2", 259 | }, 260 | { 261 | name: "DecimalMin", 262 | argumentMap: { 263 | inclusive: true, 264 | value: "0.0", 265 | }, 266 | errorMessage: "Income must be greater than zero", 267 | }, 268 | ], 269 | }, 270 | { 271 | path: "endDate", 272 | propertyType: "java.time.Instant", 273 | javascriptType: "date", 274 | validatorList: [ 275 | { 276 | name: "NotNull", 277 | argumentMap: {}, 278 | errorMessage: "Cannot be null", 279 | }, 280 | ], 281 | }, 282 | { 283 | path: "phonePrefix", 284 | propertyType: "java.lang.String", 285 | javascriptType: "string", 286 | validatorList: [ 287 | { 288 | name: "Size", 289 | argumentMap: { 290 | min: 3, 291 | max: 3, 292 | }, 293 | errorMessage: "Size must be between: 3 and 3", 294 | }, 295 | ], 296 | }, 297 | ], 298 | }; 299 | 300 | export const mockFormYupConfigurations: (FormYupConfiguration & FormConfiguration)[] = [ 301 | { 302 | formId: "form-configuration.demo-request", 303 | yupSchema: yup.object().shape({ 304 | username: yup.string().required(), 305 | password: yup.string().required(), 306 | oib: yup.number().min(10).max(11).required(), 307 | }), 308 | constrainedPropertyConfigurationList: [ 309 | { 310 | path: "firstName", 311 | propertyType: "java.lang.String", 312 | javascriptType: "string", 313 | validatorList: [ 314 | { 315 | name: "NotBlank", 316 | argumentMap: {}, 317 | errorMessage: "Cannot be blank", 318 | }, 319 | ], 320 | }, 321 | { 322 | path: "phone", 323 | propertyType: "java.lang.String", 324 | javascriptType: "string", 325 | validatorList: [ 326 | { 327 | name: "Size", 328 | argumentMap: { 329 | min: 5, 330 | max: 9, 331 | }, 332 | errorMessage: "Size must be between: 9 and 5", 333 | }, 334 | ], 335 | }, 336 | { 337 | path: "startDate", 338 | propertyType: "java.time.Instant", 339 | javascriptType: "date", 340 | validatorList: [ 341 | { 342 | name: "NotNull", 343 | argumentMap: {}, 344 | errorMessage: "Cannot be null", 345 | }, 346 | ], 347 | }, 348 | { 349 | path: "hours", 350 | propertyType: "java.lang.Integer", 351 | javascriptType: "number", 352 | validatorList: [ 353 | { 354 | name: "Min", 355 | argumentMap: { 356 | value: 0, 357 | }, 358 | errorMessage: "Minimum value is: 0", 359 | }, 360 | { 361 | name: "Max", 362 | argumentMap: { 363 | value: 23, 364 | }, 365 | errorMessage: "Maximum value is: 23", 366 | }, 367 | ], 368 | }, 369 | { 370 | path: "income", 371 | propertyType: "java.math.BigDecimal", 372 | javascriptType: "number", 373 | validatorList: [ 374 | { 375 | name: "Digits", 376 | argumentMap: { 377 | integer: 10, 378 | fraction: 2, 379 | }, 380 | errorMessage: "Maximum number of digits is: 10 and scale is: 2", 381 | }, 382 | { 383 | name: "DecimalMin", 384 | argumentMap: { 385 | inclusive: true, 386 | value: "0.0", 387 | }, 388 | errorMessage: "Income must be greater than zero", 389 | }, 390 | ], 391 | }, 392 | { 393 | path: "endDate", 394 | propertyType: "java.time.Instant", 395 | javascriptType: "date", 396 | validatorList: [ 397 | { 398 | name: "NotNull", 399 | argumentMap: {}, 400 | errorMessage: "Cannot be null", 401 | }, 402 | ], 403 | }, 404 | { 405 | path: "phonePrefix", 406 | propertyType: "java.lang.String", 407 | javascriptType: "string", 408 | validatorList: [ 409 | { 410 | name: "Size", 411 | argumentMap: { 412 | min: 3, 413 | max: 3, 414 | }, 415 | errorMessage: "Size must be between: 3 and 3", 416 | }, 417 | ], 418 | }, 419 | ], 420 | }, { 421 | formId: "form-configuration.demo-request-copy", 422 | yupSchema: yup.object().shape({ 423 | username: yup.string().required(), 424 | password: yup.string().required(), 425 | oib: yup.number().min(10).max(11).required(), 426 | }), 427 | constrainedPropertyConfigurationList: [ 428 | { 429 | path: "firstName", 430 | propertyType: "java.lang.String", 431 | javascriptType: "string", 432 | validatorList: [ 433 | { 434 | name: "NotBlank", 435 | argumentMap: {}, 436 | errorMessage: "Cannot be blank", 437 | }, 438 | ], 439 | }, 440 | { 441 | path: "phone", 442 | propertyType: "java.lang.String", 443 | javascriptType: "string", 444 | validatorList: [ 445 | { 446 | name: "Size", 447 | argumentMap: { 448 | min: 5, 449 | max: 9, 450 | }, 451 | errorMessage: "Size must be between: 9 and 5", 452 | }, 453 | ], 454 | }, 455 | { 456 | path: "startDate", 457 | propertyType: "java.time.Instant", 458 | javascriptType: "date", 459 | validatorList: [ 460 | { 461 | name: "NotNull", 462 | argumentMap: {}, 463 | errorMessage: "Cannot be null", 464 | }, 465 | ], 466 | }, 467 | { 468 | path: "hours", 469 | propertyType: "java.lang.Integer", 470 | javascriptType: "number", 471 | validatorList: [ 472 | { 473 | name: "Min", 474 | argumentMap: { 475 | value: 0, 476 | }, 477 | errorMessage: "Minimum value is: 0", 478 | }, 479 | { 480 | name: "Max", 481 | argumentMap: { 482 | value: 23, 483 | }, 484 | errorMessage: "Maximum value is: 23", 485 | }, 486 | ], 487 | }, 488 | { 489 | path: "income", 490 | propertyType: "java.math.BigDecimal", 491 | javascriptType: "number", 492 | validatorList: [ 493 | { 494 | name: "Digits", 495 | argumentMap: { 496 | integer: 10, 497 | fraction: 2, 498 | }, 499 | errorMessage: "Maximum number of digits is: 10 and scale is: 2", 500 | }, 501 | { 502 | name: "DecimalMin", 503 | argumentMap: { 504 | inclusive: true, 505 | value: "0.0", 506 | }, 507 | errorMessage: "Income must be greater than zero", 508 | }, 509 | ], 510 | }, 511 | { 512 | path: "endDate", 513 | propertyType: "java.time.Instant", 514 | javascriptType: "date", 515 | validatorList: [ 516 | { 517 | name: "NotNull", 518 | argumentMap: {}, 519 | errorMessage: "Cannot be null", 520 | }, 521 | ], 522 | }, 523 | { 524 | path: "phonePrefix", 525 | propertyType: "java.lang.String", 526 | javascriptType: "string", 527 | validatorList: [ 528 | { 529 | name: "Size", 530 | argumentMap: { 531 | min: 3, 532 | max: 3, 533 | }, 534 | errorMessage: "Size must be between: 3 and 3", 535 | }, 536 | ], 537 | }, 538 | ], 539 | }, 540 | ]; 541 | 542 | export const mockFormConfigurations: FormConfiguration[] = [ 543 | { 544 | formId: "form-configuration.demo-request", 545 | constrainedPropertyConfigurationList: [ 546 | { 547 | path: "firstName", 548 | propertyType: "java.lang.String", 549 | javascriptType: "string", 550 | validatorList: [ 551 | { 552 | name: "NotBlank", 553 | argumentMap: {}, 554 | errorMessage: "Cannot be blank", 555 | }, 556 | ], 557 | }, 558 | { 559 | path: "phone", 560 | propertyType: "java.lang.String", 561 | javascriptType: "string", 562 | validatorList: [ 563 | { 564 | name: "Size", 565 | argumentMap: { 566 | min: 5, 567 | max: 9, 568 | }, 569 | errorMessage: "Size must be between: 9 and 5", 570 | }, 571 | ], 572 | }, 573 | { 574 | path: "startDate", 575 | propertyType: "java.time.Instant", 576 | javascriptType: "date", 577 | validatorList: [ 578 | { 579 | name: "NotNull", 580 | argumentMap: {}, 581 | errorMessage: "Cannot be null", 582 | }, 583 | ], 584 | }, 585 | { 586 | path: "hours", 587 | propertyType: "java.lang.Integer", 588 | javascriptType: "number", 589 | validatorList: [ 590 | { 591 | name: "Min", 592 | argumentMap: { 593 | value: 0, 594 | }, 595 | errorMessage: "Minimum value is: 0", 596 | }, 597 | { 598 | name: "Max", 599 | argumentMap: { 600 | value: 23, 601 | }, 602 | errorMessage: "Maximum value is: 23", 603 | }, 604 | ], 605 | }, 606 | { 607 | path: "income", 608 | propertyType: "java.math.BigDecimal", 609 | javascriptType: "number", 610 | validatorList: [ 611 | { 612 | name: "Digits", 613 | argumentMap: { 614 | integer: 10, 615 | fraction: 2, 616 | }, 617 | errorMessage: "Maximum number of digits is: 10 and scale is: 2", 618 | }, 619 | { 620 | name: "DecimalMin", 621 | argumentMap: { 622 | inclusive: true, 623 | value: "0.0", 624 | }, 625 | errorMessage: "Income must be greater than zero", 626 | }, 627 | ], 628 | }, 629 | { 630 | path: "endDate", 631 | propertyType: "java.time.Instant", 632 | javascriptType: "date", 633 | validatorList: [ 634 | { 635 | name: "NotNull", 636 | argumentMap: {}, 637 | errorMessage: "Cannot be null", 638 | }, 639 | ], 640 | }, 641 | { 642 | path: "phonePrefix", 643 | propertyType: "java.lang.String", 644 | javascriptType: "string", 645 | validatorList: [ 646 | { 647 | name: "Size", 648 | argumentMap: { 649 | min: 3, 650 | max: 3, 651 | }, 652 | errorMessage: "Size must be between: 3 and 3", 653 | }, 654 | ], 655 | }, 656 | ], 657 | }, { 658 | formId: "form-configuration.demo-request-copy", 659 | constrainedPropertyConfigurationList: [ 660 | { 661 | path: "title", 662 | propertyType: "java.lang.String", 663 | javascriptType: "string", 664 | validatorList: [ 665 | { 666 | name: "InList", 667 | argumentMap: { 668 | value: [ 669 | "mr", 670 | "mrs", 671 | "miss", 672 | ], 673 | }, 674 | errorMessage: "Not in list: mr, mrs, miss", 675 | }, 676 | ], 677 | }, 678 | { 679 | path: "email", 680 | propertyType: "java.lang.String", 681 | javascriptType: "string", 682 | validatorList: [ 683 | { 684 | name: "Email", 685 | argumentMap: { 686 | regexp: ".*", 687 | flags: [], 688 | }, 689 | errorMessage: "Email is not in the correct format", 690 | }, 691 | { 692 | name: "NotBlank", 693 | argumentMap: {}, 694 | errorMessage: "Email cannot be empty", 695 | }, 696 | ], 697 | }, 698 | { 699 | path: "nestedFormConfigurationDemoRequest", 700 | propertyType: "net.croz.nrichdemobackend.formconfiguration.request.NestedFormConfigurationDemoRequest", 701 | javascriptType: "object", 702 | validatorList: [ 703 | { 704 | name: "NotNull", 705 | argumentMap: {}, 706 | errorMessage: "Cannot be null", 707 | }, 708 | ], 709 | }, 710 | { 711 | path: "nestedFormConfigurationDemoRequest.country", 712 | propertyType: "java.lang.String", 713 | javascriptType: "string", 714 | validatorList: [ 715 | { 716 | name: "Size", 717 | argumentMap: { 718 | min: 2, 719 | max: 100, 720 | }, 721 | errorMessage: "Country is not correctly defined", 722 | }, 723 | ], 724 | }, 725 | { 726 | path: "nestedFormConfigurationDemoRequest.city", 727 | propertyType: "java.lang.String", 728 | javascriptType: "string", 729 | validatorList: [ 730 | { 731 | name: "Size", 732 | argumentMap: { 733 | min: 2, 734 | max: 150, 735 | }, 736 | errorMessage: "City is not correctly defined", 737 | }, 738 | ], 739 | }, 740 | { 741 | path: "nestedFormConfigurationDemoRequest.street", 742 | propertyType: "java.lang.String", 743 | javascriptType: "string", 744 | validatorList: [ 745 | { 746 | name: "NotNull", 747 | argumentMap: {}, 748 | errorMessage: "Cannot be null", 749 | }, 750 | ], 751 | }, 752 | ], 753 | }, 754 | ]; 755 | 756 | export const mockValidatorConverters: ValidatorConverter[] = [ 757 | { 758 | supports: (configuration) => ["NotNull", "NotBlank", "NotEmpty"].includes(configuration.name), 759 | convert: (configuration, validator) => validator.required(configuration.errorMessage), 760 | }, 761 | { 762 | supports: (configuration) => ["Size", "Length"].includes(configuration.name), 763 | convert: (configuration, validator) => validator.min(configuration.argumentMap.min, configuration.errorMessage) 764 | .max(configuration.argumentMap.max, configuration.errorMessage), 765 | }, 766 | ]; 767 | --------------------------------------------------------------------------------