@salesforce/templates 66.13.5 → 66.13.7
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 +680 -408
- 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 +690 -418
- 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 +1409 -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 +953 -714
- 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_/_ls_/LanguageSwitcher.tsx +116 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/__examples__/language-switcher-example.tsx +36 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/__examples__/vite-config-site-example.ts +44 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/buildLanguageUrl.ts +158 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/index.ts +28 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_ls_/languages.ts +46 -0
- 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/README.md +246 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +116 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +97 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +38 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/hooks/useSearchableContentTypes.ts +128 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +54 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +15 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +56 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +101 -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 +136 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.tsx +98 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +294 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +203 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +88 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +187 -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 +107 -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/CmsResultRow.tsx +101 -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/components/results/resolveResultRenderer.ts +49 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -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 +966 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -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 +100 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +376 -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 +8 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +53 -18
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -27
- 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 +21 -8
- 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/types/globals.d.ts +23 -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 +1223 -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 +813 -538
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +10 -9
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +246 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/apiUtils.ts +56 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/orgApiVersionService.ts +37 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +116 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/channelResolver.ts +40 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +97 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeSessionCache.ts +134 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +38 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/hooks/useSearchableContentTypes.ts +128 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +54 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/parseResponse.ts +65 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +15 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/types.ts +66 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/registry.ts +38 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/index.ts +19 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/parseResponse.ts +56 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/sobject/queryFragment.ts +141 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +101 -0
- 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 +136 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/GlobalSearchBox.tsx +98 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +294 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +203 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +88 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +187 -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 +107 -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/CmsResultRow.tsx +101 -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/components/results/resolveResultRenderer.ts +49 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +88 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/constants.ts +8 -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 +966 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +115 -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 +100 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +376 -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/Home.tsx +3 -27
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +12 -5
- 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 +680 -408
- package/lib/templates/uiBundles/angularbasic/package.json +2 -2
- package/lib/templates/uiBundles/reactbasic/package-lock.json +738 -440
- package/lib/utils/template-placeholders.js +14 -0
- package/lib/utils/template-placeholders.js.map +1 -1
- package/lib/utils/uiBundleTemplateUtils.d.ts +5 -1
- package/lib/utils/uiBundleTemplateUtils.js +7 -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/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +0 -297
- 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/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +0 -297
- 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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Input } from "../../../../../components/ui/input";
|
|
3
|
+
import { useFilterField } from "../FilterContext";
|
|
4
|
+
import { FilterFieldWrapper } from "./FilterFieldWrapper";
|
|
5
|
+
import { debounce, FILTER_DEBOUNCE_MS } from "../../../utils/debounce";
|
|
6
|
+
|
|
7
|
+
interface TextFilterProps {
|
|
8
|
+
field: string;
|
|
9
|
+
label: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
helpText?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TextFilter({ field, label, placeholder, helpText }: TextFilterProps) {
|
|
15
|
+
const { value, onChange } = useFilterField(field);
|
|
16
|
+
const [draft, setDraft] = useState(value?.value ?? "");
|
|
17
|
+
|
|
18
|
+
const onChangeRef = useRef(onChange);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
onChangeRef.current = onChange;
|
|
21
|
+
});
|
|
22
|
+
// Build the debounced caller in a mount effect, not during render: creating it
|
|
23
|
+
// inline would read onChangeRef.current at render time (react-hooks/refs). It
|
|
24
|
+
// stays a single stable instance and always calls the latest onChange via the ref.
|
|
25
|
+
const debouncedRef = useRef<((next: string) => void) | undefined>(undefined);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
debouncedRef.current = debounce((next: string) => {
|
|
28
|
+
if (next === "") onChangeRef.current(undefined);
|
|
29
|
+
else onChangeRef.current({ field, label, type: "text", value: next });
|
|
30
|
+
}, FILTER_DEBOUNCE_MS);
|
|
31
|
+
}, [field, label]);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setDraft(value?.value ?? "");
|
|
35
|
+
}, [value?.value]);
|
|
36
|
+
|
|
37
|
+
const id = `filter-${field}`;
|
|
38
|
+
return (
|
|
39
|
+
<FilterFieldWrapper label={label} htmlFor={id} helpText={helpText}>
|
|
40
|
+
<Input
|
|
41
|
+
id={id}
|
|
42
|
+
type="text"
|
|
43
|
+
value={draft}
|
|
44
|
+
placeholder={placeholder}
|
|
45
|
+
onChange={(e) => {
|
|
46
|
+
setDraft(e.target.value);
|
|
47
|
+
debouncedRef.current?.(e.target.value);
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</FilterFieldWrapper>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-node row for a CMS source. Used by both the per-source section
|
|
3
|
+
* (`SearchResults`) and the merged grid (`MergedSearchResults`) via the shared
|
|
4
|
+
* `resolveResultRenderer` (UI-owned) whenever `source.kind === "cms"`.
|
|
5
|
+
*
|
|
6
|
+
* Renders a title, a published-date subtitle, an optional content-type badge,
|
|
7
|
+
* and a react-router `<Link>` when a route can be built.
|
|
8
|
+
*
|
|
9
|
+
* Link target:
|
|
10
|
+
* - `source.routePattern` tokens: `:id` (managedContentId), `:key`
|
|
11
|
+
* (managedContentKey), `:contentType` (contentType).
|
|
12
|
+
* - falls back to `/content/:contentType/:id` when no `routePattern` is
|
|
13
|
+
* configured.
|
|
14
|
+
*
|
|
15
|
+
* The content-type label is rendered only when its FQN passes `isValidCmsFqn`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ReactNode } from "react";
|
|
19
|
+
import { Link } from "react-router";
|
|
20
|
+
import type { CmsSourceConfig, CmsSearchItem } from "../../adapters/cms/types";
|
|
21
|
+
import { formatContentTypeLabel, isValidCmsFqn } from "../../adapters/cms/contentTypeUtils";
|
|
22
|
+
|
|
23
|
+
/** Formats an ISO date string for display; echoes the raw string on failure. */
|
|
24
|
+
function formatPublishedDate(dateStr: string): string {
|
|
25
|
+
const date = new Date(dateStr);
|
|
26
|
+
if (isNaN(date.getTime())) return dateStr;
|
|
27
|
+
return date.toLocaleDateString(undefined, {
|
|
28
|
+
year: "numeric",
|
|
29
|
+
month: "short",
|
|
30
|
+
day: "numeric",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Substitutes `:id` / `:key` / `:contentType` tokens in a route pattern with
|
|
36
|
+
* the item's values (URL-encoded). Returns `null` if a required token has no
|
|
37
|
+
* value.
|
|
38
|
+
*/
|
|
39
|
+
function resolveRoute(pattern: string, item: CmsSearchItem): string | null {
|
|
40
|
+
const tokens: Record<string, string | undefined> = {
|
|
41
|
+
id: item.managedContentId,
|
|
42
|
+
key: item.managedContentKey,
|
|
43
|
+
contentType: item.contentType,
|
|
44
|
+
};
|
|
45
|
+
let failed = false;
|
|
46
|
+
const out = pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_match, token: string) => {
|
|
47
|
+
const value = tokens[token];
|
|
48
|
+
if (value == null || value === "") {
|
|
49
|
+
failed = true;
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
return encodeURIComponent(value);
|
|
53
|
+
});
|
|
54
|
+
return failed ? null : out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CmsResultRowProps {
|
|
58
|
+
node: unknown;
|
|
59
|
+
source: CmsSourceConfig;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function CmsResultRow({ node, source }: CmsResultRowProps) {
|
|
63
|
+
const item = node as CmsSearchItem;
|
|
64
|
+
|
|
65
|
+
const publishedDate = item.managedContentChannelDeliveryDetails?.[0]?.publishedDate;
|
|
66
|
+
const showType = typeof item.contentType === "string" && isValidCmsFqn(item.contentType);
|
|
67
|
+
|
|
68
|
+
const subtitleParts: string[] = [];
|
|
69
|
+
if (showType) subtitleParts.push(formatContentTypeLabel(item.contentType));
|
|
70
|
+
if (publishedDate) subtitleParts.push(`Published: ${formatPublishedDate(publishedDate)}`);
|
|
71
|
+
|
|
72
|
+
const content: ReactNode = (
|
|
73
|
+
<>
|
|
74
|
+
<span className="font-medium">{item.title ?? "—"}</span>
|
|
75
|
+
{item.highlightedSnippet && (
|
|
76
|
+
<p className="text-sm text-muted-foreground">{item.highlightedSnippet}</p>
|
|
77
|
+
)}
|
|
78
|
+
{subtitleParts.length > 0 && (
|
|
79
|
+
<p className="text-sm text-muted-foreground">{subtitleParts.join(" · ")}</p>
|
|
80
|
+
)}
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Only render a drill-down link when the source explicitly configures a
|
|
85
|
+
// routePattern. Without one, content rows render as plain (non-linked) rows
|
|
86
|
+
// so they never navigate to an unbuilt detail route.
|
|
87
|
+
const href = source.routePattern ? resolveRoute(source.routePattern, item) : null;
|
|
88
|
+
if (href) {
|
|
89
|
+
return (
|
|
90
|
+
<Link
|
|
91
|
+
to={href}
|
|
92
|
+
className="block hover:bg-accent rounded-md px-3 -mx-3 py-1 transition-colors"
|
|
93
|
+
aria-label={`View content: ${item.title}`}
|
|
94
|
+
>
|
|
95
|
+
{content}
|
|
96
|
+
</Link>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return <div className="px-3 -mx-3 py-1">{content}</div>;
|
|
101
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default per-node row used when no `renderResult` is supplied for a source.
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* - Title: first entry in `displayFields` (preferring displayValue).
|
|
6
|
+
* - Subtitle: remaining display fields joined with " · ".
|
|
7
|
+
* - When `routePattern` is configured on the source, the row is wrapped in
|
|
8
|
+
* a react-router `<Link>` whose `to` is built by substituting `:fieldName`
|
|
9
|
+
* tokens with the corresponding field values (special token `:id`
|
|
10
|
+
* resolves to the configured `idField`, default "Id").
|
|
11
|
+
*
|
|
12
|
+
* Applications can still pass `renderResult[sourceKey]` to override.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ReactNode } from "react";
|
|
16
|
+
import { Link } from "react-router";
|
|
17
|
+
import { fieldValue } from "../../utils/fieldUtils";
|
|
18
|
+
import type { DisplayField, SObjectSourceConfig } from "../../types";
|
|
19
|
+
|
|
20
|
+
const ROUTE_TOKEN = /:([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
21
|
+
|
|
22
|
+
function fieldName(f: DisplayField): string {
|
|
23
|
+
return typeof f === "string" ? f : f.name;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Resolves a single dot-path against a node, returning a string or null. */
|
|
27
|
+
function readPath(node: unknown, path: string): string | null {
|
|
28
|
+
if (!node || typeof node !== "object") return null;
|
|
29
|
+
const parts = path.split(".");
|
|
30
|
+
let cursor: unknown = node;
|
|
31
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
32
|
+
if (!cursor || typeof cursor !== "object") return null;
|
|
33
|
+
cursor = (cursor as Record<string, unknown>)[parts[i]];
|
|
34
|
+
}
|
|
35
|
+
const leaf = (cursor as Record<string, unknown> | null)?.[parts[parts.length - 1]];
|
|
36
|
+
if (leaf == null) return null;
|
|
37
|
+
if (typeof leaf === "object") {
|
|
38
|
+
return fieldValue(leaf as { value?: unknown; displayValue?: string | null });
|
|
39
|
+
}
|
|
40
|
+
return String(leaf);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Reads the user-facing string for a top-level display field. */
|
|
44
|
+
function readDisplayField(node: unknown, field: DisplayField): string | null {
|
|
45
|
+
if (typeof field === "string") return readPath(node, field);
|
|
46
|
+
if ("raw" in field) return readPath(node, field.name);
|
|
47
|
+
// Relationship: prefer the first scalar subfield's display value.
|
|
48
|
+
const sub = field.subfields[0];
|
|
49
|
+
if (!sub) return null;
|
|
50
|
+
const subName = fieldName(sub);
|
|
51
|
+
return readPath(node, `${field.name}.${subName}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Substitutes `:fieldName` tokens in routePattern with node values. */
|
|
55
|
+
function resolveRoute(pattern: string, node: unknown, idField: string): string | null {
|
|
56
|
+
let failed = false;
|
|
57
|
+
const out = pattern.replace(ROUTE_TOKEN, (_, token) => {
|
|
58
|
+
const path = token === "id" ? idField : token;
|
|
59
|
+
const value = readPath(node, path);
|
|
60
|
+
if (value == null) {
|
|
61
|
+
failed = true;
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
return encodeURIComponent(value);
|
|
65
|
+
});
|
|
66
|
+
return failed ? null : out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface DefaultResultRowProps {
|
|
70
|
+
node: unknown;
|
|
71
|
+
source: SObjectSourceConfig;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function DefaultResultRow({ node, source }: DefaultResultRowProps) {
|
|
75
|
+
const idField = source.idField ?? "Id";
|
|
76
|
+
const fields = source.displayFields;
|
|
77
|
+
const titleField = fields[0];
|
|
78
|
+
const title = titleField ? readDisplayField(node, titleField) : null;
|
|
79
|
+
|
|
80
|
+
const subtitleParts = fields
|
|
81
|
+
.slice(1)
|
|
82
|
+
.map((f) => readDisplayField(node, f))
|
|
83
|
+
.filter((s): s is string => s != null && s !== "");
|
|
84
|
+
|
|
85
|
+
const content: ReactNode = (
|
|
86
|
+
<>
|
|
87
|
+
<span className="font-medium">{title ?? "—"}</span>
|
|
88
|
+
{subtitleParts.length > 0 && (
|
|
89
|
+
<p className="text-sm text-muted-foreground">{subtitleParts.join(" · ")}</p>
|
|
90
|
+
)}
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (source.routePattern) {
|
|
95
|
+
const href = resolveRoute(source.routePattern, node, idField);
|
|
96
|
+
if (href) {
|
|
97
|
+
return (
|
|
98
|
+
<Link
|
|
99
|
+
to={href}
|
|
100
|
+
className="block hover:bg-accent rounded-md px-3 -mx-3 py-1 transition-colors"
|
|
101
|
+
>
|
|
102
|
+
{content}
|
|
103
|
+
</Link>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return <div className="px-3 -mx-3 py-1">{content}</div>;
|
|
109
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for choosing how one source's result node is rendered.
|
|
3
|
+
*
|
|
4
|
+
* Every result layout (`SearchResults`, `MergedSearchResults`, and any future
|
|
5
|
+
* grid/list) MUST resolve its per-node renderer through this helper so the
|
|
6
|
+
* kind-aware default can never be missed in one mode. The rule, in order:
|
|
7
|
+
*
|
|
8
|
+
* 1. A `renderResult[source.key]` config override wins in EVERY mode —
|
|
9
|
+
* including `false`, which hides the source entirely.
|
|
10
|
+
* 2. Otherwise `source.kind === "cms"` → {@link CmsResultRow}.
|
|
11
|
+
* 3. Otherwise (SObject) → {@link DefaultResultRow}.
|
|
12
|
+
*
|
|
13
|
+
* Returning the same union the callers already use (`renderer | false`) lets a
|
|
14
|
+
* component early-return `null` for a hidden source without special-casing.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createElement, type ReactNode } from "react";
|
|
18
|
+
import type { SourceConfig } from "../../types";
|
|
19
|
+
import { DefaultResultRow } from "./DefaultResultRow";
|
|
20
|
+
import { CmsResultRow } from "./CmsResultRow";
|
|
21
|
+
|
|
22
|
+
/** A per-node renderer, or `false` to hide the source's results. */
|
|
23
|
+
export type ResultRenderer = ((node: unknown) => ReactNode) | false;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the renderer for one source. See the module doc for the precedence
|
|
27
|
+
* rules. `renderResult` is the optional per-source override map from `<Search>`.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveResultRenderer(
|
|
30
|
+
source: SourceConfig,
|
|
31
|
+
renderResult?: Record<string, ResultRenderer>,
|
|
32
|
+
): ResultRenderer {
|
|
33
|
+
const override = renderResult?.[source.key];
|
|
34
|
+
// A config override wins in every mode — including `false` (hide).
|
|
35
|
+
if (override !== undefined) return override;
|
|
36
|
+
|
|
37
|
+
// Assign a `displayName` so React DevTools shows a readable label for the
|
|
38
|
+
// per-node component factory and the `react/display-name` lint rule is
|
|
39
|
+
// satisfied. (This is a debug label only — it does not affect reference
|
|
40
|
+
// identity; a new function is still created on each call.)
|
|
41
|
+
if (source.kind === "cms") {
|
|
42
|
+
const renderCmsRow = (node: unknown) => createElement(CmsResultRow, { node, source });
|
|
43
|
+
renderCmsRow.displayName = "CmsResultRowRenderer";
|
|
44
|
+
return renderCmsRow;
|
|
45
|
+
}
|
|
46
|
+
const renderDefaultRow = (node: unknown) => createElement(DefaultResultRow, { node, source });
|
|
47
|
+
renderDefaultRow.displayName = "DefaultResultRowRenderer";
|
|
48
|
+
return renderDefaultRow;
|
|
49
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pagination": {
|
|
3
|
+
"mode": "per-source",
|
|
4
|
+
"pageSize": 10,
|
|
5
|
+
"pageSizeOptions": [5, 10, 20]
|
|
6
|
+
},
|
|
7
|
+
"sources": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "sobject",
|
|
10
|
+
"key": "accounts",
|
|
11
|
+
"objectName": "Account",
|
|
12
|
+
"label": "Accounts",
|
|
13
|
+
"labelSingular": "Account",
|
|
14
|
+
"routePattern": "/accounts/:id",
|
|
15
|
+
"searchableFields": ["Name", "Phone", "Industry"],
|
|
16
|
+
"displayFields": [
|
|
17
|
+
"Name",
|
|
18
|
+
"Industry",
|
|
19
|
+
"Type",
|
|
20
|
+
"Phone",
|
|
21
|
+
"AnnualRevenue",
|
|
22
|
+
{ "name": "Owner", "subfields": ["Name"] }
|
|
23
|
+
],
|
|
24
|
+
"filterBy": [
|
|
25
|
+
{ "field": "Industry", "label": "Industry", "type": "picklist" },
|
|
26
|
+
{ "field": "Type", "label": "Type", "type": "picklist" },
|
|
27
|
+
{ "field": "AnnualRevenue", "label": "Annual Revenue", "type": "numeric" }
|
|
28
|
+
],
|
|
29
|
+
"sortBy": [
|
|
30
|
+
{ "field": "Name", "label": "Name" },
|
|
31
|
+
{ "field": "Industry", "label": "Industry" },
|
|
32
|
+
{ "field": "AnnualRevenue", "label": "Annual Revenue" },
|
|
33
|
+
{ "field": "CreatedDate", "label": "Created Date" }
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "sobject",
|
|
38
|
+
"key": "contacts",
|
|
39
|
+
"objectName": "Contact",
|
|
40
|
+
"label": "Contacts",
|
|
41
|
+
"labelSingular": "Contact",
|
|
42
|
+
"searchableFields": ["Name", "Email", "Phone"],
|
|
43
|
+
"displayFields": [
|
|
44
|
+
"Name",
|
|
45
|
+
"Title",
|
|
46
|
+
"Email",
|
|
47
|
+
"Phone",
|
|
48
|
+
{ "name": "Account", "subfields": ["Name"] }
|
|
49
|
+
],
|
|
50
|
+
"sortBy": [
|
|
51
|
+
{ "field": "Name", "label": "Name" },
|
|
52
|
+
{ "field": "CreatedDate", "label": "Created Date" }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"kind": "sobject",
|
|
57
|
+
"key": "opportunities",
|
|
58
|
+
"objectName": "Opportunity",
|
|
59
|
+
"label": "Opportunities",
|
|
60
|
+
"labelSingular": "Opportunity",
|
|
61
|
+
"searchableFields": ["Name"],
|
|
62
|
+
"displayFields": [
|
|
63
|
+
"Name",
|
|
64
|
+
"StageName",
|
|
65
|
+
"Amount",
|
|
66
|
+
"CloseDate",
|
|
67
|
+
{ "name": "Account", "subfields": ["Name"] }
|
|
68
|
+
],
|
|
69
|
+
"filterBy": [
|
|
70
|
+
{ "field": "StageName", "label": "Stage", "type": "picklist" },
|
|
71
|
+
{ "field": "Amount", "label": "Amount", "type": "numeric" },
|
|
72
|
+
{ "field": "CloseDate", "label": "Close Date", "type": "daterange" }
|
|
73
|
+
],
|
|
74
|
+
"sortBy": [
|
|
75
|
+
{ "field": "CloseDate", "label": "Close Date" },
|
|
76
|
+
{ "field": "Amount", "label": "Amount" },
|
|
77
|
+
{ "field": "Name", "label": "Name" }
|
|
78
|
+
],
|
|
79
|
+
"defaultSort": { "field": "CloseDate", "direction": "DESC" }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"kind": "cms",
|
|
83
|
+
"key": "content",
|
|
84
|
+
"label": "Content",
|
|
85
|
+
"labelSingular": "Content"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimum trimmed keyword length before a search fires. Both the SObject and
|
|
3
|
+
* CMS search backends reject terms shorter than this (returning errors that the
|
|
4
|
+
* UI would otherwise render as "Search failed for this source." on every
|
|
5
|
+
* source), so the query is gated client-side until the user types at least this
|
|
6
|
+
* many characters. Shared so every `<Search>` consumer inherits the same floor.
|
|
7
|
+
*/
|
|
8
|
+
export const MIN_QUERY_LENGTH = 3;
|
|
@@ -7,13 +7,9 @@ interface UseAsyncDataResult<T> {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Runs an async fetcher on mount and whenever `deps` change.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* A cleanup flag prevents state updates if the component unmounts
|
|
15
|
-
* or deps change before the fetch completes (avoids React warnings
|
|
16
|
-
* and stale updates from out-of-order responses).
|
|
10
|
+
* Runs an async fetcher on mount and whenever `deps` change. Stale responses
|
|
11
|
+
* (deps changed during a fetch, or component unmounted) are dropped via a
|
|
12
|
+
* cancellation flag.
|
|
17
13
|
*/
|
|
18
14
|
export function useAsyncData<T>(
|
|
19
15
|
fetcher: () => Promise<T>,
|
|
@@ -29,7 +25,6 @@ export function useAsyncData<T>(
|
|
|
29
25
|
fetcherRef.current = fetcher;
|
|
30
26
|
});
|
|
31
27
|
|
|
32
|
-
// Detect dep changes during render to reset loading state and bump generation
|
|
33
28
|
const [prevDeps, setPrevDeps] = useState(deps);
|
|
34
29
|
if (deps.length !== prevDeps.length || deps.some((d, i) => d !== prevDeps[i])) {
|
|
35
30
|
setPrevDeps(deps);
|
|
@@ -40,7 +35,6 @@ export function useAsyncData<T>(
|
|
|
40
35
|
|
|
41
36
|
useEffect(() => {
|
|
42
37
|
let cancelled = false;
|
|
43
|
-
|
|
44
38
|
fetcherRef
|
|
45
39
|
.current()
|
|
46
40
|
.then((result) => {
|
|
@@ -53,7 +47,6 @@ export function useAsyncData<T>(
|
|
|
53
47
|
.finally(() => {
|
|
54
48
|
if (!cancelled) setLoading(false);
|
|
55
49
|
});
|
|
56
|
-
|
|
57
50
|
return () => {
|
|
58
51
|
cancelled = true;
|
|
59
52
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caches and serves distinct picklist values fetched via the GraphQL
|
|
3
|
+
* aggregate API. The cache key is `${objectName}.${fieldName}`; entries
|
|
4
|
+
* persist for the lifetime of the page so repeated mounts of the same
|
|
5
|
+
* picklist filter share the same response.
|
|
6
|
+
*
|
|
7
|
+
* Pass `null` to short-circuit the fetch (e.g. when inline options are
|
|
8
|
+
* available on the filter config).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from "react";
|
|
12
|
+
import { fetchDistinctValues, type PicklistOption } from "../api/distinctValuesService";
|
|
13
|
+
|
|
14
|
+
const cache = new Map<string, PicklistOption[]>();
|
|
15
|
+
const inflight = new Map<string, Promise<PicklistOption[]>>();
|
|
16
|
+
|
|
17
|
+
interface DistinctValuesQuery {
|
|
18
|
+
objectName: string;
|
|
19
|
+
fieldName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useDistinctValues(query: DistinctValuesQuery | null): PicklistOption[] | null {
|
|
23
|
+
const cacheKey = query ? `${query.objectName}.${query.fieldName}` : null;
|
|
24
|
+
const [options, setOptions] = useState<PicklistOption[] | null>(() =>
|
|
25
|
+
cacheKey ? (cache.get(cacheKey) ?? null) : null,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!query || !cacheKey) return;
|
|
30
|
+
const cached = cache.get(cacheKey);
|
|
31
|
+
if (cached) {
|
|
32
|
+
setOptions(cached);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
const existing = inflight.get(cacheKey);
|
|
37
|
+
const promise =
|
|
38
|
+
existing ??
|
|
39
|
+
fetchDistinctValues(query.objectName, query.fieldName).then((result) => {
|
|
40
|
+
cache.set(cacheKey, result);
|
|
41
|
+
inflight.delete(cacheKey);
|
|
42
|
+
return result;
|
|
43
|
+
});
|
|
44
|
+
if (!existing) inflight.set(cacheKey, promise);
|
|
45
|
+
promise
|
|
46
|
+
.then((result) => {
|
|
47
|
+
if (!cancelled) setOptions(result);
|
|
48
|
+
})
|
|
49
|
+
.catch((err: unknown) => {
|
|
50
|
+
inflight.delete(cacheKey);
|
|
51
|
+
console.error(`Failed to fetch distinct values for ${cacheKey}:`, err);
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
56
|
+
}, [cacheKey, query]);
|
|
57
|
+
|
|
58
|
+
return options;
|
|
59
|
+
}
|