@salesforce/ui-bundle-template-app-react-template-b2x 1.117.2

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.
Files changed (171) hide show
  1. package/LICENSE.txt +82 -0
  2. package/README.md +52 -0
  3. package/dist/.forceignore +15 -0
  4. package/dist/.husky/pre-commit +4 -0
  5. package/dist/.prettierignore +11 -0
  6. package/dist/.prettierrc +17 -0
  7. package/dist/AGENT.md +193 -0
  8. package/dist/CHANGELOG.md +2128 -0
  9. package/dist/README.md +72 -0
  10. package/dist/config/project-scratch-def.json +13 -0
  11. package/dist/eslint.config.js +7 -0
  12. package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls +68 -0
  13. package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls-meta.xml +5 -0
  14. package/dist/force-app/main/default/classes/UIBundleChangePassword.cls +77 -0
  15. package/dist/force-app/main/default/classes/UIBundleChangePassword.cls-meta.xml +5 -0
  16. package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls +71 -0
  17. package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls-meta.xml +5 -0
  18. package/dist/force-app/main/default/classes/UIBundleLogin.cls +105 -0
  19. package/dist/force-app/main/default/classes/UIBundleLogin.cls-meta.xml +5 -0
  20. package/dist/force-app/main/default/classes/UIBundleRegistration.cls +162 -0
  21. package/dist/force-app/main/default/classes/UIBundleRegistration.cls-meta.xml +5 -0
  22. package/dist/force-app/main/default/digitalExperienceConfigs/reactexternalapp1.digitalExperienceConfig-meta.xml +8 -0
  23. package/dist/force-app/main/default/digitalExperiences/site/reactexternalapp1/reactexternalapp1.digitalExperience-meta.xml +11 -0
  24. package/dist/force-app/main/default/digitalExperiences/site/reactexternalapp1/sfdc_cms__site/reactexternalapp1/_meta.json +5 -0
  25. package/dist/force-app/main/default/digitalExperiences/site/reactexternalapp1/sfdc_cms__site/reactexternalapp1/content.json +10 -0
  26. package/dist/force-app/main/default/networks/reactexternalapp.network-meta.xml +60 -0
  27. package/dist/force-app/main/default/package.xml +20 -0
  28. package/dist/force-app/main/default/sites/reactexternalapp.site-meta.xml +31 -0
  29. package/dist/force-app/main/default/uiBundles/reactexternalapp/.forceignore +15 -0
  30. package/dist/force-app/main/default/uiBundles/reactexternalapp/.graphqlrc.yml +2 -0
  31. package/dist/force-app/main/default/uiBundles/reactexternalapp/.prettierignore +9 -0
  32. package/dist/force-app/main/default/uiBundles/reactexternalapp/.prettierrc +11 -0
  33. package/dist/force-app/main/default/uiBundles/reactexternalapp/CHANGELOG.md +10 -0
  34. package/dist/force-app/main/default/uiBundles/reactexternalapp/README.md +35 -0
  35. package/dist/force-app/main/default/uiBundles/reactexternalapp/codegen.yml +95 -0
  36. package/dist/force-app/main/default/uiBundles/reactexternalapp/components.json +18 -0
  37. package/dist/force-app/main/default/uiBundles/reactexternalapp/e2e/app.spec.ts +17 -0
  38. package/dist/force-app/main/default/uiBundles/reactexternalapp/eslint.config.js +169 -0
  39. package/dist/force-app/main/default/uiBundles/reactexternalapp/index.html +12 -0
  40. package/dist/force-app/main/default/uiBundles/reactexternalapp/package.json +70 -0
  41. package/dist/force-app/main/default/uiBundles/reactexternalapp/playwright.config.ts +24 -0
  42. package/dist/force-app/main/default/uiBundles/reactexternalapp/reactexternalapp.uibundle-meta.xml +8 -0
  43. package/dist/force-app/main/default/uiBundles/reactexternalapp/scripts/get-graphql-schema.mjs +68 -0
  44. package/dist/force-app/main/default/uiBundles/reactexternalapp/scripts/rewrite-e2e-assets.mjs +23 -0
  45. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/accountSearchService.ts +46 -0
  46. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/distinctAccountIndustries.graphql +19 -0
  47. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/distinctAccountTypes.graphql +19 -0
  48. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/getAccountDetail.graphql +121 -0
  49. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/searchAccounts.graphql +51 -0
  50. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/graphql-operations-types.ts +11260 -0
  51. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/graphqlClient.ts +25 -0
  52. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/app.tsx +16 -0
  53. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/appLayout.tsx +83 -0
  54. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/book.svg +3 -0
  55. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/copy.svg +4 -0
  56. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/rocket.svg +3 -0
  57. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/star.svg +3 -0
  58. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/codey-1.png +0 -0
  59. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/codey-2.png +0 -0
  60. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/codey-3.png +0 -0
  61. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/vibe-codey.svg +194 -0
  62. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/alerts/status-alert.tsx +49 -0
  63. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/layouts/card-layout.tsx +29 -0
  64. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/alert.tsx +76 -0
  65. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/badge.tsx +48 -0
  66. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/breadcrumb.tsx +109 -0
  67. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/button.tsx +67 -0
  68. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/calendar.tsx +232 -0
  69. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/card.tsx +103 -0
  70. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/checkbox.tsx +32 -0
  71. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/collapsible.tsx +33 -0
  72. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/datePicker.tsx +127 -0
  73. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/dialog.tsx +162 -0
  74. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/field.tsx +237 -0
  75. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/index.ts +84 -0
  76. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/input.tsx +19 -0
  77. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/label.tsx +22 -0
  78. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/pagination.tsx +132 -0
  79. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/popover.tsx +89 -0
  80. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/select.tsx +193 -0
  81. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/separator.tsx +26 -0
  82. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/skeleton.tsx +14 -0
  83. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/sonner.tsx +20 -0
  84. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/spinner.tsx +16 -0
  85. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/table.tsx +114 -0
  86. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/tabs.tsx +88 -0
  87. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/api/userProfileApi.ts +95 -0
  88. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/authHelpers.ts +73 -0
  89. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/authenticationConfig.ts +61 -0
  90. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/context/AuthContext.tsx +95 -0
  91. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/footers/footer-link.tsx +36 -0
  92. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/forms/auth-form.tsx +81 -0
  93. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/forms/submit-button.tsx +49 -0
  94. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/form.tsx +120 -0
  95. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/useCountdownTimer.ts +266 -0
  96. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/useRetryWithBackoff.ts +109 -0
  97. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layout/card-skeleton.tsx +38 -0
  98. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layout/centered-page-layout.tsx +87 -0
  99. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/AuthAppLayout.tsx +12 -0
  100. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/authenticationRouteLayout.tsx +21 -0
  101. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/privateRouteLayout.tsx +44 -0
  102. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ChangePassword.tsx +107 -0
  103. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ForgotPassword.tsx +73 -0
  104. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Login.tsx +97 -0
  105. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Profile.tsx +161 -0
  106. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Register.tsx +133 -0
  107. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ResetPassword.tsx +107 -0
  108. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +602 -0
  109. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/sessionTimeout/sessionTimeService.ts +149 -0
  110. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts +77 -0
  111. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/utils/helpers.ts +121 -0
  112. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  113. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  114. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  115. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  116. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  117. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  118. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
  119. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/Home.tsx +34 -0
  120. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/api/objectSearchService.ts +84 -0
  121. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/ActiveFilters.tsx +89 -0
  122. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/FilterContext.tsx +83 -0
  123. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  124. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/PaginationControls.tsx +109 -0
  125. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/SearchBar.tsx +41 -0
  126. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/SortControl.tsx +143 -0
  127. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/BooleanFilter.tsx +78 -0
  128. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/DateFilter.tsx +128 -0
  129. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
  130. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
  131. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
  132. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
  133. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SearchFilter.tsx +50 -0
  134. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SelectFilter.tsx +97 -0
  135. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/TextFilter.tsx +91 -0
  136. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useAsyncData.ts +54 -0
  137. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
  138. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useDebouncedCallback.ts +34 -0
  139. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useObjectSearchParams.ts +252 -0
  140. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/debounce.ts +25 -0
  141. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/fieldUtils.ts +29 -0
  142. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/filterUtils.ts +395 -0
  143. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/sortUtils.ts +38 -0
  144. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/lib/utils.ts +6 -0
  145. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/navigationMenu.tsx +80 -0
  146. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountObjectDetailPage.tsx +361 -0
  147. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountSearch.tsx +305 -0
  148. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/Home.tsx +34 -0
  149. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/NotFound.tsx +18 -0
  150. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/router-utils.tsx +35 -0
  151. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/routes.tsx +81 -0
  152. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/styles/global.css +135 -0
  153. package/dist/force-app/main/default/uiBundles/reactexternalapp/tsconfig.json +42 -0
  154. package/dist/force-app/main/default/uiBundles/reactexternalapp/tsconfig.node.json +13 -0
  155. package/dist/force-app/main/default/uiBundles/reactexternalapp/ui-bundle.json +7 -0
  156. package/dist/force-app/main/default/uiBundles/reactexternalapp/vite-env.d.ts +1 -0
  157. package/dist/force-app/main/default/uiBundles/reactexternalapp/vite.config.ts +106 -0
  158. package/dist/force-app/main/default/uiBundles/reactexternalapp/vitest-env.d.ts +2 -0
  159. package/dist/force-app/main/default/uiBundles/reactexternalapp/vitest.config.ts +11 -0
  160. package/dist/force-app/main/default/uiBundles/reactexternalapp/vitest.setup.ts +1 -0
  161. package/dist/jest.config.js +6 -0
  162. package/dist/package-lock.json +9995 -0
  163. package/dist/package.json +40 -0
  164. package/dist/scripts/apex/hello.apex +10 -0
  165. package/dist/scripts/graphql-search.sh +191 -0
  166. package/dist/scripts/prepare-import-unique-fields.js +122 -0
  167. package/dist/scripts/setup-cli.mjs +563 -0
  168. package/dist/scripts/sf-project-setup.mjs +66 -0
  169. package/dist/scripts/soql/account.soql +6 -0
  170. package/dist/sfdx-project.json +12 -0
  171. package/package.json +40 -0
@@ -0,0 +1,305 @@
1
+ import { useMemo, useState } from "react";
2
+ import { Link } from "react-router";
3
+ import { AlertCircle, ChevronDown, SearchX } from "lucide-react";
4
+ import {
5
+ searchAccounts,
6
+ fetchDistinctIndustries,
7
+ fetchDistinctTypes,
8
+ } from "../api/account/accountSearchService";
9
+ import { useCachedAsyncData } from "../features/object-search/hooks/useCachedAsyncData";
10
+ import { fieldValue } from "../features/object-search/utils/fieldUtils";
11
+ import { useObjectSearchParams } from "../features/object-search/hooks/useObjectSearchParams";
12
+ import { Alert, AlertTitle, AlertDescription } from "../components/ui/alert";
13
+ import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
14
+ import { Button } from "../components/ui/button";
15
+ import {
16
+ Collapsible,
17
+ CollapsibleContent,
18
+ CollapsibleTrigger,
19
+ } from "../components/ui/collapsible";
20
+ import { Skeleton } from "../components/ui/skeleton";
21
+ import {
22
+ FilterProvider,
23
+ FilterResetButton,
24
+ } from "../features/object-search/components/FilterContext";
25
+ import { SearchFilter } from "../features/object-search/components/filters/SearchFilter";
26
+ import { TextFilter } from "../features/object-search/components/filters/TextFilter";
27
+ import { SelectFilter } from "../features/object-search/components/filters/SelectFilter";
28
+ import { MultiSelectFilter } from "../features/object-search/components/filters/MultiSelectFilter";
29
+ import { NumericRangeFilter } from "../features/object-search/components/filters/NumericRangeFilter";
30
+ import { DateFilter } from "../features/object-search/components/filters/DateFilter";
31
+ import { DateRangeFilter } from "../features/object-search/components/filters/DateRangeFilter";
32
+ import { ActiveFilters } from "../features/object-search/components/ActiveFilters";
33
+ import { SortControl } from "../features/object-search/components/SortControl";
34
+ import type { FilterFieldConfig } from "../features/object-search/utils/filterUtils";
35
+ import type { SortFieldConfig } from "../features/object-search/utils/sortUtils";
36
+ import type { Account_Filter, Account_OrderBy } from "../api/graphql-operations-types";
37
+ import type { AccountSearchResult } from "../api/account/accountSearchService";
38
+ import { ObjectBreadcrumb } from "../features/object-search/components/ObjectBreadcrumb";
39
+ import PaginationControls from "../features/object-search/components/PaginationControls";
40
+ import type { PaginationConfig } from "../features/object-search/hooks/useObjectSearchParams";
41
+
42
+ const PAGINATION_CONFIG: PaginationConfig = {
43
+ defaultPageSize: 6,
44
+ validPageSizes: [6, 12, 24, 48],
45
+ };
46
+
47
+ type AccountNode = NonNullable<
48
+ NonNullable<NonNullable<AccountSearchResult["edges"]>[number]>["node"]
49
+ >;
50
+
51
+ const FILTER_CONFIGS: FilterFieldConfig[] = [
52
+ {
53
+ field: "search",
54
+ label: "Search",
55
+ type: "search",
56
+ searchFields: ["Name", "Phone", "Industry"],
57
+ placeholder: "Search by name, phone, or industry...",
58
+ },
59
+ { field: "Name", label: "Account Name", type: "text", placeholder: "Search by name..." },
60
+ { field: "Industry", label: "Industry", type: "picklist" },
61
+ { field: "Type", label: "Type", type: "multipicklist" },
62
+ { field: "AnnualRevenue", label: "Annual Revenue", type: "numeric" },
63
+ { field: "CreatedDate", label: "Created Date", type: "datetime" },
64
+ { field: "LastModifiedDate", label: "Last Modified Date", type: "datetimerange" },
65
+ ];
66
+
67
+ const ACCOUNT_SORT_CONFIGS: SortFieldConfig<keyof Account_OrderBy>[] = [
68
+ { field: "Name", label: "Name" },
69
+ { field: "AnnualRevenue", label: "Annual Revenue" },
70
+ { field: "Industry", label: "Industry" },
71
+ { field: "CreatedDate", label: "Created Date" },
72
+ ];
73
+
74
+ // -- Component --------------------------------------------------------------
75
+
76
+ export default function AccountSearch() {
77
+ const [filtersOpen, setFiltersOpen] = useState(true);
78
+ const { data: industryOptions } = useCachedAsyncData(fetchDistinctIndustries, [], {
79
+ key: "distinctIndustries",
80
+ ttl: 300_000,
81
+ });
82
+ const { data: typeOptions } = useCachedAsyncData(fetchDistinctTypes, [], {
83
+ key: "distinctTypes",
84
+ ttl: 300_000,
85
+ });
86
+
87
+ const { filters, sort, query, pagination, resetAll } = useObjectSearchParams<
88
+ Account_Filter,
89
+ Account_OrderBy
90
+ >(FILTER_CONFIGS, ACCOUNT_SORT_CONFIGS, PAGINATION_CONFIG);
91
+
92
+ const searchKey = `accounts:${JSON.stringify({ where: query.where, orderBy: query.orderBy, first: pagination.pageSize, after: pagination.afterCursor })}`;
93
+ const { data, loading, error } = useCachedAsyncData(
94
+ () =>
95
+ searchAccounts({
96
+ where: query.where,
97
+ orderBy: query.orderBy,
98
+ first: pagination.pageSize,
99
+ after: pagination.afterCursor,
100
+ }),
101
+ [query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
102
+ { key: searchKey },
103
+ );
104
+
105
+ const pageInfo = data?.pageInfo;
106
+ const totalCount = data?.totalCount;
107
+ const hasNextPage = pageInfo?.hasNextPage ?? false;
108
+ const hasPreviousPage = pagination.pageIndex > 0;
109
+
110
+ const validAccountNodes = useMemo(
111
+ () =>
112
+ (data?.edges ?? []).reduce<AccountNode[]>((acc, edge) => {
113
+ if (edge?.node) acc.push(edge.node);
114
+ return acc;
115
+ }, []),
116
+ [data?.edges],
117
+ );
118
+
119
+ return (
120
+ <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
121
+ <ObjectBreadcrumb listPath="/accounts" listLabel="Accounts" />
122
+
123
+ <h1 className="text-2xl font-bold mb-4">Search Accounts</h1>
124
+
125
+ <div className="flex flex-col lg:flex-row gap-6">
126
+ {/* Sidebar — Filter Panel */}
127
+ <aside className="w-full lg:w-80 shrink-0">
128
+ <FilterProvider
129
+ filters={filters.active}
130
+ onFilterChange={filters.set}
131
+ onFilterRemove={filters.remove}
132
+ onReset={resetAll}
133
+ >
134
+ <Card>
135
+ <Collapsible open={filtersOpen} onOpenChange={setFiltersOpen}>
136
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
137
+ <CardTitle className="text-base font-semibold">
138
+ <h2>Filters</h2>
139
+ </CardTitle>
140
+ <div className="flex items-center gap-1">
141
+ <FilterResetButton variant="destructive" size="sm" />
142
+ <CollapsibleTrigger asChild>
143
+ <Button variant="ghost" size="icon">
144
+ <ChevronDown
145
+ className={`h-4 w-4 transition-transform ${filtersOpen ? "" : "-rotate-90"}`}
146
+ />
147
+ <span className="sr-only">Toggle filters</span>
148
+ </Button>
149
+ </CollapsibleTrigger>
150
+ </div>
151
+ </CardHeader>
152
+ <CollapsibleContent>
153
+ <CardContent className="space-y-1 pt-0">
154
+ <SearchFilter
155
+ field="search"
156
+ label="Search"
157
+ placeholder="Search by name, phone, or industry..."
158
+ />
159
+ <TextFilter field="Name" label="Account Name" placeholder="Search by name..." />
160
+ <SelectFilter
161
+ field="Industry"
162
+ label="Industry"
163
+ options={industryOptions ?? []}
164
+ />
165
+ <MultiSelectFilter field="Type" label="Type" options={typeOptions ?? []} />
166
+ <NumericRangeFilter field="AnnualRevenue" label="Annual Revenue" />
167
+ <DateFilter field="CreatedDate" label="Created Date" filterType="datetime" />
168
+ <DateRangeFilter
169
+ field="LastModifiedDate"
170
+ label="Last Modified Date"
171
+ filterType="datetimerange"
172
+ />
173
+ </CardContent>
174
+ </CollapsibleContent>
175
+ </Collapsible>
176
+ </Card>
177
+ </FilterProvider>
178
+ </aside>
179
+
180
+ {/* Main area — Sort + Results */}
181
+ <div className="flex-1 min-w-0">
182
+ {/* Sort control + active filters */}
183
+ <div className="flex flex-wrap items-center gap-2 mb-4">
184
+ <SortControl
185
+ configs={ACCOUNT_SORT_CONFIGS}
186
+ sort={sort.current}
187
+ onSortChange={sort.set}
188
+ />
189
+ <ActiveFilters filters={filters.active} onRemove={filters.remove} />
190
+ </div>
191
+
192
+ <div className="min-h-112">
193
+ {/* Loading state */}
194
+ {loading && (
195
+ <>
196
+ <Skeleton className="h-5 w-30 mb-3" />
197
+ <div className="divide-y">
198
+ {Array.from({ length: pagination.pageSize }, (_, i) => (
199
+ <div key={i} className="flex items-center justify-between py-3">
200
+ <div className="space-y-2">
201
+ <Skeleton className="h-5 w-40" />
202
+ <Skeleton className="h-4 w-28" />
203
+ </div>
204
+ <div className="space-y-2 flex flex-col items-end">
205
+ <Skeleton className="h-4 w-24" />
206
+ <Skeleton className="h-4 w-20" />
207
+ </div>
208
+ </div>
209
+ ))}
210
+ </div>
211
+ </>
212
+ )}
213
+
214
+ {/* Error state */}
215
+ {error && (
216
+ <>
217
+ <p className="text-sm text-muted-foreground mb-3">0 accounts found</p>
218
+ <Alert variant="destructive" role="alert">
219
+ <AlertCircle />
220
+ <AlertTitle>Failed to load accounts</AlertTitle>
221
+ <AlertDescription>
222
+ Something went wrong while loading accounts. Please try again later.
223
+ </AlertDescription>
224
+ </Alert>
225
+ </>
226
+ )}
227
+
228
+ {/* Results list */}
229
+ {!loading && !error && validAccountNodes.length > 0 && (
230
+ <>
231
+ <p className="text-sm text-muted-foreground mb-3">
232
+ {totalCount != null && (hasNextPage || hasPreviousPage)
233
+ ? `${totalCount} account${totalCount !== 1 ? "s" : ""} found`
234
+ : `Showing ${validAccountNodes.length} account${validAccountNodes.length !== 1 ? "s" : ""}`}
235
+ </p>
236
+ <AccountResultsList nodes={validAccountNodes} />
237
+ </>
238
+ )}
239
+
240
+ {/* No results state */}
241
+ {!loading && !error && validAccountNodes.length === 0 && (
242
+ <div className="flex flex-col items-center justify-center py-16 text-center">
243
+ <SearchX className="size-12 text-muted-foreground mb-4" />
244
+ <h2 className="text-lg font-semibold mb-1">No accounts found</h2>
245
+ <p className="text-sm text-muted-foreground">
246
+ Try adjusting your filters or search criteria.
247
+ </p>
248
+ </div>
249
+ )}
250
+ </div>
251
+
252
+ {/* Pagination — always visible, disabled while loading or on error */}
253
+ <PaginationControls
254
+ pageIndex={pagination.pageIndex}
255
+ hasNextPage={hasNextPage}
256
+ hasPreviousPage={hasPreviousPage}
257
+ pageSize={pagination.pageSize}
258
+ pageSizeOptions={PAGINATION_CONFIG.validPageSizes}
259
+ onNextPage={() => {
260
+ if (pageInfo?.endCursor) pagination.goToNextPage(pageInfo.endCursor);
261
+ }}
262
+ onPreviousPage={pagination.goToPreviousPage}
263
+ onPageSizeChange={pagination.setPageSize}
264
+ disabled={loading || !!error}
265
+ />
266
+ </div>
267
+ </div>
268
+ </div>
269
+ );
270
+ }
271
+
272
+ // -- Result Components ------------------------------------------------------
273
+
274
+ function AccountResultsList({ nodes }: { nodes: AccountNode[] }) {
275
+ return (
276
+ <ul className="divide-y">
277
+ {nodes.map((node) => (
278
+ <AccountResultItem key={node.Id} node={node} />
279
+ ))}
280
+ </ul>
281
+ );
282
+ }
283
+
284
+ function AccountResultItem({ node }: { node: AccountNode }) {
285
+ return (
286
+ <li>
287
+ <Link
288
+ to={`/accounts/${node.Id}`}
289
+ className="flex items-center justify-between py-3 px-3 -mx-3 rounded-md transition-colors hover:bg-accent"
290
+ >
291
+ <div>
292
+ <span className="font-medium">{fieldValue(node.Name) ?? "\u2014"}</span>
293
+ <p className="text-sm text-muted-foreground">
294
+ {[fieldValue(node.Industry), fieldValue(node.Type)].filter(Boolean).join(" \u00B7 ") ||
295
+ "\u2014"}
296
+ </p>
297
+ </div>
298
+ <div className="text-right text-sm">
299
+ <p>{fieldValue(node.Phone) ?? ""}</p>
300
+ <p className="text-muted-foreground">{fieldValue(node.Owner?.Name) ?? ""}</p>
301
+ </div>
302
+ </Link>
303
+ </li>
304
+ );
305
+ }
@@ -0,0 +1,34 @@
1
+ import { useState } from "react";
2
+ import { useNavigate } from "react-router";
3
+ import { SearchBar } from "../features/object-search/components/SearchBar";
4
+ import { Button } from "../components/ui/button";
5
+
6
+ export default function HomePage() {
7
+ const navigate = useNavigate();
8
+ const [text, setText] = useState("");
9
+
10
+ const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
11
+ e.preventDefault();
12
+ const params = text ? `?q=${encodeURIComponent(text)}` : "";
13
+ navigate(`/accounts${params}`);
14
+ };
15
+
16
+ return (
17
+ <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
18
+ <div className="flex items-center gap-6 mb-6">
19
+ <h1 className="text-2xl font-bold">Account Search</h1>
20
+ <Button variant="outline" size="sm" onClick={() => navigate("/accounts")}>
21
+ Browse All Accounts
22
+ </Button>
23
+ </div>
24
+ <form onSubmit={handleSubmit} className="flex gap-2">
25
+ <SearchBar
26
+ placeholder="Search by name, phone, or industry..."
27
+ value={text}
28
+ handleChange={setText}
29
+ />
30
+ <Button type="submit">Search</Button>
31
+ </form>
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,18 @@
1
+ import { Link } from 'react-router';
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
6
+ <div className="text-center">
7
+ <h1 className="text-4xl font-bold text-gray-900 mb-4">404</h1>
8
+ <p className="text-lg text-gray-600 mb-8">Page not found</p>
9
+ <Link
10
+ to="/"
11
+ className="inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
12
+ >
13
+ Go to Home
14
+ </Link>
15
+ </div>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,35 @@
1
+ import type { RouteObject } from 'react-router';
2
+ import { routes } from './routes';
3
+
4
+ export type RouteWithFullPath = RouteObject & { fullPath: string };
5
+
6
+ const flatMapRoutes = (
7
+ route: RouteObject,
8
+ parentPath: string = ''
9
+ ): RouteWithFullPath[] => {
10
+ let fullPath: string;
11
+
12
+ if (route.index) {
13
+ fullPath = parentPath || '/';
14
+ } else if (route.path) {
15
+ if (route.path.startsWith('/')) {
16
+ fullPath = route.path;
17
+ } else {
18
+ fullPath =
19
+ parentPath === '/' ? `/${route.path}` : `${parentPath}/${route.path}`;
20
+ }
21
+ } else {
22
+ fullPath = parentPath;
23
+ }
24
+
25
+ const routeWithPath = { ...route, fullPath };
26
+
27
+ const childRoutes =
28
+ route.children?.flatMap(child => flatMapRoutes(child, fullPath)) || [];
29
+
30
+ return [routeWithPath, ...childRoutes];
31
+ };
32
+
33
+ export const getAllRoutes = (): RouteWithFullPath[] => {
34
+ return routes.flatMap(route => flatMapRoutes(route));
35
+ };
@@ -0,0 +1,81 @@
1
+ import type { RouteObject } from 'react-router';
2
+ import Home from './pages/Home';
3
+ import NotFound from './pages/NotFound';
4
+ import Login from "./features/authentication/pages/Login";
5
+ import Register from "./features/authentication/pages/Register";
6
+ import ForgotPassword from "./features/authentication/pages/ForgotPassword";
7
+ import ResetPassword from "./features/authentication/pages/ResetPassword";
8
+ import Profile from "./features/authentication/pages/Profile";
9
+ import ChangePassword from "./features/authentication/pages/ChangePassword";
10
+ import AuthenticationRoute from "./features/authentication/layouts/authenticationRouteLayout";
11
+ import PrivateRoute from "./features/authentication/layouts/privateRouteLayout";
12
+ import { ROUTES } from "./features/authentication/authenticationConfig";
13
+ import AccountSearch from "./pages/AccountSearch";
14
+ import AccountObjectDetail from "./pages/AccountObjectDetailPage";
15
+ import AuthAppLayout from "./features/authentication/layouts/AuthAppLayout";
16
+
17
+ export const routes: RouteObject[] = [
18
+ {
19
+ path: "/",
20
+ element: <AuthAppLayout />,
21
+ children: [
22
+ {
23
+ index: true,
24
+ element: <Home />,
25
+ handle: { showInNavigation: true, label: "Home" }
26
+ },
27
+ {
28
+ path: '*',
29
+ element: <NotFound />
30
+ },
31
+ {
32
+ element: <AuthenticationRoute />,
33
+ children: [
34
+ {
35
+ path: ROUTES.LOGIN.PATH,
36
+ element: <Login />,
37
+ handle: { showInNavigation: true, label: "Login", title: ROUTES.LOGIN.TITLE }
38
+ },
39
+ {
40
+ path: ROUTES.REGISTER.PATH,
41
+ element: <Register />,
42
+ handle: { showInNavigation: false, title: ROUTES.REGISTER.TITLE }
43
+ },
44
+ {
45
+ path: ROUTES.FORGOT_PASSWORD.PATH,
46
+ element: <ForgotPassword />,
47
+ handle: { showInNavigation: false, title: ROUTES.FORGOT_PASSWORD.TITLE }
48
+ },
49
+ {
50
+ path: ROUTES.RESET_PASSWORD.PATH,
51
+ element: <ResetPassword />,
52
+ handle: { showInNavigation: false, title: ROUTES.RESET_PASSWORD.TITLE }
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ element: <PrivateRoute showCardSkeleton />,
58
+ children: [
59
+ {
60
+ path: ROUTES.PROFILE.PATH,
61
+ element: <Profile />,
62
+ handle: { showInNavigation: true, label: "Profile", title: ROUTES.PROFILE.TITLE }
63
+ },
64
+ {
65
+ path: ROUTES.CHANGE_PASSWORD.PATH,
66
+ element: <ChangePassword />,
67
+ handle: { showInNavigation: false, title: ROUTES.CHANGE_PASSWORD.TITLE }
68
+ }
69
+ ]
70
+ },
71
+ {
72
+ path: "accounts/:recordId",
73
+ element: <AccountObjectDetail />
74
+ },
75
+ {
76
+ path: "accounts",
77
+ element: <AccountSearch />
78
+ }
79
+ ]
80
+ }
81
+ ];
@@ -0,0 +1,135 @@
1
+ @import 'tailwindcss';
2
+
3
+ @layer base {
4
+ html,
5
+ body,
6
+ #root {
7
+ @apply min-h-screen;
8
+ }
9
+
10
+ body {
11
+ @apply antialiased bg-white;
12
+ }
13
+ }
14
+
15
+ @import 'tw-animate-css';
16
+ @import 'shadcn/tailwind.css';
17
+
18
+ @custom-variant dark (&:is(.dark *));
19
+
20
+ @theme inline {
21
+ --color-background: var(--background);
22
+ --color-foreground: var(--foreground);
23
+ --color-card: var(--card);
24
+ --color-card-foreground: var(--card-foreground);
25
+ --color-popover: var(--popover);
26
+ --color-popover-foreground: var(--popover-foreground);
27
+ --color-primary: var(--primary);
28
+ --color-primary-foreground: var(--primary-foreground);
29
+ --color-secondary: var(--secondary);
30
+ --color-secondary-foreground: var(--secondary-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-muted-foreground: var(--muted-foreground);
33
+ --color-accent: var(--accent);
34
+ --color-accent-foreground: var(--accent-foreground);
35
+ --color-destructive: var(--destructive);
36
+ --color-destructive-foreground: var(--destructive-foreground);
37
+ --color-border: var(--border);
38
+ --color-input: var(--input);
39
+ --color-ring: var(--ring);
40
+ --color-chart-1: var(--chart-1);
41
+ --color-chart-2: var(--chart-2);
42
+ --color-chart-3: var(--chart-3);
43
+ --color-chart-4: var(--chart-4);
44
+ --color-chart-5: var(--chart-5);
45
+ --radius-sm: calc(var(--radius) - 4px);
46
+ --radius-md: calc(var(--radius) - 2px);
47
+ --radius-lg: var(--radius);
48
+ --radius-xl: calc(var(--radius) + 4px);
49
+ --color-sidebar: var(--sidebar);
50
+ --color-sidebar-foreground: var(--sidebar-foreground);
51
+ --color-sidebar-primary: var(--sidebar-primary);
52
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
53
+ --color-sidebar-accent: var(--sidebar-accent);
54
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
55
+ --color-sidebar-border: var(--sidebar-border);
56
+ --color-sidebar-ring: var(--sidebar-ring);
57
+ }
58
+
59
+ :root {
60
+ --radius: 0.625rem;
61
+ --background: oklch(1 0 0);
62
+ --foreground: oklch(0.145 0 0);
63
+ --card: oklch(1 0 0);
64
+ --card-foreground: oklch(0.145 0 0);
65
+ --popover: oklch(1 0 0);
66
+ --popover-foreground: oklch(0.145 0 0);
67
+ --primary: oklch(0.205 0 0);
68
+ --primary-foreground: oklch(0.985 0 0);
69
+ --secondary: oklch(0.97 0 0);
70
+ --secondary-foreground: oklch(0.205 0 0);
71
+ --muted: oklch(0.97 0 0);
72
+ --muted-foreground: oklch(0.556 0 0);
73
+ --accent: oklch(0.97 0 0);
74
+ --accent-foreground: oklch(0.205 0 0);
75
+ --destructive: oklch(0.577 0.245 27.325);
76
+ --border: oklch(0.922 0 0);
77
+ --input: oklch(0.922 0 0);
78
+ --ring: oklch(0.708 0 0);
79
+ --chart-1: oklch(0.646 0.222 41.116);
80
+ --chart-2: oklch(0.6 0.118 184.704);
81
+ --chart-3: oklch(0.398 0.07 227.392);
82
+ --chart-4: oklch(0.828 0.189 84.429);
83
+ --chart-5: oklch(0.769 0.188 70.08);
84
+ --sidebar: oklch(0.985 0 0);
85
+ --sidebar-foreground: oklch(0.145 0 0);
86
+ --sidebar-primary: oklch(0.205 0 0);
87
+ --sidebar-primary-foreground: oklch(0.985 0 0);
88
+ --sidebar-accent: oklch(0.97 0 0);
89
+ --sidebar-accent-foreground: oklch(0.205 0 0);
90
+ --sidebar-border: oklch(0.922 0 0);
91
+ --sidebar-ring: oklch(0.708 0 0);
92
+ }
93
+
94
+ .dark {
95
+ --background: oklch(0.145 0 0);
96
+ --foreground: oklch(0.985 0 0);
97
+ --card: oklch(0.205 0 0);
98
+ --card-foreground: oklch(0.985 0 0);
99
+ --popover: oklch(0.205 0 0);
100
+ --popover-foreground: oklch(0.985 0 0);
101
+ --primary: oklch(0.922 0 0);
102
+ --primary-foreground: oklch(0.205 0 0);
103
+ --secondary: oklch(0.269 0 0);
104
+ --secondary-foreground: oklch(0.985 0 0);
105
+ --muted: oklch(0.269 0 0);
106
+ --muted-foreground: oklch(0.708 0 0);
107
+ --accent: oklch(0.269 0 0);
108
+ --accent-foreground: oklch(0.985 0 0);
109
+ --destructive: oklch(0.704 0.191 22.216);
110
+ --border: oklch(1 0 0 / 10%);
111
+ --input: oklch(1 0 0 / 15%);
112
+ --ring: oklch(0.556 0 0);
113
+ --chart-1: oklch(0.488 0.243 264.376);
114
+ --chart-2: oklch(0.696 0.17 162.48);
115
+ --chart-3: oklch(0.769 0.188 70.08);
116
+ --chart-4: oklch(0.627 0.265 303.9);
117
+ --chart-5: oklch(0.645 0.246 16.439);
118
+ --sidebar: oklch(0.205 0 0);
119
+ --sidebar-foreground: oklch(0.985 0 0);
120
+ --sidebar-primary: oklch(0.488 0.243 264.376);
121
+ --sidebar-primary-foreground: oklch(0.985 0 0);
122
+ --sidebar-accent: oklch(0.269 0 0);
123
+ --sidebar-accent-foreground: oklch(0.985 0 0);
124
+ --sidebar-border: oklch(1 0 0 / 10%);
125
+ --sidebar-ring: oklch(0.556 0 0);
126
+ }
127
+
128
+ @layer base {
129
+ * {
130
+ @apply border-border outline-ring/50;
131
+ }
132
+ body {
133
+ @apply bg-background text-foreground;
134
+ }
135
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+
23
+ /* Path mapping */
24
+ "baseUrl": ".",
25
+ "paths": {
26
+ "@/*": ["./src/*"],
27
+ "@api/*": ["./src/api/*"],
28
+ "@components/*": ["./src/components/*"],
29
+ "@utils/*": ["./src/utils/*"],
30
+ "@styles/*": ["./src/styles/*"],
31
+ "@assets/*": ["./src/assets/*"]
32
+ }
33
+ },
34
+ "include": [
35
+ "src",
36
+ "e2e",
37
+ "vite-env.d.ts",
38
+ "vitest-env.d.ts",
39
+ "vitest.setup.ts"
40
+ ],
41
+ "references": [{ "path": "./tsconfig.node.json" }]
42
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5
+ "skipLibCheck": true,
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "outDir": "./build"
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "outputDir": "dist",
3
+ "routing": {
4
+ "trailingSlash": "never",
5
+ "fallback": "index.html"
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />