@salesforce/templates 66.1.1 → 66.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/generators/projectGenerator.d.ts +6 -0
- package/lib/generators/projectGenerator.js +42 -3
- package/lib/generators/projectGenerator.js.map +1 -1
- package/lib/templates/project/reactb2e/.a4drules/README.md +35 -0
- package/lib/templates/project/reactb2e/.a4drules/a4d-webapp-generate.md +27 -0
- package/lib/templates/project/reactb2e/.a4drules/build-validation.md +78 -0
- package/lib/templates/project/reactb2e/.a4drules/code-quality.md +137 -0
- package/lib/templates/project/reactb2e/.a4drules/graphql/tools/knowledge/lds-explore-graphql-schema.md +227 -0
- package/lib/templates/project/reactb2e/.a4drules/graphql/tools/knowledge/lds-generate-graphql-mutationquery.md +212 -0
- package/lib/templates/project/reactb2e/.a4drules/graphql/tools/knowledge/lds-generate-graphql-readquery.md +185 -0
- package/lib/templates/project/reactb2e/.a4drules/graphql/tools/knowledge/lds-guide-graphql.md +205 -0
- package/lib/templates/project/reactb2e/.a4drules/graphql/tools/schemas/shared.graphqls +1150 -0
- package/lib/templates/project/reactb2e/.a4drules/graphql.md +409 -0
- package/lib/templates/project/reactb2e/.a4drules/images.md +13 -0
- package/lib/templates/project/reactb2e/.a4drules/react.md +387 -0
- package/lib/templates/project/reactb2e/.a4drules/react_image_processing.md +45 -0
- package/lib/templates/project/reactb2e/.a4drules/typescript.md +224 -0
- package/lib/templates/project/reactb2e/.a4drules/ui-layout.md +23 -0
- package/lib/templates/project/reactb2e/.a4drules/webapp-nav-and-placeholders.md +33 -0
- package/lib/templates/project/reactb2e/.a4drules/webapp-no-node-e.md +25 -0
- package/lib/templates/project/reactb2e/.a4drules/webapp-ui-first.md +32 -0
- package/lib/templates/project/reactb2e/.a4drules/webapp.md +75 -0
- package/lib/templates/project/reactb2e/.forceignore +15 -0
- package/lib/templates/project/reactb2e/.husky/pre-commit +4 -0
- package/lib/templates/project/reactb2e/.prettierignore +11 -0
- package/lib/templates/project/reactb2e/.prettierrc +17 -0
- package/lib/templates/project/reactb2e/AGENT.md +75 -0
- package/lib/templates/project/reactb2e/CHANGELOG.md +696 -0
- package/lib/templates/project/reactb2e/README.md +18 -0
- package/lib/templates/project/reactb2e/config/project-scratch-def.json +13 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/.graphqlrc.yml +2 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/.prettierignore +9 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/.prettierrc +11 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/appreacttemplateb2e.webapplication-meta.xml +7 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/build/vite.config.d.ts +2 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/build/vite.config.js +93 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/codegen.yml +94 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/e2e/app.spec.ts +17 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/eslint.config.js +141 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/index.html +13 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/package-lock.json +14392 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/package.json +58 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/playwright.config.ts +24 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/scripts/get-graphql-schema.mjs +68 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/scripts/rewrite-e2e-assets.mjs +23 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/api/graphql-operations-types.ts +116 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/api/utils/accounts.ts +33 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/api/utils/query/highRevenueAccountsQuery.graphql +29 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/app.tsx +22 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/appLayout.tsx +19 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/book.svg +3 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/copy.svg +4 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/rocket.svg +3 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/star.svg +3 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/codey-1.png +0 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/codey-2.png +0 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/codey-3.png +0 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/vibe-codey.svg +194 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/components/AgentforceConversationClient.tsx +127 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/index.ts +6 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/navigationMenu.tsx +80 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/Home.tsx +12 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/NotFound.tsx +18 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/router-utils.tsx +35 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/routes.tsx +22 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/styles/global.css +13 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/src/types/conversation.ts +21 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/tsconfig.json +36 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/tsconfig.node.json +13 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/vite-env.d.ts +1 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/vite.config.ts +102 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/vitest-env.d.ts +2 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/vitest.config.ts +11 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/vitest.setup.ts +1 -0
- package/lib/templates/project/reactb2e/force-app/main/default/webapplications/appreacttemplateb2e/webapplication.json +7 -0
- package/lib/templates/project/reactb2e/jest.config.js +6 -0
- package/lib/templates/project/reactb2e/package.json +38 -0
- package/lib/templates/project/reactb2e/scripts/apex/hello.apex +10 -0
- package/lib/templates/project/reactb2e/scripts/soql/account.soql +6 -0
- package/lib/templates/project/reactb2e/sfdx-project.json +12 -0
- package/lib/templates/project/reactb2x/.a4drules/README.md +35 -0
- package/lib/templates/project/reactb2x/.a4drules/a4d-webapp-generate.md +27 -0
- package/lib/templates/project/reactb2x/.a4drules/build-validation.md +78 -0
- package/lib/templates/project/reactb2x/.a4drules/code-quality.md +137 -0
- package/lib/templates/project/reactb2x/.a4drules/graphql/tools/knowledge/lds-explore-graphql-schema.md +227 -0
- package/lib/templates/project/reactb2x/.a4drules/graphql/tools/knowledge/lds-generate-graphql-mutationquery.md +212 -0
- package/lib/templates/project/reactb2x/.a4drules/graphql/tools/knowledge/lds-generate-graphql-readquery.md +185 -0
- package/lib/templates/project/reactb2x/.a4drules/graphql/tools/knowledge/lds-guide-graphql.md +205 -0
- package/lib/templates/project/reactb2x/.a4drules/graphql/tools/schemas/shared.graphqls +1150 -0
- package/lib/templates/project/reactb2x/.a4drules/graphql.md +409 -0
- package/lib/templates/project/reactb2x/.a4drules/images.md +13 -0
- package/lib/templates/project/reactb2x/.a4drules/react.md +387 -0
- package/lib/templates/project/reactb2x/.a4drules/react_image_processing.md +45 -0
- package/lib/templates/project/reactb2x/.a4drules/typescript.md +224 -0
- package/lib/templates/project/reactb2x/.a4drules/ui-layout.md +23 -0
- package/lib/templates/project/reactb2x/.a4drules/webapp-nav-and-placeholders.md +33 -0
- package/lib/templates/project/reactb2x/.a4drules/webapp-no-node-e.md +25 -0
- package/lib/templates/project/reactb2x/.a4drules/webapp-ui-first.md +32 -0
- package/lib/templates/project/reactb2x/.a4drules/webapp.md +75 -0
- package/lib/templates/project/reactb2x/.forceignore +15 -0
- package/lib/templates/project/reactb2x/.husky/pre-commit +4 -0
- package/lib/templates/project/reactb2x/.prettierignore +11 -0
- package/lib/templates/project/reactb2x/.prettierrc +17 -0
- package/lib/templates/project/reactb2x/AGENT.md +75 -0
- package/lib/templates/project/reactb2x/CHANGELOG.md +696 -0
- package/lib/templates/project/reactb2x/README.md +18 -0
- package/lib/templates/project/reactb2x/config/project-scratch-def.json +13 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppAuthUtils.cls +68 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppAuthUtils.cls-meta.xml +5 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppChangePassword.cls +77 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppChangePassword.cls-meta.xml +5 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppForgotPassword.cls +71 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppForgotPassword.cls-meta.xml +5 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppLogin.cls +105 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppLogin.cls-meta.xml +5 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppRegistration.cls +162 -0
- package/lib/templates/project/reactb2x/force-app/main/default/classes/WebAppRegistration.cls-meta.xml +5 -0
- package/lib/templates/project/reactb2x/force-app/main/default/digitalExperienceConfigs/appreacttemplateb2x1.digitalExperienceConfig +8 -0
- package/lib/templates/project/reactb2x/force-app/main/default/digitalExperiences/site/appreacttemplateb2x1/appreacttemplateb2x1.digitalExperience-meta.xml +11 -0
- package/lib/templates/project/reactb2x/force-app/main/default/digitalExperiences/site/appreacttemplateb2x1/sfdc_cms__site/appreacttemplateb2x1/_meta.json +5 -0
- package/lib/templates/project/reactb2x/force-app/main/default/digitalExperiences/site/appreacttemplateb2x1/sfdc_cms__site/appreacttemplateb2x1/content.json +10 -0
- package/lib/templates/project/reactb2x/force-app/main/default/networks/appreacttemplateb2x.network +60 -0
- package/lib/templates/project/reactb2x/force-app/main/default/package.xml +20 -0
- package/lib/templates/project/reactb2x/force-app/main/default/sites/appreacttemplateb2x.site +31 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/.graphqlrc.yml +2 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/.prettierignore +9 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/.prettierrc +11 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/appreacttemplateb2x.webapplication-meta.xml +7 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/build/vite.config.d.ts +2 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/build/vite.config.js +93 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/codegen.yml +94 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/e2e/app.spec.ts +17 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/eslint.config.js +141 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/index.html +13 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/package-lock.json +18408 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/package.json +66 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/playwright.config.ts +24 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/scripts/get-graphql-schema.mjs +68 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/scripts/rewrite-e2e-assets.mjs +23 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/api/graphql-operations-types.ts +116 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/api/utils/accounts.ts +33 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/api/utils/query/highRevenueAccountsQuery.graphql +29 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/app.tsx +13 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/appLayout.tsx +11 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/book.svg +3 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/copy.svg +4 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/rocket.svg +3 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/star.svg +3 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/codey-1.png +0 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/codey-2.png +0 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/codey-3.png +0 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/vibe-codey.svg +194 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/alerts/status-alert.tsx +45 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/authHelpers.ts +73 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/authenticationConfig.ts +61 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/authenticationRouteLayout.tsx +21 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/privateRouteLayout.tsx +36 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/sessionTimeout/SessionTimeoutValidator.tsx +616 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/sessionTimeout/sessionTimeService.ts +161 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/sessionTimeout/sessionTimeoutConfig.ts +77 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/footers/footer-link.tsx +36 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/forms/auth-form.tsx +81 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/forms/submit-button.tsx +49 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/layout/card-layout.tsx +23 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/layout/card-skeleton.tsx +38 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/layout/centered-page-layout.tsx +73 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/alert.tsx +69 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/button.tsx +67 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/card.tsx +92 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/dialog.tsx +143 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/field.tsx +222 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/index.ts +72 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/input.tsx +19 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/label.tsx +19 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/pagination.tsx +112 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/select.tsx +183 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/separator.tsx +26 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/skeleton.tsx +14 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/spinner.tsx +15 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/table.tsx +87 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/tabs.tsx +78 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components.json +18 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/context/AuthContext.tsx +95 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/hooks/form.tsx +116 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/hooks/useCountdownTimer.ts +266 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/hooks/useRetryWithBackoff.ts +109 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/layouts/AuthAppLayout.tsx +12 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/lib/data-sdk.ts +21 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/lib/utils.ts +6 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/navigationMenu.tsx +80 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/ChangePassword.tsx +107 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/ForgotPassword.tsx +73 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Home.tsx +12 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Login.tsx +97 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/NotFound.tsx +18 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Profile.tsx +178 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Register.tsx +138 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/ResetPassword.tsx +107 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/router-utils.tsx +35 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/routes.tsx +71 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/styles/global.css +135 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/src/utils/helpers.ts +121 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/tsconfig.json +36 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/tsconfig.node.json +13 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/vite-env.d.ts +1 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/vite.config.ts +102 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/vitest-env.d.ts +2 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/vitest.config.ts +11 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/vitest.setup.ts +1 -0
- package/lib/templates/project/reactb2x/force-app/main/default/webapplications/appreacttemplateb2x/webapplication.json +7 -0
- package/lib/templates/project/reactb2x/jest.config.js +6 -0
- package/lib/templates/project/reactb2x/package.json +38 -0
- package/lib/templates/project/reactb2x/scripts/apex/hello.apex +10 -0
- package/lib/templates/project/reactb2x/scripts/soql/account.soql +6 -0
- package/lib/templates/project/reactb2x/sfdx-project.json +12 -0
- package/lib/templates/webapplication/reactbasic/.graphqlrc.yml +2 -0
- package/lib/templates/webapplication/reactbasic/build/vite.config.js +20 -1
- package/lib/templates/webapplication/reactbasic/codegen.yml +94 -0
- package/lib/templates/webapplication/reactbasic/e2e/app.spec.ts +0 -7
- package/lib/templates/webapplication/reactbasic/eslint.config.js +28 -0
- package/lib/templates/webapplication/reactbasic/package-lock.json +10090 -2874
- package/lib/templates/webapplication/reactbasic/package.json +16 -4
- package/lib/templates/webapplication/reactbasic/scripts/get-graphql-schema.mjs +68 -0
- package/lib/templates/webapplication/reactbasic/src/api/graphql-operations-types.ts +23 -34
- package/lib/templates/webapplication/reactbasic/src/api/utils/accounts.ts +33 -0
- package/lib/templates/webapplication/reactbasic/src/app.tsx +10 -1
- package/lib/templates/webapplication/reactbasic/src/appLayout.tsx +2 -0
- package/lib/templates/webapplication/reactbasic/src/navigationMenu.tsx +80 -0
- package/lib/templates/webapplication/reactbasic/src/router-utils.tsx +35 -0
- package/lib/templates/webapplication/reactbasic/src/routes.tsx +1 -7
- package/lib/templates/webapplication/reactbasic/vite.config.ts +20 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/types.d.ts +1 -1
- package/lib/utils/webappTemplateUtils.d.ts +54 -0
- package/lib/utils/webappTemplateUtils.js +219 -0
- package/lib/utils/webappTemplateUtils.js.map +1 -0
- package/package.json +7 -5
- package/lib/templates/webapplication/reactbasic/src/pages/About.tsx +0 -12
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for countdown timer with accessibility features
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Accessibility configuration for countdown timer
|
|
9
|
+
*/
|
|
10
|
+
export interface CountdownTimerA11yConfig {
|
|
11
|
+
/** Announce time remaining at these specific second marks */
|
|
12
|
+
ANNOUNCE_AT_SECONDS: readonly number[];
|
|
13
|
+
/** Announce every N seconds (after initial period) */
|
|
14
|
+
ANNOUNCE_INTERVAL_SECONDS: number;
|
|
15
|
+
/** Minimum elapsed time before starting interval announcements */
|
|
16
|
+
MIN_ELAPSED_FOR_INTERVAL: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Return value from useCountdownTimer hook
|
|
21
|
+
*/
|
|
22
|
+
export interface CountdownTimerResult {
|
|
23
|
+
/** Current time remaining in seconds */
|
|
24
|
+
displayTime: number;
|
|
25
|
+
/** Formatted time string (MM:SS) */
|
|
26
|
+
formattedTime: string;
|
|
27
|
+
/** ISO 8601 duration string (e.g., PT2M15S) */
|
|
28
|
+
isoTime: string;
|
|
29
|
+
/** Accessibility announcement text for screen readers */
|
|
30
|
+
accessibilityAnnouncement: string;
|
|
31
|
+
/** Start the countdown timer */
|
|
32
|
+
start: () => void;
|
|
33
|
+
/** Stop the countdown timer */
|
|
34
|
+
stop: () => void;
|
|
35
|
+
/** Reset the countdown timer to initial time */
|
|
36
|
+
reset: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for countdown timer hook
|
|
41
|
+
*/
|
|
42
|
+
export interface CountdownTimerConfig {
|
|
43
|
+
/** Initial time in seconds */
|
|
44
|
+
initialTime: number;
|
|
45
|
+
/** Callback when countdown reaches 0 */
|
|
46
|
+
onExpire: () => void;
|
|
47
|
+
/** Optional accessibility configuration */
|
|
48
|
+
a11yConfig?: CountdownTimerA11yConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default accessibility configuration
|
|
53
|
+
*/
|
|
54
|
+
const DEFAULT_A11Y_CONFIG: CountdownTimerA11yConfig = {
|
|
55
|
+
ANNOUNCE_AT_SECONDS: [5, 1],
|
|
56
|
+
ANNOUNCE_INTERVAL_SECONDS: 10,
|
|
57
|
+
MIN_ELAPSED_FOR_INTERVAL: 10,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format time remaining as MM:SS string
|
|
62
|
+
* Uses Intl.NumberFormat for zero-padding and internationalization
|
|
63
|
+
*
|
|
64
|
+
* @param seconds - Total seconds remaining
|
|
65
|
+
* @returns Formatted time string (e.g., "05:23")
|
|
66
|
+
*/
|
|
67
|
+
function formatTimeRemaining(seconds: number): string {
|
|
68
|
+
const minutes = Math.floor(seconds / 60);
|
|
69
|
+
const secs = seconds % 60;
|
|
70
|
+
|
|
71
|
+
// Use Intl.NumberFormat for zero-padding with internationalization
|
|
72
|
+
const formatter = new Intl.NumberFormat(navigator.language, {
|
|
73
|
+
minimumIntegerDigits: 2,
|
|
74
|
+
useGrouping: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return `${formatter.format(minutes)}:${formatter.format(secs)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Format time remaining as ISO 8601 duration for ARIA
|
|
82
|
+
* Used in datetime attribute of <time> element
|
|
83
|
+
*
|
|
84
|
+
* @param seconds - Total seconds remaining
|
|
85
|
+
* @returns ISO 8601 duration string (e.g., "PT2M15S")
|
|
86
|
+
*/
|
|
87
|
+
function formatISODuration(seconds: number): string {
|
|
88
|
+
const minutes = Math.floor(seconds / 60);
|
|
89
|
+
const secs = seconds % 60;
|
|
90
|
+
return `PT${minutes}M${secs}S`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Format time remaining for screen reader announcement
|
|
95
|
+
* Uses Intl.DurationFormat if available, falls back to manual formatting
|
|
96
|
+
*
|
|
97
|
+
* @param seconds - Total seconds remaining
|
|
98
|
+
* @returns Formatted announcement text (e.g., "2 minutes 15 seconds")
|
|
99
|
+
*/
|
|
100
|
+
function formatAccessibilityAnnouncement(seconds: number): string {
|
|
101
|
+
const minutes = Math.floor(seconds / 60);
|
|
102
|
+
const secs = seconds % 60;
|
|
103
|
+
|
|
104
|
+
// Try using Intl.DurationFormat (newer API, may not be available in all browsers)
|
|
105
|
+
if (typeof Intl !== "undefined" && "DurationFormat" in Intl) {
|
|
106
|
+
try {
|
|
107
|
+
// @ts-expect-error - DurationFormat is not yet in TypeScript lib
|
|
108
|
+
const formatter = new Intl.DurationFormat(navigator.language, { style: "long" });
|
|
109
|
+
return formatter.format({ minutes, seconds: secs });
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// Fallback to manual formatting
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Manual fallback
|
|
116
|
+
const parts: string[] = [];
|
|
117
|
+
if (minutes > 0) {
|
|
118
|
+
parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
|
|
119
|
+
}
|
|
120
|
+
if (secs > 0 || minutes === 0) {
|
|
121
|
+
parts.push(`${secs} ${secs === 1 ? "second" : "seconds"}`);
|
|
122
|
+
}
|
|
123
|
+
return parts.join(" ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Determine if an accessibility announcement should be made at this time
|
|
128
|
+
*
|
|
129
|
+
* @param currentTime - Current countdown time in seconds
|
|
130
|
+
* @param initialTime - Initial countdown time in seconds
|
|
131
|
+
* @param config - Accessibility configuration
|
|
132
|
+
* @returns True if announcement should be made
|
|
133
|
+
*/
|
|
134
|
+
function shouldAnnounce(
|
|
135
|
+
currentTime: number,
|
|
136
|
+
initialTime: number,
|
|
137
|
+
config: CountdownTimerA11yConfig,
|
|
138
|
+
): boolean {
|
|
139
|
+
// Announce at specific second marks (5s, 1s)
|
|
140
|
+
if (config.ANNOUNCE_AT_SECONDS.includes(currentTime)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Calculate elapsed time
|
|
145
|
+
const elapsed = initialTime - currentTime;
|
|
146
|
+
|
|
147
|
+
// Announce every N seconds after minimum elapsed time
|
|
148
|
+
if (elapsed >= config.MIN_ELAPSED_FOR_INTERVAL) {
|
|
149
|
+
return currentTime % config.ANNOUNCE_INTERVAL_SECONDS === 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Custom hook for countdown timer with accessibility
|
|
157
|
+
* Decrements from initial time to 0, provides formatted output for display and ARIA
|
|
158
|
+
*
|
|
159
|
+
* @param config - Timer configuration
|
|
160
|
+
* @returns Timer state and control functions
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const timer = useCountdownTimer({
|
|
164
|
+
* initialTime: 300, // 5 minutes
|
|
165
|
+
* onExpire: () => handleLogout()
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* timer.start(); // Begin countdown
|
|
169
|
+
*
|
|
170
|
+
* <time dateTime={timer.isoTime} role="timer">
|
|
171
|
+
* {timer.formattedTime}
|
|
172
|
+
* </time>
|
|
173
|
+
* <div role="status" aria-live="polite" className="sr-only">
|
|
174
|
+
* {timer.accessibilityAnnouncement}
|
|
175
|
+
* </div>
|
|
176
|
+
*/
|
|
177
|
+
export function useCountdownTimer({
|
|
178
|
+
initialTime,
|
|
179
|
+
onExpire,
|
|
180
|
+
a11yConfig = DEFAULT_A11Y_CONFIG,
|
|
181
|
+
}: CountdownTimerConfig): CountdownTimerResult {
|
|
182
|
+
const [displayTime, setDisplayTime] = useState(initialTime);
|
|
183
|
+
const [accessibilityAnnouncement, setAccessibilityAnnouncement] = useState("");
|
|
184
|
+
const [isActive, setIsActive] = useState(false);
|
|
185
|
+
|
|
186
|
+
// Use refs to avoid stale closure issues
|
|
187
|
+
const initialTimeRef = useRef(initialTime);
|
|
188
|
+
const onExpireRef = useRef(onExpire);
|
|
189
|
+
const a11yConfigRef = useRef(a11yConfig);
|
|
190
|
+
const endTimeRef = useRef<number>(0);
|
|
191
|
+
const previousTimeRef = useRef<number>(initialTime);
|
|
192
|
+
|
|
193
|
+
// Update refs when props change
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
initialTimeRef.current = initialTime;
|
|
196
|
+
}, [initialTime]);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
onExpireRef.current = onExpire;
|
|
200
|
+
}, [onExpire]);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
a11yConfigRef.current = a11yConfig;
|
|
204
|
+
}, [a11yConfig]);
|
|
205
|
+
|
|
206
|
+
// Countdown effect using Date.now() for accuracy
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!isActive) return;
|
|
209
|
+
|
|
210
|
+
// Set the target end time when timer starts
|
|
211
|
+
endTimeRef.current = Date.now() + initialTimeRef.current * 1000;
|
|
212
|
+
previousTimeRef.current = initialTimeRef.current;
|
|
213
|
+
|
|
214
|
+
const intervalId = setInterval(() => {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const remainingMs = endTimeRef.current - now;
|
|
217
|
+
const newTime = Math.max(0, Math.ceil(remainingMs / 1000));
|
|
218
|
+
|
|
219
|
+
setDisplayTime(newTime);
|
|
220
|
+
|
|
221
|
+
// Check if we should make an accessibility announcement
|
|
222
|
+
// Only announce when the second value changes
|
|
223
|
+
if (
|
|
224
|
+
newTime !== previousTimeRef.current &&
|
|
225
|
+
shouldAnnounce(newTime, initialTimeRef.current, a11yConfigRef.current)
|
|
226
|
+
) {
|
|
227
|
+
setAccessibilityAnnouncement(formatAccessibilityAnnouncement(newTime));
|
|
228
|
+
}
|
|
229
|
+
previousTimeRef.current = newTime;
|
|
230
|
+
|
|
231
|
+
// Check if countdown expired
|
|
232
|
+
if (newTime <= 0) {
|
|
233
|
+
clearInterval(intervalId);
|
|
234
|
+
setIsActive(false);
|
|
235
|
+
onExpireRef.current();
|
|
236
|
+
}
|
|
237
|
+
}, 100); // Check more frequently for smoother updates
|
|
238
|
+
|
|
239
|
+
return () => clearInterval(intervalId);
|
|
240
|
+
}, [isActive]);
|
|
241
|
+
|
|
242
|
+
// Control functions
|
|
243
|
+
const start = useCallback(() => {
|
|
244
|
+
setIsActive(true);
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
const stop = useCallback(() => {
|
|
248
|
+
setIsActive(false);
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
const reset = useCallback(() => {
|
|
252
|
+
setDisplayTime(initialTimeRef.current);
|
|
253
|
+
setIsActive(false);
|
|
254
|
+
setAccessibilityAnnouncement("");
|
|
255
|
+
}, []);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
displayTime,
|
|
259
|
+
formattedTime: formatTimeRemaining(displayTime),
|
|
260
|
+
isoTime: formatISODuration(displayTime),
|
|
261
|
+
accessibilityAnnouncement,
|
|
262
|
+
start,
|
|
263
|
+
stop,
|
|
264
|
+
reset,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for retry logic with exponential backoff
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useCallback } from "react";
|
|
6
|
+
|
|
7
|
+
export interface UseRetryWithBackoffOptions {
|
|
8
|
+
/** Initial delay in milliseconds */
|
|
9
|
+
initialDelay: number;
|
|
10
|
+
/** Maximum number of retry attempts */
|
|
11
|
+
maxAttempts: number;
|
|
12
|
+
/** Maximum delay in milliseconds */
|
|
13
|
+
maxDelay: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseRetryWithBackoffResult {
|
|
17
|
+
/** Current number of retry attempts */
|
|
18
|
+
retryAttempts: number;
|
|
19
|
+
/** Current retry delay in milliseconds */
|
|
20
|
+
currentRetryDelay: number;
|
|
21
|
+
/** Whether max retry attempts have been reached */
|
|
22
|
+
maxRetriesReached: boolean;
|
|
23
|
+
/** Schedule a retry with exponential backoff, returns timeout ID for cancellation */
|
|
24
|
+
scheduleRetry: (callback: () => void) => ReturnType<typeof setTimeout> | undefined;
|
|
25
|
+
/** Reset retry state after successful operation */
|
|
26
|
+
resetRetry: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hook for managing retry logic with exponential backoff
|
|
31
|
+
*
|
|
32
|
+
* @param options - Configuration for retry behavior
|
|
33
|
+
* @returns Retry state and control functions
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const retry = useRetryWithBackoff({
|
|
37
|
+
* initialDelay: 2000,
|
|
38
|
+
* maxAttempts: 10,
|
|
39
|
+
* maxDelay: 1800000
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* async function fetchData() {
|
|
43
|
+
* try {
|
|
44
|
+
* const data = await apiCall();
|
|
45
|
+
* retry.resetRetry();
|
|
46
|
+
* return data;
|
|
47
|
+
* } catch (error) {
|
|
48
|
+
* if (retry.maxRetriesReached) {
|
|
49
|
+
* console.error('Max retries reached');
|
|
50
|
+
* return;
|
|
51
|
+
* }
|
|
52
|
+
* retry.scheduleRetry(() => fetchData());
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
export function useRetryWithBackoff(
|
|
57
|
+
options: UseRetryWithBackoffOptions,
|
|
58
|
+
): UseRetryWithBackoffResult {
|
|
59
|
+
const { initialDelay, maxAttempts, maxDelay } = options;
|
|
60
|
+
|
|
61
|
+
const [retryAttempts, setRetryAttempts] = useState<number>(0);
|
|
62
|
+
const [currentRetryDelay, setCurrentRetryDelay] = useState<number>(initialDelay);
|
|
63
|
+
|
|
64
|
+
const maxRetriesReached = retryAttempts >= maxAttempts;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reset retry state after successful operation
|
|
68
|
+
*/
|
|
69
|
+
const resetRetry = useCallback(() => {
|
|
70
|
+
setRetryAttempts(0);
|
|
71
|
+
setCurrentRetryDelay(initialDelay);
|
|
72
|
+
}, [initialDelay]);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Schedule a retry with exponential backoff
|
|
76
|
+
* Returns the timeout ID which can be used to cancel the retry if needed
|
|
77
|
+
*/
|
|
78
|
+
const scheduleRetry = useCallback(
|
|
79
|
+
(callback: () => void) => {
|
|
80
|
+
if (retryAttempts >= maxAttempts) {
|
|
81
|
+
console.error("[useRetryWithBackoff] Max retry attempts reached");
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.warn(
|
|
86
|
+
`[useRetryWithBackoff] Retry attempt ${retryAttempts + 1}/${maxAttempts} in ${currentRetryDelay}ms`,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const timeoutId = setTimeout(() => {
|
|
90
|
+
callback();
|
|
91
|
+
}, currentRetryDelay);
|
|
92
|
+
|
|
93
|
+
setRetryAttempts((prev) => prev + 1);
|
|
94
|
+
// Double the delay for next retry, capped at maxDelay
|
|
95
|
+
setCurrentRetryDelay((prev) => Math.min(prev * 2, maxDelay));
|
|
96
|
+
|
|
97
|
+
return timeoutId;
|
|
98
|
+
},
|
|
99
|
+
[retryAttempts, currentRetryDelay, maxAttempts, maxDelay],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
retryAttempts,
|
|
104
|
+
currentRetryDelay,
|
|
105
|
+
maxRetriesReached,
|
|
106
|
+
scheduleRetry,
|
|
107
|
+
resetRetry,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import SessionTimeoutValidator from "../components/auth/sessionTimeout/SessionTimeoutValidator";
|
|
2
|
+
import { AuthProvider } from "../context/AuthContext";
|
|
3
|
+
import AppLayout from "../appLayout";
|
|
4
|
+
|
|
5
|
+
export default function AuthAppLayout() {
|
|
6
|
+
return (
|
|
7
|
+
<AuthProvider>
|
|
8
|
+
<SessionTimeoutValidator basePath="" />
|
|
9
|
+
<AppLayout />
|
|
10
|
+
</AuthProvider>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createDataSDK, type DataSDK } from "@salesforce/sdk-data";
|
|
2
|
+
|
|
3
|
+
type FetchableSDK = DataSDK & { fetch: typeof fetch };
|
|
4
|
+
|
|
5
|
+
let sdkPromise: Promise<FetchableSDK> | null = null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns a singleton DataSDK instance with a guaranteed fetch method.
|
|
9
|
+
* Lazily initialized on first call; subsequent calls return the same instance.
|
|
10
|
+
*/
|
|
11
|
+
export function getDataSDK(): Promise<FetchableSDK> {
|
|
12
|
+
if (!sdkPromise) {
|
|
13
|
+
sdkPromise = createDataSDK().then((sdk) => {
|
|
14
|
+
if (!sdk.fetch) {
|
|
15
|
+
throw new Error("DataSDK fetch is not available on this surface.");
|
|
16
|
+
}
|
|
17
|
+
return sdk as FetchableSDK;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return sdkPromise;
|
|
21
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Link, useLocation } from 'react-router';
|
|
2
|
+
import { getAllRoutes } from './router-utils';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export default function NavigationMenu() {
|
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
+
const location = useLocation();
|
|
8
|
+
|
|
9
|
+
const isActive = (path: string) => location.pathname === path;
|
|
10
|
+
|
|
11
|
+
const toggleMenu = () => setIsOpen(!isOpen);
|
|
12
|
+
|
|
13
|
+
const navigationRoutes: { path: string; label: string }[] = getAllRoutes()
|
|
14
|
+
.filter(
|
|
15
|
+
route =>
|
|
16
|
+
route.handle?.showInNavigation === true &&
|
|
17
|
+
route.fullPath !== undefined &&
|
|
18
|
+
route.handle?.label !== undefined
|
|
19
|
+
)
|
|
20
|
+
.map(
|
|
21
|
+
route =>
|
|
22
|
+
({
|
|
23
|
+
path: route.fullPath,
|
|
24
|
+
label: route.handle?.label,
|
|
25
|
+
}) as { path: string; label: string }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<nav className="bg-white border-b border-gray-200">
|
|
30
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
31
|
+
<div className="flex justify-between items-center h-16">
|
|
32
|
+
<Link to="/" className="text-xl font-semibold text-gray-900">
|
|
33
|
+
React App
|
|
34
|
+
</Link>
|
|
35
|
+
<button
|
|
36
|
+
onClick={toggleMenu}
|
|
37
|
+
className="p-2 rounded-md text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
38
|
+
aria-label="Toggle menu"
|
|
39
|
+
>
|
|
40
|
+
<div className="w-6 h-6 flex flex-col justify-center space-y-1.5">
|
|
41
|
+
<span
|
|
42
|
+
className={`block h-0.5 w-6 bg-current transition-all ${
|
|
43
|
+
isOpen ? 'rotate-45 translate-y-2' : ''
|
|
44
|
+
}`}
|
|
45
|
+
/>
|
|
46
|
+
<span
|
|
47
|
+
className={`block h-0.5 w-6 bg-current transition-all ${isOpen ? 'opacity-0' : ''}`}
|
|
48
|
+
/>
|
|
49
|
+
<span
|
|
50
|
+
className={`block h-0.5 w-6 bg-current transition-all ${
|
|
51
|
+
isOpen ? '-rotate-45 -translate-y-2' : ''
|
|
52
|
+
}`}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
{isOpen && (
|
|
58
|
+
<div className="pb-4">
|
|
59
|
+
<div className="flex flex-col space-y-2">
|
|
60
|
+
{navigationRoutes.map(item => (
|
|
61
|
+
<Link
|
|
62
|
+
key={item.path}
|
|
63
|
+
to={item.path}
|
|
64
|
+
onClick={() => setIsOpen(false)}
|
|
65
|
+
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
66
|
+
isActive(item.path)
|
|
67
|
+
? 'bg-blue-100 text-blue-700'
|
|
68
|
+
: 'text-gray-700 hover:bg-gray-100'
|
|
69
|
+
}`}
|
|
70
|
+
>
|
|
71
|
+
{item.label}
|
|
72
|
+
</Link>
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</nav>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Link } from "react-router";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { CenteredPageLayout } from "../components/layout/centered-page-layout";
|
|
5
|
+
import { AuthForm } from "../components/forms/auth-form";
|
|
6
|
+
import { useAppForm } from "../hooks/form";
|
|
7
|
+
import { getDataSDK } from "../lib/data-sdk";
|
|
8
|
+
import { ROUTES, AUTH_PLACEHOLDERS } from "../components/auth/authenticationConfig";
|
|
9
|
+
import { newPasswordSchema } from "../components/auth/authHelpers";
|
|
10
|
+
import { handleApiResponse, getErrorMessage } from "../utils/helpers";
|
|
11
|
+
|
|
12
|
+
const changePasswordSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
currentPassword: z.string().min(1, "Current password is required"),
|
|
15
|
+
})
|
|
16
|
+
.and(newPasswordSchema);
|
|
17
|
+
|
|
18
|
+
export default function ChangePassword() {
|
|
19
|
+
const [success, setSuccess] = useState(false);
|
|
20
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
21
|
+
|
|
22
|
+
const form = useAppForm({
|
|
23
|
+
defaultValues: { currentPassword: "", newPassword: "", confirmPassword: "" },
|
|
24
|
+
validators: { onChange: changePasswordSchema, onSubmit: changePasswordSchema },
|
|
25
|
+
onSubmit: async ({ value: formFieldValues }) => {
|
|
26
|
+
setSubmitError(null);
|
|
27
|
+
setSuccess(false);
|
|
28
|
+
try {
|
|
29
|
+
// [Dev Note] Custom Apex Endpoint: /auth/change-password
|
|
30
|
+
// You must ensure this Apex class exists in your org
|
|
31
|
+
const sdk = await getDataSDK();
|
|
32
|
+
const response = await sdk.fetch("/sfdcapi/services/apexrest/auth/change-password", {
|
|
33
|
+
method: "POST",
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
currentPassword: formFieldValues.currentPassword,
|
|
36
|
+
newPassword: formFieldValues.newPassword,
|
|
37
|
+
}),
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
Accept: "application/json",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
await handleApiResponse(response, "Password change failed");
|
|
44
|
+
setSuccess(true);
|
|
45
|
+
form.reset();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
setSubmitError(getErrorMessage(err, "Password change failed"));
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
onSubmitInvalid: () => {},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<CenteredPageLayout title={ROUTES.CHANGE_PASSWORD.TITLE}>
|
|
55
|
+
<form.AppForm>
|
|
56
|
+
<AuthForm
|
|
57
|
+
title="Change Password"
|
|
58
|
+
description="Enter your current and new password below"
|
|
59
|
+
error={submitError}
|
|
60
|
+
success={
|
|
61
|
+
success && (
|
|
62
|
+
<>
|
|
63
|
+
Password changed successfully!{" "}
|
|
64
|
+
<Link to={ROUTES.PROFILE.PATH} className="underline">
|
|
65
|
+
Back to Profile
|
|
66
|
+
</Link>
|
|
67
|
+
</>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
submit={{ text: "Change Password", loadingText: "Changing…", disabled: success }}
|
|
71
|
+
footer={{ link: ROUTES.PROFILE.PATH, linkText: "Back to Profile" }}
|
|
72
|
+
>
|
|
73
|
+
<form.AppField name="currentPassword">
|
|
74
|
+
{(field) => (
|
|
75
|
+
<field.PasswordField
|
|
76
|
+
label="Current Password"
|
|
77
|
+
placeholder={AUTH_PLACEHOLDERS.PASSWORD}
|
|
78
|
+
autoComplete="current-password"
|
|
79
|
+
disabled={success}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
</form.AppField>
|
|
83
|
+
<form.AppField name="newPassword">
|
|
84
|
+
{(field) => (
|
|
85
|
+
<field.PasswordField
|
|
86
|
+
label="New Password"
|
|
87
|
+
placeholder={AUTH_PLACEHOLDERS.PASSWORD_NEW}
|
|
88
|
+
autoComplete="new-password"
|
|
89
|
+
disabled={success}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</form.AppField>
|
|
93
|
+
<form.AppField name="confirmPassword">
|
|
94
|
+
{(field) => (
|
|
95
|
+
<field.PasswordField
|
|
96
|
+
label="Confirm Password"
|
|
97
|
+
placeholder={AUTH_PLACEHOLDERS.PASSWORD_NEW_CONFIRM}
|
|
98
|
+
autoComplete="new-password"
|
|
99
|
+
disabled={success}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
</form.AppField>
|
|
103
|
+
</AuthForm>
|
|
104
|
+
</form.AppForm>
|
|
105
|
+
</CenteredPageLayout>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CenteredPageLayout } from "../components/layout/centered-page-layout";
|
|
4
|
+
import { AuthForm } from "../components/forms/auth-form";
|
|
5
|
+
import { useAppForm } from "../hooks/form";
|
|
6
|
+
import { getDataSDK } from "../lib/data-sdk";
|
|
7
|
+
import { ROUTES, AUTH_PLACEHOLDERS } from "../components/auth/authenticationConfig";
|
|
8
|
+
import { handleApiResponse, getErrorMessage } from "../utils/helpers";
|
|
9
|
+
|
|
10
|
+
const forgotPasswordSchema = z.object({
|
|
11
|
+
username: z.string().trim().toLowerCase().email("Please enter a valid username"),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default function ForgotPassword() {
|
|
15
|
+
const [success, setSuccess] = useState(false);
|
|
16
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
const form = useAppForm({
|
|
19
|
+
defaultValues: { username: "" },
|
|
20
|
+
validators: { onChange: forgotPasswordSchema, onSubmit: forgotPasswordSchema },
|
|
21
|
+
onSubmit: async ({ value }) => {
|
|
22
|
+
setSubmitError(null);
|
|
23
|
+
setSuccess(false);
|
|
24
|
+
try {
|
|
25
|
+
// [Dev Note] Custom Apex Endpoint: /auth/forgot-password
|
|
26
|
+
// You must ensure this Apex class exists in your org
|
|
27
|
+
const sdk = await getDataSDK();
|
|
28
|
+
const response = await sdk.fetch("/sfdcapi/services/apexrest/auth/forgot-password", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: JSON.stringify({ username: value.username.trim() }),
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
Accept: "application/json",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
await handleApiResponse(response, "Failed to send reset link");
|
|
37
|
+
setSuccess(true);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setSubmitError(getErrorMessage(err, "Failed to send reset link"));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
onSubmitInvalid: () => {},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<CenteredPageLayout title={ROUTES.FORGOT_PASSWORD.TITLE}>
|
|
47
|
+
<form.AppForm>
|
|
48
|
+
<AuthForm
|
|
49
|
+
title="Forgot Password"
|
|
50
|
+
description="Enter your username and we'll send you a reset link"
|
|
51
|
+
error={submitError}
|
|
52
|
+
success={
|
|
53
|
+
success &&
|
|
54
|
+
"If that username exists in our system, you will receive a reset link shortly."
|
|
55
|
+
}
|
|
56
|
+
submit={{ text: "Send Reset Link", loadingText: "Sending…", disabled: success }}
|
|
57
|
+
footer={{ text: "Remember your password?", link: ROUTES.LOGIN.PATH, linkText: "Sign in" }}
|
|
58
|
+
>
|
|
59
|
+
<form.AppField name="username">
|
|
60
|
+
{(field) => (
|
|
61
|
+
<field.TextField
|
|
62
|
+
label="Username"
|
|
63
|
+
placeholder={AUTH_PLACEHOLDERS.USERNAME}
|
|
64
|
+
autoComplete="username"
|
|
65
|
+
disabled={success}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
</form.AppField>
|
|
69
|
+
</AuthForm>
|
|
70
|
+
</form.AppForm>
|
|
71
|
+
</CenteredPageLayout>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default function Home() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
4
|
+
<div className="text-center">
|
|
5
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-4">Home</h1>
|
|
6
|
+
<p className="text-lg text-gray-600 mb-8">
|
|
7
|
+
Welcome to your React application.
|
|
8
|
+
</p>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
}
|