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