diff --git a/package-lock.json b/package-lock.json index edc3507..64c0e0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,10 @@ "src/@episerver/forms-sdk", "samples/sample-react-app", "samples/managementsite" - ] + ], + "dependencies": { + "react-router": "^6.21.0" + } }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", @@ -3458,6 +3461,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz", + "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "25.0.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", @@ -4106,6 +4117,11 @@ "@types/node": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4222,6 +4238,25 @@ "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -10385,6 +10420,32 @@ "he": "bin/he" } }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -15650,6 +15711,74 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz", + "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==", + "dependencies": { + "@remix-run/router": "1.14.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/react-router-dom/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/react-router-dom/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-router-dom/node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -15893,6 +16022,11 @@ "node": ">=4" } }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -17757,6 +17891,16 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -18223,6 +18367,11 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -19435,8 +19584,10 @@ "@types/node": "^16.18.52", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^5.3.4", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -19530,7 +19681,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@episerver/forms-sdk": "file:../forms-sdk" + "@episerver/forms-sdk": "file:../forms-sdk", + "@types/react-router-dom": "^5.3.3", + "react-router-dom": "^5.3.4" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.4", @@ -22464,8 +22617,10 @@ "@rollup/plugin-typescript": "^11.1.3", "@types/react": "^18.2.24", "@types/react-dom": "^18.2.8", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "5.3.4", "rollup": "^3.29.1", "rollup-plugin-dts": "^6.0.2", "rollup-plugin-postcss": "^4.0.2", @@ -24239,6 +24394,11 @@ "source-map": "^0.7.3" } }, + "@remix-run/router": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz", + "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==" + }, "@rollup/plugin-commonjs": { "version": "25.0.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", @@ -24699,6 +24859,11 @@ "@types/node": "*" } }, + "@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -24815,6 +24980,25 @@ "@types/react": "*" } }, + "@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -29265,6 +29449,34 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -32864,6 +33076,64 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz", + "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==", + "requires": { + "@remix-run/router": "1.14.0" + } + }, + "react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "requires": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "requires": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + } + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -33051,6 +33321,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -33370,8 +33645,10 @@ "@types/node": "^16.18.52", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^5.3.4", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -34452,6 +34729,16 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -34786,6 +35073,11 @@ } } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index efec093..75ae6fa 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,9 @@ "start-frontend": "npm start -w sample-react-app", "publish-forms-sdk": "npm publish -w @episerver/forms-sdk", "publish-forms-react": "npm publish -w @episerver/forms-react", - "publish":"npm run publish-forms-sdk && npm run publish-forms-react" + "publish": "npm run publish-forms-sdk && npm run publish-forms-react" + }, + "dependencies": { + "react-router": "^6.21.0" } } diff --git a/samples/sample-react-app/package.json b/samples/sample-react-app/package.json index 1f8a2d9..877f67f 100644 --- a/samples/sample-react-app/package.json +++ b/samples/sample-react-app/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@episerver/forms-react": "file:../../src/@episerver/forms-react", + "@episerver/forms-sdk": "file:../../src/@episerver/forms-sdk", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -10,13 +12,13 @@ "@types/node": "^16.18.52", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^5.3.4", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4", - "@episerver/forms-sdk": "file:../../src/@episerver/forms-sdk", - "@episerver/forms-react": "file:../../src/@episerver/forms-react" + "web-vitals": "^2.1.4" }, "scripts": { "start": "react-scripts start", diff --git a/samples/sample-react-app/src/App.tsx b/samples/sample-react-app/src/App.tsx index 7e3d0bf..85f0908 100644 --- a/samples/sample-react-app/src/App.tsx +++ b/samples/sample-react-app/src/App.tsx @@ -3,51 +3,59 @@ import { useFetch } from './hooks/useFetch'; import { Form, FormLogin } from '@episerver/forms-react'; import { extractParams } from './helpers/urlHelper'; import { FormCache, FormConstants, IdentityInfo, isNullOrEmpty } from '@episerver/forms-sdk'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { BrowserRouter, useHistory, useLocation } from 'react-router-dom'; function App() { - const { relativePath, language } = extractParams(window.location.pathname) - const url = `${process.env.REACT_APP_ENDPOINT_GET_FORM_BY_PAGE_URL}${relativePath}`; - const {data: pageData, loading} = useFetch(url); - const formCache = new FormCache(); - const [identityInfo, setIdentityInfo] = useState({ - accessToken: formCache.get(FormConstants.FormAccessToken) - } as IdentityInfo); + const location = useLocation(); + const { relativePath, language } = extractParams(window.location.pathname) + const url = `${process.env.REACT_APP_ENDPOINT_GET_FORM_BY_PAGE_URL}${location.pathname}`; + + const { data: pageData, loading } = useFetch(url); + + const formCache = new FormCache(); + const [identityInfo, setIdentityInfo] = useState({ + accessToken: formCache.get(FormConstants.FormAccessToken) + } as IdentityInfo); - const handleAuthen = (identityInfo: IdentityInfo) => { - setIdentityInfo(identityInfo); - } + const history = useHistory() - return ( -
- {loading &&
Loading...
} - - {!loading && pageData && ( - <> -

{pageData.title}

-
-
- {pageData.childrens.map((c: any) => ( -
- ))} -
-
-

Login

- -
-
- - )} -
- ); + const handleAuthen = (identityInfo: IdentityInfo) => { + setIdentityInfo(identityInfo); + } + + return ( +
+ {loading &&
Loading...
} + + {!loading && pageData && ( + <> +

{pageData.title}

+
+
+ {pageData.childrens.map((c: any) => ( + + ))} +
+
+

Login

+ +
+
+ + )} +
+ ); } export default App; diff --git a/samples/sample-react-app/src/index.tsx b/samples/sample-react-app/src/index.tsx index 23b3422..966e52d 100644 --- a/samples/sample-react-app/src/index.tsx +++ b/samples/sample-react-app/src/index.tsx @@ -2,12 +2,24 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; +import { + BrowserRouter as Router, + Route, + Switch +} from "react-router-dom"; + const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById('root') as HTMLElement ); root.render( - - - + + + + + + + + + ); \ No newline at end of file diff --git a/src/@episerver/forms-react/package.json b/src/@episerver/forms-react/package.json index 7adbf45..32db8f7 100644 --- a/src/@episerver/forms-react/package.json +++ b/src/@episerver/forms-react/package.json @@ -18,7 +18,9 @@ }, "keywords": [], "dependencies": { - "@episerver/forms-sdk": "file:../forms-sdk" + "@episerver/forms-sdk": "file:../forms-sdk", + "@types/react-router-dom": "^5.3.3", + "react-router-dom": "^5.3.4" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.4", diff --git a/src/@episerver/forms-react/src/Form.tsx b/src/@episerver/forms-react/src/Form.tsx index 159f52b..3cb0753 100644 --- a/src/@episerver/forms-react/src/Form.tsx +++ b/src/@episerver/forms-react/src/Form.tsx @@ -21,14 +21,16 @@ interface FormProps { * Access token for form submit */ identityInfo?: IdentityInfo + + history? : any } -export const Form = ({formKey, language, baseUrl, identityInfo}: FormProps) => { +export const Form = ({formKey, language, baseUrl, identityInfo, history}: FormProps) => { const {data: formData } = useFormLoader({ formKey, language, baseUrl } as UseFormLoaderProps) return ( <> - {formData && } + {formData && } ); } \ No newline at end of file diff --git a/src/@episerver/forms-react/src/components/FormBody.tsx b/src/@episerver/forms-react/src/components/FormBody.tsx index f9ed512..2b99743 100644 --- a/src/@episerver/forms-react/src/components/FormBody.tsx +++ b/src/@episerver/forms-react/src/components/FormBody.tsx @@ -3,10 +3,12 @@ import { useForms, useFormsDispatch } from "../context/store"; import { FormContainer, FormSubmitter, IdentityInfo, SubmitButtonType, equals, isInArray, isNull, isNullOrEmpty, FormSubmitModel, FormSubmitResult, SubmitButton } from "@episerver/forms-sdk"; import { RenderElementInStep } from "./RenderElementInStep"; import { DispatchFunctions } from "../context/dispatchFunctions"; +import { FormStepNavigation } from "./FormStepNavigation"; interface FormBodyProps { identityInfo?: IdentityInfo; baseUrl: string; + history?: any } export const FormBody = (props: FormBodyProps) => { @@ -17,62 +19,52 @@ export const FormBody = (props: FormBodyProps) => { const dispatchFunctions = new DispatchFunctions(dispatch); const formTitleId = `${form.key}_label`; - const stepCount = form.steps.length; const statusMessage = useRef(""); const statusDisplay = useRef("hide"); const stepLocalizations = useRef>(form.steps?.filter(s => !isNull(s.formStep.localizations))[0]?.formStep.localizations); //TODO: these variables should be get from api or sdk const validateFail = useRef(false), - isFormFinalized = useRef(false), - isProgressiveSubmit = useRef(false), - isSuccess = useRef(false), - submittable = true, - submissionWarning = useRef(false), - message = useRef(""), - isReadOnlyMode = false, - readOnlyModeMessage = "", - currentStepIndex = formContext?.currentStepIndex ?? 0, - isStepValidToDisplay = true; + isFormFinalized = useRef(false), + isProgressiveSubmit = useRef(false), + isSuccess = useRef(false), + submittable = true, + submissionWarning = useRef(false), + message = useRef(""), + isReadOnlyMode = false, + readOnlyModeMessage = "", + currentStepIndex = formContext?.currentStepIndex ?? 0, + isStepValidToDisplay = true; - if((isFormFinalized.current || isProgressiveSubmit.current) && isSuccess.current) - { + if (isSuccess.current && isFormFinalized.current) { statusDisplay.current = "Form__Success__Message"; statusMessage.current = form.properties.submitSuccessMessage ?? message.current; } - else if((submissionWarning.current || (!submittable && !isSuccess.current)) - && !isNullOrEmpty(message.current)) - { + else if ((submissionWarning.current || (!submittable && !isSuccess.current)) + && !isNullOrEmpty(message.current)) { statusDisplay.current = "Form__Warning__Message"; statusMessage.current = message.current; } - const isLastStep = currentStepIndex == stepCount - 1; const validationCssClass = validateFail.current ? "ValidationFail" : "ValidationSuccess"; - const isShowStepNavigation = stepCount > 1 && currentStepIndex > -1 && currentStepIndex < stepCount && !isFormFinalized.current; - const prevButtonDisableState = (currentStepIndex == 0) || !submittable; - const nextButtonDisableState = isLastStep || !submittable; - const currentDisplayStepIndex = currentStepIndex + 1; - const progressWidth = (100 * currentDisplayStepIndex / stepCount) + "%"; const handleSubmit = (e: any) => { e.preventDefault(); - - if(!form.properties.allowAnonymousSubmission && isNullOrEmpty(formContext?.identityInfo?.accessToken)){ + + if (!form.properties.allowAnonymousSubmission && isNullOrEmpty(formContext?.identityInfo?.accessToken)) { return; } //Find submit button, if found then check property 'finalizeForm' of submit button. Otherwise, button Next/Previous was clicked. let buttonId = e.nativeEvent.submitter.id; let submitButton = form.formElements.filter(fe => fe.key === buttonId)[0] as SubmitButton; - if(!isNull(submitButton)){ - //when submitting by SubmitButton, then isProgressiveSubmit is true + if (!isNull(submitButton)) { + //when submitting by SubmitButton, isProgressiveSubmit default is true isProgressiveSubmit.current = true; } - //remove submissions of inactive elements and submissions with undefined value let formSubmissions = (formContext?.formSubmissions ?? []) - //only post value of active elements - .filter(fs => !isInArray(fs.elementKey, formContext?.dependencyInactiveElements ?? []) && !isNull(fs.value)); + //only post value of active elements + .filter(fs => !isInArray(fs.elementKey, formContext?.dependencyInactiveElements ?? []) && !isNull(fs.value)); //validate all submission data before submit let formValidationResults = formSubmitter.doValidate(formSubmissions); @@ -85,13 +77,14 @@ export const FormBody = (props: FormBodyProps) => { )[0]?.elementKey; if(!isNullOrEmpty(invalid)){ dispatchFunctions.updateFocusOn(invalid); + isFormFinalized.current = false; return; } - + let model: FormSubmitModel = { formKey: form.key, locale: form.locale, - isFinalized: submitButton?.properties?.finalizeForm || isLastStep, + isFinalized: submitButton?.properties?.finalizeForm || formContext?.currentStepIndex === form.steps.length - 1, partialSubmissionKey: formContext?.submissionKey ?? "", hostedPageUrl: window.location.pathname, submissionData: formSubmissions, @@ -99,7 +92,6 @@ export const FormBody = (props: FormBodyProps) => { } dispatchFunctions.updateIsSubmitting(true); - formSubmitter.doSubmit(model).then((response: FormSubmitResult)=>{ if(response.success){ message.current = response.messages.map(m => m.message).join("
"); @@ -109,19 +101,16 @@ export const FormBody = (props: FormBodyProps) => { //ignore validation message message.current = response.messages.filter(m => isNullOrEmpty(m.identifier)).map(m => m.message).join("
"); } - - validateFail.current = response.validationFail; - isSuccess.current = response.success; - isFormFinalized.current = isLastStep && response.success; + isFormFinalized.current = isSuccess.current = response.success; dispatchFunctions.updateSubmissionKey(response.submissionKey); dispatchFunctions.updateIsSubmitting(false); }); } - useEffect(()=>{ + useEffect(() => { dispatchFunctions.updateIdentity(props.identityInfo); - if(isNullOrEmpty(props.identityInfo?.accessToken) && !form.properties.allowAnonymousSubmission){ + if (isNullOrEmpty(props.identityInfo?.accessToken) && !form.properties.allowAnonymousSubmission) { statusDisplay.current = "Form__Warning__Message"; statusMessage.current = "You must be logged in to submit this form. If you are logged in and still cannot post, make sure \"Do not track\" in your browser settings is disabled."; } @@ -129,28 +118,28 @@ export const FormBody = (props: FormBodyProps) => { statusMessage.current = ""; statusDisplay.current = "hide"; } - },[props.identityInfo?.accessToken]); + }, [props.identityInfo?.accessToken]); return ( - - {form.properties.title && + {form.properties.title &&

{form.properties.title}

} - {form.properties.description && + {form.properties.description && } - {isReadOnlyMode && readOnlyModeMessage && + {isReadOnlyMode && readOnlyModeMessage &&
{readOnlyModeMessage} @@ -161,15 +150,15 @@ export const FormBody = (props: FormBodyProps) => {
{/* render element */} - {form.steps.map((e, i)=>{ + {form.steps.map((e, i) => { let stepDisplaying = (currentStepIndex === i && !isFormFinalized.current && isStepValidToDisplay) ? "" : "hide"; return (
@@ -178,28 +167,18 @@ export const FormBody = (props: FormBodyProps) => { ); })} - {/* render step navigation */} - {isShowStepNavigation && - - } + {/* render step navigation + */} +
) diff --git a/src/@episerver/forms-react/src/components/FormContainerBlock.tsx b/src/@episerver/forms-react/src/components/FormContainerBlock.tsx index 011ade5..7fc201d 100644 --- a/src/@episerver/forms-react/src/components/FormContainerBlock.tsx +++ b/src/@episerver/forms-react/src/components/FormContainerBlock.tsx @@ -7,6 +7,7 @@ export interface FormContainerProps { form: FormContainer; identityInfo?: IdentityInfo; baseUrl: string; + history?: any; } export function FormContainerBlock(props: FormContainerProps){ @@ -17,7 +18,7 @@ export function FormContainerBlock(props: FormContainerProps){ {/* finally return the form */} return ( - + ) } \ No newline at end of file diff --git a/src/@episerver/forms-react/src/components/FormStepNavigation.tsx b/src/@episerver/forms-react/src/components/FormStepNavigation.tsx new file mode 100644 index 0000000..1f4bcb0 --- /dev/null +++ b/src/@episerver/forms-react/src/components/FormStepNavigation.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import { useForms, useFormsDispatch } from "../context/store"; +import { FormCache, FormConstants, FormContainer, FormStep, StepDependCondition, SubmitButtonType, isNull } from "@episerver/forms-sdk"; +import { DispatchFunctions } from "../context/dispatchFunctions"; +import { useHistory } from "react-router-dom"; + +interface FormStepNavigationProps { + stepLocalizations: React.MutableRefObject> + form: FormContainer + isFormFinalized: React.MutableRefObject + history?: any +} + +export const FormStepNavigation = (props: FormStepNavigationProps) => { + const formContext = useForms() + const formCache = new FormCache() + const dispatch = useFormsDispatch() + const history = props.history + const depend = new StepDependCondition(props.form, formContext?.dependencyInactiveElements ?? []) + const { stepLocalizations, form, isFormFinalized } = props; + const dispatchFuncs = new DispatchFunctions(dispatch); + + const submittable = true + const stepCount = form.steps.length; + + const currentStepIndex = formContext?.currentStepIndex ?? 0 + const currentDisplayStepIndex = currentStepIndex + 1; + const prevButtonDisableState = (currentStepIndex == 0) || !submittable; + const nextButtonDisableState = (currentStepIndex == stepCount - 1) || !submittable; + const progressWidth = (100 * currentDisplayStepIndex / stepCount) + "%"; + + const isShowStepNavigation = stepCount > 1 && currentStepIndex > -1 && currentStepIndex < stepCount && !isFormFinalized.current; + + const handlePrevStep = (event: React.MouseEvent) => { + event.preventDefault() + goToStep(depend.findPreviousStep(currentStepIndex) ?? 0) + } + + const handleNextStep = (event: React.MouseEvent) => { + event.preventDefault() + goToStep(depend.findNextStep(currentStepIndex) ?? 0) + } + + const goToStep = (stepIndex: number) => { + var step = form.steps[stepIndex].formStep as FormStep + + formCache.set(FormConstants.FormCurrentStep + props.form.key, stepIndex) + dispatchFuncs.updateCurrentStepIndex(stepIndex) + + if (!isNull(step) && !isNull(step.properties.attachedContentLink)) { + let url = new URL(step.properties.attachedContentLink) + history && history.push(url.pathname); + } + } + + return ( + <> + {isShowStepNavigation && + + } + + ) +} \ No newline at end of file diff --git a/src/@episerver/forms-sdk/src/helpers/initFormState.ts b/src/@episerver/forms-sdk/src/helpers/initFormState.ts index cdeca13..0338cbd 100644 --- a/src/@episerver/forms-sdk/src/helpers/initFormState.ts +++ b/src/@episerver/forms-sdk/src/helpers/initFormState.ts @@ -1,5 +1,6 @@ +import { FormCache } from "../form-cache"; import { FormStorage } from "../form-storage"; -import { ElementValidationResult, FormContainer, FormState, FormSubmission, FormValidationResult, StepDependencies, ValidatableElementBaseProperties } from "../models"; +import { ElementValidationResult, FormConstants, FormContainer, FormState, FormSubmission, FormValidationResult, StepDependencies, ValidatableElementBaseProperties } from "../models"; import { getDefaultValue } from "./elementHelper"; import { isNull } from "./utils"; @@ -8,10 +9,11 @@ import { isNull } from "./utils"; * @param formContainer A form container * @returns An object of FormState */ -export function initFormState(formContainer: FormContainer): FormState{ +export function initFormState(formContainer: FormContainer): FormState { const formStorage = new FormStorage(formContainer); const formData = formStorage.loadFormDataFromStorage(); - + const formCache = new FormCache() + let formSubmissions = [] as FormSubmission[]; let formValidationResults = [] as FormValidationResult[]; let stepDependencies = [] as StepDependencies[]; @@ -26,25 +28,30 @@ export function initFormState(formContainer: FormContainer): FormState{ let elementValidationResults = [] as ElementValidationResult[]; //some elements don't have validator - if(!isNull(validatableProps.validators)) - { + if (!isNull(validatableProps.validators)) { validatableProps.validators.forEach(v => { - elementValidationResults = elementValidationResults.concat({type: v.type, valid: true}); //default valid = true to hide message + elementValidationResults = elementValidationResults.concat({ type: v.type, valid: true }); //default valid = true to hide message }); } - formValidationResults = formValidationResults.concat({elementKey: e.key, results: elementValidationResults}); + formValidationResults = formValidationResults.concat({ elementKey: e.key, results: elementValidationResults }); }); - stepDependencies = stepDependencies.concat({elementKey: s.formStep.key, isSatisfied: false }); + stepDependencies = stepDependencies.concat({ elementKey: s.formStep.key, isSatisfied: false }); }); //binding the elements with stored input value between Next/Prev navigation - if(formData.length > 0){ + if (formData.length > 0) { formSubmissions = formData; } return { - isReset: false, focusOn: "", dependencyInactiveElements: [], currentStepIndex: 0, - formSubmissions, formValidationResults, stepDependencies, formContainer + isReset: false, + focusOn: "", + dependencyInactiveElements: [], + currentStepIndex: parseInt(formCache.get(FormConstants.FormCurrentStep + formContainer.key) ?? "0"), + formSubmissions, + formValidationResults, + stepDependencies, + formContainer } as FormState; } \ No newline at end of file diff --git a/src/@episerver/forms-sdk/src/models/enums/FormConstants.ts b/src/@episerver/forms-sdk/src/models/enums/FormConstants.ts index dbeab6c..c28722e 100644 --- a/src/@episerver/forms-sdk/src/models/enums/FormConstants.ts +++ b/src/@episerver/forms-sdk/src/models/enums/FormConstants.ts @@ -1,4 +1,5 @@ export enum FormConstants { FormAccessToken = "form_access_token", - FormFieldPrefix = "__field_" + FormFieldPrefix = "__field_", + FormCurrentStep = "form_current_step_" } \ No newline at end of file