@salesforce/ui-bundle-template-app-angular-template-b2x 11.30.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/LICENSE.txt +82 -0
- package/dist/.forceignore +15 -0
- package/dist/.husky/pre-commit +4 -0
- package/dist/.prettierignore +11 -0
- package/dist/.prettierrc +17 -0
- package/dist/CHANGELOG.md +3966 -0
- package/dist/README.md +77 -0
- package/dist/config/project-scratch-def.json +13 -0
- package/dist/eslint.config.js +7 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls +68 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls +77 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls +71 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls +105 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls +162 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls-meta.xml +5 -0
- package/dist/force-app/main/default/digitalExperienceConfigs/angularexternalapp1.digitalExperienceConfig-meta.xml +8 -0
- package/dist/force-app/main/default/digitalExperiences/site/angularexternalapp1/angularexternalapp1.digitalExperience-meta.xml +11 -0
- package/dist/force-app/main/default/digitalExperiences/site/angularexternalapp1/sfdc_cms__site/angularexternalapp1/_meta.json +5 -0
- package/dist/force-app/main/default/digitalExperiences/site/angularexternalapp1/sfdc_cms__site/angularexternalapp1/content.json +10 -0
- package/dist/force-app/main/default/networks/angularexternalapp.network-meta.xml +60 -0
- package/dist/force-app/main/default/package.xml +24 -0
- package/dist/force-app/main/default/permissionsets/angularexternalapp_Guest_User_Api_Access.permissionset-meta.xml +9 -0
- package/dist/force-app/main/default/sites/angularexternalapp.site-meta.xml +31 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/.forceignore +15 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/.postcssrc.json +5 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/.prettierignore +8 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/.prettierrc +12 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/CHANGELOG.md +4 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/README.md +85 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/angular.json +98 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/angularexternalapp.uibundle-meta.xml +8 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/e2e/app.spec.ts +15 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/esbuild/api-version.mjs +17 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/eslint.config.js +60 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/middleware/html.mjs +3 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/middleware/proxy.mjs +12 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/package.json +56 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/playwright.config.ts +25 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/public/favicon.ico +0 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/scripts/rewrite-e2e-assets.mjs +30 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/account/account-search.service.ts +196 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/account/distinctAccountIndustries.ts +26 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/account/distinctAccountTypes.ts +26 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/account/getAccountDetail.ts +128 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/account/searchAccounts.ts +58 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/data-client.service.spec.ts +184 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/data-client.service.ts +74 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/api/user-profile.service.ts +126 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/app.config.ts +22 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/app.html +1 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/app.routes.ts +77 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/app.spec.ts +18 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/app.ts +15 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/layout/app-layout/app-layout.component.html +53 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/layout/app-layout/app-layout.component.spec.ts +89 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/layout/app-layout/app-layout.component.ts +81 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/_field-size.scss +42 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/alert/alert.component.html +31 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/alert/alert.component.ts +53 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/button/button.component.html +18 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/button/button.component.scss +112 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/button/button.component.ts +57 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/card/card.component.scss +34 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/card/card.component.ts +110 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/collapsible/collapsible.component.html +10 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/collapsible/collapsible.component.ts +26 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/date-picker/date-picker.component.html +25 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/date-picker/date-picker.component.scss +36 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/date-picker/date-picker.component.ts +41 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/date-range-picker/date-range-picker.component.html +34 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/date-range-picker/date-range-picker.component.scss +33 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/date-range-picker/date-range-picker.component.ts +66 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/dialog/dialog.component.html +37 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/dialog/dialog.component.ts +55 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/field/field.component.html +21 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/field/field.component.ts +36 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/field-size.ts +8 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/input/input.component.html +19 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/input/input.component.scss +35 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/input/input.component.ts +36 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/label/label.component.html +3 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/label/label.component.ts +17 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/paginator/paginator.component.html +10 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/paginator/paginator.component.ts +37 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/popover/popover.component.html +41 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/popover/popover.component.ts +40 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/select/select.component.html +24 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/select/select.component.scss +35 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/select/select.component.ts +72 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/separator/separator.component.html +1 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/separator/separator.component.scss +23 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/separator/separator.component.ts +56 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/skeleton/skeleton.component.html +1 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/skeleton/skeleton.component.scss +28 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/skeleton/skeleton.component.ts +24 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/components/ui/spinner/spinner.component.ts +35 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/auth/auth.guard.ts +50 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/auth/auth.service.ts +126 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/components/status-alert/status-alert.component.html +7 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/components/status-alert/status-alert.component.ts +52 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/config/authentication.config.ts +75 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/auth-form/auth-form.component.html +38 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/auth-form/auth-form.component.ts +94 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/footer-link/footer-link.component.html +11 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/footer-link/footer-link.component.ts +26 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/reactive-form-page.base.ts +50 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/submit-button/submit-button.component.html +8 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/submit-button/submit-button.component.scss +17 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/forms/submit-button/submit-button.component.ts +35 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/layouts/auth-app-layout/auth-app-layout.component.html +2 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/layouts/auth-app-layout/auth-app-layout.component.ts +30 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/layouts/card-layout/card-layout.component.html +17 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/layouts/card-layout/card-layout.component.ts +40 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/menu/auth-menu/auth-menu.component.html +26 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/menu/auth-menu/auth-menu.component.ts +46 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/session-timeout/session-time-servlet.ts +130 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/session-timeout/session-timeout-validator/session-timeout-validator.component.html +57 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/session-timeout/session-timeout-validator/session-timeout-validator.component.ts +205 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/session-timeout/session-timeout.config.ts +83 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/session-timeout/session-timeout.service.ts +201 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/utils/auth-helpers.ts +68 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/utils/auth-validators.ts +72 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/authentication/utils/helpers.ts +85 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/active-filters/active-filters.component.html +23 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/active-filters/active-filters.component.ts +78 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/date-filter/date-filter.component.html +18 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/date-filter/date-filter.component.ts +137 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/date-range-filter/date-range-filter.component.html +11 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/date-range-filter/date-range-filter.component.ts +69 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/filter-field-wrapper/filter-field-wrapper.component.html +11 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/filter-field-wrapper/filter-field-wrapper.component.ts +36 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/multi-select-filter/multi-select-filter.component.html +10 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/multi-select-filter/multi-select-filter.component.ts +58 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/numeric-range-filter/numeric-range-filter.component.html +22 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/numeric-range-filter/numeric-range-filter.component.ts +146 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/search-filter/search-filter.component.html +8 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/search-filter/search-filter.component.ts +82 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/select-filter/select-filter.component.html +12 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/select-filter/select-filter.component.ts +65 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/text-filter/text-filter.component.html +12 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/filters/text-filter/text-filter.component.ts +90 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/object-breadcrumb/object-breadcrumb.component.html +55 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/object-breadcrumb/object-breadcrumb.component.ts +40 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/pagination-controls/pagination-controls.component.html +8 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/pagination-controls/pagination-controls.component.ts +61 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/search-bar/search-bar.component.html +59 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/search-bar/search-bar.component.ts +108 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/sort-control/sort-control.component.html +50 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/components/sort-control/sort-control.component.ts +72 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/object-search-state.service.ts +232 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/utils/debounce.ts +31 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/utils/field-utils.ts +54 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/utils/filter-utils.ts +354 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/utils/index.ts +12 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/features/object-search/utils/sort-utils.ts +46 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/account-detail/account-detail-page.component.html +174 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/account-detail/account-detail-page.component.scss +7 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/account-detail/account-detail-page.component.ts +199 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/account-search/account-search-page.component.html +111 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/account-search/account-search-page.component.ts +217 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/change-password/change-password-page.component.html +68 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/change-password/change-password-page.component.ts +82 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/forgot-password/forgot-password-page.component.html +35 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/forgot-password/forgot-password-page.component.ts +87 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/home/home-page.component.html +18 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/home/home-page.component.ts +47 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/home/home.component.html +6 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/home/home.component.spec.ts +23 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/home/home.component.ts +13 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/login/login-page.component.html +47 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/login/login-page.component.ts +89 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/not-found/not-found.component.html +12 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/not-found/not-found.component.spec.ts +33 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/not-found/not-found.component.ts +15 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/profile/profile-page.component.html +126 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/profile/profile-page.component.ts +194 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/register/register-page.component.html +80 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/register/register-page.component.ts +93 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/reset-password/reset-password-page.component.html +60 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/pages/reset-password/reset-password-page.component.ts +96 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/app/utils/async-data.ts +99 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/index.html +14 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/main.ts +5 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/styles.scss +170 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/theme.scss +39 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/src/types/sf-globals.d.ts +9 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/tsconfig.app.json +11 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/tsconfig.json +33 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/tsconfig.spec.json +10 -0
- package/dist/force-app/main/default/uiBundles/angularexternalapp/ui-bundle.json +7 -0
- package/dist/jest.config.js +6 -0
- package/dist/package-lock.json +10066 -0
- package/dist/package.json +46 -0
- package/dist/scripts/apex/hello.apex +10 -0
- package/dist/scripts/gitignore-templates.json +4 -0
- package/dist/scripts/graphql-search.sh +191 -0
- package/dist/scripts/org-setup-config-schema.mjs +103 -0
- package/dist/scripts/org-setup-dev.mjs +87 -0
- package/dist/scripts/org-setup-utils.mjs +369 -0
- package/dist/scripts/org-setup-xml.mjs +182 -0
- package/dist/scripts/org-setup.config.json +9 -0
- package/dist/scripts/org-setup.mjs +1702 -0
- package/dist/scripts/sf-project-setup.mjs +103 -0
- package/dist/scripts/soql/account.soql +6 -0
- package/dist/scripts/validate-org-setup-config.mjs +38 -0
- package/dist/sfdx-project.json +12 -0
- package/package.json +40 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import { Injectable, signal } from "@angular/core";
|
|
7
|
+
import {
|
|
8
|
+
extendSessionTime,
|
|
9
|
+
pollSessionTimeServlet,
|
|
10
|
+
type SessionResponse,
|
|
11
|
+
} from "./session-time-servlet";
|
|
12
|
+
import {
|
|
13
|
+
INITIAL_RETRY_DELAY,
|
|
14
|
+
MAX_RETRY_ATTEMPTS,
|
|
15
|
+
MAX_RETRY_DELAY,
|
|
16
|
+
SESSION_WARNING_TIME,
|
|
17
|
+
} from "./session-timeout.config";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Session-timeout monitoring service — the Angular port of the React
|
|
21
|
+
* `useSessionTimeout` hook (plus the inlined `useRetryWithBackoff`). Polls
|
|
22
|
+
* SessionTimeServlet at calculated intervals, shows the warning window when the
|
|
23
|
+
* session nears expiry, and retries failed polls with exponential backoff.
|
|
24
|
+
*
|
|
25
|
+
* State is exposed as signals; the owning `SessionTimeoutValidatorComponent`
|
|
26
|
+
* reacts to them and performs navigation/logout (this service, like the React
|
|
27
|
+
* hook, never navigates — it only tracks time and schedules checks).
|
|
28
|
+
*
|
|
29
|
+
* NOT `providedIn: 'root'`: it is provided by the validator component so its
|
|
30
|
+
* timers are torn down with that component (mirrors the hook's unmount cleanup).
|
|
31
|
+
*/
|
|
32
|
+
@Injectable()
|
|
33
|
+
export class SessionTimeoutService {
|
|
34
|
+
// -- Public session state (signals) --------------------------------------
|
|
35
|
+
/** Seconds remaining in the current session (null until first response). */
|
|
36
|
+
readonly timeLeftInSession = signal<number | null>(null);
|
|
37
|
+
/** Whether the warning modal should be displayed. */
|
|
38
|
+
readonly showWarningModal = signal<boolean>(false);
|
|
39
|
+
/** Whether a poll is currently in flight. */
|
|
40
|
+
readonly isPolling = signal<boolean>(false);
|
|
41
|
+
|
|
42
|
+
// -- Retry (exponential backoff) state ------------------------------------
|
|
43
|
+
private retryAttempts = 0;
|
|
44
|
+
private currentRetryDelay = INITIAL_RETRY_DELAY;
|
|
45
|
+
|
|
46
|
+
// -- Timer / guard bookkeeping --------------------------------------------
|
|
47
|
+
private basePath = "";
|
|
48
|
+
private pollTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
49
|
+
private retryTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
50
|
+
private isPollingGuard = false;
|
|
51
|
+
|
|
52
|
+
private get maxRetriesReached(): boolean {
|
|
53
|
+
return this.retryAttempts >= MAX_RETRY_ATTEMPTS;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private resetRetry(): void {
|
|
57
|
+
this.retryAttempts = 0;
|
|
58
|
+
this.currentRetryDelay = INITIAL_RETRY_DELAY;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private clearPollTimeout(): void {
|
|
62
|
+
if (this.pollTimeoutId) {
|
|
63
|
+
clearTimeout(this.pollTimeoutId);
|
|
64
|
+
this.pollTimeoutId = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private clearRetryTimeout(): void {
|
|
69
|
+
if (this.retryTimeoutId) {
|
|
70
|
+
clearTimeout(this.retryTimeoutId);
|
|
71
|
+
this.retryTimeoutId = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Schedule the next session check, clearing any pending poll/retry to prevent
|
|
77
|
+
* concurrent polling. Mirrors the hook's `scheduleCheck`.
|
|
78
|
+
*/
|
|
79
|
+
private scheduleCheck(delay: number): void {
|
|
80
|
+
this.clearPollTimeout();
|
|
81
|
+
this.clearRetryTimeout();
|
|
82
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
83
|
+
void this.checkSession();
|
|
84
|
+
}, delay);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Handle a failed poll/extend with exponential backoff. Mirrors the hook's
|
|
89
|
+
* `handleRetryWithBackoff` + `useRetryWithBackoff.scheduleRetry`.
|
|
90
|
+
*/
|
|
91
|
+
private handleRetryWithBackoff(retryAction: () => void): void {
|
|
92
|
+
if (this.maxRetriesReached) {
|
|
93
|
+
console.error("[SessionTimeout] Max retry attempts reached. Stopping polling.");
|
|
94
|
+
this.isPolling.set(false);
|
|
95
|
+
this.isPollingGuard = false;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.isPollingGuard) {
|
|
100
|
+
console.warn("[SessionTimeout] Poll already in progress, skipping retry scheduling");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.clearRetryTimeout();
|
|
105
|
+
|
|
106
|
+
const delay = this.currentRetryDelay;
|
|
107
|
+
console.warn(
|
|
108
|
+
`[SessionTimeout] Scheduling retry attempt ${this.retryAttempts + 1}/${MAX_RETRY_ATTEMPTS} in ${delay}ms`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
this.retryTimeoutId = setTimeout(() => {
|
|
112
|
+
this.retryTimeoutId = null;
|
|
113
|
+
retryAction();
|
|
114
|
+
}, delay);
|
|
115
|
+
|
|
116
|
+
this.retryAttempts += 1;
|
|
117
|
+
// Double the delay for the next retry, capped at maxDelay.
|
|
118
|
+
this.currentRetryDelay = Math.min(this.currentRetryDelay * 2, MAX_RETRY_DELAY);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Process a session response and schedule the next check. Mirrors the hook's
|
|
123
|
+
* `processTimeoutResponse`. When `sr <= 0` the component's effect performs
|
|
124
|
+
* logout; when within the warning window the modal is shown and a check is
|
|
125
|
+
* scheduled for expiry; otherwise a check is scheduled for when the warning
|
|
126
|
+
* should appear.
|
|
127
|
+
*/
|
|
128
|
+
private processTimeoutResponse(secondsRemaining: number): void {
|
|
129
|
+
this.timeLeftInSession.set(secondsRemaining);
|
|
130
|
+
|
|
131
|
+
if (secondsRemaining <= 0) {
|
|
132
|
+
this.showWarningModal.set(false);
|
|
133
|
+
// logout() is triggered by the component reacting to timeLeftInSession.
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const shouldShowWarning = secondsRemaining <= SESSION_WARNING_TIME;
|
|
138
|
+
|
|
139
|
+
if (shouldShowWarning) {
|
|
140
|
+
this.showWarningModal.set(true);
|
|
141
|
+
this.scheduleCheck(secondsRemaining * 1000);
|
|
142
|
+
} else {
|
|
143
|
+
const timeUntilWarning = (secondsRemaining - SESSION_WARNING_TIME) * 1000;
|
|
144
|
+
this.showWarningModal.set(false);
|
|
145
|
+
this.scheduleCheck(timeUntilWarning);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Check session status via the servlet. Mirrors the hook's `checkSession`. */
|
|
150
|
+
async checkSession(): Promise<void> {
|
|
151
|
+
if (this.isPollingGuard) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.isPollingGuard = true;
|
|
156
|
+
this.isPolling.set(true);
|
|
157
|
+
|
|
158
|
+
const response = await pollSessionTimeServlet(this.basePath);
|
|
159
|
+
this.isPollingGuard = false;
|
|
160
|
+
this.isPolling.set(false);
|
|
161
|
+
|
|
162
|
+
this.applyResponse(response, () => void this.checkSession());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Extend the session (user clicked "Continue"). Mirrors `extendSession`. */
|
|
166
|
+
async extendSession(): Promise<void> {
|
|
167
|
+
const response = await extendSessionTime(this.basePath);
|
|
168
|
+
this.applyResponse(response, () => void this.extendSession());
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private applyResponse(response: SessionResponse | undefined, retryAction: () => void): void {
|
|
172
|
+
if (response) {
|
|
173
|
+
this.resetRetry();
|
|
174
|
+
this.processTimeoutResponse(response.sr);
|
|
175
|
+
} else {
|
|
176
|
+
this.handleRetryWithBackoff(retryAction);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Begin monitoring. Skips guests (mirrors the hook's guest short-circuit) and
|
|
182
|
+
* kicks off the first check.
|
|
183
|
+
*/
|
|
184
|
+
start(basePath: string, isGuest: boolean): void {
|
|
185
|
+
this.basePath = basePath;
|
|
186
|
+
if (isGuest) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
void this.checkSession();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Stop monitoring and reset state. Mirrors the hook's `logout` cleanup. */
|
|
193
|
+
stop(): void {
|
|
194
|
+
this.clearPollTimeout();
|
|
195
|
+
this.clearRetryTimeout();
|
|
196
|
+
this.showWarningModal.set(false);
|
|
197
|
+
this.timeLeftInSession.set(null);
|
|
198
|
+
this.isPolling.set(false);
|
|
199
|
+
this.isPollingGuard = false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import { AUTH_REDIRECT_PARAM } from "../config/authentication.config";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extracts the startUrl from the current query params, defaulting to '/'.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY NOTE: This function strictly validates the URL to prevent Open
|
|
12
|
+
* Redirect vulnerabilities. It allows only relative paths.
|
|
13
|
+
*
|
|
14
|
+
* Direct port of the React `getStartUrl`. The React version takes a
|
|
15
|
+
* `URLSearchParams`; Angular's `ParamMap` exposes the same `get(key)` contract,
|
|
16
|
+
* so this accepts a minimal `{ get(name): string | null }` shape and works with
|
|
17
|
+
* both an `ActivatedRoute` `ParamMap` and a `URLSearchParams`.
|
|
18
|
+
*
|
|
19
|
+
* @param params - The query-param map (e.g. ActivatedRoute.snapshot.queryParamMap).
|
|
20
|
+
* @returns The start URL for post-authentication redirect.
|
|
21
|
+
*/
|
|
22
|
+
export function getStartUrl(params: { get(name: string): string | null }): string {
|
|
23
|
+
const url = params.get(AUTH_REDIRECT_PARAM);
|
|
24
|
+
if (url && isValidRedirect(url)) {
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
return "/";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Security: Validates that the redirect URL is a relative path to prevent Open
|
|
32
|
+
* Redirect vulnerabilities.
|
|
33
|
+
*
|
|
34
|
+
* Security Checks:
|
|
35
|
+
* 1. Rejects protocol-relative URLs (//)
|
|
36
|
+
* 2. Rejects backslash usage which some browsers treat as slashes (/\)
|
|
37
|
+
* 3. Rejects control characters
|
|
38
|
+
*
|
|
39
|
+
* Ported from the React `isValidRedirect`, with one intentional hardening: the
|
|
40
|
+
* character-range ceiling is tightened from the React source's U+00FF to the
|
|
41
|
+
* printable-ASCII bound U+007E (~) so DEL / C1 controls / NBSP are rejected in
|
|
42
|
+
* line with check #3. Do not relax any of these checks without security review.
|
|
43
|
+
*/
|
|
44
|
+
export function isValidRedirect(url: string): boolean {
|
|
45
|
+
// Basic structure check
|
|
46
|
+
if (!url.startsWith("/") || url.startsWith("//")) return false;
|
|
47
|
+
// Security: Reject backslashes to prevent /\example.com bypasses
|
|
48
|
+
if (url.includes("\\")) return false;
|
|
49
|
+
// Robustness: Ensure it doesn't contain whitespace/control characters.
|
|
50
|
+
// Allowed range is the printable-ASCII band U+0021 (!) through U+007E (~).
|
|
51
|
+
// This deliberately tightens the React reference's U+00FF ceiling, which
|
|
52
|
+
// admitted U+007F (DEL), the C1 control block (U+0080-U+009F, incl. NEL),
|
|
53
|
+
// and U+00A0 (NBSP) - all of which contradict the "reject control
|
|
54
|
+
// characters" contract and are open-redirect / request-splitting vectors.
|
|
55
|
+
if (/[^!-~]/.test(url)) return false;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Shared response type for authentication endpoints (login/register). Success
|
|
61
|
+
* responses contain `success: true` and `redirectUrl`. Error responses contain
|
|
62
|
+
* an `errors` array.
|
|
63
|
+
*/
|
|
64
|
+
export interface AuthResponse {
|
|
65
|
+
success?: boolean;
|
|
66
|
+
redirectUrl?: string | null;
|
|
67
|
+
errors?: string[];
|
|
68
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Angular Reactive Forms validators that mirror the React feature's zod schemas
|
|
10
|
+
* (`authHelpers.ts`). The React port dropped zod in favour of native Angular
|
|
11
|
+
* validators; these reproduce the same rules and messages so the UX is
|
|
12
|
+
* identical. Each validator returns `{ <key>: message }` on failure and `null`
|
|
13
|
+
* on success; templates read the first message via `control.errors`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Mirrors zod `emailSchema`: trimmed, valid email. */
|
|
17
|
+
export const emailValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
|
|
18
|
+
const value = (control.value ?? "").toString().trim();
|
|
19
|
+
if (value === "") {
|
|
20
|
+
return { email: "Please enter a valid email address" };
|
|
21
|
+
}
|
|
22
|
+
// Same intent as zod's email check: a single @ with non-empty local/domain
|
|
23
|
+
// parts and a dotted domain. Kept deliberately permissive, like zod.
|
|
24
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
25
|
+
return emailPattern.test(value) ? null : { email: "Please enter a valid email address" };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Mirrors zod `passwordSchema`: minimum 8 characters. */
|
|
29
|
+
export const passwordMinValidator: ValidatorFn = (
|
|
30
|
+
control: AbstractControl,
|
|
31
|
+
): ValidationErrors | null => {
|
|
32
|
+
const value = (control.value ?? "").toString();
|
|
33
|
+
return value.length >= 8 ? null : { password: "Password must be at least 8 characters" };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** A required-with-custom-message validator (mirrors zod `min(1, message)`). */
|
|
37
|
+
export function requiredWithMessage(message: string): ValidatorFn {
|
|
38
|
+
return (control: AbstractControl): ValidationErrors | null => {
|
|
39
|
+
const value = (control.value ?? "").toString().trim();
|
|
40
|
+
return value.length >= 1 ? null : { required: message };
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Cross-field validator mirroring the `newPasswordSchema` refine: the
|
|
46
|
+
* `confirmPassword` control must equal the `newPassword` control. Attach to the
|
|
47
|
+
* FormGroup; sets `{ passwordMismatch: '...' }` on the confirm control (matching
|
|
48
|
+
* the zod `path: ['confirmPassword']`) so the error renders under that field.
|
|
49
|
+
*/
|
|
50
|
+
export function passwordsMatchValidator(
|
|
51
|
+
passwordKey = "newPassword",
|
|
52
|
+
confirmKey = "confirmPassword",
|
|
53
|
+
): ValidatorFn {
|
|
54
|
+
return (group: AbstractControl): ValidationErrors | null => {
|
|
55
|
+
const password = group.get(passwordKey);
|
|
56
|
+
const confirm = group.get(confirmKey);
|
|
57
|
+
if (!password || !confirm) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (confirm.value !== password.value) {
|
|
61
|
+
const existing = confirm.errors ?? {};
|
|
62
|
+
confirm.setErrors({ ...existing, passwordMismatch: "Passwords do not match" });
|
|
63
|
+
return { passwordMismatch: "Passwords do not match" };
|
|
64
|
+
}
|
|
65
|
+
// Clear only our own error, preserving any other errors on the control.
|
|
66
|
+
if (confirm.errors) {
|
|
67
|
+
const { passwordMismatch: _removed, ...rest } = confirm.errors;
|
|
68
|
+
confirm.setErrors(Object.keys(rest).length ? rest : null);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when the API returns a non-OK response with structured error
|
|
9
|
+
* messages. The `errors` array contains user-facing messages that are safe to
|
|
10
|
+
* display — backend Apex classes guarantee that system exceptions are never
|
|
11
|
+
* exposed. Direct port of the React `ApiError`.
|
|
12
|
+
*/
|
|
13
|
+
export class ApiError extends Error {
|
|
14
|
+
readonly errors: string[];
|
|
15
|
+
constructor(errors: string[]) {
|
|
16
|
+
super(errors[0]);
|
|
17
|
+
this.name = "ApiError";
|
|
18
|
+
this.errors = errors;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Helper to parse the fetch Response. Handles the distinction between success
|
|
24
|
+
* (JSON) and failure (throwing Error). Ported from the React `handleApiResponse`.
|
|
25
|
+
*/
|
|
26
|
+
export async function handleApiResponse<T = unknown>(response: Response): Promise<T> {
|
|
27
|
+
// Robustness: Handle 204 No Content gracefully
|
|
28
|
+
if (response.status === 204) {
|
|
29
|
+
return {} as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let data: { errors?: string[] } | null = null;
|
|
33
|
+
|
|
34
|
+
const contentType = response.headers.get("content-type");
|
|
35
|
+
if (contentType?.includes("application/json")) {
|
|
36
|
+
data = (await response.json()) as { errors?: string[] };
|
|
37
|
+
} else {
|
|
38
|
+
// If Salesforce returns HTML (e.g. standard error page), consume the text
|
|
39
|
+
// to avoid parsing errors.
|
|
40
|
+
await response.text();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
console.error("API request failed", data);
|
|
45
|
+
if (data?.errors?.length) {
|
|
46
|
+
throw new ApiError(data.errors);
|
|
47
|
+
}
|
|
48
|
+
throw new Error("An unexpected error occurred");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return data as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** UI API Record response structure. */
|
|
55
|
+
export type RecordResponse = {
|
|
56
|
+
fields: Record<string, { value: string }>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* GraphQL can return a complex nested structure. This helper flattens it to a
|
|
61
|
+
* simple object for easier form binding. Ported from the React
|
|
62
|
+
* `flattenGraphQLRecord`.
|
|
63
|
+
*
|
|
64
|
+
* @param data - Extracted payload from the GraphQL response.
|
|
65
|
+
* @param fallbackError - Fallback error message if data is null/undefined or not an object.
|
|
66
|
+
* @throws {Error} If data is not valid.
|
|
67
|
+
* @returns Flattened object with values mapped directly to the fields.
|
|
68
|
+
*/
|
|
69
|
+
export function flattenGraphQLRecord<T>(
|
|
70
|
+
data: unknown,
|
|
71
|
+
fallbackError = "An unknown error occurred",
|
|
72
|
+
): T {
|
|
73
|
+
if (!data || typeof data !== "object") {
|
|
74
|
+
throw new Error(fallbackError);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return Object.fromEntries(
|
|
78
|
+
Object.entries(data as Record<string, unknown>).map(([key, field]) => [
|
|
79
|
+
key,
|
|
80
|
+
field !== null && typeof field === "object" && "value" in field
|
|
81
|
+
? (field as { value: unknown }).value
|
|
82
|
+
: (field ?? null),
|
|
83
|
+
]),
|
|
84
|
+
) as T;
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@if (chips().length > 0) {
|
|
2
|
+
<div class="flex flex-wrap gap-2">
|
|
3
|
+
@for (chip of chips(); track chip.field) {
|
|
4
|
+
<app-button
|
|
5
|
+
appearance="outlined"
|
|
6
|
+
size="sm"
|
|
7
|
+
iconEnd="close"
|
|
8
|
+
[ariaLabel]="'Remove filter ' + chip.label"
|
|
9
|
+
(clicked)="onRemove(chip.field)"
|
|
10
|
+
>
|
|
11
|
+
{{ chip.label }}
|
|
12
|
+
</app-button>
|
|
13
|
+
}
|
|
14
|
+
<app-button
|
|
15
|
+
appearance="destructive"
|
|
16
|
+
size="sm"
|
|
17
|
+
ariaLabel="Clear all filters"
|
|
18
|
+
(clicked)="onClearAll()"
|
|
19
|
+
>
|
|
20
|
+
Clear all
|
|
21
|
+
</app-button>
|
|
22
|
+
</div>
|
|
23
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import { ChangeDetectionStrategy, Component, computed, inject } from "@angular/core";
|
|
7
|
+
import { ButtonComponent } from "../../../../components/ui/button/button.component";
|
|
8
|
+
import { ObjectSearchStateService } from "../../object-search-state.service";
|
|
9
|
+
import type { ActiveFilterValue } from "../../utils/filter-utils";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format the display label for one active filter chip. Mirrors the React
|
|
13
|
+
* `formatFilterLabel`; a switch over the filter's declared `type`.
|
|
14
|
+
*/
|
|
15
|
+
function formatFilterLabel(filter: ActiveFilterValue): string {
|
|
16
|
+
const { label, type, value, min, max } = filter;
|
|
17
|
+
switch (type) {
|
|
18
|
+
case "search":
|
|
19
|
+
return `Search: ${value}`;
|
|
20
|
+
case "text":
|
|
21
|
+
case "picklist":
|
|
22
|
+
return `${label}: ${value}`;
|
|
23
|
+
case "multipicklist": {
|
|
24
|
+
const values = value ? value.split(",") : [];
|
|
25
|
+
if (values.length <= 2) return `${label}: ${values.join(", ")}`;
|
|
26
|
+
return `${label}: ${values.length} selected`;
|
|
27
|
+
}
|
|
28
|
+
case "boolean":
|
|
29
|
+
return `${label}: ${value === "true" ? "Yes" : "No"}`;
|
|
30
|
+
case "numeric": {
|
|
31
|
+
if (min && max) return `${label}: ${min} - ${max}`;
|
|
32
|
+
if (min) return `${label}: >= ${min}`;
|
|
33
|
+
return `${label}: <= ${max}`;
|
|
34
|
+
}
|
|
35
|
+
case "date":
|
|
36
|
+
case "datetime":
|
|
37
|
+
case "daterange":
|
|
38
|
+
case "datetimerange": {
|
|
39
|
+
if (min && max) return `${label}: ${min} to ${max}`;
|
|
40
|
+
if (min) return `${label}: from ${min}`;
|
|
41
|
+
return `${label}: until ${max}`;
|
|
42
|
+
}
|
|
43
|
+
default:
|
|
44
|
+
return label;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Removable-chip strip for the currently active filters.
|
|
50
|
+
*
|
|
51
|
+
* Reads `filters()` from {@link ObjectSearchStateService}; clicking a chip
|
|
52
|
+
* removes that filter, and the `Clear all` chip clears everything via
|
|
53
|
+
* `resetAll()`. Mirrors the React `ActiveFilters` component.
|
|
54
|
+
*/
|
|
55
|
+
@Component({
|
|
56
|
+
selector: "app-active-filters",
|
|
57
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
58
|
+
imports: [ButtonComponent],
|
|
59
|
+
templateUrl: "./active-filters.component.html",
|
|
60
|
+
})
|
|
61
|
+
export class ActiveFiltersComponent {
|
|
62
|
+
private readonly state = inject(ObjectSearchStateService);
|
|
63
|
+
|
|
64
|
+
/** Active filters signal from the state service. */
|
|
65
|
+
protected readonly filters = this.state.filters;
|
|
66
|
+
/** Display strings paired with the underlying field for chip actions. */
|
|
67
|
+
protected readonly chips = computed(() =>
|
|
68
|
+
this.filters().map((f) => ({ field: f.field, label: formatFilterLabel(f) })),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
protected onRemove(field: string): void {
|
|
72
|
+
this.state.removeFilter(field);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected onClearAll(): void {
|
|
76
|
+
this.state.resetAll();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<app-filter-field-wrapper [label]="config().label" [helpText]="config().helpText">
|
|
2
|
+
<div class="flex gap-2">
|
|
3
|
+
<app-select
|
|
4
|
+
class="flex-1"
|
|
5
|
+
[options]="operatorOptions"
|
|
6
|
+
[value]="operator()"
|
|
7
|
+
(valueChange)="onOperatorChange($event)"
|
|
8
|
+
/>
|
|
9
|
+
<app-date-picker
|
|
10
|
+
class="flex-1"
|
|
11
|
+
placeholder="Pick a date"
|
|
12
|
+
[readonly]="true"
|
|
13
|
+
[max]="maxDate()"
|
|
14
|
+
[value]="currentDate()"
|
|
15
|
+
(valueChange)="onDateChange($event)"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</app-filter-field-wrapper>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
import { ChangeDetectionStrategy, Component, computed, inject, input } from "@angular/core";
|
|
7
|
+
// Base-app UI wrappers, inherited (not recreated) by this feature. Post-compose
|
|
8
|
+
// they land at uiBundles/<target>/src/app/components/ui/{date-picker,select}/..., four
|
|
9
|
+
// levels up.
|
|
10
|
+
import { DatePickerComponent } from "../../../../../components/ui/date-picker/date-picker.component";
|
|
11
|
+
import {
|
|
12
|
+
SelectComponent,
|
|
13
|
+
type AppSelectOption,
|
|
14
|
+
} from "../../../../../components/ui/select/select.component";
|
|
15
|
+
import { ObjectSearchStateService } from "../../../object-search-state.service";
|
|
16
|
+
import type { FilterFieldConfig, FilterFieldType } from "../../../utils/filter-utils";
|
|
17
|
+
import { FilterFieldWrapperComponent } from "../filter-field-wrapper/filter-field-wrapper.component";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Single-date filter — operator select ("After" / "Before") plus a date
|
|
21
|
+
* picker. Emits a filter whose `value` is the operator (`'gt'` or `'lt'`) and
|
|
22
|
+
* whose date is placed in `min` (for `gt`) or `max` (for `lt`), matching the
|
|
23
|
+
* React reference and the `date` / `datetime` branch of `buildSingleFilter`.
|
|
24
|
+
*
|
|
25
|
+
* The `filterType` input toggles the emitted `type` between `'date'` and
|
|
26
|
+
* `'datetime'` (the latter adds start/end-of-day promotion in the filter
|
|
27
|
+
* builder).
|
|
28
|
+
*/
|
|
29
|
+
type DateOperator = "gt" | "lt";
|
|
30
|
+
|
|
31
|
+
const OPERATOR_OPTIONS: readonly AppSelectOption[] = [
|
|
32
|
+
{ value: "gt", label: "After" },
|
|
33
|
+
{ value: "lt", label: "Before" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
@Component({
|
|
37
|
+
selector: "app-date-filter",
|
|
38
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
39
|
+
imports: [DatePickerComponent, SelectComponent, FilterFieldWrapperComponent],
|
|
40
|
+
templateUrl: "./date-filter.component.html",
|
|
41
|
+
})
|
|
42
|
+
export class DateFilterComponent {
|
|
43
|
+
private readonly state = inject(ObjectSearchStateService);
|
|
44
|
+
|
|
45
|
+
/** Field config: `field`, `label`, `helpText`, ... */
|
|
46
|
+
readonly config = input.required<FilterFieldConfig>();
|
|
47
|
+
/** Underlying filter type — `'date'` (default) or `'datetime'`. */
|
|
48
|
+
readonly filterType = input<FilterFieldType>("date");
|
|
49
|
+
|
|
50
|
+
protected readonly operatorOptions = OPERATOR_OPTIONS;
|
|
51
|
+
|
|
52
|
+
/** Active filter (if any) — the single source of truth for both fields. */
|
|
53
|
+
private readonly active = computed(() => this.state.filterFor(this.config().field));
|
|
54
|
+
|
|
55
|
+
/** Cap the picker at today when the field can't hold a future date. */
|
|
56
|
+
protected readonly maxDate = computed<Date | null>(() =>
|
|
57
|
+
this.config().disableFuture ? new Date() : null,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Selected operator. Defaults to `'gt'` when no filter is active, mirroring
|
|
62
|
+
* the React initial state (`min` present -> `gt`, else `lt`, but with no
|
|
63
|
+
* active filter React ended up on `lt`; we prefer `'gt'` as the safer
|
|
64
|
+
* "After a date" default when nothing is selected).
|
|
65
|
+
*/
|
|
66
|
+
protected readonly operator = computed<DateOperator>(() => {
|
|
67
|
+
const a = this.active();
|
|
68
|
+
if (a?.value === "gt" || a?.value === "lt") return a.value;
|
|
69
|
+
if (a?.min) return "gt";
|
|
70
|
+
if (a?.max) return "lt";
|
|
71
|
+
return "gt";
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** Date picker value — parsed from either `min` (gt) or `max` (lt). */
|
|
75
|
+
protected readonly currentDate = computed<Date | null>(() => {
|
|
76
|
+
const a = this.active();
|
|
77
|
+
return toDate(a?.min ?? a?.max);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
protected onOperatorChange(v: string): void {
|
|
81
|
+
const op = v === "lt" ? "lt" : "gt";
|
|
82
|
+
const date = this.currentDate();
|
|
83
|
+
// Only re-emit if we already had a date; otherwise wait until the user
|
|
84
|
+
// picks one (matches React `emitChange` gate on `currentDate`).
|
|
85
|
+
if (date) this.emit(op, date);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected onDateChange(date: Date | null): void {
|
|
89
|
+
const cfg = this.config();
|
|
90
|
+
if (!date) {
|
|
91
|
+
this.state.removeFilter(cfg.field);
|
|
92
|
+
} else {
|
|
93
|
+
this.emit(this.operator(), date);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private emit(op: DateOperator, date: Date): void {
|
|
98
|
+
const cfg = this.config();
|
|
99
|
+
const dateStr = toDateString(date);
|
|
100
|
+
const isMin = op === "gt";
|
|
101
|
+
this.state.setFilter(cfg.field, {
|
|
102
|
+
field: cfg.field,
|
|
103
|
+
label: cfg.label,
|
|
104
|
+
type: this.filterType(),
|
|
105
|
+
value: op,
|
|
106
|
+
min: isMin ? dateStr : undefined,
|
|
107
|
+
max: isMin ? undefined : dateStr,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Parses `YYYY-MM-DD` (or any ISO date) into a `Date`, or `null` on failure. */
|
|
113
|
+
export function toDate(value: string | undefined): Date | null {
|
|
114
|
+
if (!value) return null;
|
|
115
|
+
// Interpret bare YYYY-MM-DD as a local-time date so the picker doesn't
|
|
116
|
+
// display "yesterday" in negative UTC offsets.
|
|
117
|
+
const parts = value.split("T")[0].split("-");
|
|
118
|
+
if (parts.length === 3) {
|
|
119
|
+
const [y, m, d] = parts.map((p) => Number(p));
|
|
120
|
+
const dt = new Date(y, m - 1, d);
|
|
121
|
+
return isNaN(dt.getTime()) ? null : dt;
|
|
122
|
+
}
|
|
123
|
+
const dt = new Date(value);
|
|
124
|
+
return isNaN(dt.getTime()) ? null : dt;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Formats a `Date` as `YYYY-MM-DD` in local time. */
|
|
128
|
+
export function toDateString(date: Date | null | undefined): string {
|
|
129
|
+
if (!date || isNaN(date.getTime())) return "";
|
|
130
|
+
const y = date.getFullYear();
|
|
131
|
+
// Guard against partially-typed years (e.g. `202` instead of `2026`), which
|
|
132
|
+
// would serialize to an invalid `202-07-09` and be rejected by the API.
|
|
133
|
+
if (y < 1000 || y > 9999) return "";
|
|
134
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
135
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
136
|
+
return `${y}-${m}-${d}`;
|
|
137
|
+
}
|