@salesforce/templates 66.1.1 → 66.2.0
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 +43 -4
- package/lib/generators/projectGenerator.js.map +1 -1
- package/lib/templates/project/react-b2e/.a4drules/README.md +35 -0
- package/lib/templates/project/react-b2e/.a4drules/a4d-webapp-generate.md +27 -0
- package/lib/templates/project/react-b2e/.a4drules/build-validation.md +78 -0
- package/lib/templates/project/react-b2e/.a4drules/code-quality.md +137 -0
- package/lib/templates/project/react-b2e/.a4drules/graphql/tools/knowledge/lds-explore-graphql-schema.md +227 -0
- package/lib/templates/project/react-b2e/.a4drules/graphql/tools/knowledge/lds-generate-graphql-mutationquery.md +212 -0
- package/lib/templates/project/react-b2e/.a4drules/graphql/tools/knowledge/lds-generate-graphql-readquery.md +185 -0
- package/lib/templates/project/react-b2e/.a4drules/graphql/tools/knowledge/lds-guide-graphql.md +205 -0
- package/lib/templates/project/react-b2e/.a4drules/graphql/tools/schemas/shared.graphqls +1150 -0
- package/lib/templates/project/react-b2e/.a4drules/graphql.md +409 -0
- package/lib/templates/project/react-b2e/.a4drules/images.md +13 -0
- package/lib/templates/project/react-b2e/.a4drules/react.md +387 -0
- package/lib/templates/project/react-b2e/.a4drules/react_image_processing.md +45 -0
- package/lib/templates/project/react-b2e/.a4drules/typescript.md +224 -0
- package/lib/templates/project/react-b2e/.a4drules/ui-layout.md +23 -0
- package/lib/templates/project/react-b2e/.a4drules/webapp-nav-and-placeholders.md +33 -0
- package/lib/templates/project/react-b2e/.a4drules/webapp-no-node-e.md +25 -0
- package/lib/templates/project/react-b2e/.a4drules/webapp-ui-first.md +32 -0
- package/lib/templates/project/react-b2e/.a4drules/webapp.md +75 -0
- package/lib/templates/project/react-b2e/.forceignore +15 -0
- package/lib/templates/project/react-b2e/.husky/pre-commit +4 -0
- package/lib/templates/project/react-b2e/.prettierignore +11 -0
- package/lib/templates/project/react-b2e/.prettierrc +17 -0
- package/lib/templates/project/react-b2e/AGENT.md +75 -0
- package/lib/templates/project/react-b2e/CHANGELOG.md +696 -0
- package/lib/templates/project/react-b2e/README.md +18 -0
- package/lib/templates/project/react-b2e/config/project-scratch-def.json +13 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/.graphqlrc.yml +2 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/.prettierignore +9 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/.prettierrc +11 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/appreacttemplateb2e.webapplication-meta.xml +7 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/build/vite.config.d.ts +2 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/build/vite.config.js +93 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/codegen.yml +94 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/e2e/app.spec.ts +17 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/eslint.config.js +141 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/index.html +13 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/package-lock.json +14392 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/package.json +58 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/playwright.config.ts +24 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/scripts/get-graphql-schema.mjs +68 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/scripts/rewrite-e2e-assets.mjs +23 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/api/graphql-operations-types.ts +116 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/api/utils/accounts.ts +33 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/api/utils/query/highRevenueAccountsQuery.graphql +29 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/app.tsx +22 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/appLayout.tsx +19 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/book.svg +3 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/copy.svg +4 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/rocket.svg +3 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/icons/star.svg +3 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/codey-1.png +0 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/codey-2.png +0 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/codey-3.png +0 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/assets/images/vibe-codey.svg +194 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/components/AgentforceConversationClient.tsx +127 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/index.ts +6 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/navigationMenu.tsx +80 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/Home.tsx +12 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/NotFound.tsx +18 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/router-utils.tsx +35 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/routes.tsx +22 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/styles/global.css +13 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/src/types/conversation.ts +21 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/tsconfig.json +36 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/tsconfig.node.json +13 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/vite-env.d.ts +1 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/vite.config.ts +102 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/vitest-env.d.ts +2 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/vitest.config.ts +11 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/vitest.setup.ts +1 -0
- package/lib/templates/project/react-b2e/force-app/main/default/webapplications/appreacttemplateb2e/webapplication.json +7 -0
- package/lib/templates/project/react-b2e/jest.config.js +6 -0
- package/lib/templates/project/react-b2e/package.json +38 -0
- package/lib/templates/project/react-b2e/scripts/apex/hello.apex +10 -0
- package/lib/templates/project/react-b2e/scripts/soql/account.soql +6 -0
- package/lib/templates/project/react-b2e/sfdx-project.json +12 -0
- package/lib/templates/project/react-b2x/.a4drules/README.md +35 -0
- package/lib/templates/project/react-b2x/.a4drules/a4d-webapp-generate.md +27 -0
- package/lib/templates/project/react-b2x/.a4drules/build-validation.md +78 -0
- package/lib/templates/project/react-b2x/.a4drules/code-quality.md +137 -0
- package/lib/templates/project/react-b2x/.a4drules/graphql/tools/knowledge/lds-explore-graphql-schema.md +227 -0
- package/lib/templates/project/react-b2x/.a4drules/graphql/tools/knowledge/lds-generate-graphql-mutationquery.md +212 -0
- package/lib/templates/project/react-b2x/.a4drules/graphql/tools/knowledge/lds-generate-graphql-readquery.md +185 -0
- package/lib/templates/project/react-b2x/.a4drules/graphql/tools/knowledge/lds-guide-graphql.md +205 -0
- package/lib/templates/project/react-b2x/.a4drules/graphql/tools/schemas/shared.graphqls +1150 -0
- package/lib/templates/project/react-b2x/.a4drules/graphql.md +409 -0
- package/lib/templates/project/react-b2x/.a4drules/images.md +13 -0
- package/lib/templates/project/react-b2x/.a4drules/react.md +387 -0
- package/lib/templates/project/react-b2x/.a4drules/react_image_processing.md +45 -0
- package/lib/templates/project/react-b2x/.a4drules/typescript.md +224 -0
- package/lib/templates/project/react-b2x/.a4drules/ui-layout.md +23 -0
- package/lib/templates/project/react-b2x/.a4drules/webapp-nav-and-placeholders.md +33 -0
- package/lib/templates/project/react-b2x/.a4drules/webapp-no-node-e.md +25 -0
- package/lib/templates/project/react-b2x/.a4drules/webapp-ui-first.md +32 -0
- package/lib/templates/project/react-b2x/.a4drules/webapp.md +75 -0
- package/lib/templates/project/react-b2x/.forceignore +15 -0
- package/lib/templates/project/react-b2x/.husky/pre-commit +4 -0
- package/lib/templates/project/react-b2x/.prettierignore +11 -0
- package/lib/templates/project/react-b2x/.prettierrc +17 -0
- package/lib/templates/project/react-b2x/AGENT.md +75 -0
- package/lib/templates/project/react-b2x/CHANGELOG.md +696 -0
- package/lib/templates/project/react-b2x/README.md +18 -0
- package/lib/templates/project/react-b2x/config/project-scratch-def.json +13 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppAuthUtils.cls +68 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppAuthUtils.cls-meta.xml +5 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppChangePassword.cls +77 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppChangePassword.cls-meta.xml +5 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppForgotPassword.cls +71 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppForgotPassword.cls-meta.xml +5 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppLogin.cls +105 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppLogin.cls-meta.xml +5 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppRegistration.cls +162 -0
- package/lib/templates/project/react-b2x/force-app/main/default/classes/WebAppRegistration.cls-meta.xml +5 -0
- package/lib/templates/project/react-b2x/force-app/main/default/digitalExperienceConfigs/appreacttemplateb2x1.digitalExperienceConfig +8 -0
- package/lib/templates/project/react-b2x/force-app/main/default/digitalExperiences/site/appreacttemplateb2x1/appreacttemplateb2x1.digitalExperience-meta.xml +11 -0
- package/lib/templates/project/react-b2x/force-app/main/default/digitalExperiences/site/appreacttemplateb2x1/sfdc_cms__site/appreacttemplateb2x1/_meta.json +5 -0
- package/lib/templates/project/react-b2x/force-app/main/default/digitalExperiences/site/appreacttemplateb2x1/sfdc_cms__site/appreacttemplateb2x1/content.json +10 -0
- package/lib/templates/project/react-b2x/force-app/main/default/networks/appreacttemplateb2x.network +60 -0
- package/lib/templates/project/react-b2x/force-app/main/default/package.xml +20 -0
- package/lib/templates/project/react-b2x/force-app/main/default/sites/appreacttemplateb2x.site +31 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/.graphqlrc.yml +2 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/.prettierignore +9 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/.prettierrc +11 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/appreacttemplateb2x.webapplication-meta.xml +7 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/build/vite.config.d.ts +2 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/build/vite.config.js +93 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/codegen.yml +94 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/e2e/app.spec.ts +17 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/eslint.config.js +141 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/index.html +13 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/package-lock.json +18408 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/package.json +66 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/playwright.config.ts +24 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/scripts/get-graphql-schema.mjs +68 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/scripts/rewrite-e2e-assets.mjs +23 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/api/graphql-operations-types.ts +116 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/api/utils/accounts.ts +33 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/api/utils/query/highRevenueAccountsQuery.graphql +29 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/app.tsx +13 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/appLayout.tsx +11 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/book.svg +3 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/copy.svg +4 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/rocket.svg +3 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/icons/star.svg +3 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/codey-1.png +0 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/codey-2.png +0 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/codey-3.png +0 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/assets/images/vibe-codey.svg +194 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/alerts/status-alert.tsx +45 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/authHelpers.ts +73 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/authenticationConfig.ts +61 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/authenticationRouteLayout.tsx +21 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/privateRouteLayout.tsx +36 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/sessionTimeout/SessionTimeoutValidator.tsx +616 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/sessionTimeout/sessionTimeService.ts +161 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/auth/sessionTimeout/sessionTimeoutConfig.ts +77 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/footers/footer-link.tsx +36 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/forms/auth-form.tsx +81 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/forms/submit-button.tsx +49 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/layout/card-layout.tsx +23 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/layout/card-skeleton.tsx +38 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/layout/centered-page-layout.tsx +73 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/alert.tsx +69 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/button.tsx +67 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/card.tsx +92 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/dialog.tsx +143 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/field.tsx +222 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/index.ts +72 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/input.tsx +19 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/label.tsx +19 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/pagination.tsx +112 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/select.tsx +183 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/separator.tsx +26 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/skeleton.tsx +14 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/spinner.tsx +15 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/table.tsx +87 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/tabs.tsx +78 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/components.json +18 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/context/AuthContext.tsx +95 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/hooks/form.tsx +116 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/hooks/useCountdownTimer.ts +266 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/hooks/useRetryWithBackoff.ts +109 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/layouts/AuthAppLayout.tsx +12 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/lib/data-sdk.ts +21 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/lib/utils.ts +6 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/navigationMenu.tsx +80 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/ChangePassword.tsx +107 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/ForgotPassword.tsx +73 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Home.tsx +12 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Login.tsx +97 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/NotFound.tsx +18 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Profile.tsx +178 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Register.tsx +138 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/ResetPassword.tsx +107 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/router-utils.tsx +35 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/routes.tsx +71 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/styles/global.css +135 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/src/utils/helpers.ts +121 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/tsconfig.json +36 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/tsconfig.node.json +13 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/vite-env.d.ts +1 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/vite.config.ts +102 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/vitest-env.d.ts +2 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/vitest.config.ts +11 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/vitest.setup.ts +1 -0
- package/lib/templates/project/react-b2x/force-app/main/default/webapplications/appreacttemplateb2x/webapplication.json +7 -0
- package/lib/templates/project/react-b2x/jest.config.js +6 -0
- package/lib/templates/project/react-b2x/package.json +38 -0
- package/lib/templates/project/react-b2x/scripts/apex/hello.apex +10 -0
- package/lib/templates/project/react-b2x/scripts/soql/account.soql +6 -0
- package/lib/templates/project/react-b2x/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 +49 -0
- package/lib/utils/webappTemplateUtils.js +210 -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,97 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useNavigate, useSearchParams, 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 } from "../components/auth/authenticationConfig";
|
|
9
|
+
import { emailSchema, getStartUrl, type AuthResponse } from "../components/auth/authHelpers";
|
|
10
|
+
import { handleApiResponse, getErrorMessage } from "../utils/helpers";
|
|
11
|
+
|
|
12
|
+
const loginSchema = z.object({
|
|
13
|
+
email: emailSchema,
|
|
14
|
+
password: z.string().min(1, "Password is required"),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export default function Login() {
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
const [searchParams] = useSearchParams();
|
|
20
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
21
|
+
|
|
22
|
+
const form = useAppForm({
|
|
23
|
+
defaultValues: { email: "", password: "" },
|
|
24
|
+
validators: { onChange: loginSchema, onSubmit: loginSchema },
|
|
25
|
+
onSubmit: async ({ value }) => {
|
|
26
|
+
setSubmitError(null);
|
|
27
|
+
try {
|
|
28
|
+
// [Dev Note] Salesforce Integration:
|
|
29
|
+
// We use the Data SDK fetch to make an authenticated (or guest) call to Salesforce.
|
|
30
|
+
// "/sfdcapi/services/apexrest/auth/login" refers to a custom Apex REST resource.
|
|
31
|
+
// You must ensure this Apex class exists in your org and handles the login logic
|
|
32
|
+
// (e.g., creating a session or returning a token).
|
|
33
|
+
const sdk = await getDataSDK();
|
|
34
|
+
const response = await sdk.fetch("/sfdcapi/services/apexrest/auth/login", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
email: value.email.trim().toLowerCase(),
|
|
38
|
+
password: value.password,
|
|
39
|
+
startUrl: getStartUrl(searchParams),
|
|
40
|
+
}),
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
Accept: "application/json",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const result = await handleApiResponse<AuthResponse>(response, "Login failed");
|
|
47
|
+
if (result?.redirectUrl) {
|
|
48
|
+
// Hard navigate to the URL which establishes the server session cookie
|
|
49
|
+
window.location.replace(result.redirectUrl);
|
|
50
|
+
} else {
|
|
51
|
+
// In case redirectUrl is null, navigate to home
|
|
52
|
+
navigate("/", { replace: true });
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
setSubmitError(getErrorMessage(err, "Login failed"));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
onSubmitInvalid: () => {},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<CenteredPageLayout title={ROUTES.LOGIN.TITLE}>
|
|
63
|
+
<form.AppForm>
|
|
64
|
+
<AuthForm
|
|
65
|
+
title="Login"
|
|
66
|
+
description="Enter your email below to login to your account"
|
|
67
|
+
error={submitError}
|
|
68
|
+
submit={{ text: "Login", loadingText: "Logging in…" }}
|
|
69
|
+
footer={{
|
|
70
|
+
text: "Don't have an account?",
|
|
71
|
+
link: ROUTES.REGISTER.PATH,
|
|
72
|
+
linkText: "Sign up",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<form.AppField name="email">
|
|
76
|
+
{(field) => <field.EmailField label="Email" />}
|
|
77
|
+
</form.AppField>
|
|
78
|
+
<form.AppField name="password">
|
|
79
|
+
{(field) => (
|
|
80
|
+
<field.PasswordField
|
|
81
|
+
label="Password"
|
|
82
|
+
labelAction={
|
|
83
|
+
<Link
|
|
84
|
+
to={ROUTES.FORGOT_PASSWORD.PATH}
|
|
85
|
+
className="text-sm underline-offset-4 hover:underline"
|
|
86
|
+
>
|
|
87
|
+
Forgot your password?
|
|
88
|
+
</Link>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</form.AppField>
|
|
93
|
+
</AuthForm>
|
|
94
|
+
</form.AppForm>
|
|
95
|
+
</CenteredPageLayout>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Link } from 'react-router';
|
|
2
|
+
|
|
3
|
+
export default function NotFound() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
6
|
+
<div className="text-center">
|
|
7
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-4">404</h1>
|
|
8
|
+
<p className="text-lg text-gray-600 mb-8">Page not found</p>
|
|
9
|
+
<Link
|
|
10
|
+
to="/"
|
|
11
|
+
className="inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
|
12
|
+
>
|
|
13
|
+
Go to Home
|
|
14
|
+
</Link>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { executeGraphQL } from "@salesforce/webapp-experimental/api";
|
|
5
|
+
|
|
6
|
+
import { CenteredPageLayout } from "../components/layout/centered-page-layout";
|
|
7
|
+
import { CardSkeleton } from "../components/layout/card-skeleton";
|
|
8
|
+
import { AuthForm } from "../components/forms/auth-form";
|
|
9
|
+
import { useAppForm } from "../hooks/form";
|
|
10
|
+
import { ROUTES } from "../components/auth/authenticationConfig";
|
|
11
|
+
import { emailSchema } from "../components/auth/authHelpers";
|
|
12
|
+
import { flattenGraphQLRecord, getErrorMessage } from "../utils/helpers";
|
|
13
|
+
import { getUser } from "../context/AuthContext";
|
|
14
|
+
|
|
15
|
+
const GRAPHQL_USER_PROFILE_FIELDS = `
|
|
16
|
+
Id
|
|
17
|
+
FirstName { value }
|
|
18
|
+
LastName { value }
|
|
19
|
+
Email { value }
|
|
20
|
+
Phone { value }
|
|
21
|
+
Street { value }
|
|
22
|
+
City { value }
|
|
23
|
+
State { value }
|
|
24
|
+
PostalCode { value }
|
|
25
|
+
Country { value }`;
|
|
26
|
+
|
|
27
|
+
const QUERY_PROFILE_GRAPHQL = `
|
|
28
|
+
query GetUserProfile($userId: ID) {
|
|
29
|
+
uiapi {
|
|
30
|
+
query {
|
|
31
|
+
User(where: { Id: { eq: $userId } }) {
|
|
32
|
+
edges {
|
|
33
|
+
node {${GRAPHQL_USER_PROFILE_FIELDS}}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}`;
|
|
39
|
+
|
|
40
|
+
const MUTATE_PROFILE_GRAPHQL = `
|
|
41
|
+
mutation UpdateUserProfile($input: UserUpdateInput!) {
|
|
42
|
+
uiapi {
|
|
43
|
+
UserUpdate(input: $input) {
|
|
44
|
+
Record {${GRAPHQL_USER_PROFILE_FIELDS}}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}`;
|
|
48
|
+
|
|
49
|
+
const optionalString = z
|
|
50
|
+
.string()
|
|
51
|
+
.trim()
|
|
52
|
+
.transform((val) => (val === "" ? null : val))
|
|
53
|
+
.nullable()
|
|
54
|
+
.optional();
|
|
55
|
+
|
|
56
|
+
const profileSchema = z.object({
|
|
57
|
+
FirstName: z.string().trim().min(1, "First name is required"),
|
|
58
|
+
LastName: z.string().trim().min(1, "Last name is required"),
|
|
59
|
+
Email: emailSchema,
|
|
60
|
+
Phone: optionalString,
|
|
61
|
+
Street: optionalString,
|
|
62
|
+
City: optionalString,
|
|
63
|
+
State: optionalString,
|
|
64
|
+
PostalCode: optionalString,
|
|
65
|
+
Country: optionalString,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
type ProfileFormValues = z.infer<typeof profileSchema>;
|
|
69
|
+
|
|
70
|
+
export default function Profile() {
|
|
71
|
+
const user = getUser();
|
|
72
|
+
const [profile, setProfile] = useState<ProfileFormValues | null>(null);
|
|
73
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
74
|
+
const [success, setSuccess] = useState(false);
|
|
75
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
76
|
+
|
|
77
|
+
const form = useAppForm({
|
|
78
|
+
defaultValues: {} as ProfileFormValues,
|
|
79
|
+
validators: { onChange: profileSchema, onSubmit: profileSchema },
|
|
80
|
+
onSubmit: async ({ value }) => {
|
|
81
|
+
setSubmitError(null);
|
|
82
|
+
setSuccess(false);
|
|
83
|
+
try {
|
|
84
|
+
const result: any = await executeGraphQL(MUTATE_PROFILE_GRAPHQL, {
|
|
85
|
+
input: { Id: user.id, User: { ...value } },
|
|
86
|
+
});
|
|
87
|
+
setProfile(flattenGraphQLRecord(result?.uiapi?.UserUpdate?.Record));
|
|
88
|
+
|
|
89
|
+
setSuccess(true);
|
|
90
|
+
// Scroll to top of page after successful update so user sees it
|
|
91
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
92
|
+
} catch (err) {
|
|
93
|
+
setSubmitError(getErrorMessage(err, "Failed to update profile"));
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onSubmitInvalid: () => {},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
let mounted = true;
|
|
101
|
+
|
|
102
|
+
executeGraphQL(QUERY_PROFILE_GRAPHQL, { userId: user.id })
|
|
103
|
+
.then(
|
|
104
|
+
(result: any) =>
|
|
105
|
+
mounted && setProfile(flattenGraphQLRecord(result?.uiapi?.query?.User?.edges?.[0]?.node)),
|
|
106
|
+
)
|
|
107
|
+
.catch((err: any) => {
|
|
108
|
+
if (mounted) {
|
|
109
|
+
setLoadError(getErrorMessage(err, "Failed to load profile"));
|
|
110
|
+
} else {
|
|
111
|
+
console.error("Failed to load profile", err);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return () => {
|
|
115
|
+
mounted = false;
|
|
116
|
+
};
|
|
117
|
+
}, [user.id]);
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (profile) {
|
|
121
|
+
const formData = profileSchema.parse(profile);
|
|
122
|
+
form.reset(formData);
|
|
123
|
+
}
|
|
124
|
+
}, [profile]);
|
|
125
|
+
|
|
126
|
+
if (!profile && !loadError) {
|
|
127
|
+
return <CardSkeleton contentMaxWidth="md" loadingText="Loading profile…" />;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<CenteredPageLayout contentMaxWidth="md" title={ROUTES.PROFILE.TITLE}>
|
|
132
|
+
<form.AppForm>
|
|
133
|
+
<AuthForm
|
|
134
|
+
title="Profile"
|
|
135
|
+
description="Update your account information"
|
|
136
|
+
error={loadError ?? submitError}
|
|
137
|
+
success={success && "Profile updated!"}
|
|
138
|
+
submit={{ text: "Save Changes", loadingText: "Saving…" }}
|
|
139
|
+
footer={{ link: ROUTES.CHANGE_PASSWORD.PATH, linkText: "Change password" }}
|
|
140
|
+
>
|
|
141
|
+
<form.AppField name="Email">
|
|
142
|
+
{(field) => <field.EmailField label="Email" disabled />}
|
|
143
|
+
</form.AppField>
|
|
144
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
|
|
145
|
+
<form.AppField name="FirstName">
|
|
146
|
+
{(field) => <field.TextField label="First name" autoComplete="given-name" />}
|
|
147
|
+
</form.AppField>
|
|
148
|
+
<form.AppField name="LastName">
|
|
149
|
+
{(field) => <field.TextField label="Last name" autoComplete="family-name" />}
|
|
150
|
+
</form.AppField>
|
|
151
|
+
</div>
|
|
152
|
+
<form.AppField name="Phone">
|
|
153
|
+
{(field) => <field.TextField label="Phone" type="tel" autoComplete="tel" />}
|
|
154
|
+
</form.AppField>
|
|
155
|
+
<form.AppField name="Street">
|
|
156
|
+
{(field) => <field.TextField label="Street" autoComplete="street-address" />}
|
|
157
|
+
</form.AppField>
|
|
158
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
|
|
159
|
+
<form.AppField name="City">
|
|
160
|
+
{(field) => <field.TextField label="City" autoComplete="address-level2" />}
|
|
161
|
+
</form.AppField>
|
|
162
|
+
<form.AppField name="State">
|
|
163
|
+
{(field) => <field.TextField label="State" autoComplete="address-level1" />}
|
|
164
|
+
</form.AppField>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
|
|
167
|
+
<form.AppField name="PostalCode">
|
|
168
|
+
{(field) => <field.TextField label="Postal Code" autoComplete="postal-code" />}
|
|
169
|
+
</form.AppField>
|
|
170
|
+
<form.AppField name="Country">
|
|
171
|
+
{(field) => <field.TextField label="Country" autoComplete="country-name" />}
|
|
172
|
+
</form.AppField>
|
|
173
|
+
</div>
|
|
174
|
+
</AuthForm>
|
|
175
|
+
</form.AppForm>
|
|
176
|
+
</CenteredPageLayout>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useNavigate, useSearchParams } 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 {
|
|
10
|
+
emailSchema,
|
|
11
|
+
passwordSchema,
|
|
12
|
+
getStartUrl,
|
|
13
|
+
type AuthResponse,
|
|
14
|
+
} from "../components/auth/authHelpers";
|
|
15
|
+
import { handleApiResponse, getErrorMessage } from "../utils/helpers";
|
|
16
|
+
|
|
17
|
+
const registerSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
firstName: z.string().trim().min(1, "First name is required"),
|
|
20
|
+
lastName: z.string().trim().min(1, "Last name is required"),
|
|
21
|
+
email: emailSchema,
|
|
22
|
+
password: passwordSchema,
|
|
23
|
+
confirmPassword: z.string().min(1, "Please confirm your password"),
|
|
24
|
+
startUrl: z.string(),
|
|
25
|
+
})
|
|
26
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
27
|
+
message: "Passwords do not match",
|
|
28
|
+
path: ["confirmPassword"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export default function Register() {
|
|
32
|
+
const navigate = useNavigate();
|
|
33
|
+
const [searchParams] = useSearchParams();
|
|
34
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
35
|
+
|
|
36
|
+
const form = useAppForm({
|
|
37
|
+
defaultValues: {
|
|
38
|
+
firstName: "",
|
|
39
|
+
lastName: "",
|
|
40
|
+
email: "",
|
|
41
|
+
password: "",
|
|
42
|
+
confirmPassword: "",
|
|
43
|
+
startUrl: getStartUrl(searchParams) || "",
|
|
44
|
+
},
|
|
45
|
+
validators: { onChange: registerSchema, onSubmit: registerSchema },
|
|
46
|
+
onSubmit: async ({ value: formFieldValues }) => {
|
|
47
|
+
setSubmitError(null);
|
|
48
|
+
try {
|
|
49
|
+
// [Dev Note] Salesforce Integration:
|
|
50
|
+
// We use the Data SDK fetch to make an authenticated (or guest) call to Salesforce.
|
|
51
|
+
// "/sfdcapi/services/apexrest/auth/register" refers to a custom Apex Class exposed as a REST resource.
|
|
52
|
+
// You must ensure this Apex class exists in your org and handles registration
|
|
53
|
+
// (e.g., duplicate checks and user creation such as Site.createExternalUser).
|
|
54
|
+
const { confirmPassword, ...request } = formFieldValues;
|
|
55
|
+
const sdk = await getDataSDK();
|
|
56
|
+
const response = await sdk.fetch("/sfdcapi/services/apexrest/auth/register", {
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: JSON.stringify({ request }),
|
|
59
|
+
headers: {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
Accept: "application/json",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const result = await handleApiResponse<AuthResponse>(response, "Registration failed");
|
|
65
|
+
if (result?.redirectUrl) {
|
|
66
|
+
// Hard navigate to the URL which logs the new user in
|
|
67
|
+
window.location.replace(result.redirectUrl);
|
|
68
|
+
} else {
|
|
69
|
+
// In case redirectUrl is null, redirect to the login page
|
|
70
|
+
navigate(ROUTES.LOGIN.PATH, { replace: true });
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setSubmitError(getErrorMessage(err, "Registration failed"));
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
onSubmitInvalid: () => {},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<CenteredPageLayout title={ROUTES.REGISTER.TITLE}>
|
|
81
|
+
<form.AppForm>
|
|
82
|
+
<AuthForm
|
|
83
|
+
title="Sign Up"
|
|
84
|
+
description="Enter your information to create an account"
|
|
85
|
+
error={submitError}
|
|
86
|
+
submit={{ text: "Create an account", loadingText: "Creating account…" }}
|
|
87
|
+
footer={{
|
|
88
|
+
text: "Already have an account?",
|
|
89
|
+
link: ROUTES.LOGIN.PATH,
|
|
90
|
+
linkText: "Sign in",
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
|
|
94
|
+
<form.AppField name="firstName">
|
|
95
|
+
{(field) => (
|
|
96
|
+
<field.TextField
|
|
97
|
+
label="First name"
|
|
98
|
+
placeholder={AUTH_PLACEHOLDERS.FIRST_NAME}
|
|
99
|
+
autoComplete="given-name"
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
</form.AppField>
|
|
103
|
+
<form.AppField name="lastName">
|
|
104
|
+
{(field) => (
|
|
105
|
+
<field.TextField
|
|
106
|
+
label="Last name"
|
|
107
|
+
placeholder={AUTH_PLACEHOLDERS.LAST_NAME}
|
|
108
|
+
autoComplete="family-name"
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
</form.AppField>
|
|
112
|
+
</div>
|
|
113
|
+
<form.AppField name="email">
|
|
114
|
+
{(field) => <field.EmailField label="Email" />}
|
|
115
|
+
</form.AppField>
|
|
116
|
+
<form.AppField name="password">
|
|
117
|
+
{(field) => (
|
|
118
|
+
<field.PasswordField
|
|
119
|
+
label="Password"
|
|
120
|
+
placeholder={AUTH_PLACEHOLDERS.PASSWORD_CREATE}
|
|
121
|
+
autoComplete="new-password"
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
</form.AppField>
|
|
125
|
+
<form.AppField name="confirmPassword">
|
|
126
|
+
{(field) => (
|
|
127
|
+
<field.PasswordField
|
|
128
|
+
label="Confirm Password"
|
|
129
|
+
placeholder={AUTH_PLACEHOLDERS.PASSWORD_CONFIRM}
|
|
130
|
+
autoComplete="new-password"
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
</form.AppField>
|
|
134
|
+
</AuthForm>
|
|
135
|
+
</form.AppForm>
|
|
136
|
+
</CenteredPageLayout>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Link, useSearchParams } from "react-router";
|
|
3
|
+
import { CardLayout } from "../components/layout/card-layout";
|
|
4
|
+
import { CenteredPageLayout } from "../components/layout/centered-page-layout";
|
|
5
|
+
import { AuthForm } from "../components/forms/auth-form";
|
|
6
|
+
import { StatusAlert } from "../components/alerts/status-alert";
|
|
7
|
+
import { useAppForm } from "../hooks/form";
|
|
8
|
+
import { getDataSDK } from "../lib/data-sdk";
|
|
9
|
+
import { ROUTES, AUTH_PLACEHOLDERS } from "../components/auth/authenticationConfig";
|
|
10
|
+
import { newPasswordSchema } from "../components/auth/authHelpers";
|
|
11
|
+
import { handleApiResponse, getErrorMessage } from "../utils/helpers";
|
|
12
|
+
|
|
13
|
+
export default function ResetPassword() {
|
|
14
|
+
const [searchParams] = useSearchParams();
|
|
15
|
+
const token = searchParams.get("token");
|
|
16
|
+
const [success, setSuccess] = useState(false);
|
|
17
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
18
|
+
|
|
19
|
+
const form = useAppForm({
|
|
20
|
+
defaultValues: { newPassword: "", confirmPassword: "" },
|
|
21
|
+
validators: { onChange: newPasswordSchema, onSubmit: newPasswordSchema },
|
|
22
|
+
onSubmit: async ({ value }) => {
|
|
23
|
+
setSubmitError(null);
|
|
24
|
+
setSuccess(false);
|
|
25
|
+
try {
|
|
26
|
+
// [Dev Note] Custom Apex Endpoint: /auth/reset-password
|
|
27
|
+
// You must ensure this Apex class exists in your org
|
|
28
|
+
const sdk = await getDataSDK();
|
|
29
|
+
const response = await sdk.fetch("/sfdcapi/services/apexrest/auth/reset-password", {
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: JSON.stringify({ token, newPassword: value.newPassword }),
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
Accept: "application/json",
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
await handleApiResponse(response, "Password reset failed");
|
|
38
|
+
setSuccess(true);
|
|
39
|
+
// Scroll to top of page after successful submission so user sees it
|
|
40
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
41
|
+
} catch (err) {
|
|
42
|
+
setSubmitError(getErrorMessage(err, "Password reset failed"));
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
onSubmitInvalid: () => {},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!token) {
|
|
49
|
+
return (
|
|
50
|
+
<CenteredPageLayout title={ROUTES.RESET_PASSWORD.TITLE}>
|
|
51
|
+
<CardLayout title="Reset Password">
|
|
52
|
+
<StatusAlert>
|
|
53
|
+
Reset token is invalid or expired.{" "}
|
|
54
|
+
<Link to={ROUTES.FORGOT_PASSWORD.PATH} className="underline underline-offset-4">
|
|
55
|
+
Request a new password reset link.
|
|
56
|
+
</Link>
|
|
57
|
+
</StatusAlert>
|
|
58
|
+
</CardLayout>
|
|
59
|
+
</CenteredPageLayout>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<CenteredPageLayout title={ROUTES.RESET_PASSWORD.TITLE}>
|
|
65
|
+
<form.AppForm>
|
|
66
|
+
<AuthForm
|
|
67
|
+
title="Reset Password"
|
|
68
|
+
description="Enter your new password below"
|
|
69
|
+
error={submitError}
|
|
70
|
+
success={
|
|
71
|
+
success && (
|
|
72
|
+
<>
|
|
73
|
+
Password reset successfully!{" "}
|
|
74
|
+
<Link to={ROUTES.LOGIN.PATH} className="underline">
|
|
75
|
+
Sign in
|
|
76
|
+
</Link>
|
|
77
|
+
</>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
submit={{ text: "Reset Password", loadingText: "Resetting…" }}
|
|
81
|
+
footer={{ text: "Remember your password?", link: ROUTES.LOGIN.PATH, linkText: "Sign in" }}
|
|
82
|
+
>
|
|
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,35 @@
|
|
|
1
|
+
import type { RouteObject } from 'react-router';
|
|
2
|
+
import { routes } from './routes';
|
|
3
|
+
|
|
4
|
+
export type RouteWithFullPath = RouteObject & { fullPath: string };
|
|
5
|
+
|
|
6
|
+
const flatMapRoutes = (
|
|
7
|
+
route: RouteObject,
|
|
8
|
+
parentPath: string = ''
|
|
9
|
+
): RouteWithFullPath[] => {
|
|
10
|
+
let fullPath: string;
|
|
11
|
+
|
|
12
|
+
if (route.index) {
|
|
13
|
+
fullPath = parentPath || '/';
|
|
14
|
+
} else if (route.path) {
|
|
15
|
+
if (route.path.startsWith('/')) {
|
|
16
|
+
fullPath = route.path;
|
|
17
|
+
} else {
|
|
18
|
+
fullPath =
|
|
19
|
+
parentPath === '/' ? `/${route.path}` : `${parentPath}/${route.path}`;
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
fullPath = parentPath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const routeWithPath = { ...route, fullPath };
|
|
26
|
+
|
|
27
|
+
const childRoutes =
|
|
28
|
+
route.children?.flatMap(child => flatMapRoutes(child, fullPath)) || [];
|
|
29
|
+
|
|
30
|
+
return [routeWithPath, ...childRoutes];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getAllRoutes = (): RouteWithFullPath[] => {
|
|
34
|
+
return routes.flatMap(route => flatMapRoutes(route));
|
|
35
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RouteObject } from 'react-router';
|
|
2
|
+
import Home from './pages/Home';
|
|
3
|
+
import NotFound from './pages/NotFound';
|
|
4
|
+
import Login from "./pages/Login";
|
|
5
|
+
import Register from "./pages/Register";
|
|
6
|
+
import ForgotPassword from "./pages/ForgotPassword";
|
|
7
|
+
import ResetPassword from "./pages/ResetPassword";
|
|
8
|
+
import Profile from "./pages/Profile";
|
|
9
|
+
import ChangePassword from "./pages/ChangePassword";
|
|
10
|
+
import AuthenticationRoute from "./components/auth/authenticationRouteLayout";
|
|
11
|
+
import PrivateRoute from "./components/auth/privateRouteLayout";
|
|
12
|
+
import { ROUTES } from "./components/auth/authenticationConfig";
|
|
13
|
+
import AppLayout from "./appLayout";
|
|
14
|
+
|
|
15
|
+
export const routes: RouteObject[] = [
|
|
16
|
+
{
|
|
17
|
+
path: "/",
|
|
18
|
+
element: <AppLayout />,
|
|
19
|
+
children: [
|
|
20
|
+
{
|
|
21
|
+
index: true,
|
|
22
|
+
element: <Home />,
|
|
23
|
+
handle: { showInNavigation: true, label: 'Home' }
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: '*',
|
|
27
|
+
element: <NotFound />
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
element: <AuthenticationRoute />,
|
|
31
|
+
children: [
|
|
32
|
+
{
|
|
33
|
+
path: ROUTES.LOGIN.PATH,
|
|
34
|
+
element: <Login />,
|
|
35
|
+
handle: { showInNavigation: true, label: "Login", title: ROUTES.LOGIN.TITLE }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: ROUTES.REGISTER.PATH,
|
|
39
|
+
element: <Register />,
|
|
40
|
+
handle: { showInNavigation: false, title: ROUTES.REGISTER.TITLE }
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: ROUTES.FORGOT_PASSWORD.PATH,
|
|
44
|
+
element: <ForgotPassword />,
|
|
45
|
+
handle: { showInNavigation: false, title: ROUTES.FORGOT_PASSWORD.TITLE }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: ROUTES.RESET_PASSWORD.PATH,
|
|
49
|
+
element: <ResetPassword />,
|
|
50
|
+
handle: { showInNavigation: false, title: ROUTES.RESET_PASSWORD.TITLE }
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
element: <PrivateRoute />,
|
|
56
|
+
children: [
|
|
57
|
+
{
|
|
58
|
+
path: ROUTES.PROFILE.PATH,
|
|
59
|
+
element: <Profile />,
|
|
60
|
+
handle: { showInNavigation: true, label: "Profile", title: ROUTES.PROFILE.TITLE }
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
path: ROUTES.CHANGE_PASSWORD.PATH,
|
|
64
|
+
element: <ChangePassword />,
|
|
65
|
+
handle: { showInNavigation: false, title: ROUTES.CHANGE_PASSWORD.TITLE }
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
];
|