From f46e145cd9ed341143a22d661d6c255614a05898 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 14 Jul 2025 13:47:25 -0400 Subject: [PATCH 1/4] feat: add base routes for checkout --- .env | 1 + .env.development | 1 + .env.test | 3 +- jest.config.js | 6 +- package-lock.json | 1329 ++++++++++++++++- package.json | 9 +- src/components/app/App.tsx | 87 ++ src/components/{ => app}/Layout.tsx | 0 .../app/data/hooks/useNProgressLoader.ts | 47 + src/components/app/index.tsx | 3 + src/components/app/routes/RouterFallback.tsx | 12 + src/components/app/routes/createAppRouter.ts | 18 + src/index.scss | 10 +- src/index.tsx | 2 +- src/routes.tsx | 39 + src/utils/common.ts | 60 + tsconfig.json | 3 +- webpack.dev-stage.config.js | 26 + 18 files changed, 1614 insertions(+), 42 deletions(-) create mode 100644 src/components/app/App.tsx rename src/components/{ => app}/Layout.tsx (100%) create mode 100644 src/components/app/data/hooks/useNProgressLoader.ts create mode 100644 src/components/app/index.tsx create mode 100644 src/components/app/routes/RouterFallback.tsx create mode 100644 src/components/app/routes/createAppRouter.ts create mode 100644 src/routes.tsx create mode 100644 src/utils/common.ts create mode 100644 webpack.dev-stage.config.js diff --git a/.env b/.env index 447a7eea..a535ea40 100644 --- a/.env +++ b/.env @@ -21,3 +21,4 @@ USER_INFO_COOKIE_NAME='' APP_ID='' MFE_CONFIG_API_URL='' ENABLE_NEW_RELIC='false' +PARAGON_THEMES_URLS={} diff --git a/.env.development b/.env.development index 0bd7e9ea..34f07213 100644 --- a/.env.development +++ b/.env.development @@ -22,3 +22,4 @@ USER_INFO_COOKIE_NAME='edx-user-info' APP_ID='' MFE_CONFIG_API_URL='' ENABLE_NEW_RELIC='false' +PARAGON_THEMES_URLS={} diff --git a/.env.test b/.env.test index 420a16d8..caafb6ce 100644 --- a/.env.test +++ b/.env.test @@ -1,5 +1,5 @@ ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' -BASE_URL='http://localhost:1995' +BASE_URL='http://localhost:1989' CREDENTIALS_BASE_URL='http://localhost:18150' CSRF_TOKEN_API_PATH='/csrf/api/v1/token' ECOMMERCE_BASE_URL='http://localhost:18130' @@ -19,3 +19,4 @@ SITE_NAME=localhost USER_INFO_COOKIE_NAME='edx-user-info' APP_ID='' MFE_CONFIG_API_URL='' +PARAGON_THEMES_URLS={} diff --git a/jest.config.js b/jest.config.js index 1df99c3d..bf38c0b7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,8 +1,10 @@ const { createConfig } = require('@openedx/frontend-build'); +process.env.TZ='UTC' + module.exports = createConfig('jest', { - // setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want. - // If you want to add config BEFORE jest loads, use setupFiles instead. + // setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want. + // If you want to add config BEFORE jest loads, use setupFiles instead. setupFilesAfterEnv: [ '/src/setupTest.js', ], diff --git a/package-lock.json b/package-lock.json index d8be3ae8..531c2450 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,15 @@ "version": "0.1.0", "license": "AGPL-3.0", "dependencies": { - "@edx/brand": "npm:@edx/brand-edx.org@^2.1.3", + "@edx/brand": "npm:@edx/elm-theme@^1.11.0", "@edx/frontend-component-header": "^6.4.0", "@edx/frontend-platform": "^8.3.5", "@edx/typescript-config": "^1.1.0", "@hookform/resolvers": "^4.1.3", "@openedx/frontend-slot-footer": "^1.1.0", - "@openedx/paragon": "^22.17.0", + "@openedx/paragon": "^23.14.0", + "@tanstack/react-query": "^5.83.0", + "@tanstack/react-query-devtools": "^5.83.0", "classnames": "^2.5.1", "prop-types": "^15.8.1", "react": "^18.3.1", @@ -1891,6 +1893,206 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, + "node_modules/@bundled-es-modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-Rk453EklPUPC3NRWc3VUNI/SSUjdBaFoaQvFRmNBNtMHVtOFD5AntiWg5kEE1hqcPqedYFDzxE3ZcMYPcA195w==", + "license": "ISC", + "dependencies": { + "deepmerge": "^4.3.1" + } + }, + "node_modules/@bundled-es-modules/glob": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "buffer": "^6.0.3", + "events": "^3.3.0", + "glob": "^10.4.2", + "patch-package": "^8.0.0", + "path": "^0.12.7", + "stream": "^0.0.3", + "string_decoder": "^1.3.0", + "url": "^0.11.3" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bundled-es-modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==", + "license": "Apache-2.0", + "dependencies": { + "assert": "^2.1.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "memfs": "^4.17.0", + "path": "^0.12.7", + "stream": "^0.0.3", + "util": "^0.12.5" + } + }, + "node_modules/@bundled-es-modules/memfs/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@bundled-es-modules/memfs/node_modules/memfs": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/@bundled-es-modules/postcss-calc-ast-parser": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.6.tgz", + "integrity": "sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==", + "license": "ISC", + "dependencies": { + "postcss-calc-ast-parser": "^0.1.4" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, "node_modules/@cospired/i18n-iso-languages": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@cospired/i18n-iso-languages/-/i18n-iso-languages-4.2.0.tgz", @@ -1997,11 +2199,10 @@ } }, "node_modules/@edx/brand": { - "name": "@edx/brand-edx.org", - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@edx/brand-edx.org/-/brand-edx.org-2.1.3.tgz", - "integrity": "sha512-1TwUwW7YVgvhh7aO1ql1poAosUCA0zd7/DNuqeSO0wXui0oCHL+WQW8b9tXS2tRJ5BMRKjG8t22fcOcKX3ljbg==", - "license": "UNLICENSED" + "name": "@edx/elm-theme", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@edx/elm-theme/-/elm-theme-1.11.0.tgz", + "integrity": "sha512-V7Bk/7kxFdYVB31Bq5Sxiy1kAuBuHSti1nSr+u4OMmpJ5v70t+ScHMHxPqYFTH2K3RxtP6Vwn0q8BtQBKAFCaA==" }, "node_modules/@edx/browserslist-config": { "version": "1.5.0", @@ -2690,6 +2891,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -2699,6 +2901,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -2817,6 +3020,102 @@ "deprecated": "Use @eslint/object-schema instead", "license": "BSD-3-Clause" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3260,6 +3559,60 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -3581,9 +3934,9 @@ } }, "node_modules/@openedx/paragon": { - "version": "22.17.0", - "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.17.0.tgz", - "integrity": "sha512-MzOLQ0myaOErwumPJwxVZXTw7zJKrARtu4YMSaISF5Sz6pE1/dYz9qfRcqaraYRcJGNdbPRzOG0v3iqbZo1uHQ==", + "version": "23.14.0", + "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.14.0.tgz", + "integrity": "sha512-+z5PspPI5D+/Xh87KG0ZYt/E+mOlgEMj6/IXeBWSj0vUvawj/pYzKgj+hlGbFGV9lIKkKnQJA8iurQrR++VXZA==", "license": "Apache-2.0", "workspaces": [ "example", @@ -3593,20 +3946,33 @@ "dependent-usage-analyzer" ], "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.1.1", - "@fortawesome/react-fontawesome": "^0.1.18", "@popperjs/core": "^2.11.4", + "@tokens-studio/sd-transforms": "^1.2.4", + "axios": "^0.27.2", "bootstrap": "^4.6.2", "chalk": "^4.1.2", "child_process": "^1.0.2", + "chroma-js": "^2.4.2", "classnames": "^2.3.1", + "cli-progress": "^3.12.0", + "commander": "^9.4.1", "email-prop-type": "^3.0.0", "file-selector": "^0.6.0", - "font-awesome": "^4.7.0", "glob": "^8.0.3", "inquirer": "^8.2.5", + "js-toml": "^1.0.0", "lodash.uniqby": "^4.7.0", + "log-update": "^4.0.0", + "lz-string": "^1.5.0", "mailto-link": "^2.0.0", + "minimist": "^1.2.8", + "ora": "^5.4.1", + "postcss": "^8.4.21", + "postcss-combine-duplicated-selectors": "^10.0.3", + "postcss-custom-media": "^9.1.2", + "postcss-import": "^15.1.0", + "postcss-map": "^0.11.0", + "postcss-minify": "^1.1.0", "prop-types": "^15.8.1", "react-bootstrap": "^1.6.5", "react-colorful": "^5.6.1", @@ -3619,6 +3985,8 @@ "react-responsive": "^8.2.0", "react-table": "^7.7.0", "react-transition-group": "^4.4.2", + "sass": "^1.58.3", + "style-dictionary": "^4.3.2", "tabbable": "^5.3.3", "uncontrollable": "^7.2.1", "uuid": "^9.0.0" @@ -3632,17 +4000,14 @@ "react-intl": "^5.25.1 || ^6.4.0" } }, - "node_modules/@openedx/paragon/node_modules/@fortawesome/react-fontawesome": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", - "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", + "node_modules/@openedx/paragon/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "license": "MIT", "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.x" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "node_modules/@openedx/paragon/node_modules/brace-expansion": { @@ -3654,6 +4019,15 @@ "balanced-match": "^1.0.0" } }, + "node_modules/@openedx/paragon/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/@openedx/paragon/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -3686,6 +4060,34 @@ "node": ">=10" } }, + "node_modules/@openedx/paragon/node_modules/postcss-custom-media": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.5.tgz", + "integrity": "sha512-GStyWMz7Qbo/Gtw1xVspzVSX8eipgNg4lpsO3CAeY4/A1mzok+RV6MCv3fg62trWijh/lYEj6vps4o8JcBBpDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.2.0", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -3991,6 +4393,16 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", @@ -4378,6 +4790,84 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz", + "integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.81.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz", + "integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz", + "integrity": "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.83.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.83.0.tgz", + "integrity": "sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==", + "dependencies": { + "@tanstack/query-devtools": "5.81.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.83.0", + "react": "^18 || ^19" + } + }, + "node_modules/@tokens-studio/sd-transforms": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@tokens-studio/sd-transforms/-/sd-transforms-1.3.0.tgz", + "integrity": "sha512-zVbiYjTGWpSuwzZwiuvcWf79CQEcTMKSxrOaQJ0zHXFxEmrpETWeIRxv2IO8rtMos/cS8mvnDwPngoHQOMs1SA==", + "license": "MIT", + "dependencies": { + "@bundled-es-modules/deepmerge": "^4.3.1", + "@bundled-es-modules/postcss-calc-ast-parser": "^0.1.6", + "@tokens-studio/types": "^0.5.1", + "colorjs.io": "^0.5.2", + "expr-eval-fork": "^2.0.2", + "is-mergeable-object": "^1.1.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "style-dictionary": "^4.3.0 || ^5.0.0-rc.0" + } + }, + "node_modules/@tokens-studio/types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.5.2.tgz", + "integrity": "sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==", + "license": "MIT" + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -5533,6 +6023,23 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.63", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.63.tgz", + "integrity": "sha512-B02i6QDMUQ4c+5F9LmliBGA+jFsiEHIlF0eLQ6rWLaQOD3YwI6vyWwGkVCNJnVVguE2xYyr9fAwSD/3valm1/Q==", + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -5973,6 +6480,19 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/assert-ok": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz", @@ -5985,6 +6505,15 @@ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "license": "ISC" }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -6878,6 +7407,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "license": "MIT" + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -6893,6 +7428,20 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "license": "MIT" }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, "node_modules/child_process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", @@ -6929,6 +7478,12 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/chroma-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.6.0.tgz", + "integrity": "sha512-BLHvCB9s8Z1EV4ethr6xnkl/P2YRFOGqfgvuMG/MyCbZPrTA+NeiByY6XvgF0zP4/2deU2CXnWyMa3zu1LqQ3A==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -7013,6 +7568,18 @@ "node": ">=8" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", @@ -7154,6 +7721,12 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -8308,6 +8881,12 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "license": "MIT" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9628,6 +10207,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expr-eval-fork": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-2.0.2.tgz", + "integrity": "sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==", + "license": "MIT" + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -10021,6 +10606,15 @@ "node": ">=8" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -10098,15 +10692,6 @@ } } }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", - "license": "(OFL-1.1 AND MIT)", - "engines": { - "node": ">=0.10.3" - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -10122,6 +10707,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", @@ -11086,6 +11699,15 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", @@ -11445,6 +12067,22 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -11775,6 +12413,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-mergeable-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", + "integrity": "sha512-CPduJfuGg8h8vW74WOxHtHmtQutyQBzR+3MjQ6iDHIYdbOnm1YC7jv43SqCoU8OPGTJD4nibmiryA4kmogbGrA==", + "license": "MIT" + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -12205,6 +12865,21 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -12922,6 +13597,16 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-toml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-toml/-/js-toml-1.0.1.tgz", + "integrity": "sha512-rHd/IolpFm2V5BmHCEY8CckHs8NDsYZZ64H5RNgA6Opsr9vX4QyTiQPplgtqg7b3ztqYShZC38nl6CUg7QuhXg==", + "license": "MIT", + "dependencies": { + "chevrotain": "^11.0.3", + "xregexp": "^5.1.1" + } + }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -13107,6 +13792,15 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -13248,6 +13942,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -13306,6 +14006,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -13336,6 +14054,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -13608,6 +14335,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -13857,6 +14593,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -14184,6 +14936,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -14255,6 +15013,86 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -14294,6 +15132,28 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -14309,6 +15169,27 @@ "node": ">=8" } }, + "node_modules/path-unified": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/path-unified/-/path-unified-0.2.0.tgz", + "integrity": "sha512-MNKqvrKbbbb5p7XHXV6ZAsf/1f/yJQa13S/fcX0uua8ew58Tgc6jXV+16JyAbnR/clgCH+euKDxrF2STxMHdrg==", + "license": "MIT" + }, + "node_modules/path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/path/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -14601,6 +15482,24 @@ "postcss": "^8.2.2" } }, + "node_modules/postcss-calc-ast-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.4.tgz", + "integrity": "sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^3.3.1" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/postcss-calc-ast-parser/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "license": "MIT" + }, "node_modules/postcss-colormin": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", @@ -14619,6 +15518,21 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-combine-duplicated-selectors": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/postcss-combine-duplicated-selectors/-/postcss-combine-duplicated-selectors-10.0.3.tgz", + "integrity": "sha512-IP0BmwFloCskv7DV7xqvzDXqMHpwdczJa6ZvIW8abgHdcIHs9mCJX2ltFhu3EwA51ozp13DByng30+Ke+eIExA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-convert-values": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", @@ -14711,6 +15625,23 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/postcss-loader": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", @@ -14745,6 +15676,52 @@ "node": ">=10" } }, + "node_modules/postcss-map": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/postcss-map/-/postcss-map-0.11.0.tgz", + "integrity": "sha512-cgHYZrH9aAMds90upYUPhYz8xnAcRD45SwuNns/nQHONIrPQDhpwk3JLsAQGOndQxnRVXfB6nB+3WqSMy8fqlA==", + "license": "Unlicense", + "dependencies": { + "js-yaml": "^3.12.0", + "postcss": "^7.0.2", + "reduce-function-call": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/postcss-map/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/postcss-map/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss-merge-longhand": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", @@ -14779,6 +15756,19 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-minify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-minify/-/postcss-minify-1.2.0.tgz", + "integrity": "sha512-Cvyz+hW5eBG0okSSOGDeussQy4v9mZHRhMevP2jwADqDS1v2gfoLo94+g4fCeVYtESZKqr+ViMPv3yPkWZFmFQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0 || ^7.0", + "postcss-value-parser": "^4.1" + }, + "peerDependencies": { + "postcss": "^8.0" + } + }, "node_modules/postcss-minify-font-values": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", @@ -15245,6 +16235,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -15281,6 +16286,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -16232,6 +17246,24 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -16282,6 +17314,15 @@ "node": ">=6.0.0" } }, + "node_modules/reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", @@ -17391,6 +18432,23 @@ "node": ">=6" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/slugify": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", @@ -17592,6 +18650,27 @@ "node": ">= 0.8" } }, + "node_modules/stream": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", + "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", + "license": "MIT", + "dependencies": { + "component-emitter": "^2.0.0" + } + }, + "node_modules/stream/node_modules/component-emitter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", + "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/streamx": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", @@ -17650,6 +18729,27 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -17751,6 +18851,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -17781,6 +18894,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-dictionary": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/style-dictionary/-/style-dictionary-4.4.0.tgz", + "integrity": "sha512-+xU0IA1StzqAqFs/QtXkK+XJa7wpS4X5H+JQccRKsRCElgeLGocFU1U/UMvMUylKFw6vwGV+Y/a2wb2pm5rFFQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@bundled-es-modules/deepmerge": "^4.3.1", + "@bundled-es-modules/glob": "^10.4.2", + "@bundled-es-modules/memfs": "^4.9.4", + "@zip.js/zip.js": "^2.7.44", + "chalk": "^5.3.0", + "change-case": "^5.3.0", + "commander": "^12.1.0", + "is-plain-obj": "^4.1.0", + "json5": "^2.2.2", + "patch-package": "^8.0.0", + "path-unified": "^0.2.0", + "prettier": "^3.3.3", + "tinycolor2": "^1.6.0" + }, + "bin": { + "style-dictionary": "bin/style-dictionary.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/style-dictionary/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/style-dictionary/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/style-dictionary/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/style-loader": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", @@ -18149,6 +19323,18 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "license": "MIT" }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -18173,6 +19359,12 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", @@ -18299,6 +19491,22 @@ "node": ">=12" } }, + "node_modules/tree-dump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", + "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -18826,6 +20034,19 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/url-loader": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", @@ -18881,6 +20102,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -18933,6 +20160,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -19589,6 +20829,24 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -19644,6 +20902,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/xregexp": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz", + "integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.9" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index d99454f8..9bc0b2ff 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "lint": "fedx-scripts eslint --ext .js --ext .jsx --ext .ts --ext .tsx .", "lint:fix": "npm run lint -- --fix", "start": "fedx-scripts webpack-dev-server --progress", + "start:stage": "npm run start -- --config=webpack.dev-stage.config.js", + "start:with-theme": "paragon install-theme && npm run start && npm install", + "start:stage:with-theme": "paragon install-theme && npm run start:stage && npm install", "test": "fedx-scripts jest --coverage --passWithNoTests", "test:watch": "npm run test -- --watch", "test:watch:no-cov": "npm run test:watch -- --collectCoverage=false", @@ -33,13 +36,15 @@ "url": "https://github.com/edx/frontend-app-enterprise-checkout/issues" }, "dependencies": { - "@edx/brand": "npm:@edx/brand-edx.org@^2.1.3", + "@edx/brand": "npm:@edx/elm-theme@^1.11.0", "@edx/frontend-component-header": "^6.4.0", "@edx/frontend-platform": "^8.3.5", "@edx/typescript-config": "^1.1.0", "@hookform/resolvers": "^4.1.3", "@openedx/frontend-slot-footer": "^1.1.0", - "@openedx/paragon": "^22.17.0", + "@openedx/paragon": "^23.14.0", + "@tanstack/react-query": "^5.83.0", + "@tanstack/react-query-devtools": "^5.83.0", "classnames": "^2.5.1", "prop-types": "^15.8.1", "react": "^18.3.1", diff --git a/src/components/app/App.tsx b/src/components/app/App.tsx new file mode 100644 index 00000000..3a95de9a --- /dev/null +++ b/src/components/app/App.tsx @@ -0,0 +1,87 @@ +import { + Suspense, lazy, useEffect, useMemo, useState, +} from 'react'; +import { RouterProvider } from 'react-router-dom'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { + QueryCache, + QueryClient, + QueryClientProvider, + keepPreviousData, +} from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; + +import { + queryCacheOnErrorHandler, + defaultQueryClientRetryHandler, +} from '@/utils/common'; +import { RouterFallback, createAppRouter } from './routes'; + +// @ts-ignore +const ReactQueryDevtoolsProduction = lazy(() => import('@tanstack/react-query-devtools/production').then((d) => ({ + default: d.ReactQueryDevtools, +}))); + +function useAppQueryClient() { + const [queryClient] = useState(() => new QueryClient({ + queryCache: new QueryCache({ + onError: queryCacheOnErrorHandler, + }), + defaultOptions: { + queries: { + // Specifying a longer `staleTime` of 20 seconds means queries will not refetch their data + // as often; mitigates making duplicate queries when within the `staleTime` window, instead + // relying on the cached data until the `staleTime` window has exceeded. This may be modified + // per-query, as needed, if certain queries expect to be more up-to-date than others. Allows + // `useQuery` to be used as a state manager. + staleTime: 1000 * 20, // 20 seconds + // To prevent hard loading states if/when query keys change during automatic query background + // re-fetches, we can utilize `keepPreviousData` to keep the previous data until the new + // data is fetched. By enabling this option, UI components generally will not need to consider + // hard loading states when query keys change. + placeholderData: keepPreviousData, + // If a query fails, it will retry up to 3 times for queries with non-404 errors. + retry: defaultQueryClientRetryHandler, + }, + }, + })); + return queryClient; +} + +function useReactQueryDevTools() { + const [showReactQueryDevtools, setShowReactQueryDevtools] = useState(false); + useEffect(() => { + // @ts-ignore + window.toggleReactQueryDevtools = () => setShowReactQueryDevtools((prevState) => !prevState); + }); + return showReactQueryDevtools; +} + +const App = () => { + const queryClient = useAppQueryClient(); + + // Create the app router during render vs. at the top-level of the module to ensure + // the logging and auth modules are initialized before the router is created. + const router = useMemo(() => createAppRouter(queryClient), [queryClient]); + + const showReactQueryDevtools = useReactQueryDevTools(); + + return ( + + + {showReactQueryDevtools && ( + + + + )} + + } + /> + + + ); +}; + +export default App; diff --git a/src/components/Layout.tsx b/src/components/app/Layout.tsx similarity index 100% rename from src/components/Layout.tsx rename to src/components/app/Layout.tsx diff --git a/src/components/app/data/hooks/useNProgressLoader.ts b/src/components/app/data/hooks/useNProgressLoader.ts new file mode 100644 index 00000000..da1e6fad --- /dev/null +++ b/src/components/app/data/hooks/useNProgressLoader.ts @@ -0,0 +1,47 @@ +import { useEffect, useState} from 'react'; +import { useFetchers, useNavigation } from 'react-router-dom'; +import nprogress from 'accessible-nprogress'; +import { useIsFetching } from '@tanstack/react-query'; + +// Determines amount of time that must elapse before the +// NProgress loader is shown in the UI. No need to show it +// for quick route transitions. +export const NPROGRESS_DELAY_MS = 300; + +export interface UseNProgressLoaderOptions { + // Whether to wait for the navigation to complete before completing the loader. + shouldCompleteBeforeUnmount?: boolean; + // Whether to wait for the query fetching to complete before completing the loader. + handleQueryFetching?: boolean; +} + +function useNProgressLoader({ + shouldCompleteBeforeUnmount = true, + handleQueryFetching = false, +}: UseNProgressLoaderOptions = {}) { + const isAuthenticatedUserHydrated = !!authenticatedUser?.extendedProfile; + const navigation = useNavigation(); + const fetchers = useFetchers(); + const isFetching = useIsFetching() > 0 && handleQueryFetching; + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + const timeoutId = setTimeout(() => { + const fetchersIdle = fetchers.every((f) => f.state === 'idle'); + if (shouldCompleteBeforeUnmount && navigation.state === 'idle' && fetchersIdle && !isFetching && isAuthenticatedUserHydrated) { + nprogress.done(); + setIsLoaded(true); + } else { + nprogress.start(); + } + }, NPROGRESS_DELAY_MS); + return () => { + nprogress.done(); + clearTimeout(timeoutId); + }; + }, [navigation, fetchers, isFetching, isAuthenticatedUserHydrated, shouldCompleteBeforeUnmount]); + + return isLoaded; +} + +export default useNProgressLoader; diff --git a/src/components/app/index.tsx b/src/components/app/index.tsx new file mode 100644 index 00000000..e548f90b --- /dev/null +++ b/src/components/app/index.tsx @@ -0,0 +1,3 @@ +export { default as App } from './App'; +export { default as Root } from './Root'; +export { default as AppErrorBoundary } from './AppErrorBoundary'; diff --git a/src/components/app/routes/RouterFallback.tsx b/src/components/app/routes/RouterFallback.tsx new file mode 100644 index 00000000..61608c38 --- /dev/null +++ b/src/components/app/routes/RouterFallback.tsx @@ -0,0 +1,12 @@ +import { useNProgressLoader, UseNProgressLoaderOptions } from '../data'; + +interface RouterFallbackProps { + loaderOptions?: UseNProgressLoaderOptions; +} + +const RouterFallback = ({ loaderOptions = {} as UseNProgressLoaderOptions }: RouterFallbackProps) => { + useNProgressLoader(loaderOptions); + return null; +}; + +export default RouterFallback; diff --git a/src/components/app/routes/createAppRouter.ts b/src/components/app/routes/createAppRouter.ts new file mode 100644 index 00000000..2c5b834e --- /dev/null +++ b/src/components/app/routes/createAppRouter.ts @@ -0,0 +1,18 @@ + +import type { Router } from '@remix-run/router'; +import { QueryClient } from '@tanstack/react-query'; +import { createBrowserRouter } from 'react-router-dom'; + +import { getRoutes } from '../../../routes'; + +/** + * Creates a React Router browser router. + * + * @param {Object} queryClient React Query query client. + * @returns {Object} React Router browser router. + */ +export default function createAppRouter(queryClient: QueryClient): Router { + const { routes } = getRoutes(queryClient); + const router = createBrowserRouter(routes); + return router; +} diff --git a/src/index.scss b/src/index.scss index cc183d92..b5ba5b7a 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,7 +1,9 @@ -@import "@edx/brand/paragon/fonts.scss"; -@import "@edx/brand/paragon/variables.scss"; -@import "@openedx/paragon/scss/core/core.scss"; -@import "@edx/brand/paragon/overrides.scss"; +@use "~@openedx/paragon/styles/scss/core/core.scss"; + +@import "~@openedx/paragon/styles/css/core/index.css"; +@import "~@openedx/paragon/styles/css/themes/light/index.css"; +@import "~@edx/brand/dist/core.css"; +@import "~@edx/brand/dist/light.css"; @import "~@edx/frontend-component-header/dist/index"; @import "~@edx/frontend-component-footer/dist/footer"; diff --git a/src/index.tsx b/src/index.tsx index c26572c0..ac2c9f57 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,7 +10,7 @@ import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom' import messages from './i18n'; -import Layout from './components/Layout'; +import Layout from '@/components/app/Layout'; import CheckoutPage from './components/CheckoutPage'; import ConfirmationPage from './components/ConfirmationPage'; diff --git a/src/routes.tsx b/src/routes.tsx new file mode 100644 index 00000000..c76fd560 --- /dev/null +++ b/src/routes.tsx @@ -0,0 +1,39 @@ +import {QueryClient} from "@tanstack/react-query"; +import {RouteObject} from "react-router"; +import {PageWrap} from "@edx/frontend-platform/react"; +import {Suspense} from "react"; + +/** + * Returns the routes for the application. + */ +export function getRoutes(queryClient?: QueryClient) { + const otherRoutes = getOtherRoutes(); + const rootChildRoutes: RouteObject[] = [ + ...otherRoutes, + { + path: '*', + element: (
Not Found
), + }, + ]; + + const routes: RouteObject[] = [ + { + path: '/', + element: ( + + }> + + + + ), + children: rootChildRoutes, + errorElement: (
Error Boundary
), + }, + ]; + + return { + routes, + rootChildRoutes, + otherRoutes, + }; +} diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 00000000..6964c700 --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,60 @@ +import { logError } from '@edx/frontend-platform/logging'; + +/** + * Given an error, returns the status code from the custom attributes (Axios error) + * or the standard JS error response. + * @param {Error} error An error object + * @returns {number} The status code (e.g., 404) + */ +function getErrorResponseStatusCode(error) { + return error.customAttributes?.httpErrorStatus || error.response?.status; +} + +/** + * Determines whether a React Query query should be retried on failure. + * + * @param {number} failureCount The number of times the query has failed + * @param {Error} error The error that caused the query to fail + * @returns {boolean} Whether the query should be retried + */ +function defaultQueryClientRetryHandler(failureCount: number, error: any): boolean { + return !(failureCount >= 3 || getErrorResponseStatusCode(error) === 404); +} + +/** + * Logs a react-query query error message on failure, if present. + * @param {Query} query The query object + * @returns {void} + */ +function queryCacheOnErrorHandler(query) { + if (query.meta?.errorMessage) { + logError(query.meta?.errorMessage); + } +} + +/** + * Given a CSS variable name, returns the computed value of the CSS variable. + * @param {string} cssVariableName A string representing a CSS variable. + * @returns {string} The computed value of the CSS variable. + */ +function getComputedStylePropertyCSSVariable(cssVariableName: string) { + return getComputedStyle(document.documentElement).getPropertyValue(cssVariableName); +} + +const formatPrice = (price: number, options = {}) => { + const USDollar = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + ...options, + }); + return USDollar.format(Math.abs(price)); +}; + +export { + defaultQueryClientRetryHandler, + getComputedStylePropertyCSSVariable, + formatPrice, + queryCacheOnErrorHandler, +}; diff --git a/tsconfig.json b/tsconfig.json index 319412ca..375188f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "paths": { "@/components/*": ["./src/components/*"], "@/hooks/*": ["./src/hooks/*"], - "@/constants": ["./src/constants"] + "@/constants": ["./src/constants"], + "@/utils/*":["./src/utils/*"] } }, "include": [ diff --git a/webpack.dev-stage.config.js b/webpack.dev-stage.config.js new file mode 100644 index 00000000..7f6b3f48 --- /dev/null +++ b/webpack.dev-stage.config.js @@ -0,0 +1,26 @@ +const { createConfig } = require('@openedx/frontend-build'); +const path = require('path'); +const dotenv = require('dotenv'); + +/** + * Injects stage-specific env vars from .env.development-stage. + * + * Note: ideally, we could use the base config for `webpack-dev-stage` in + * `getBaseConfig` above, however it appears to have a bug so we have to + * manually load the stage-specific env vars ourselves for now. + * + * The .env.development-stage env vars must be loaded before the base + * config is created. + */ +dotenv.config({ + path: path.resolve(process.cwd(), '.env.development-stage'), +}); + +const config = createConfig('webpack-dev', { + devServer: { + allowedHosts: 'all', + server: 'https', + }, +}); + +module.exports = config; From 9188f33addfd98264be2c8fec1cbb5914c3c4a86 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Tue, 15 Jul 2025 11:26:50 -0400 Subject: [PATCH 2/4] feat: create base infrastructure for routing --- package-lock.json | 7 +++ package.json | 1 + src/components/app/Root.tsx | 20 ++++++ src/components/app/data/hooks/index.ts | 1 + .../app/data/hooks/useNProgressLoader.ts | 7 +-- src/components/app/data/index.ts | 1 + src/components/app/index.tsx | 3 - src/components/app/routes/createAppRouter.ts | 4 +- src/components/app/routes/index.ts | 2 + src/index.tsx | 41 +----------- src/routes.tsx | 63 ++++++++++++++++--- src/types/types.d.ts | 6 ++ 12 files changed, 100 insertions(+), 56 deletions(-) create mode 100644 src/components/app/Root.tsx create mode 100644 src/components/app/data/hooks/index.ts create mode 100644 src/components/app/data/index.ts delete mode 100644 src/components/app/index.tsx create mode 100644 src/components/app/routes/index.ts create mode 100644 src/types/types.d.ts diff --git a/package-lock.json b/package-lock.json index 531c2450..ece1a120 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@openedx/paragon": "^23.14.0", "@tanstack/react-query": "^5.83.0", "@tanstack/react-query-devtools": "^5.83.0", + "accessible-nprogress": "^2.1.2", "classnames": "^2.5.1", "prop-types": "^15.8.1", "react": "^18.3.1", @@ -6060,6 +6061,12 @@ "node": ">= 0.6" } }, + "node_modules/accessible-nprogress": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/accessible-nprogress/-/accessible-nprogress-2.1.2.tgz", + "integrity": "sha512-reIwMbbt+ZGOmQLWPXGcPf5X1F4fzsZAekY9alCxpekxizRhQMAd/QInaA8k7WtwTcGMzD9hnYswGLcaJDRY/A==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", diff --git a/package.json b/package.json index 9bc0b2ff..724d01c3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@openedx/paragon": "^23.14.0", "@tanstack/react-query": "^5.83.0", "@tanstack/react-query-devtools": "^5.83.0", + "accessible-nprogress": "^2.1.2", "classnames": "^2.5.1", "prop-types": "^15.8.1", "react": "^18.3.1", diff --git a/src/components/app/Root.tsx b/src/components/app/Root.tsx new file mode 100644 index 00000000..a297de03 --- /dev/null +++ b/src/components/app/Root.tsx @@ -0,0 +1,20 @@ +import { Outlet, ScrollRestoration } from 'react-router-dom'; + +import { useNProgressLoader } from './data'; + +const Root = () => { + const isAppDataHydrated = useNProgressLoader(); + + if (!isAppDataHydrated) { + return null; + } + + return ( + <> + + + + ); +}; + +export default Root; diff --git a/src/components/app/data/hooks/index.ts b/src/components/app/data/hooks/index.ts new file mode 100644 index 00000000..c73159bc --- /dev/null +++ b/src/components/app/data/hooks/index.ts @@ -0,0 +1 @@ +export { default as useNProgressLoader, type UseNProgressLoaderOptions } from './useNProgressLoader'; diff --git a/src/components/app/data/hooks/useNProgressLoader.ts b/src/components/app/data/hooks/useNProgressLoader.ts index da1e6fad..9c3e93a3 100644 --- a/src/components/app/data/hooks/useNProgressLoader.ts +++ b/src/components/app/data/hooks/useNProgressLoader.ts @@ -1,4 +1,4 @@ -import { useEffect, useState} from 'react'; +import { useEffect, useState } from 'react'; import { useFetchers, useNavigation } from 'react-router-dom'; import nprogress from 'accessible-nprogress'; import { useIsFetching } from '@tanstack/react-query'; @@ -19,7 +19,6 @@ function useNProgressLoader({ shouldCompleteBeforeUnmount = true, handleQueryFetching = false, }: UseNProgressLoaderOptions = {}) { - const isAuthenticatedUserHydrated = !!authenticatedUser?.extendedProfile; const navigation = useNavigation(); const fetchers = useFetchers(); const isFetching = useIsFetching() > 0 && handleQueryFetching; @@ -28,7 +27,7 @@ function useNProgressLoader({ useEffect(() => { const timeoutId = setTimeout(() => { const fetchersIdle = fetchers.every((f) => f.state === 'idle'); - if (shouldCompleteBeforeUnmount && navigation.state === 'idle' && fetchersIdle && !isFetching && isAuthenticatedUserHydrated) { + if (shouldCompleteBeforeUnmount && navigation.state === 'idle' && fetchersIdle && !isFetching) { nprogress.done(); setIsLoaded(true); } else { @@ -39,7 +38,7 @@ function useNProgressLoader({ nprogress.done(); clearTimeout(timeoutId); }; - }, [navigation, fetchers, isFetching, isAuthenticatedUserHydrated, shouldCompleteBeforeUnmount]); + }, [navigation, fetchers, isFetching, shouldCompleteBeforeUnmount]); return isLoaded; } diff --git a/src/components/app/data/index.ts b/src/components/app/data/index.ts new file mode 100644 index 00000000..4cc90d02 --- /dev/null +++ b/src/components/app/data/index.ts @@ -0,0 +1 @@ +export * from './hooks'; diff --git a/src/components/app/index.tsx b/src/components/app/index.tsx deleted file mode 100644 index e548f90b..00000000 --- a/src/components/app/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export { default as App } from './App'; -export { default as Root } from './Root'; -export { default as AppErrorBoundary } from './AppErrorBoundary'; diff --git a/src/components/app/routes/createAppRouter.ts b/src/components/app/routes/createAppRouter.ts index 2c5b834e..69e1d9bb 100644 --- a/src/components/app/routes/createAppRouter.ts +++ b/src/components/app/routes/createAppRouter.ts @@ -1,4 +1,3 @@ - import type { Router } from '@remix-run/router'; import { QueryClient } from '@tanstack/react-query'; import { createBrowserRouter } from 'react-router-dom'; @@ -13,6 +12,5 @@ import { getRoutes } from '../../../routes'; */ export default function createAppRouter(queryClient: QueryClient): Router { const { routes } = getRoutes(queryClient); - const router = createBrowserRouter(routes); - return router; + return createBrowserRouter(routes); } diff --git a/src/components/app/routes/index.ts b/src/components/app/routes/index.ts new file mode 100644 index 00000000..791c8acd --- /dev/null +++ b/src/components/app/routes/index.ts @@ -0,0 +1,2 @@ +export { default as createAppRouter } from './createAppRouter'; +export { default as RouterFallback } from './RouterFallback'; diff --git a/src/index.tsx b/src/index.tsx index ac2c9f57..331e2ddf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,56 +4,21 @@ import { APP_INIT_ERROR, APP_READY, subscribe, initialize, } from '@edx/frontend-platform'; import { - AppProvider, AuthenticatedPageRoute, ErrorPage, PageWrap, + ErrorPage, } from '@edx/frontend-platform/react'; -import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom'; +import App from '@/components/app/App'; import messages from './i18n'; -import Layout from '@/components/app/Layout'; -import CheckoutPage from './components/CheckoutPage'; -import ConfirmationPage from './components/ConfirmationPage'; - import './index.scss'; -const router = createBrowserRouter([ // Data router - { - path: '/', - element: , - children: [ - { - index: true, - element: , - }, - { - path: 'checkout/:step?', - element: ( - - - - ), - }, - { - path: 'checkout/confirmation', - element: ( - - - - ), - }, - ], - }, -]); - const container = document.getElementById('root'); const root = createRoot(container); subscribe(APP_READY, () => { root.render( - - - , + , ); }); diff --git a/src/routes.tsx b/src/routes.tsx index c76fd560..2971bd5f 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,15 +1,63 @@ -import {QueryClient} from "@tanstack/react-query"; -import {RouteObject} from "react-router"; -import {PageWrap} from "@edx/frontend-platform/react"; -import {Suspense} from "react"; +import { QueryClient } from '@tanstack/react-query'; +import { RouteObject } from 'react-router'; +import { AuthenticatedPageRoute, PageWrap } from '@edx/frontend-platform/react'; +import { Suspense } from 'react'; +import Root from '@/components/app/Root'; +import RouterFallback from '@/components/app/routes/RouterFallback'; +import { Navigate } from 'react-router-dom'; +import CheckoutPage from '@/components/CheckoutPage'; +import ConfirmationPage from '@/components/ConfirmationPage'; +import Layout from '@/components/app/Layout'; + +function getCheckoutRoutes(queryClient: QueryClient) { + const checkoutChildRoutes: RouteObject[] = [ + { + index: true, + element: , + }, + { + path: 'checkout/:step?', + element: ( + + + + ), + }, + { + path: 'checkout/confirmation', + element: ( + + + + ), + }, + ]; + const checkoutRoutes: RouteObject[] = [ + { + path: '/', + element: , + children: checkoutChildRoutes, + shouldRevalidate: ({ currentUrl, nextUrl, defaultShouldRevalidate }) => { + // If the pathname changed, we should revalidate + if (currentUrl.pathname !== nextUrl.pathname) { + return true; + } + + // If the pathname didn't change, fallback to the default behavior + return defaultShouldRevalidate; + }, + }, + ]; + return checkoutRoutes; +} /** * Returns the routes for the application. */ -export function getRoutes(queryClient?: QueryClient) { - const otherRoutes = getOtherRoutes(); +export function getRoutes(queryClient: QueryClient) { + const checkoutRoutes = getCheckoutRoutes(queryClient); const rootChildRoutes: RouteObject[] = [ - ...otherRoutes, + ...checkoutRoutes, { path: '*', element: (
Not Found
), @@ -34,6 +82,5 @@ export function getRoutes(queryClient?: QueryClient) { return { routes, rootChildRoutes, - otherRoutes, }; } diff --git a/src/types/types.d.ts b/src/types/types.d.ts new file mode 100644 index 00000000..3224c487 --- /dev/null +++ b/src/types/types.d.ts @@ -0,0 +1,6 @@ +import { LoaderFunction } from 'react-router-dom'; + +type MakeRouteLoaderFunction = (queryClient?: QueryClient) => LoaderFunction; +type MakeRouteLoaderFunctionWithQueryClient = (queryClient: QueryClient) => LoaderFunction; + +export {}; From 7fcc6fb89c0886103055cb3ff779fca02992f373 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Wed, 16 Jul 2025 10:41:02 -0400 Subject: [PATCH 3/4] feat: add authenticated routes --- src/components/{ => FormFields}/Field.tsx | 0 src/components/{ => Header}/Header.tsx | 0 .../{ => Stepper}/CheckoutStepper.tsx | 18 +++++++--- src/components/{ => Stepper}/StepCounter.tsx | 2 +- .../Steps/BuildTrial.tsx} | 25 ++++++------- .../Stepper/Steps/CreateAccessLink.tsx | 13 +++++++ .../Steps/CreateAccount.tsx} | 36 +++++++++---------- src/components/Stepper/Steps/StartTrial.tsx | 13 +++++++ src/components/Stepper/Steps/Success.tsx | 13 +++++++ src/components/Stepper/Steps/index.tsx | 5 +++ src/{ => components/Stepper}/constants.ts | 25 ++++++++++--- .../{ => SubscriptionSummary}/Currency.tsx | 0 .../SubscriptionSummary.tsx | 8 ++--- src/components/SubscriptionSummary/index.tsx | 1 + src/components/app/Layout.tsx | 2 +- .../{ => checkout-page}/CheckoutPage.tsx | 8 ++--- .../ConfirmationPage.tsx | 0 src/routes.tsx | 32 +++++++++-------- types.d.ts | 24 ++++++++----- 19 files changed, 153 insertions(+), 72 deletions(-) rename src/components/{ => FormFields}/Field.tsx (100%) rename src/components/{ => Header}/Header.tsx (100%) rename src/components/{ => Stepper}/CheckoutStepper.tsx (59%) rename src/components/{ => Stepper}/StepCounter.tsx (92%) rename src/components/{PlanDetails.tsx => Stepper/Steps/BuildTrial.tsx} (90%) create mode 100644 src/components/Stepper/Steps/CreateAccessLink.tsx rename src/components/{AccountDetails.tsx => Stepper/Steps/CreateAccount.tsx} (93%) create mode 100644 src/components/Stepper/Steps/StartTrial.tsx create mode 100644 src/components/Stepper/Steps/Success.tsx create mode 100644 src/components/Stepper/Steps/index.tsx rename src/{ => components/Stepper}/constants.ts (71%) rename src/components/{ => SubscriptionSummary}/Currency.tsx (100%) rename src/components/{ => SubscriptionSummary}/SubscriptionSummary.tsx (96%) create mode 100644 src/components/SubscriptionSummary/index.tsx rename src/components/{ => checkout-page}/CheckoutPage.tsx (65%) rename src/components/{ => confirmation-page}/ConfirmationPage.tsx (100%) diff --git a/src/components/Field.tsx b/src/components/FormFields/Field.tsx similarity index 100% rename from src/components/Field.tsx rename to src/components/FormFields/Field.tsx diff --git a/src/components/Header.tsx b/src/components/Header/Header.tsx similarity index 100% rename from src/components/Header.tsx rename to src/components/Header/Header.tsx diff --git a/src/components/CheckoutStepper.tsx b/src/components/Stepper/CheckoutStepper.tsx similarity index 59% rename from src/components/CheckoutStepper.tsx rename to src/components/Stepper/CheckoutStepper.tsx index 1b4e160e..1907ddf1 100644 --- a/src/components/CheckoutStepper.tsx +++ b/src/components/Stepper/CheckoutStepper.tsx @@ -1,20 +1,28 @@ import { Stepper } from '@openedx/paragon'; import { Navigate, useParams } from 'react-router-dom'; -import PlanDetails from '@/components/PlanDetails'; -import AccountDetails from '@/components/AccountDetails'; +import { + BuildTrial, + CreateAccount, + CreateAccessLink, + StartTrial, + Success, +} from '@/components/Stepper/Steps'; const Steps: React.FC = () => (
- - + + + + +
); const CheckoutStepper: React.FC = () => { const { step } = useParams<{ step: Step }>(); if (!step) { - return ; + return ; } return ( diff --git a/src/components/StepCounter.tsx b/src/components/Stepper/StepCounter.tsx similarity index 92% rename from src/components/StepCounter.tsx rename to src/components/Stepper/StepCounter.tsx index 3aa66138..299463eb 100644 --- a/src/components/StepCounter.tsx +++ b/src/components/Stepper/StepCounter.tsx @@ -1,7 +1,7 @@ import { useParams } from 'react-router'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { steps } from '@/constants'; +import { steps } from '@/components/Stepper/constants'; const StepCounter: React.FC = () => { const { step } = useParams<{ step: Step }>(); diff --git a/src/components/PlanDetails.tsx b/src/components/Stepper/Steps/BuildTrial.tsx similarity index 90% rename from src/components/PlanDetails.tsx rename to src/components/Stepper/Steps/BuildTrial.tsx index 18621bc8..cf276052 100644 --- a/src/components/PlanDetails.tsx +++ b/src/components/Stepper/Steps/BuildTrial.tsx @@ -7,19 +7,19 @@ import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { PlanSchema, steps } from '@/constants'; -import Field, { useIsFieldInvalid, useIsFieldValid } from '@/components/Field'; -import StepCounter from '@/components/StepCounter'; +import { BuildTrialSchema, steps } from '@/components/Stepper/constants'; +import Field, { useIsFieldInvalid, useIsFieldValid } from '@/components/FormFields/Field'; +import StepCounter from '@/components/Stepper/StepCounter'; import useCheckoutFormStore from '@/hooks/useCheckoutFormStore'; -const PlanDetails: React.FC = () => { - const planFormData = useCheckoutFormStore((state) => state.formData.plan); +const BuildTrial: React.FC = () => { + const planFormData = useCheckoutFormStore((state) => state.formData.buildTrial); const setFormData = useCheckoutFormStore((state) => state.setFormData); const navigate = useNavigate(); - const form = useForm({ + const form = useForm({ mode: 'onTouched', - resolver: zodResolver(PlanSchema), + resolver: zodResolver(BuildTrialSchema), defaultValues: planFormData, }); const { @@ -28,9 +28,9 @@ const PlanDetails: React.FC = () => { formState: { isValid }, } = form; - const onSubmit = (data: PlanData) => { - setFormData('plan', data); - navigate('/checkout/account'); + const onSubmit = (data: BuildTrial) => { + setFormData('buildTrial', data); + navigate('/create-account'); }; const isFieldValid = useIsFieldValid(form); @@ -61,6 +61,7 @@ const PlanDetails: React.FC = () => { registerOptions={{ validate: () => { // Check react-hook-form docs for more info... + console.log('validating users'); }, }} autoFocus @@ -86,7 +87,7 @@ const PlanDetails: React.FC = () => { ariaLabelledby="planTypeLabel" value={value} onChange={(e) => { - setFormData('plan', { + setFormData('buildTrial', { ...planFormData, planType: e.target.value as PlanType, }); @@ -150,4 +151,4 @@ const PlanDetails: React.FC = () => { ); }; -export default PlanDetails; +export default BuildTrial; diff --git a/src/components/Stepper/Steps/CreateAccessLink.tsx b/src/components/Stepper/Steps/CreateAccessLink.tsx new file mode 100644 index 00000000..1ac53fcb --- /dev/null +++ b/src/components/Stepper/Steps/CreateAccessLink.tsx @@ -0,0 +1,13 @@ +import { Stepper } from '@openedx/paragon'; +import { steps } from '@/components/Stepper/constants'; + +const CreateAccessLink = () => { + const eventKey = steps[2]; + return ( + + Create Access Link + + ); +}; + +export default CreateAccessLink; diff --git a/src/components/AccountDetails.tsx b/src/components/Stepper/Steps/CreateAccount.tsx similarity index 93% rename from src/components/AccountDetails.tsx rename to src/components/Stepper/Steps/CreateAccount.tsx index 9daa6330..86c8b023 100644 --- a/src/components/AccountDetails.tsx +++ b/src/components/Stepper/Steps/CreateAccount.tsx @@ -14,9 +14,9 @@ import { zodResolver } from '@hookform/resolvers/zod'; import slugify from 'slugify'; import classNames from 'classnames'; -import { AccountSchema, PlanSchema, steps } from '@/constants'; -import Field from '@/components/Field'; -import StepCounter from '@/components/StepCounter'; +import { CreateAccountSchema, BuildTrialSchema, steps } from '@/components/Stepper/constants'; +import Field from '@/components/FormFields/Field'; +import StepCounter from '@/components/Stepper/StepCounter'; import useCheckoutFormStore from '@/hooks/useCheckoutFormStore'; interface StatefulPurchaseButtonProps { @@ -73,7 +73,7 @@ interface OrganizatonEmailHelpTextProps { isInvalid: boolean; } -const OrganizatonEmailHelpText: React.FC = ({ isInvalid }) => { +const OrganizationEmailHelpText: React.FC = ({ isInvalid }) => { if (isInvalid) { return null; } @@ -88,26 +88,26 @@ const OrganizatonEmailHelpText: React.FC = ({ isI ); }; -OrganizatonEmailHelpText.propTypes = { +OrganizationEmailHelpText.propTypes = { isInvalid: PropTypes.bool.isRequired, }; -const AccountDetails: React.FC = () => { +const CreateAccount: React.FC = () => { const intl = useIntl(); const navigate = useNavigate(); - const planFormData = useCheckoutFormStore((state) => state.formData.plan); - const accountFormData = useCheckoutFormStore((state) => state.formData.account); + const planFormData = useCheckoutFormStore((state) => state.formData.buildTrial); + const accountFormData = useCheckoutFormStore((state) => state.formData.createAccount); useEffect(() => { - if (!planFormData || !PlanSchema.safeParse(planFormData).success) { - navigate('/checkout/plan'); + if (!planFormData || !BuildTrialSchema.safeParse(planFormData).success) { + navigate('/build-trial'); } }, [planFormData, navigate]); const setFormData = useCheckoutFormStore((state) => state.setFormData); - const form = useForm({ + const form = useForm({ mode: 'onTouched', - resolver: zodResolver(AccountSchema), + resolver: zodResolver(CreateAccountSchema), }); const { handleSubmit, @@ -127,12 +127,12 @@ const AccountDetails: React.FC = () => { }, [isEditingSlug]); const handlePrevious = () => { - navigate('/checkout/plan'); + navigate('/build-trial'); }; - const onSubmit = (data: AccountData) => { - setFormData('account', data); - navigate('/checkout/confirmation'); + const onSubmit = (data: CreateAccount) => { + setFormData('createAccount', data); + navigate('/create-access-link'); }; const orgName = watch('orgName'); @@ -198,7 +198,7 @@ const AccountDetails: React.FC = () => { placeholder="john.doe@organization.com" defaultValue={accountFormData?.orgEmail} controlClassName="mr-0" - controlFooterNode={OrganizatonEmailHelpText} + controlFooterNode={OrganizationEmailHelpText} /> { ); }; -export default AccountDetails; +export default CreateAccount; diff --git a/src/components/Stepper/Steps/StartTrial.tsx b/src/components/Stepper/Steps/StartTrial.tsx new file mode 100644 index 00000000..e0e319cb --- /dev/null +++ b/src/components/Stepper/Steps/StartTrial.tsx @@ -0,0 +1,13 @@ +import { steps } from '@/components/Stepper/constants'; +import { Stepper } from '@openedx/paragon'; + +const StartTrial = () => { + const eventKey = steps[3]; + return ( + + Start Trial + + ); +}; + +export default StartTrial; diff --git a/src/components/Stepper/Steps/Success.tsx b/src/components/Stepper/Steps/Success.tsx new file mode 100644 index 00000000..0b9ed3a6 --- /dev/null +++ b/src/components/Stepper/Steps/Success.tsx @@ -0,0 +1,13 @@ +import { steps } from '@/components/Stepper/constants'; +import { Stepper } from '@openedx/paragon'; + +const Success = () => { + const eventKey = steps[4]; + return ( + + Success + + ); +}; + +export default Success; diff --git a/src/components/Stepper/Steps/index.tsx b/src/components/Stepper/Steps/index.tsx new file mode 100644 index 00000000..a442bfaa --- /dev/null +++ b/src/components/Stepper/Steps/index.tsx @@ -0,0 +1,5 @@ +export { default as BuildTrial } from './BuildTrial'; +export { default as CreateAccount } from './CreateAccount'; +export { default as CreateAccessLink } from './CreateAccessLink'; +export { default as StartTrial } from './StartTrial'; +export { default as Success } from './Success'; diff --git a/src/constants.ts b/src/components/Stepper/constants.ts similarity index 71% rename from src/constants.ts rename to src/components/Stepper/constants.ts index c3daa1ea..6e0095e9 100644 --- a/src/constants.ts +++ b/src/components/Stepper/constants.ts @@ -1,23 +1,32 @@ import { z } from 'zod'; -export const steps = [ - 'plan', - 'account', +export const authenticatedSteps: AuthStep[] = [ + 'create-access-link', + 'start-trial', + 'success', ] as const; +export const steps = [ + 'build-trial', + 'create-account', + 'create-access-link', + 'start-trial', + 'success', +]; + export const planTypes = [ 'annual', 'quarterly', ] as const; -export const PlanSchema = z.object({ +export const BuildTrialSchema = z.object({ numUsers: z.coerce.number() .min(5, 'Minimum 5 users') .max(500, 'Maximum 500 users'), planType: z.enum(planTypes), }); -export const AccountSchema = z.object({ +export const CreateAccountSchema = z.object({ fullName: z.string().trim() .min(1, 'Full name is required') .max(255), @@ -38,6 +47,12 @@ export const AccountSchema = z.object({ .min(1, 'Country is required'), }); +export const CreateAccessLinkSchema = z.object({}); + +export const StartTrialSchema = z.object({}); + +export const SuccessSchema = z.object({}); + // TODO: these should be fetched from the Stripe, likely via // an exposed REST API endpoint on the server. export const SUBSCRIPTION_ANNUAL_PRICE_PER_USER = 394; diff --git a/src/components/Currency.tsx b/src/components/SubscriptionSummary/Currency.tsx similarity index 100% rename from src/components/Currency.tsx rename to src/components/SubscriptionSummary/Currency.tsx diff --git a/src/components/SubscriptionSummary.tsx b/src/components/SubscriptionSummary/SubscriptionSummary.tsx similarity index 96% rename from src/components/SubscriptionSummary.tsx rename to src/components/SubscriptionSummary/SubscriptionSummary.tsx index 6f96cd49..7ac04dd3 100644 --- a/src/components/SubscriptionSummary.tsx +++ b/src/components/SubscriptionSummary/SubscriptionSummary.tsx @@ -2,8 +2,8 @@ import { Link, useParams } from 'react-router-dom'; import { Button, Card, Stack } from '@openedx/paragon'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { SUBSCRIPTION_ANNUAL_PRICE_PER_USER } from '@/constants'; -import Currency from '@/components/Currency'; +import { SUBSCRIPTION_ANNUAL_PRICE_PER_USER } from '@/components/Stepper/constants'; +import Currency from '@/components/SubscriptionSummary/Currency'; import useCheckoutFormStore from '@/hooks/useCheckoutFormStore'; function calculateSubscriptionCost(numUsers?: number) { @@ -45,13 +45,13 @@ const SubscriptionSummary: React.FC = () => { defaultMessage="Number of users" description="Label for the number of users" /> - {step !== 'plan' && ( + {step !== 'build-trial' && (