@salesforce/templates 66.13.5 → 66.13.6
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/templates/project/angularextapp/CHANGELOG.md +258 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +299 -279
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularextapp/package.json +1 -1
- package/lib/templates/project/angularextapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularextapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularextapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/angularintapp/CHANGELOG.md +266 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +309 -289
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/features/agentforce/agentforce-embed.service.ts +6 -1
- package/lib/templates/project/angularintapp/package.json +1 -1
- package/lib/templates/project/angularintapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularintapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularintapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/reactexternalapp/CHANGELOG.md +1313 -0
- package/lib/templates/project/reactexternalapp/PRD.md +38 -0
- package/lib/templates/project/reactexternalapp/README.md +14 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_ps_/reactexternalapp_Guest_User_Api_Access.permissionset-meta.xml +25 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +587 -626
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +9 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +8 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/components/SocialLogin.tsx +141 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +2 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- package/lib/templates/project/{reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +24 -8
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +7 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/CustomerWebClientChat.tsx +74 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/index.ts +6 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +22 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/TestChatPage.tsx +26 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +11 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/chat.ts +36 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +133 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +101 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +4 -0
- package/lib/templates/project/reactexternalapp/package.json +8 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +120 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +6 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +1509 -328
- package/lib/templates/project/reactexternalapp/scripts/validate-org-setup-config.mjs +38 -0
- package/lib/templates/project/reactinternalapp/CHANGELOG.md +1127 -0
- package/lib/templates/project/reactinternalapp/PRD.md +35 -0
- package/lib/templates/project/reactinternalapp/README.md +14 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +436 -439
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +9 -8
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- package/lib/templates/project/{reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +16 -132
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +21 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +4 -4
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactinternalapp/package.json +4 -2
- package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +24 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +1162 -214
- package/lib/templates/uiBundles/angularbasic/angular.json +1 -1
- package/lib/templates/uiBundles/angularbasic/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/uiBundles/angularbasic/eslint.config.js +9 -1
- package/lib/templates/uiBundles/angularbasic/package-lock.json +299 -279
- package/lib/templates/uiBundles/angularbasic/package.json +2 -2
- package/lib/templates/uiBundles/reactbasic/package-lock.json +372 -352
- package/lib/utils/template-placeholders.js +7 -0
- package/lib/utils/template-placeholders.js.map +1 -1
- package/lib/utils/uiBundleTemplateUtils.d.ts +3 -1
- package/lib/utils/uiBundleTemplateUtils.js +4 -1
- package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
- package/package.json +7 -7
- package/lib/templates/project/reactexternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactexternalapp/scripts/prepare-import-unique-fields.js +0 -122
- package/lib/templates/project/reactinternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactinternalapp/scripts/prepare-import-unique-fields.js +0 -122
- /package/lib/templates/project/angularextapp/_p_/_m_/{permissionsets → _ps_}/angularextapp_Guest_User_Api_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/angularintapp/_p_/_m_/{permissionsets → _ps_}/angularintapp_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/reactinternalapp/_p_/_m_/{permissionsets → _ps_}/reactinternalapp_Access.permissionset-meta.xml +0 -0
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@salesforce/platform-sdk": "^
|
|
22
|
-
"@salesforce/ui-bundle": "^
|
|
21
|
+
"@salesforce/platform-sdk": "^11.53.1",
|
|
22
|
+
"@salesforce/ui-bundle": "^11.53.1",
|
|
23
23
|
"@tailwindcss/vite": "^4.1.17",
|
|
24
24
|
"class-variance-authority": "^0.7.1",
|
|
25
25
|
"clsx": "^2.1.1",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"react": "^19.2.0",
|
|
30
30
|
"react-day-picker": "^9.14.0",
|
|
31
31
|
"react-dom": "^19.2.0",
|
|
32
|
-
"react-router": "^
|
|
32
|
+
"react-router": "^8.3.0",
|
|
33
33
|
"shadcn": "^3.8.5",
|
|
34
34
|
"sonner": "^1.7.0",
|
|
35
35
|
"tailwind-merge": "^3.5.0",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
47
47
|
"@graphql-tools/utils": "^11.0.0",
|
|
48
48
|
"@playwright/test": "^1.49.0",
|
|
49
|
-
"@salesforce/
|
|
49
|
+
"@salesforce/graphiti": "^11.53.1",
|
|
50
|
+
"@salesforce/vite-plugin-ui-bundle": "^11.53.1",
|
|
50
51
|
"@testing-library/jest-dom": "^6.6.3",
|
|
51
52
|
"@testing-library/react": "^16.1.0",
|
|
52
53
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"@types/react": "^19.2.5",
|
|
55
56
|
"@types/react-dom": "^19.2.3",
|
|
56
57
|
"@vitejs/plugin-react": "^5.1.1",
|
|
57
|
-
"@vitest/ui": "^4.0
|
|
58
|
+
"@vitest/ui": "^4.1.0",
|
|
58
59
|
"eslint": "^9.39.1",
|
|
59
60
|
"eslint-plugin-react": "^7.37.2",
|
|
60
61
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
@@ -68,9 +69,10 @@
|
|
|
68
69
|
"typescript-eslint": "^8.46.4",
|
|
69
70
|
"vite": "^7.2.4",
|
|
70
71
|
"vite-plugin-graphql-codegen": "^3.6.3",
|
|
71
|
-
"vitest": "^4.0
|
|
72
|
+
"vitest": "^4.1.0"
|
|
72
73
|
},
|
|
73
74
|
"overrides": {
|
|
74
|
-
"lodash": "^4.18.1"
|
|
75
|
+
"lodash": "^4.18.1",
|
|
76
|
+
"undici": "^7.28.0"
|
|
75
77
|
}
|
|
76
78
|
}
|
package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs
CHANGED
|
@@ -15,13 +15,16 @@ import { buildClientSchema, getIntrospectionQuery, printSchema } from 'graphql';
|
|
|
15
15
|
import { pruneSchema } from '@graphql-tools/utils';
|
|
16
16
|
|
|
17
17
|
const DEFAULT_SCHEMA_PATH = '../../../../../schema.graphql';
|
|
18
|
+
const TARGET_ORG = process.env.SF_TARGET_ORG || undefined;
|
|
18
19
|
|
|
19
20
|
async function executeSalesforceGraphQLQuery(query, variables, operationName) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const orgInfo = await getOrgInfo(TARGET_ORG);
|
|
22
|
+
if (!orgInfo) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'Could not resolve a Salesforce org. Set SF_TARGET_ORG or a default org.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const { rawInstanceUrl: instanceUrl, apiVersion, accessToken } = orgInfo;
|
|
25
28
|
|
|
26
29
|
const targetUrl = `${instanceUrl}/services/data/v${apiVersion}/graphql`;
|
|
27
30
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useSearchParams } from "react-router";
|
|
3
|
+
import { createDataSDK } from "@salesforce/platform-sdk";
|
|
4
|
+
import { getStartUrl } from "../authHelpers";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Social Login (IDP) Component for React Experience Cloud Sites.
|
|
8
|
+
*
|
|
9
|
+
* Replicates the OOTB community_login/socialLogin LWC behavior which does not
|
|
10
|
+
* render on React (Site Container) sites due to a platform restriction
|
|
11
|
+
* (isSiteContainer check in node-loginsettings.ftl).
|
|
12
|
+
*
|
|
13
|
+
* Behavior:
|
|
14
|
+
* - On mount, fetches configured Auth Providers + SAML Providers
|
|
15
|
+
* - If only one IDP is configured and username/password is disabled, auto-redirects
|
|
16
|
+
* - Renders a button per provider with icon (Auth Providers) or without (SAML)
|
|
17
|
+
* - On click, redirects to the SSO URL to initiate the OAuth/SAML flow
|
|
18
|
+
*
|
|
19
|
+
* Backend: UIBundleSocialLoginConfig.cls (Apex REST endpoint)
|
|
20
|
+
* Uses: Auth.AuthConfiguration API
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface AuthProviderInfo {
|
|
24
|
+
developerName: string;
|
|
25
|
+
friendlyName: string;
|
|
26
|
+
iconUrl: string | null;
|
|
27
|
+
ssoUrl: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SamlProviderInfo {
|
|
31
|
+
id: string;
|
|
32
|
+
masterLabel: string;
|
|
33
|
+
ssoUrl: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface SocialLoginConfigResponse {
|
|
37
|
+
authProviders: AuthProviderInfo[];
|
|
38
|
+
samlProviders: SamlProviderInfo[];
|
|
39
|
+
autoRedirectUrl: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface SocialLoginProps {
|
|
43
|
+
/** Whether to show header text above buttons. Default: true */
|
|
44
|
+
showHeader?: boolean;
|
|
45
|
+
/** Header text. Default: "Or log in using:" */
|
|
46
|
+
headerText?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchSocialLoginConfig(startUrl: string): Promise<SocialLoginConfigResponse | null> {
|
|
50
|
+
try {
|
|
51
|
+
const sdk = await createDataSDK();
|
|
52
|
+
const response = await sdk.fetch!(
|
|
53
|
+
`/services/apexrest/auth/social-login-config?startURL=${encodeURIComponent(startUrl)}`,
|
|
54
|
+
{
|
|
55
|
+
method: "GET",
|
|
56
|
+
headers: { Accept: "application/json" },
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return await response.json();
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function SocialLogin({
|
|
71
|
+
showHeader = true,
|
|
72
|
+
headerText = "Or log in using:",
|
|
73
|
+
}: SocialLoginProps) {
|
|
74
|
+
const [searchParams] = useSearchParams();
|
|
75
|
+
const startUrl = getStartUrl(searchParams);
|
|
76
|
+
|
|
77
|
+
const [authProviders, setAuthProviders] = useState<AuthProviderInfo[]>([]);
|
|
78
|
+
const [samlProviders, setSamlProviders] = useState<SamlProviderInfo[]>([]);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
fetchSocialLoginConfig(startUrl).then((config) => {
|
|
82
|
+
if (!config) return;
|
|
83
|
+
|
|
84
|
+
if (config.autoRedirectUrl) {
|
|
85
|
+
window.location.assign(config.autoRedirectUrl);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setAuthProviders(config.authProviders || []);
|
|
90
|
+
setSamlProviders(config.samlProviders || []);
|
|
91
|
+
});
|
|
92
|
+
}, [startUrl]);
|
|
93
|
+
|
|
94
|
+
const hasProviders = authProviders.length > 0 || samlProviders.length > 0;
|
|
95
|
+
|
|
96
|
+
if (!hasProviders) return null;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="mt-6">
|
|
100
|
+
{showHeader && (
|
|
101
|
+
<div className="relative">
|
|
102
|
+
<div className="absolute inset-0 flex items-center">
|
|
103
|
+
<span className="w-full border-t" />
|
|
104
|
+
</div>
|
|
105
|
+
<div className="relative flex justify-center text-xs uppercase">
|
|
106
|
+
<span className="bg-background px-2 text-muted-foreground">{headerText}</span>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<div className="mt-4 grid gap-2">
|
|
112
|
+
{authProviders.map((provider) => (
|
|
113
|
+
<button
|
|
114
|
+
key={provider.developerName}
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => window.location.assign(provider.ssoUrl)}
|
|
117
|
+
aria-label={`Sign in with ${provider.friendlyName}`}
|
|
118
|
+
className="inline-flex w-full items-center justify-center gap-2 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
119
|
+
>
|
|
120
|
+
{provider.iconUrl && (
|
|
121
|
+
<img src={provider.iconUrl} alt="" className="h-4 w-4" aria-hidden="true" />
|
|
122
|
+
)}
|
|
123
|
+
<span>{provider.friendlyName}</span>
|
|
124
|
+
</button>
|
|
125
|
+
))}
|
|
126
|
+
|
|
127
|
+
{samlProviders.map((provider) => (
|
|
128
|
+
<button
|
|
129
|
+
key={provider.id}
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={() => window.location.assign(provider.ssoUrl)}
|
|
132
|
+
aria-label={`Sign in with ${provider.masterLabel}`}
|
|
133
|
+
className="inline-flex w-full items-center justify-center gap-2 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
134
|
+
>
|
|
135
|
+
<span>{provider.masterLabel}</span>
|
|
136
|
+
</button>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -8,6 +8,7 @@ import { createDataSDK } from "@salesforce/platform-sdk";
|
|
|
8
8
|
import { ROUTES } from "../authenticationConfig";
|
|
9
9
|
import { emailSchema, getStartUrl, type AuthResponse } from "../authHelpers";
|
|
10
10
|
import { ApiError, handleApiResponse } from "../utils/helpers";
|
|
11
|
+
import { SocialLogin } from "../components/SocialLogin";
|
|
11
12
|
|
|
12
13
|
const loginSchema = z.object({
|
|
13
14
|
email: emailSchema,
|
|
@@ -106,6 +107,7 @@ export default function Login() {
|
|
|
106
107
|
)}
|
|
107
108
|
</form.AppField>
|
|
108
109
|
</AuthForm>
|
|
110
|
+
<SocialLogin />
|
|
109
111
|
</form.AppForm>
|
|
110
112
|
</CenteredPageLayout>
|
|
111
113
|
);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches distinct values for a picklist field via the uiapi aggregate
|
|
3
|
+
* `groupBy` query.
|
|
4
|
+
*
|
|
5
|
+
* Used by the auto-render pathway when a picklist filter does not declare
|
|
6
|
+
* inline `options`. Each call hits the GraphQL endpoint once and returns the
|
|
7
|
+
* sorted set of distinct values plus their display labels.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createDataSDK } from "@salesforce/platform-sdk";
|
|
11
|
+
|
|
12
|
+
export interface PicklistOption {
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18
|
+
|
|
19
|
+
function assertValidIdentifier(name: string, kind: string): void {
|
|
20
|
+
if (!KEY_PATTERN.test(name)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Invalid ${kind} "${name}". Must match /^[A-Za-z_][A-Za-z0-9_]*$/ to be a valid GraphQL identifier.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds and executes a `uiapi.aggregate.<objectName>(groupBy: { <field>: { group: true } })`
|
|
29
|
+
* query and extracts the distinct values.
|
|
30
|
+
*/
|
|
31
|
+
export async function fetchDistinctValues(
|
|
32
|
+
objectName: string,
|
|
33
|
+
fieldName: string,
|
|
34
|
+
): Promise<PicklistOption[]> {
|
|
35
|
+
assertValidIdentifier(objectName, "objectName");
|
|
36
|
+
assertValidIdentifier(fieldName, "fieldName");
|
|
37
|
+
|
|
38
|
+
const document = `query DistinctValues {
|
|
39
|
+
uiapi {
|
|
40
|
+
aggregate {
|
|
41
|
+
${objectName}(groupBy: { ${fieldName}: { group: true } }) {
|
|
42
|
+
edges {
|
|
43
|
+
node {
|
|
44
|
+
aggregate @optional {
|
|
45
|
+
${fieldName} @optional {
|
|
46
|
+
value
|
|
47
|
+
displayValue
|
|
48
|
+
label
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}`;
|
|
57
|
+
|
|
58
|
+
const data = await createDataSDK();
|
|
59
|
+
const response = await data.graphql!.query<unknown>({ query: document });
|
|
60
|
+
|
|
61
|
+
if (response.errors?.length) {
|
|
62
|
+
throw new Error(response.errors.map((e) => e.message).join("; "));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const root = response.data as Record<string, unknown> | undefined;
|
|
66
|
+
const aggregate = (root?.uiapi as Record<string, unknown> | undefined)?.aggregate as
|
|
67
|
+
| Record<string, unknown>
|
|
68
|
+
| undefined;
|
|
69
|
+
const objectAgg = aggregate?.[objectName] as
|
|
70
|
+
| { edges?: Array<{ node?: { aggregate?: Record<string, unknown> } }> }
|
|
71
|
+
| undefined;
|
|
72
|
+
const edges = objectAgg?.edges ?? [];
|
|
73
|
+
|
|
74
|
+
return edges
|
|
75
|
+
.map((edge) => {
|
|
76
|
+
const field = edge?.node?.aggregate?.[fieldName] as
|
|
77
|
+
| { value?: string | null; displayValue?: string | null; label?: string | null }
|
|
78
|
+
| undefined;
|
|
79
|
+
const value = field?.value;
|
|
80
|
+
if (value == null || value === "") return null;
|
|
81
|
+
return { value, label: field?.label ?? field?.displayValue ?? value };
|
|
82
|
+
})
|
|
83
|
+
.filter((opt): opt is PicklistOption => opt !== null);
|
|
84
|
+
}
|
package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes a search request and parses results into a per-source map.
|
|
3
|
+
*
|
|
4
|
+
* Today there's only one adapter — SObjects via the platform-sdk uiapi GraphQL
|
|
5
|
+
* bridge. Future adapters (CMS, REST, etc.) can run alongside this one and
|
|
6
|
+
* have their results merged into the same `Record<sourceKey, SourceResult>`
|
|
7
|
+
* by the hook.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createDataSDK } from "@salesforce/platform-sdk";
|
|
11
|
+
import { buildSearchQuery, type SourceRequest } from "../queryBuilder";
|
|
12
|
+
import type { SourceResult } from "../types";
|
|
13
|
+
|
|
14
|
+
interface RawSourceResult {
|
|
15
|
+
edges?: Array<{ node?: unknown }> | null;
|
|
16
|
+
pageInfo?: {
|
|
17
|
+
hasNextPage?: boolean | null;
|
|
18
|
+
hasPreviousPage?: boolean | null;
|
|
19
|
+
startCursor?: string | null;
|
|
20
|
+
endCursor?: string | null;
|
|
21
|
+
} | null;
|
|
22
|
+
totalCount?: number | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Runs a single multi-aliased GraphQL request against uiapi.query.
|
|
27
|
+
*
|
|
28
|
+
* Returns a plain object keyed by source.key. A source whose alias is missing
|
|
29
|
+
* from the response (e.g. due to a partial GraphQL error) is omitted; the
|
|
30
|
+
* caller can detect this by `result[sourceKey] == null`.
|
|
31
|
+
*/
|
|
32
|
+
export async function runSearch(requests: SourceRequest[]): Promise<Record<string, SourceResult>> {
|
|
33
|
+
const { document, variables } = buildSearchQuery(requests);
|
|
34
|
+
|
|
35
|
+
const data = await createDataSDK();
|
|
36
|
+
const response = await data.graphql!.query<unknown, Record<string, unknown>>({
|
|
37
|
+
query: document,
|
|
38
|
+
variables,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (response.errors?.length) {
|
|
42
|
+
throw new Error(response.errors.map((e) => e.message).join("; "));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const root = response.data as Record<string, unknown> | undefined;
|
|
46
|
+
const queryRoot = (root?.uiapi as Record<string, unknown> | undefined)?.query as
|
|
47
|
+
| Record<string, RawSourceResult | null | undefined>
|
|
48
|
+
| undefined;
|
|
49
|
+
|
|
50
|
+
const out: Record<string, SourceResult> = {};
|
|
51
|
+
for (const request of requests) {
|
|
52
|
+
const raw = queryRoot?.[request.source.key];
|
|
53
|
+
if (raw == null) continue;
|
|
54
|
+
const nodes = (raw.edges ?? [])
|
|
55
|
+
.map((edge) => edge?.node)
|
|
56
|
+
.filter((node): node is unknown => node != null);
|
|
57
|
+
out[request.source.key] = {
|
|
58
|
+
nodes,
|
|
59
|
+
pageInfo: raw.pageInfo
|
|
60
|
+
? {
|
|
61
|
+
hasNextPage: raw.pageInfo.hasNextPage ?? false,
|
|
62
|
+
hasPreviousPage: raw.pageInfo.hasPreviousPage ?? false,
|
|
63
|
+
startCursor: raw.pageInfo.startCursor ?? null,
|
|
64
|
+
endCursor: raw.pageInfo.endCursor ?? null,
|
|
65
|
+
}
|
|
66
|
+
: null,
|
|
67
|
+
totalCount: raw.totalCount ?? null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merged results layout — a single combined grid across every in-scope source,
|
|
3
|
+
* driven by one global pager over the cumulative result set.
|
|
4
|
+
*
|
|
5
|
+
* This is the counterpart to {@link SearchResults} (which renders one stacked
|
|
6
|
+
* section per source). Use it when you want a unified "search everything" grid:
|
|
7
|
+
* heterogeneous cards laid out together, one shared pagination control, and a
|
|
8
|
+
* single results count — the layout the drop-in `<Search>` renders when the
|
|
9
|
+
* config sets `pagination.mode: "merged"`, and the same shape most apps
|
|
10
|
+
* hand-build on top of `useSearch`.
|
|
11
|
+
*
|
|
12
|
+
* It reads three aggregate accessors off the {@link SearchHandle}
|
|
13
|
+
* (`mergedResults`, `globalPagination`, `inScopeSources`), so it honours the
|
|
14
|
+
* config's `pagination.mode` / `mergeOrder` automatically. For a true combined
|
|
15
|
+
* grid, pair it with `pagination.mode: "merged"` (see the feature README, §9).
|
|
16
|
+
*
|
|
17
|
+
* Layout, top to bottom:
|
|
18
|
+
* - **Single-source filter chrome** — when the scope is narrowed to one
|
|
19
|
+
* source (selected or `lockedScope`), that source's filter panel + sort +
|
|
20
|
+
* active-filter chips render here. Under "All", per-source filters can't be
|
|
21
|
+
* combined across heterogeneous objects, so this row is omitted.
|
|
22
|
+
* - **Results count** — the cumulative in-scope total, on its own row directly
|
|
23
|
+
* above the grid, in every scope. Hidden when there are no results.
|
|
24
|
+
* - **The grid** — one card per `mergedResults` item, rendered via the
|
|
25
|
+
* per-source `renderResult[sourceKey]` callback. In a mixed grid (2+ sources
|
|
26
|
+
* in scope) each card is topped with an auto source badge — the source's
|
|
27
|
+
* `labelSingular` (falling back to `label`), colored from a palette by its
|
|
28
|
+
* config order — so heterogeneous results stay self-identifying with no
|
|
29
|
+
* configuration. Skeletons while loading; an empty-state message when there
|
|
30
|
+
* are none.
|
|
31
|
+
* - **Global pager** — one {@link PaginationControls} over the cumulative set.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type { ReactNode } from "react";
|
|
35
|
+
import { AlertCircle } from "lucide-react";
|
|
36
|
+
import { Alert, AlertDescription, AlertTitle } from "../../../components/ui/alert";
|
|
37
|
+
import { Skeleton } from "../../../components/ui/skeleton";
|
|
38
|
+
import { ActiveFilters } from "./filters/ActiveFilters";
|
|
39
|
+
import { DefaultFilterPanel } from "./filters/DefaultFilterPanel";
|
|
40
|
+
import { FilterProvider, FilterResetButton } from "./filters/FilterContext";
|
|
41
|
+
import { PaginationControls } from "./controls/PaginationControls";
|
|
42
|
+
import { SortControl } from "./controls/SortControl";
|
|
43
|
+
import { DefaultResultRow } from "./results/DefaultResultRow";
|
|
44
|
+
import { ALL_SCOPE, type SearchHandle, type SourceController } from "../types";
|
|
45
|
+
|
|
46
|
+
type ResultRenderer = ((node: unknown) => ReactNode) | false;
|
|
47
|
+
|
|
48
|
+
export interface MergedSearchResultsProps {
|
|
49
|
+
handle: SearchHandle;
|
|
50
|
+
/**
|
|
51
|
+
* Per source key: how to render one node in the merged grid. Falls back to
|
|
52
|
+
* the built-in {@link DefaultResultRow} for any source without an entry.
|
|
53
|
+
* Pass `false` to skip a source's cards entirely.
|
|
54
|
+
*/
|
|
55
|
+
renderResult?: Record<string, ResultRenderer>;
|
|
56
|
+
/** Message shown when there are no results. Defaults to "No results found." */
|
|
57
|
+
emptyMessage?: string;
|
|
58
|
+
/** Number of skeleton placeholders to show while loading. Defaults to `pageSize`. */
|
|
59
|
+
skeletonCount?: number;
|
|
60
|
+
/** Extra classes on the grid container. Defaults to a responsive 1/2/3-column grid. */
|
|
61
|
+
gridClassName?: string;
|
|
62
|
+
/** Extra classes on the outer wrapper. */
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const DEFAULT_GRID_CLASS = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Palette for the auto source badges in a mixed grid. A source's color is its
|
|
70
|
+
* index in the config's `sources` array, wrapped around the palette — so badges
|
|
71
|
+
* stay stable and distinct without any per-source configuration. Add more
|
|
72
|
+
* entries here if you routinely mix more source types than there are colors.
|
|
73
|
+
*/
|
|
74
|
+
const BADGE_PALETTE = [
|
|
75
|
+
"bg-blue-100 text-blue-700",
|
|
76
|
+
"bg-amber-100 text-amber-700",
|
|
77
|
+
"bg-purple-100 text-purple-700",
|
|
78
|
+
"bg-pink-100 text-pink-700",
|
|
79
|
+
"bg-emerald-100 text-emerald-700",
|
|
80
|
+
"bg-sky-100 text-sky-700",
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
export function MergedSearchResults({
|
|
84
|
+
handle,
|
|
85
|
+
renderResult,
|
|
86
|
+
emptyMessage,
|
|
87
|
+
skeletonCount,
|
|
88
|
+
gridClassName = DEFAULT_GRID_CLASS,
|
|
89
|
+
className,
|
|
90
|
+
}: MergedSearchResultsProps) {
|
|
91
|
+
const { globalPagination: page, mergedResults, loading, error } = handle;
|
|
92
|
+
|
|
93
|
+
// A single source is in scope when the scope is narrowed (selected from the
|
|
94
|
+
// dropdown) or locked (`lockedScope` / restrictTo). Its filter chrome can
|
|
95
|
+
// render meaningfully; under "All" it can't (heterogeneous sources).
|
|
96
|
+
const singleSourceInScope = handle.scope !== ALL_SCOPE;
|
|
97
|
+
const soleController = singleSourceInScope ? handle.inScopeSources[0] : undefined;
|
|
98
|
+
// Per-card source badges only make sense when the grid is genuinely mixed —
|
|
99
|
+
// a single-source grid (narrowed or locked) has one type throughout.
|
|
100
|
+
const showSourceBadges = !singleSourceInScope;
|
|
101
|
+
// Assign each source a stable badge color by its declaration order. Badges
|
|
102
|
+
// only show in "All" scope, where `inScopeSources` is every source in config
|
|
103
|
+
// order, so the index here is the source's config position.
|
|
104
|
+
const badgeColorByKey: Record<string, string> = {};
|
|
105
|
+
handle.inScopeSources.forEach((controller, i) => {
|
|
106
|
+
badgeColorByKey[controller.config.key] = BADGE_PALETTE[i % BADGE_PALETTE.length];
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const skeletons = skeletonCount ?? page.pageSize;
|
|
110
|
+
|
|
111
|
+
// Persistent, always-mounted live region: screen readers announce result
|
|
112
|
+
// updates (searching → count → empty → error) without an Enter keypress.
|
|
113
|
+
const liveMessage = loading
|
|
114
|
+
? "Searching…"
|
|
115
|
+
: error
|
|
116
|
+
? "Search failed."
|
|
117
|
+
: mergedResults.length === 0
|
|
118
|
+
? (emptyMessage ?? "No results found.")
|
|
119
|
+
: `${page.totalCount} result${page.totalCount === 1 ? "" : "s"}`;
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div className={`space-y-6 ${className ?? ""}`}>
|
|
123
|
+
<p className="sr-only" role="status" aria-live="polite">
|
|
124
|
+
{liveMessage}
|
|
125
|
+
</p>
|
|
126
|
+
|
|
127
|
+
{soleController && <SingleSourceChrome controller={soleController} />}
|
|
128
|
+
|
|
129
|
+
{error && (
|
|
130
|
+
<Alert variant="destructive" role="alert">
|
|
131
|
+
<AlertCircle />
|
|
132
|
+
<AlertTitle>Search failed</AlertTitle>
|
|
133
|
+
<AlertDescription>{error}</AlertDescription>
|
|
134
|
+
</Alert>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* Results count — its own row, directly above the grid, in every scope.
|
|
138
|
+
Hidden when there are none (the empty state covers that case). */}
|
|
139
|
+
{!loading && !error && page.totalCount > 0 && (
|
|
140
|
+
<p className="text-sm text-muted-foreground">
|
|
141
|
+
{page.totalCount} result{page.totalCount === 1 ? "" : "s"}
|
|
142
|
+
</p>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{loading ? (
|
|
146
|
+
<div className={gridClassName}>
|
|
147
|
+
{Array.from({ length: Math.min(skeletons, 6) }, (_, i) => (
|
|
148
|
+
<Skeleton key={i} className="h-48 w-full rounded-lg" />
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
) : mergedResults.length === 0 ? (
|
|
152
|
+
<p className="py-12 text-center text-sm text-muted-foreground">
|
|
153
|
+
{emptyMessage ?? "No results found."}
|
|
154
|
+
</p>
|
|
155
|
+
) : (
|
|
156
|
+
<ul className={gridClassName}>
|
|
157
|
+
{mergedResults.map(({ sourceKey, node }, i) => {
|
|
158
|
+
const override = renderResult?.[sourceKey];
|
|
159
|
+
if (override === false) return null;
|
|
160
|
+
const source = handle.sources[sourceKey]?.config;
|
|
161
|
+
const rendered = override ? (
|
|
162
|
+
override(node)
|
|
163
|
+
) : source ? (
|
|
164
|
+
<DefaultResultRow node={node} source={source} />
|
|
165
|
+
) : null;
|
|
166
|
+
const badge =
|
|
167
|
+
showSourceBadges && source ? (
|
|
168
|
+
<SourceBadge
|
|
169
|
+
label={source.labelSingular ?? source.label}
|
|
170
|
+
colorClass={badgeColorByKey[sourceKey]}
|
|
171
|
+
/>
|
|
172
|
+
) : null;
|
|
173
|
+
return (
|
|
174
|
+
<li
|
|
175
|
+
key={getItemKey(sourceKey, node, i)}
|
|
176
|
+
className="flex flex-col gap-1.5 [&>:last-child]:min-h-0 [&>:last-child]:flex-1"
|
|
177
|
+
>
|
|
178
|
+
{badge}
|
|
179
|
+
{rendered}
|
|
180
|
+
</li>
|
|
181
|
+
);
|
|
182
|
+
})}
|
|
183
|
+
</ul>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{page.totalCount > 0 && (
|
|
187
|
+
<PaginationControls
|
|
188
|
+
pageIndex={page.pageIndex}
|
|
189
|
+
pageCount={page.pageCount}
|
|
190
|
+
hasNextPage={page.hasNextPage}
|
|
191
|
+
hasPreviousPage={page.hasPreviousPage}
|
|
192
|
+
pageSize={page.pageSize}
|
|
193
|
+
pageSizeOptions={page.pageSizeOptions}
|
|
194
|
+
onNextPage={page.goToNextPage}
|
|
195
|
+
onPreviousPage={page.goToPreviousPage}
|
|
196
|
+
onGoToPage={page.goToPage}
|
|
197
|
+
onPageSizeChange={page.setPageSize}
|
|
198
|
+
disabled={loading || !!error}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getItemKey(sourceKey: string, node: unknown, fallback: number): string {
|
|
206
|
+
const id = node && typeof node === "object" ? (node as { Id?: unknown }).Id : undefined;
|
|
207
|
+
return typeof id === "string" ? `${sourceKey}:${id}` : `${sourceKey}:${fallback}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface SourceBadgeProps {
|
|
211
|
+
label: string;
|
|
212
|
+
colorClass: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Auto source badge shown above each card in a mixed grid — the source's
|
|
217
|
+
* `labelSingular` (falling back to `label`), colored from {@link BADGE_PALETTE}
|
|
218
|
+
* by its config order. Identifies which object type a heterogeneous card came
|
|
219
|
+
* from with no per-app config.
|
|
220
|
+
*/
|
|
221
|
+
function SourceBadge({ label, colorClass }: SourceBadgeProps) {
|
|
222
|
+
return (
|
|
223
|
+
<span className={`w-fit rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}`}>
|
|
224
|
+
{label}
|
|
225
|
+
</span>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface SingleSourceChromeProps {
|
|
230
|
+
controller: SourceController;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Filter panel + sort + active-filter chips for the one source in scope. Shown
|
|
235
|
+
* whenever the search is narrowed to a single object (selected or locked).
|
|
236
|
+
* Hidden under "All", where heterogeneous per-source filters can't be combined.
|
|
237
|
+
*/
|
|
238
|
+
function SingleSourceChrome({ controller }: SingleSourceChromeProps) {
|
|
239
|
+
const { config: source, filters, sort } = controller;
|
|
240
|
+
const showFilterPanel = (source.filterBy?.length ?? 0) > 0;
|
|
241
|
+
const showSortControl = (source.sortBy?.length ?? 0) > 0;
|
|
242
|
+
const showActiveChips = filters.active.length > 0;
|
|
243
|
+
|
|
244
|
+
if (!showFilterPanel && !showSortControl && !showActiveChips) return null;
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<div className="flex flex-col gap-4">
|
|
248
|
+
{showFilterPanel && (
|
|
249
|
+
<FilterProvider
|
|
250
|
+
filters={filters.active}
|
|
251
|
+
onFilterChange={filters.set}
|
|
252
|
+
onFilterRemove={filters.remove}
|
|
253
|
+
onReset={() => filters.active.forEach((f) => filters.remove(f.field))}
|
|
254
|
+
>
|
|
255
|
+
<div className="rounded-md border p-3 space-y-3">
|
|
256
|
+
<div className="flex items-center justify-between">
|
|
257
|
+
<h2 className="text-sm font-semibold">Filters</h2>
|
|
258
|
+
<FilterResetButton size="sm" />
|
|
259
|
+
</div>
|
|
260
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
261
|
+
<DefaultFilterPanel source={source} />
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</FilterProvider>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{(showSortControl || showActiveChips) && (
|
|
268
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
269
|
+
{showSortControl && source.sortBy && (
|
|
270
|
+
<SortControl configs={source.sortBy} sort={sort.current} onSortChange={sort.set} />
|
|
271
|
+
)}
|
|
272
|
+
{showActiveChips && <ActiveFilters filters={filters.active} onRemove={filters.remove} />}
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|