diff --git a/.eslintrc.js b/.eslintrc.js
index 643fcd5ec..733c40213 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,6 +7,7 @@ module.exports = {
},
rules: {
'import/extensions': 'off',
+ 'react/no-unknown-property': ['error', { ignore: ['jsx'] }],
},
overrides: [
{
diff --git a/.github/workflows/comment-and-close.yml b/.github/workflows/comment-and-close.yml
index 6aa782a08..d4a98cbc6 100644
--- a/.github/workflows/comment-and-close.yml
+++ b/.github/workflows/comment-and-close.yml
@@ -8,6 +8,6 @@ jobs:
steps:
- uses: vardevs/candc@v1
with:
- close-comment: 'If you would like to file a bug report or feature request, please refer to our issue tracker: https://jira.dhis2.org'
+ close-comment: 'If you would like to file a bug report or feature request, please refer to our issue tracker: https://dhis2.atlassian.net'
exempt-users: dhis2-bot,dependabot,kodiakhq
github-token: ${{secrets.DHIS2_BOT_GITHUB_TOKEN}}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 638dfe301..3b1fd04a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,222 @@
+## [10.3.10](https://github.com/dhis2/app-platform/compare/v10.3.9...v10.3.10) (2023-08-21)
+
+
+### Bug Fixes
+
+* support yarn.lock discovery on non-unix ([#811](https://github.com/dhis2/app-platform/issues/811)) ([22a6863](https://github.com/dhis2/app-platform/commit/22a6863c1b4a5d9c6c026c502a1b77dded318be7))
+
+## [10.3.9](https://github.com/dhis2/app-platform/compare/v10.3.8...v10.3.9) (2023-05-16)
+
+
+### Bug Fixes
+
+* move precache route to reenable navigation handler on login redirects [LIBS-473] ([#809](https://github.com/dhis2/app-platform/issues/809)) ([1ff29b6](https://github.com/dhis2/app-platform/commit/1ff29b645ec07e0bcce76efedbc08f1b76014a42))
+
+## [10.3.8](https://github.com/dhis2/app-platform/compare/v10.3.7...v10.3.8) (2023-05-03)
+
+
+### Bug Fixes
+
+* **pwa:** avoid crashing when SW is not available [LIBS-499] ([#807](https://github.com/dhis2/app-platform/issues/807)) ([b681022](https://github.com/dhis2/app-platform/commit/b68102248fad98303dd2c01d954f4430b1934a25))
+
+## [10.3.7](https://github.com/dhis2/app-platform/compare/v10.3.6...v10.3.7) (2023-04-27)
+
+
+### Bug Fixes
+
+* omit `moment-locales` from precache ([#806](https://github.com/dhis2/app-platform/issues/806)) ([c8d5494](https://github.com/dhis2/app-platform/commit/c8d5494c5eaf6a2f021166d208a1cc289701a47a))
+
+## [10.3.6](https://github.com/dhis2/app-platform/compare/v10.3.5...v10.3.6) (2023-03-22)
+
+
+### Bug Fixes
+
+* **cli:** fix envs to fix plugins in dev ([#799](https://github.com/dhis2/app-platform/issues/799)) ([ba29cea](https://github.com/dhis2/app-platform/commit/ba29ceacfe5a25d42a406f80a9896ccbc7bc82f8))
+
+## [10.3.5](https://github.com/dhis2/app-platform/compare/v10.3.4...v10.3.5) (2023-03-17)
+
+
+### Bug Fixes
+
+* **pwa:** bump ui version for headerbar connection status [LIBS-315] ([#797](https://github.com/dhis2/app-platform/issues/797)) ([61ff0a4](https://github.com/dhis2/app-platform/commit/61ff0a49e63189d892403db8df24c57e170dac0a))
+
+## [10.3.4](https://github.com/dhis2/app-platform/compare/v10.3.3...v10.3.4) (2023-03-16)
+
+
+### Bug Fixes
+
+* make loading placeholders transparent ([#795](https://github.com/dhis2/app-platform/issues/795)) ([6e64756](https://github.com/dhis2/app-platform/commit/6e64756325b366b413acbdce8dd0d6b70632d118))
+
+## [10.3.3](https://github.com/dhis2/app-platform/compare/v10.3.2...v10.3.3) (2023-03-13)
+
+
+### Bug Fixes
+
+* **plugins:** inject precache manifest correctly ([#792](https://github.com/dhis2/app-platform/issues/792)) ([c0d172e](https://github.com/dhis2/app-platform/commit/c0d172ec362182ce978e43b16e9c411ec61e5039))
+* **pwa:** add config option to omit files from precache [LIBS-482] ([#793](https://github.com/dhis2/app-platform/issues/793)) ([d089dda](https://github.com/dhis2/app-platform/commit/d089dda25433ca52f84c42c9369fce95419e4f83))
+
+## [10.3.2](https://github.com/dhis2/app-platform/compare/v10.3.1...v10.3.2) (2023-03-10)
+
+
+### Bug Fixes
+
+* **plugins:** omit launch paths when unused [LIBS-477] ([#791](https://github.com/dhis2/app-platform/issues/791)) ([e49a51f](https://github.com/dhis2/app-platform/commit/e49a51fec39a323350c71d4e09caff836aab2262))
+
+## [10.3.1](https://github.com/dhis2/app-platform/compare/v10.3.0...v10.3.1) (2023-03-06)
+
+
+### Bug Fixes
+
+* error in non-pwa apps [LIBS-315] ([#789](https://github.com/dhis2/app-platform/issues/789)) ([590530e](https://github.com/dhis2/app-platform/commit/590530e74424c65f41c540b123faec902a594e75))
+
+# [10.3.0](https://github.com/dhis2/app-platform/compare/v10.2.3...v10.3.0) (2023-03-03)
+
+
+### Features
+
+* **pwa:** track online status [LIBS-315] ([#718](https://github.com/dhis2/app-platform/issues/718)) ([1dfd1e6](https://github.com/dhis2/app-platform/commit/1dfd1e6249fa22412eb87ea8e693d908af835498))
+
+## [10.2.3](https://github.com/dhis2/app-platform/compare/v10.2.2...v10.2.3) (2023-02-16)
+
+
+### Bug Fixes
+
+* **plugins:** fix file loader behavior ([#779](https://github.com/dhis2/app-platform/issues/779)) ([dcdd918](https://github.com/dhis2/app-platform/commit/dcdd9188f5a64c5ec842a347ed6aa5acc053cc58))
+
+## [10.2.2](https://github.com/dhis2/app-platform/compare/v10.2.1...v10.2.2) (2023-02-16)
+
+
+### Bug Fixes
+
+* **cli:** fix `--testRegex` option on `test` command ([#784](https://github.com/dhis2/app-platform/issues/784)) ([049cdf3](https://github.com/dhis2/app-platform/commit/049cdf3610b28ada4b4241870ff8f2f5cc9f09fa))
+
+## [10.2.1](https://github.com/dhis2/app-platform/compare/v10.2.0...v10.2.1) (2023-02-14)
+
+
+### Bug Fixes
+
+* bump d2-i18n ([#783](https://github.com/dhis2/app-platform/issues/783)) ([2acb301](https://github.com/dhis2/app-platform/commit/2acb301c812bd804f54dfa55ca82dc3629e5ac8c))
+
+# [10.2.0](https://github.com/dhis2/app-platform/compare/v10.1.6...v10.2.0) (2022-11-30)
+
+
+### Bug Fixes
+
+* **cli:** improve plugin builds ([#749](https://github.com/dhis2/app-platform/issues/749)) ([b3b317c](https://github.com/dhis2/app-platform/commit/b3b317c781a9952b223236e33bf31c75088ae107))
+* add missing webpack dependencies to cli package ([9e58c58](https://github.com/dhis2/app-platform/commit/9e58c58322e56f905abd473af9957f8b24119b95))
+* **cli:** update webpack plugin options ([d084b44](https://github.com/dhis2/app-platform/commit/d084b441ee0dd8aecb2d563a0b1dc16275c11bcd))
+* casing ([ff5aa6b](https://github.com/dhis2/app-platform/commit/ff5aa6bd1da8705811ec62f7e47537ba7c7f0822))
+* handle webpack errors as described in https://webpack.js.org/api/node/\#error-handling ([c557534](https://github.com/dhis2/app-platform/commit/c557534f6ff18eb0990a6eaeb037b0d83bc1c87f))
+* plugin placeholder ([1eea12c](https://github.com/dhis2/app-platform/commit/1eea12c825145480d4fbf2807d3078ef608f2325))
+
+
+### Features
+
+* **cli:** add pwa to plugins; fix plugin build details ([#746](https://github.com/dhis2/app-platform/issues/746)) ([fd920a4](https://github.com/dhis2/app-platform/commit/fd920a41d6d63e1bb4e4934d39e4aef96ead1e4d))
+* include plugin launch path plugin.html in built manifests [LIBS-346] ([#745](https://github.com/dhis2/app-platform/issues/745)) ([8843f6b](https://github.com/dhis2/app-platform/commit/8843f6b6396f0f60ef5cb0c6d09cfe90360e976f))
+* **adapter:** don't render headerbar for plugins ([4ac6d54](https://github.com/dhis2/app-platform/commit/4ac6d541b7369a580f2947ad3987cbfa980bb5ee))
+* **cli:** add webpack config for JS and CSS ([a04b7c6](https://github.com/dhis2/app-platform/commit/a04b7c6c7bd82cbb1bfddbea0e9c0a28fee7ba75))
+* **cli:** add webpack config for JS and CSS ([cec6339](https://github.com/dhis2/app-platform/commit/cec63391562ace95cfc1ce94019fd395fd0f8aa8))
+* **cli:** create plugin entrypoint wrapper during compilation ([8e4dbff](https://github.com/dhis2/app-platform/commit/8e4dbfffdc923d9810d79f2faa739658f8ab768a))
+* **cli:** enable split chunks optimisation in webpack config ([e8ebcbf](https://github.com/dhis2/app-platform/commit/e8ebcbf4a5be6249754c40f623e1b2561e3e77b6))
+* **cli:** plugin start script ([9fea158](https://github.com/dhis2/app-platform/commit/9fea1583a2c9c869ed64e8b6ff2d92e324f25358))
+* **cli:** setup css minimiser webpack plugin ([3f1b1f2](https://github.com/dhis2/app-platform/commit/3f1b1f25dfc3199227bac623616fae49362fff75))
+* **cli:** setup define webpack plugin ([5d8f374](https://github.com/dhis2/app-platform/commit/5d8f3741bfd8c2b7257db2c5078bb5d5664af2f9))
+* **cli:** setup htmlwebpackplugin ([202225c](https://github.com/dhis2/app-platform/commit/202225c9dc21a2fceb44c4fdf8e449455c9d3a0e))
+* **cli:** setup ignore webpack plugin for moment.js ([223b191](https://github.com/dhis2/app-platform/commit/223b19146914cc4fad6c25070da3022d23ea159a))
+* **cli:** setup terser webpack plugin ([2693258](https://github.com/dhis2/app-platform/commit/2693258485cbeec6edca59e60513c05010b785fe))
+* webpack config for plugin ([3e4275c](https://github.com/dhis2/app-platform/commit/3e4275c9cf02b8d198ff075560f09b40ce865cea))
+* **cli:** support plugin entrypoint when validating entrypoints ([04ece0a](https://github.com/dhis2/app-platform/commit/04ece0a034ff88eedd06268f5dc0469226600a50))
+
+# [10.2.0-alpha.3](https://github.com/dhis2/app-platform/compare/v10.2.0-alpha.2...v10.2.0-alpha.3) (2022-11-30)
+
+
+### Bug Fixes
+
+* **deps:** bump platform deps and unpin ([bd2582f](https://github.com/dhis2/app-platform/commit/bd2582f3faa17e46142210c530584e79d4b5998a))
+* **pwa:** only count clients in scope ([#760](https://github.com/dhis2/app-platform/issues/760)) ([41113c0](https://github.com/dhis2/app-platform/commit/41113c04f50f6677844f3248d65fcfdbc67a2b58))
+
+## [10.1.6](https://github.com/dhis2/app-platform/compare/v10.1.5...v10.1.6) (2022-11-24)
+
+
+### Bug Fixes
+
+* **pwa:** only count clients in scope ([#760](https://github.com/dhis2/app-platform/issues/760)) ([41113c0](https://github.com/dhis2/app-platform/commit/41113c04f50f6677844f3248d65fcfdbc67a2b58))
+
+## [10.1.5](https://github.com/dhis2/app-platform/compare/v10.1.4...v10.1.5) (2022-11-15)
+
+
+### Bug Fixes
+
+* **deps:** bump platform deps and unpin ([bd2582f](https://github.com/dhis2/app-platform/commit/bd2582f3faa17e46142210c530584e79d4b5998a))
+
+## [10.1.4](https://github.com/dhis2/app-platform/compare/v10.1.3...v10.1.4) (2022-11-08)
+
+
+### Bug Fixes
+
+* **offline-interface:** protect against SW errors ([ad3e476](https://github.com/dhis2/app-platform/commit/ad3e476273823c1c4983c50ac0500e3b9eb19c30))
+* **pwa-boundary:** catch errors ([ecd8b21](https://github.com/dhis2/app-platform/commit/ecd8b21dc34f1eee7497331cb254c459252d355d))
+
+## [10.1.3](https://github.com/dhis2/app-platform/compare/v10.1.2...v10.1.3) (2022-10-26)
+
+
+### Bug Fixes
+
+* **deps:** bump app-runtime to 3.6.1 [LIBS-356] ([#763](https://github.com/dhis2/app-platform/issues/763)) ([190b9e7](https://github.com/dhis2/app-platform/commit/190b9e7a16220421d269410c6482a10c8ff2b6ea))
+
+## [10.1.2](https://github.com/dhis2/app-platform/compare/v10.1.1...v10.1.2) (2022-10-24)
+
+
+### Bug Fixes
+
+* **pwa:** file SWR filter & allow navigation 403s [LIBS-356] [LIBS-357] ([#762](https://github.com/dhis2/app-platform/issues/762)) ([bbfd3eb](https://github.com/dhis2/app-platform/commit/bbfd3eb2172e81126af0f1515e575134e1c2d79e))
+
+## [10.1.1](https://github.com/dhis2/app-platform/compare/v10.1.0...v10.1.1) (2022-10-21)
+
+
+### Bug Fixes
+
+* **deps:** update app-runtime and ui packages ([#761](https://github.com/dhis2/app-platform/issues/761)) ([f6406c5](https://github.com/dhis2/app-platform/commit/f6406c55be747793317021423e39bf98dc7f04bb))
+
+# [10.1.0](https://github.com/dhis2/app-platform/compare/v10.0.1...v10.1.0) (2022-10-06)
+
+
+### Features
+
+* headerbar PWA update notifications [LIBS-344](https://dhis2.atlassian.net/browse/LIBS-344) ([#748](https://github.com/dhis2/app-platform/issues/748)) ([b245bf1](https://github.com/dhis2/app-platform/commit/b245bf199785bf2ba62843c74a9d29f6e62c0a06))
+* display app and server debug information in headerbar profile menu [LIBS-176](https://dhis2.atlassian.net/browse/LIBS-176) ([#748](https://github.com/dhis2/app-platform/issues/748)) ([b245bf1](https://github.com/dhis2/app-platform/commit/b245bf199785bf2ba62843c74a9d29f6e62c0a06))
+
+## [10.0.1](https://github.com/dhis2/app-platform/compare/v10.0.0...v10.0.1) (2022-09-29)
+
+
+### Bug Fixes
+
+* support author parsing in d2.config.js [LIBS-347] ([#747](https://github.com/dhis2/app-platform/issues/747)) ([e6838a7](https://github.com/dhis2/app-platform/commit/e6838a7b21000fa01265b8dfbb059d8ab53eb8db))
+
+# [10.0.0](https://github.com/dhis2/app-platform/compare/v9.0.1...v10.0.0) (2022-07-26)
+
+
+### Bug Fixes
+
+* remove engines field from pwa and adapter ([c3878f2](https://github.com/dhis2/app-platform/commit/c3878f2955352667f9a1ca9c428d8ed0ff77c777))
+* remove lint step from publish step requirements ([#695](https://github.com/dhis2/app-platform/issues/695)) ([a04f8f7](https://github.com/dhis2/app-platform/commit/a04f8f715023fdcb568990fe478f19fb11e54fde))
+
+
+### chore
+
+* drop support for node 12 ([937e5e2](https://github.com/dhis2/app-platform/commit/937e5e2e3dac30af529594d03872b0ae53353882))
+
+
+### Features
+
+* update react-scripts ([#721](https://github.com/dhis2/app-platform/issues/721)) ([dc1c5cb](https://github.com/dhis2/app-platform/commit/dc1c5cb3ab0efb7bb074dd6fb581bf865bdb980e))
+
+
+### BREAKING CHANGES
+
+* dropped support for node 12. The platform now requires node 14+.
+
# [10.0.0-beta.2](https://github.com/dhis2/app-platform/compare/v10.0.0-beta.1...v10.0.0-beta.2) (2022-07-26)
diff --git a/README.md b/README.md
index 7c8a38803..bb7a66e7a 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,7 @@ See [platform.dhis2.nu](https://platform.dhis2.nu) for documentation.
## Report an issue
-The issue tracker can be found in [DHIS2 JIRA](https://jira.dhis2.org)
-under the [LIBS](https://jira.dhis2.org/projects/LIBS) project.
+The issue tracker can be found in [DHIS2 JIRA](https://dhis2.atlassian.net)
+under the [LIBS](https://dhis2.atlassian.net/browse/LIBS) project.
-Deep links:
-
-- [Bug](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10700&issuetype=10006&components=11025)
-- [Feature](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10700&issuetype=10300&components=11025)
-- [Task](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10700&issuetype=10003&components=11025)
+To report an issue create a ticket using the proper type (bug, feature) depending on your use case. You can create a new ticket by clicking `Create` on top of JIRA, then proceed to select the `LIBS` project and then define the ticket type. Make sure to select `app-platform` components drop-down accordingly.
diff --git a/adapter/i18n/en.pot b/adapter/i18n/en.pot
index cf42c5617..d5654aeda 100644
--- a/adapter/i18n/en.pot
+++ b/adapter/i18n/en.pot
@@ -5,8 +5,33 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-"POT-Creation-Date: 2021-10-12T12:20:51.045Z\n"
-"PO-Revision-Date: 2021-10-12T12:20:51.045Z\n"
+"POT-Creation-Date: 2022-09-22T19:11:17.660Z\n"
+"PO-Revision-Date: 2022-09-22T19:11:17.660Z\n"
+
+msgid "Save your data"
+msgstr "Save your data"
+
+msgid ""
+"Updating will reload all {{n}} open instances of this app, and any unsaved "
+"data will be lost. Save any data you need to, then click 'Reload' when "
+"ready."
+msgstr ""
+"Updating will reload all {{n}} open instances of this app, and any unsaved "
+"data will be lost. Save any data you need to, then click 'Reload' when "
+"ready."
+
+msgid ""
+"Updating will reload all open instances of this app, and any unsaved data "
+"will be lost. Save any data you need to, then click 'Reload' when ready."
+msgstr ""
+"Updating will reload all open instances of this app, and any unsaved data "
+"will be lost. Save any data you need to, then click 'Reload' when ready."
+
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Reload"
+msgstr "Reload"
msgid "An error occurred in the DHIS2 application."
msgstr "An error occurred in the DHIS2 application."
@@ -46,37 +71,3 @@ msgstr "Password"
msgid "Sign in"
msgstr "Sign in"
-
-msgid "Save your data"
-msgstr "Save your data"
-
-msgid ""
-"Updating will reload all {{n}} open instances of this app, and any unsaved "
-"data will be lost. Save any data you need to, then click 'Reload' when "
-"ready."
-msgstr ""
-"Updating will reload all {{n}} open instances of this app, and any unsaved "
-"data will be lost. Save any data you need to, then click 'Reload' when "
-"ready."
-
-msgid ""
-"Updating will reload all open instances of this app, and any unsaved data "
-"will be lost. Save any data you need to, then click 'Reload' when ready."
-msgstr ""
-"Updating will reload all open instances of this app, and any unsaved data "
-"will be lost. Save any data you need to, then click 'Reload' when ready."
-
-msgid "Cancel"
-msgstr "Cancel"
-
-msgid "Reload"
-msgstr "Reload"
-
-msgid "There's an update available for this app."
-msgstr "There's an update available for this app."
-
-msgid "Update and reload"
-msgstr "Update and reload"
-
-msgid "Not now"
-msgstr "Not now"
diff --git a/adapter/package.json b/adapter/package.json
index 04a685c8c..02629422f 100644
--- a/adapter/package.json
+++ b/adapter/package.json
@@ -1,6 +1,6 @@
{
"name": "@dhis2/app-adapter",
- "version": "10.0.0-beta.2",
+ "version": "10.3.10",
"repository": {
"type": "git",
"url": "https://github.com/amcgee/dhis2-app-platform",
@@ -21,11 +21,11 @@
"build"
],
"dependencies": {
- "@dhis2/pwa": "10.0.0-beta.2",
+ "@dhis2/pwa": "10.3.10",
"moment": "^2.24.0"
},
"devDependencies": {
- "@dhis2/cli-app-scripts": "10.0.0-beta.2",
+ "@dhis2/cli-app-scripts": "10.3.10",
"@testing-library/react": "^12.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
@@ -37,9 +37,9 @@
"test": "d2-app-scripts test"
},
"peerDependencies": {
- "@dhis2/app-runtime": "^3",
+ "@dhis2/app-runtime": "^3.5",
"@dhis2/d2-i18n": "^1",
- "@dhis2/ui": ">=7",
+ "@dhis2/ui": ">=8.5",
"classnames": "^2",
"moment": "^2",
"prop-types": "^15",
diff --git a/adapter/src/components/Alerts.js b/adapter/src/components/Alerts.js
index d7cb04444..5679c4bc5 100644
--- a/adapter/src/components/Alerts.js
+++ b/adapter/src/components/Alerts.js
@@ -25,9 +25,12 @@ const Alerts = () => {
)
useEffect(() => {
- if (alertManagerAlerts.length > 0 || alertStackAlerts.length > 0) {
- setAlertStackAlerts(
- mergeAlertStackAlerts(alertStackAlerts, alertManagerAlerts)
+ if (alertManagerAlerts.length > 0) {
+ setAlertStackAlerts((currentAlertStackAlerts) =>
+ mergeAlertStackAlerts(
+ currentAlertStackAlerts,
+ alertManagerAlerts
+ )
)
}
}, [alertManagerAlerts])
diff --git a/adapter/src/components/AppWrapper.js b/adapter/src/components/AppWrapper.js
index dd872561c..bbc9c799a 100644
--- a/adapter/src/components/AppWrapper.js
+++ b/adapter/src/components/AppWrapper.js
@@ -1,15 +1,14 @@
-import { HeaderBar } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useCurrentUserLocale } from '../utils/useLocale.js'
import { useVerifyLatestUser } from '../utils/useVerifyLatestUser.js'
import { Alerts } from './Alerts.js'
+import { ConnectedHeaderBar } from './ConnectedHeaderBar.js'
import { ErrorBoundary } from './ErrorBoundary.js'
import { LoadingMask } from './LoadingMask.js'
-import PWAUpdateManager from './PWAUpdateManager.js'
import { styles } from './styles/AppWrapper.style.js'
-export const AppWrapper = ({ appName, children, offlineInterface }) => {
+export const AppWrapper = ({ children, plugin }) => {
const { loading: localeLoading } = useCurrentUserLocale()
const { loading: latestUserLoading } = useVerifyLatestUser()
@@ -20,20 +19,18 @@ export const AppWrapper = ({ appName, children, offlineInterface }) => {
return (
-
+ {!plugin &&
}
window.location.reload()}>
{children}
-
)
}
AppWrapper.propTypes = {
- appName: PropTypes.string.isRequired,
- offlineInterface: PropTypes.object.isRequired,
children: PropTypes.node,
+ plugin: PropTypes.bool,
}
diff --git a/adapter/src/components/ConfirmUpdateModal.js b/adapter/src/components/ConfirmUpdateModal.js
new file mode 100644
index 000000000..8cbd603b9
--- /dev/null
+++ b/adapter/src/components/ConfirmUpdateModal.js
@@ -0,0 +1,43 @@
+import {
+ Button,
+ ButtonStrip,
+ Modal,
+ ModalActions,
+ ModalContent,
+ ModalTitle,
+} from '@dhis2/ui'
+import PropTypes from 'prop-types'
+import React from 'react'
+import i18n from '../locales'
+
+export function ConfirmUpdateModal({ clientsCount, onCancel, onConfirm }) {
+ return (
+
+ {i18n.t('Save your data')}
+
+ {clientsCount
+ ? i18n.t(
+ "Updating will reload all {{n}} open instances of this app, and any unsaved data will be lost. Save any data you need to, then click 'Reload' when ready.",
+ { n: clientsCount }
+ )
+ : // Fallback if clientsCount is unavailable:
+ i18n.t(
+ "Updating will reload all open instances of this app, and any unsaved data will be lost. Save any data you need to, then click 'Reload' when ready."
+ )}
+
+
+
+
+
+
+
+
+ )
+}
+ConfirmUpdateModal.propTypes = {
+ clientsCount: PropTypes.number,
+ onCancel: PropTypes.func,
+ onConfirm: PropTypes.func,
+}
diff --git a/adapter/src/components/ConnectedHeaderBar.js b/adapter/src/components/ConnectedHeaderBar.js
new file mode 100644
index 000000000..4da27e315
--- /dev/null
+++ b/adapter/src/components/ConnectedHeaderBar.js
@@ -0,0 +1,42 @@
+import { useConfig } from '@dhis2/app-runtime'
+import { HeaderBar } from '@dhis2/ui'
+import React from 'react'
+import { usePWAUpdateState } from '../utils/usePWAUpdateState'
+import { ConfirmUpdateModal } from './ConfirmUpdateModal'
+
+/**
+ * Check for SW updates or a first activation, displaying an update notification
+ * message in the HeaderBar profile menu. When an update is applied, if there are
+ * multiple tabs of this app open, there's anadditional warning step because all
+ * clients of the service worker will reload when there's an update, which may
+ * cause data loss.
+ */
+
+export function ConnectedHeaderBar() {
+ const { appName } = useConfig()
+ const {
+ updateAvailable,
+ confirmReload,
+ confirmationRequired,
+ clientsCount,
+ onConfirmUpdate,
+ onCancelUpdate,
+ } = usePWAUpdateState()
+
+ return (
+ <>
+
+ {confirmationRequired ? (
+
+ ) : null}
+ >
+ )
+}
diff --git a/adapter/src/components/LoadingMask.js b/adapter/src/components/LoadingMask.js
index 6be864056..f799584ee 100644
--- a/adapter/src/components/LoadingMask.js
+++ b/adapter/src/components/LoadingMask.js
@@ -2,7 +2,7 @@ import { Layer, CenteredContent, CircularLoader, layers } from '@dhis2/ui'
import React from 'react'
export const LoadingMask = () => (
-
+
diff --git a/adapter/src/components/LoginModal.js b/adapter/src/components/LoginModal.js
index cb5c2d136..1b7fdbced 100644
--- a/adapter/src/components/LoginModal.js
+++ b/adapter/src/components/LoginModal.js
@@ -1,3 +1,4 @@
+import { setBaseUrlByAppName } from '@dhis2/pwa'
import {
Modal,
ModalTitle,
@@ -6,16 +7,16 @@ import {
Button,
InputField,
} from '@dhis2/ui'
+import PropTypes from 'prop-types'
import React, { useState } from 'react'
import i18n from '../locales/index.js'
import { post } from '../utils/api.js'
+// Check if base URL is set statically as an env var (typical in production)
const staticUrl = process.env.REACT_APP_DHIS2_BASE_URL
-export const LoginModal = () => {
- const [server, setServer] = useState(
- staticUrl || window.localStorage.DHIS2_BASE_URL || ''
- )
+export const LoginModal = ({ appName, baseUrl }) => {
+ const [server, setServer] = useState(baseUrl || '')
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [isDirty, setIsDirty] = useState(false)
@@ -27,7 +28,10 @@ export const LoginModal = () => {
setIsDirty(true)
if (isValid(server) && isValid(username) && isValid(password)) {
if (!staticUrl) {
+ // keep the localStorage value here -- it's still used in some
+ // obscure cases, like in the cypress network shim
window.localStorage.DHIS2_BASE_URL = server
+ await setBaseUrlByAppName({ appName, baseUrl: server })
}
try {
await post(
@@ -99,3 +103,7 @@ export const LoginModal = () => {
)
}
+LoginModal.propTypes = {
+ appName: PropTypes.string,
+ baseUrl: PropTypes.string,
+}
diff --git a/adapter/src/components/OfflineInterfaceContext.js b/adapter/src/components/OfflineInterfaceContext.js
new file mode 100644
index 000000000..b22050000
--- /dev/null
+++ b/adapter/src/components/OfflineInterfaceContext.js
@@ -0,0 +1,18 @@
+import { OfflineInterface } from '@dhis2/pwa'
+import PropTypes from 'prop-types'
+import React, { createContext, useContext } from 'react'
+
+const theOfflineInterface = new OfflineInterface()
+const OfflineInterfaceContext = createContext(theOfflineInterface)
+
+export const OfflineInterfaceProvider = ({ children }) => (
+
+ {children}
+
+)
+
+OfflineInterfaceProvider.propTypes = {
+ children: PropTypes.node,
+}
+
+export const useOfflineInterface = () => useContext(OfflineInterfaceContext)
diff --git a/adapter/src/components/PWALoadingBoundary.js b/adapter/src/components/PWALoadingBoundary.js
new file mode 100644
index 000000000..e35f4bf56
--- /dev/null
+++ b/adapter/src/components/PWALoadingBoundary.js
@@ -0,0 +1,43 @@
+import {
+ REGISTRATION_STATE_WAITING,
+ REGISTRATION_STATE_FIRST_ACTIVATION,
+} from '@dhis2/pwa'
+import PropTypes from 'prop-types'
+import { useEffect, useState } from 'react'
+import { useOfflineInterface } from './OfflineInterfaceContext'
+
+export const PWALoadingBoundary = ({ children }) => {
+ const [pwaReady, setPWAReady] = useState(false)
+ const offlineInterface = useOfflineInterface()
+
+ useEffect(() => {
+ const checkRegistration = async () => {
+ const registrationState =
+ await offlineInterface.getRegistrationState()
+ const clientsInfo = await offlineInterface.getClientsInfo()
+ if (
+ (registrationState === REGISTRATION_STATE_WAITING ||
+ registrationState ===
+ REGISTRATION_STATE_FIRST_ACTIVATION) &&
+ clientsInfo.clientsCount === 1
+ ) {
+ console.log(
+ 'Reloading on startup to activate waiting service worker'
+ )
+ offlineInterface.useNewSW()
+ } else {
+ setPWAReady(true)
+ }
+ }
+ checkRegistration().catch((err) => {
+ console.error(err)
+ setPWAReady(true)
+ })
+ }, [offlineInterface])
+
+ return pwaReady ? children : null
+}
+
+PWALoadingBoundary.propTypes = {
+ children: PropTypes.node.isRequired,
+}
diff --git a/adapter/src/components/PWAUpdateManager.js b/adapter/src/components/PWAUpdateManager.js
deleted file mode 100644
index ccdd0b465..000000000
--- a/adapter/src/components/PWAUpdateManager.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import { useAlert } from '@dhis2/app-runtime'
-import {
- Button,
- ButtonStrip,
- Modal,
- ModalActions,
- ModalContent,
- ModalTitle,
-} from '@dhis2/ui'
-import PropTypes from 'prop-types'
-import React, { useState, useEffect } from 'react'
-import i18n from '../locales/index.js'
-
-function ConfirmReloadModal({ clientsCount, onCancel, onConfirm }) {
- return (
-
- {i18n.t('Save your data')}
-
- {clientsCount
- ? i18n.t(
- "Updating will reload all {{n}} open instances of this app, and any unsaved data will be lost. Save any data you need to, then click 'Reload' when ready.",
- { n: clientsCount }
- )
- : // Fallback if clientsCount is unavailable:
- i18n.t(
- "Updating will reload all open instances of this app, and any unsaved data will be lost. Save any data you need to, then click 'Reload' when ready."
- )}
-
-
-
-
-
-
-
-
- )
-}
-ConfirmReloadModal.propTypes = {
- clientsCount: PropTypes.number,
- onCancel: PropTypes.func,
- onConfirm: PropTypes.func,
-}
-
-/**
- * Uses the offlineInterface to check for SW updates or a first activation,
- * then prompts the user to reload the page to use the new SW and access new
- * app updates. If there are multiple tabs of this app open, there's an
- * additional warning step because all clients of the service worker will
- * reload when there's an update, which may cause data loss.
- */
-export default function PWAUpdateManager({ offlineInterface }) {
- const [confirmReloadModalOpen, setConfirmReloadModalOpen] = useState(false)
- const [clientsCountState, setClientsCountState] = useState(null)
- const { show } = useAlert(
- i18n.t("There's an update available for this app."),
- ({ onConfirm }) => ({
- permanent: true,
- actions: [
- { label: i18n.t('Update and reload'), onClick: onConfirm },
- { label: i18n.t('Not now'), onClick: () => {} },
- ],
- })
- )
-
- const confirmReload = () => {
- offlineInterface
- .getClientsInfo()
- .then(({ clientsCount }) => {
- setClientsCountState(clientsCount)
- if (clientsCount === 1) {
- // Just one client; go ahead and reload
- offlineInterface.useNewSW()
- } else {
- // Multiple clients; warn about data loss before reloading
- setConfirmReloadModalOpen(true)
- }
- })
- .catch((reason) => {
- // Didn't get clients info
- console.warn(reason)
- // Go ahead with confirmation modal with `null` as clientsCount
- setConfirmReloadModalOpen(true)
- })
- }
-
- useEffect(() => {
- offlineInterface.checkForNewSW({
- onNewSW: () => {
- show({ onConfirm: confirmReload })
- },
- })
- }, [])
-
- return confirmReloadModalOpen ? (
- offlineInterface.useNewSW()}
- onCancel={() => setConfirmReloadModalOpen(false)}
- clientsCount={clientsCountState}
- />
- ) : null
-}
-PWAUpdateManager.propTypes = {
- offlineInterface: PropTypes.shape({
- checkForNewSW: PropTypes.func.isRequired,
- getClientsInfo: PropTypes.func.isRequired,
- useNewSW: PropTypes.func.isRequired,
- }).isRequired,
-}
diff --git a/adapter/src/components/ServerVersionProvider.js b/adapter/src/components/ServerVersionProvider.js
index be03f6986..71460f4bc 100644
--- a/adapter/src/components/ServerVersionProvider.js
+++ b/adapter/src/components/ServerVersionProvider.js
@@ -1,58 +1,159 @@
import { Provider } from '@dhis2/app-runtime'
+import { getBaseUrlByAppName, setBaseUrlByAppName } from '@dhis2/pwa'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react'
import { get } from '../utils/api.js'
-import { parseServerVersion } from '../utils/parseServerVersion.js'
+import { parseDHIS2ServerVersion, parseVersion } from '../utils/parseVersion.js'
import { LoadingMask } from './LoadingMask.js'
import { LoginModal } from './LoginModal.js'
+import { useOfflineInterface } from './OfflineInterfaceContext.js'
export const ServerVersionProvider = ({
- url,
+ appName,
+ appVersion,
+ url, // url from env vars
apiVersion,
- offlineInterface,
pwaEnabled,
children,
}) => {
- const [{ loading, error, systemInfo }, setState] = useState({
+ const offlineInterface = useOfflineInterface()
+ const [systemInfoState, setSystemInfoState] = useState({
loading: true,
+ error: undefined,
+ systemInfo: undefined,
})
+ const [baseUrlState, setBaseUrlState] = useState({
+ loading: !url,
+ error: undefined,
+ baseUrl: url,
+ })
+ // Skip this loading step in non-pwa apps
+ const [offlineInterfaceLoading, setOfflineInterfaceLoading] =
+ useState(pwaEnabled)
+ const { systemInfo } = systemInfoState
+ const { baseUrl } = baseUrlState
useEffect(() => {
- if (!url) {
- setState({ loading: false, error: new Error('No url specified') })
+ // if URL prop is not set, set state to error to show login modal.
+ // Submitting valid login form with server and credentials reloads page,
+ // ostensibly with a filled url prop (now persisted locally)
+ if (!baseUrl) {
+ // Use a function as the argument to avoid needing baseUrlState as
+ // a dependency for useEffect
+ setBaseUrlState((state) =>
+ state.loading
+ ? state
+ : { loading: true, error: undefined, systemInfo: undefined }
+ )
+ // try getting URL from IndexedDB
+ getBaseUrlByAppName(appName)
+ .then((baseUrlFromDB) => {
+ if (baseUrlFromDB) {
+ // Set baseUrl in state if found in DB
+ setBaseUrlState({
+ loading: false,
+ error: undefined,
+ baseUrl: baseUrlFromDB,
+ })
+ return
+ }
+ // If no URL found in DB, try localStorage
+ // (previous adapter versions stored the base URL there)
+ const baseUrlFromLocalStorage =
+ window.localStorage.DHIS2_BASE_URL
+ if (baseUrlFromLocalStorage) {
+ setBaseUrlState({
+ loading: false,
+ error: undefined,
+ baseUrl: baseUrlFromLocalStorage,
+ })
+ // Also set it in IndexedDB for SW to access
+ return setBaseUrlByAppName({
+ appName,
+ baseUrl: baseUrlFromLocalStorage,
+ })
+ }
+ // If no base URL found in either, set error to show login modal
+ setBaseUrlState({
+ loading: false,
+ error: new Error('No url specified'),
+ baseUrl: undefined,
+ })
+ })
+ .catch((err) => {
+ console.error(err)
+ setBaseUrlState({
+ loading: false,
+ error: err,
+ baseUrl: undefined,
+ })
+ })
+
return
}
- setState((state) => (state.loading ? state : { loading: true }))
- const request = get(`${url}/api/system/info`)
+ // If url IS set, try querying API to test authentication and get
+ // server version. If it fails, set error to show login modal
+
+ setSystemInfoState((state) =>
+ state.loading
+ ? state
+ : { loading: true, error: undefined, systemInfo: undefined }
+ )
+ const request = get(`${baseUrl}/api/system/info`)
request
.then((systemInfo) => {
- setState({ loading: false, systemInfo })
+ setSystemInfoState({
+ loading: false,
+ error: undefined,
+ systemInfo: systemInfo,
+ })
})
.catch((e) => {
- setState({ loading: false, error: e })
+ // Todo: If this is a network error, the app cannot load -- handle that gracefully here
+ // if (e === 'Network error') { ... }
+ setSystemInfoState({
+ loading: false,
+ error: e,
+ systemInfo: undefined,
+ })
})
return () => {
request.abort()
}
- }, [url])
+ }, [appName, baseUrl])
- if (loading) {
- return
+ useEffect(() => {
+ if (pwaEnabled) {
+ offlineInterface.ready.then(() => {
+ setOfflineInterfaceLoading(false)
+ })
+ }
+ }, [offlineInterface, pwaEnabled])
+
+ // This needs to come before 'loading' case to show modal at correct times
+ if (systemInfoState.error || baseUrlState.error) {
+ return
}
- if (error) {
- return
+ if (
+ systemInfoState.loading ||
+ baseUrlState.loading ||
+ offlineInterfaceLoading
+ ) {
+ return
}
- const serverVersion = parseServerVersion(systemInfo.version)
+ const serverVersion = parseDHIS2ServerVersion(systemInfo.version)
const realApiVersion = serverVersion.minor
return (
(
+const AppAdapter = ({
+ appName,
+ appVersion,
+ url,
+ apiVersion,
+ pwaEnabled,
+ plugin,
+ children,
+}) => (
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
)
AppAdapter.propTypes = {
appName: PropTypes.string.isRequired,
+ appVersion: PropTypes.string.isRequired,
apiVersion: PropTypes.number,
children: PropTypes.element,
+ plugin: PropTypes.bool,
pwaEnabled: PropTypes.bool,
url: PropTypes.string,
}
diff --git a/adapter/src/utils/api.js b/adapter/src/utils/api.js
index cef437793..94e08e046 100644
--- a/adapter/src/utils/api.js
+++ b/adapter/src/utils/api.js
@@ -14,7 +14,7 @@ const request = (url, options) => {
})
.then((response) => {
if (response.status !== 200) {
- reject('Request failed', response.statusText)
+ reject('Request failed ' + response.statusText)
return
}
try {
diff --git a/adapter/src/utils/parseServerVersion.test.js b/adapter/src/utils/parseServerVersion.test.js
deleted file mode 100644
index 2addbcc76..000000000
--- a/adapter/src/utils/parseServerVersion.test.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const { parseServerVersion } = require('./parseServerVersion')
-
-describe('parseServerVersion', () => {
- let originalConsoleWarn
- beforeAll(() => {
- originalConsoleWarn = console.warn
- })
- beforeEach(() => {
- // Capture console warnings
- console.warn = jest.fn()
- })
- afterAll(() => {
- console.warn = originalConsoleWarn
- })
-
- it('Should correctly parse a simple released version', () => {
- expect(parseServerVersion('2.34.3')).toMatchObject({
- major: 2,
- minor: 34,
- patch: 3,
- tag: undefined,
- })
- expect(console.warn).toHaveBeenCalledTimes(0)
- })
-
- it('Should correctly parse a snapshot version', () => {
- expect(parseServerVersion('2.34-SNAPSHOT')).toMatchObject({
- major: 2,
- minor: 34,
- patch: undefined,
- tag: 'SNAPSHOT',
- })
- expect(console.warn).toHaveBeenCalledTimes(0)
- })
-
- it('Should not break on null or undefined', () => {
- expect(parseServerVersion(null)).toMatchObject({
- major: undefined,
- minor: undefined,
- patch: undefined,
- tag: undefined,
- })
-
- expect(parseServerVersion(undefined)).toMatchObject({
- major: undefined,
- minor: undefined,
- patch: undefined,
- tag: undefined,
- })
- expect(console.warn).toHaveBeenCalledTimes(2)
- })
-
- it('Should correctly parse a future major version', () => {
- expect(parseServerVersion('6.4.2')).toMatchObject({
- major: 6,
- minor: 4,
- patch: 2,
- tag: undefined,
- })
- expect(console.warn).toHaveBeenCalledTimes(1)
- })
-
- it('Should not break on non-numeric version string', () => {
- expect(parseServerVersion('this.is.a.version-test')).toMatchObject({
- major: undefined,
- minor: undefined,
- patch: undefined,
- tag: 'test',
- })
- expect(console.warn).toHaveBeenCalledTimes(1)
- })
-})
diff --git a/adapter/src/utils/parseServerVersion.js b/adapter/src/utils/parseVersion.js
similarity index 73%
rename from adapter/src/utils/parseServerVersion.js
rename to adapter/src/utils/parseVersion.js
index 70b65e836..e23e7aca2 100644
--- a/adapter/src/utils/parseServerVersion.js
+++ b/adapter/src/utils/parseVersion.js
@@ -1,14 +1,21 @@
-export const parseServerVersion = (versionString) => {
+export const parseVersion = (versionString) => {
const [mainVersion, tag] = versionString?.split('-') || []
const [major, minor, patch] = mainVersion?.split('.') || []
const parsedVersion = {
+ full: versionString,
major: parseInt(major) || undefined, // 2
minor: parseInt(minor) || undefined, // 34
patch: parseInt(patch) || undefined, // 1
tag, // SNAPSHOT || undefined
}
+ return parsedVersion
+}
+
+export const parseDHIS2ServerVersion = (versionString) => {
+ const parsedVersion = parseVersion(versionString)
+
if (
!parsedVersion.major ||
!parsedVersion.minor ||
diff --git a/adapter/src/utils/parseVersion.test.js b/adapter/src/utils/parseVersion.test.js
new file mode 100644
index 000000000..a88331865
--- /dev/null
+++ b/adapter/src/utils/parseVersion.test.js
@@ -0,0 +1,113 @@
+const { parseVersion, parseDHIS2ServerVersion } = require('./parseVersion')
+
+describe('parseVersion', () => {
+ let originalConsoleWarn
+ beforeAll(() => {
+ originalConsoleWarn = console.warn
+ })
+ beforeEach(() => {
+ // Capture console warnings
+ console.warn = jest.fn()
+ })
+ afterAll(() => {
+ console.warn = originalConsoleWarn
+ })
+
+ it('Should correctly parse a simple released version', () => {
+ expect(parseVersion('2.34.3')).toMatchObject({
+ full: '2.34.3',
+ major: 2,
+ minor: 34,
+ patch: 3,
+ tag: undefined,
+ })
+ expect(console.warn).toHaveBeenCalledTimes(0)
+ })
+
+ it('Should correctly parse a snapshot version', () => {
+ expect(parseVersion('2.34-SNAPSHOT')).toMatchObject({
+ full: '2.34-SNAPSHOT',
+ major: 2,
+ minor: 34,
+ patch: undefined,
+ tag: 'SNAPSHOT',
+ })
+ expect(console.warn).toHaveBeenCalledTimes(0)
+ })
+
+ it('Should not break on null or undefined', () => {
+ expect(parseVersion(null)).toMatchObject({
+ full: null,
+ major: undefined,
+ minor: undefined,
+ patch: undefined,
+ tag: undefined,
+ })
+
+ expect(parseVersion(undefined)).toMatchObject({
+ full: undefined,
+ major: undefined,
+ minor: undefined,
+ patch: undefined,
+ tag: undefined,
+ })
+ })
+})
+
+describe('parseDHIS2ServerVersion', () => {
+ let originalConsoleWarn
+ beforeAll(() => {
+ originalConsoleWarn = console.warn
+ })
+ beforeEach(() => {
+ // Capture console warnings
+ console.warn = jest.fn()
+ })
+ afterAll(() => {
+ console.warn = originalConsoleWarn
+ })
+
+ it('Should warn on null or undefined', () => {
+ expect(parseDHIS2ServerVersion(null)).toMatchObject({
+ full: null,
+ major: undefined,
+ minor: undefined,
+ patch: undefined,
+ tag: undefined,
+ })
+
+ expect(parseDHIS2ServerVersion(undefined)).toMatchObject({
+ full: undefined,
+ major: undefined,
+ minor: undefined,
+ patch: undefined,
+ tag: undefined,
+ })
+
+ expect(console.warn).toHaveBeenCalledTimes(2)
+ })
+
+ it('Should correctly parse a future major version, but log a warning', () => {
+ expect(parseDHIS2ServerVersion('6.4.2')).toMatchObject({
+ full: '6.4.2',
+ major: 6,
+ minor: 4,
+ patch: 2,
+ tag: undefined,
+ })
+ expect(console.warn).toHaveBeenCalledTimes(1)
+ })
+
+ it('Should not break on non-numeric version string, but log a warning', () => {
+ expect(parseDHIS2ServerVersion('this.is.a.version-test')).toMatchObject(
+ {
+ full: 'this.is.a.version-test',
+ major: undefined,
+ minor: undefined,
+ patch: undefined,
+ tag: 'test',
+ }
+ )
+ expect(console.warn).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/adapter/src/utils/usePWAUpdateState.js b/adapter/src/utils/usePWAUpdateState.js
new file mode 100644
index 000000000..e5292e14c
--- /dev/null
+++ b/adapter/src/utils/usePWAUpdateState.js
@@ -0,0 +1,54 @@
+import { useEffect, useState } from 'react'
+import { useOfflineInterface } from '../components/OfflineInterfaceContext'
+
+export const usePWAUpdateState = () => {
+ const offlineInterface = useOfflineInterface()
+ const [updateAvailable, setUpdateAvailable] = useState(false)
+ const [clientsCount, setClientsCount] = useState(null)
+
+ const onConfirmUpdate = () => {
+ offlineInterface.useNewSW()
+ }
+ const onCancelUpdate = () => {
+ setClientsCount(null)
+ }
+
+ const confirmReload = () => {
+ offlineInterface
+ .getClientsInfo()
+ .then(({ clientsCount }) => {
+ if (clientsCount === 1) {
+ // Just one client; go ahead and reload
+ onConfirmUpdate()
+ } else {
+ // Multiple clients; warn about data loss before reloading
+ setClientsCount(clientsCount)
+ }
+ })
+ .catch((reason) => {
+ // Didn't get clients info
+ console.warn(reason)
+
+ // Go ahead with confirmation modal with `0` as clientsCount
+ setClientsCount(0)
+ })
+ }
+
+ useEffect(() => {
+ offlineInterface.checkForNewSW({
+ onNewSW: () => {
+ setUpdateAvailable(true)
+ },
+ })
+ }, [offlineInterface])
+
+ const confirmationRequired = clientsCount !== null
+ return {
+ updateAvailable,
+ confirmReload,
+ confirmationRequired,
+ clientsCount,
+ onConfirmUpdate,
+ onCancelUpdate,
+ }
+}
diff --git a/cli/config/d2.pwa.config.js b/cli/config/d2.pwa.config.js
index ea0f28f0d..d9e9b0c3a 100644
--- a/cli/config/d2.pwa.config.js
+++ b/cli/config/d2.pwa.config.js
@@ -38,6 +38,18 @@ module.exports = {
* https://developers.google.com/web/tools/workbox/modules/workbox-precaching#explanation_of_the_precache_list
*/
additionalManifestEntries: [],
+ /**
+ * By default, all the contents of the `build` folder are added to
+ * the precache to give the app the best chances of functioning
+ * completely while offline. Developers may choose to omit some
+ * of these files (for example, thousands of font or image files)
+ * if they cause cache bloat and the app can work fine without
+ * them precached. See LIBS-482
+ *
+ * The globs should be relative to the public dir of the built app.
+ * Used in injectPrecacheManifest.js
+ */
+ globsToOmitFromPrecache: [],
},
},
}
diff --git a/cli/config/init.App.test.js b/cli/config/init.App.test.js
index 0a72d6b49..d53a3302b 100644
--- a/cli/config/init.App.test.js
+++ b/cli/config/init.App.test.js
@@ -1,7 +1,7 @@
import { CustomDataProvider } from '@dhis2/app-runtime'
import React from 'react'
import ReactDOM from 'react-dom'
-import App from './App'
+import App from './App.js'
it('renders without crashing', () => {
const div = document.createElement('div')
diff --git a/cli/config/init.entrypoint.js b/cli/config/init.entrypoint.js
index 082a4f752..c8ed94cdf 100644
--- a/cli/config/init.entrypoint.js
+++ b/cli/config/init.entrypoint.js
@@ -1,6 +1,6 @@
-import React from 'react'
import { DataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
+import React from 'react'
import classes from './App.module.css'
const query = {
@@ -13,8 +13,12 @@ const MyApp = () => (
{({ error, loading, data }) => {
- if (error) return ERROR
- if (loading) return ...
+ if (error) {
+ return ERROR
+ }
+ if (loading) {
+ return ...
+ }
return (
<>
diff --git a/cli/config/plugin.webpack.config.js b/cli/config/plugin.webpack.config.js
new file mode 100644
index 000000000..29d539570
--- /dev/null
+++ b/cli/config/plugin.webpack.config.js
@@ -0,0 +1,307 @@
+// Based on CRA Webpack config
+
+const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
+const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath')
+const TerserPlugin = require('terser-webpack-plugin')
+const webpack = require('webpack')
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
+const { getPWAEnvVars } = require('../src/lib/pwa')
+const getShellEnv = require('../src/lib/shell/env')
+const makeBabelConfig = require('./makeBabelConfig')
+
+const babelWebpackConfig = {
+ babelrc: false,
+ configFile: false,
+ cacheDirectory: true,
+ cacheCompression: false,
+}
+
+const cssRegex = /\.css$/
+const cssModuleRegex = /\.module\.css$/
+
+module.exports = ({ env: webpackEnv, config, paths }) => {
+ const isProduction = webpackEnv === 'production'
+ const isDevelopment = !isProduction
+
+ const publicPath = getPublicUrlOrPath(
+ isDevelopment,
+ null,
+ process.env.PUBLIC_URL
+ )
+
+ const shellEnv = getShellEnv({
+ plugin: 'true',
+ name: config.title,
+ ...getPWAEnvVars(config),
+ })
+
+ // "style" loader turns CSS into JS modules that inject