@salesforce/ui-bundle-template-app-react-template-b2e 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 (133) 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 +52 -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/uiBundles/reactinternalapp/.forceignore +15 -0
  13. package/dist/force-app/main/default/uiBundles/reactinternalapp/.graphqlrc.yml +2 -0
  14. package/dist/force-app/main/default/uiBundles/reactinternalapp/.prettierignore +9 -0
  15. package/dist/force-app/main/default/uiBundles/reactinternalapp/.prettierrc +11 -0
  16. package/dist/force-app/main/default/uiBundles/reactinternalapp/CHANGELOG.md +10 -0
  17. package/dist/force-app/main/default/uiBundles/reactinternalapp/README.md +35 -0
  18. package/dist/force-app/main/default/uiBundles/reactinternalapp/codegen.yml +95 -0
  19. package/dist/force-app/main/default/uiBundles/reactinternalapp/components.json +18 -0
  20. package/dist/force-app/main/default/uiBundles/reactinternalapp/e2e/app.spec.ts +17 -0
  21. package/dist/force-app/main/default/uiBundles/reactinternalapp/eslint.config.js +169 -0
  22. package/dist/force-app/main/default/uiBundles/reactinternalapp/index.html +12 -0
  23. package/dist/force-app/main/default/uiBundles/reactinternalapp/package.json +69 -0
  24. package/dist/force-app/main/default/uiBundles/reactinternalapp/playwright.config.ts +24 -0
  25. package/dist/force-app/main/default/uiBundles/reactinternalapp/reactinternalapp.uibundle-meta.xml +7 -0
  26. package/dist/force-app/main/default/uiBundles/reactinternalapp/scripts/get-graphql-schema.mjs +68 -0
  27. package/dist/force-app/main/default/uiBundles/reactinternalapp/scripts/rewrite-e2e-assets.mjs +23 -0
  28. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/account/accountSearchService.ts +46 -0
  29. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/account/query/distinctAccountIndustries.graphql +19 -0
  30. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/account/query/distinctAccountTypes.graphql +19 -0
  31. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/account/query/getAccountDetail.graphql +121 -0
  32. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/account/query/searchAccounts.graphql +51 -0
  33. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/graphql-operations-types.ts +11260 -0
  34. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/api/graphqlClient.ts +25 -0
  35. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/app.tsx +17 -0
  36. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/appLayout.tsx +85 -0
  37. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/icons/book.svg +3 -0
  38. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/icons/copy.svg +4 -0
  39. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/icons/rocket.svg +3 -0
  40. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/icons/star.svg +3 -0
  41. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/images/codey-1.png +0 -0
  42. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/images/codey-2.png +0 -0
  43. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/images/codey-3.png +0 -0
  44. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/assets/images/vibe-codey.svg +194 -0
  45. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/AgentforceConversationClient.tsx +168 -0
  46. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/__inherit_AgentforceConversationClient.tsx +3 -0
  47. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/alerts/status-alert.tsx +49 -0
  48. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/layouts/card-layout.tsx +29 -0
  49. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/alert.tsx +76 -0
  50. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/badge.tsx +48 -0
  51. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/breadcrumb.tsx +109 -0
  52. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/button.tsx +67 -0
  53. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/calendar.tsx +232 -0
  54. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/card.tsx +103 -0
  55. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/checkbox.tsx +32 -0
  56. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/collapsible.tsx +33 -0
  57. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/datePicker.tsx +127 -0
  58. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/dialog.tsx +162 -0
  59. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/field.tsx +237 -0
  60. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/index.ts +84 -0
  61. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/input.tsx +19 -0
  62. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/label.tsx +22 -0
  63. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/pagination.tsx +132 -0
  64. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/popover.tsx +89 -0
  65. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/select.tsx +193 -0
  66. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/separator.tsx +26 -0
  67. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/skeleton.tsx +14 -0
  68. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/sonner.tsx +20 -0
  69. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/spinner.tsx +16 -0
  70. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/table.tsx +114 -0
  71. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/components/ui/tabs.tsx +88 -0
  72. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  73. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  74. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  75. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  76. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  77. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  78. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
  79. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/__examples__/pages/Home.tsx +34 -0
  80. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/api/objectSearchService.ts +84 -0
  81. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/ActiveFilters.tsx +89 -0
  82. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/FilterContext.tsx +83 -0
  83. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  84. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/PaginationControls.tsx +109 -0
  85. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/SearchBar.tsx +41 -0
  86. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/SortControl.tsx +143 -0
  87. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/BooleanFilter.tsx +78 -0
  88. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/DateFilter.tsx +128 -0
  89. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
  90. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
  91. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
  92. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
  93. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/SearchFilter.tsx +50 -0
  94. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/SelectFilter.tsx +97 -0
  95. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/components/filters/TextFilter.tsx +91 -0
  96. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/hooks/useAsyncData.ts +54 -0
  97. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
  98. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/hooks/useDebouncedCallback.ts +34 -0
  99. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/hooks/useObjectSearchParams.ts +252 -0
  100. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/utils/debounce.ts +25 -0
  101. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/utils/fieldUtils.ts +29 -0
  102. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/utils/filterUtils.ts +395 -0
  103. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/features/object-search/utils/sortUtils.ts +38 -0
  104. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/index.ts +6 -0
  105. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/lib/utils.ts +6 -0
  106. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/navigationMenu.tsx +80 -0
  107. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/pages/AccountObjectDetailPage.tsx +361 -0
  108. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/pages/AccountSearch.tsx +305 -0
  109. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/pages/Home.tsx +34 -0
  110. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/pages/NotFound.tsx +18 -0
  111. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/router-utils.tsx +35 -0
  112. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/routes.tsx +32 -0
  113. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/styles/global.css +135 -0
  114. package/dist/force-app/main/default/uiBundles/reactinternalapp/src/types/conversation.ts +33 -0
  115. package/dist/force-app/main/default/uiBundles/reactinternalapp/tsconfig.json +42 -0
  116. package/dist/force-app/main/default/uiBundles/reactinternalapp/tsconfig.node.json +13 -0
  117. package/dist/force-app/main/default/uiBundles/reactinternalapp/ui-bundle.json +7 -0
  118. package/dist/force-app/main/default/uiBundles/reactinternalapp/vite-env.d.ts +1 -0
  119. package/dist/force-app/main/default/uiBundles/reactinternalapp/vite.config.ts +106 -0
  120. package/dist/force-app/main/default/uiBundles/reactinternalapp/vitest-env.d.ts +2 -0
  121. package/dist/force-app/main/default/uiBundles/reactinternalapp/vitest.config.ts +11 -0
  122. package/dist/force-app/main/default/uiBundles/reactinternalapp/vitest.setup.ts +1 -0
  123. package/dist/jest.config.js +6 -0
  124. package/dist/package-lock.json +9995 -0
  125. package/dist/package.json +40 -0
  126. package/dist/scripts/apex/hello.apex +10 -0
  127. package/dist/scripts/graphql-search.sh +191 -0
  128. package/dist/scripts/prepare-import-unique-fields.js +122 -0
  129. package/dist/scripts/setup-cli.mjs +563 -0
  130. package/dist/scripts/sf-project-setup.mjs +66 -0
  131. package/dist/scripts/soql/account.soql +6 -0
  132. package/dist/sfdx-project.json +12 -0
  133. 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,32 @@
1
+ import type { RouteObject } from 'react-router';
2
+ import AppLayout from './appLayout';
3
+ import Home from './pages/Home';
4
+ import NotFound from './pages/NotFound';
5
+ import AccountSearch from "./pages/AccountSearch";
6
+ import AccountObjectDetail from "./pages/AccountObjectDetailPage";
7
+
8
+ export const routes: RouteObject[] = [
9
+ {
10
+ path: "/",
11
+ element: <AppLayout />,
12
+ children: [
13
+ {
14
+ index: true,
15
+ element: <Home />,
16
+ handle: { showInNavigation: true, label: "Home" }
17
+ },
18
+ {
19
+ path: '*',
20
+ element: <NotFound />
21
+ },
22
+ {
23
+ path: "accounts/:recordId",
24
+ element: <AccountObjectDetail />
25
+ },
26
+ {
27
+ path: "accounts",
28
+ element: <AccountSearch />
29
+ }
30
+ ]
31
+ }
32
+ ];
@@ -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,33 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ export interface ResolvedEmbedOptions {
8
+ salesforceOrigin?: string;
9
+ frontdoorUrl?: string;
10
+ }
11
+
12
+ export type StyleTokens = Record<string, string>;
13
+
14
+ export interface AgentforceConversationClientProps {
15
+ /** Required in practice: id of the agent to load. */
16
+ agentId: string;
17
+ /** If true, renders inline. If omitted/false, renders floating. */
18
+ inline?: boolean;
19
+ /** Show/hide chat header. Defaults to true for floating; can only be set for inline mode. */
20
+ headerEnabled?: boolean;
21
+ /** Show/hide agent icon in the header. */
22
+ showHeaderIcon?: boolean;
23
+ /** Inline width. */
24
+ width?: string | number;
25
+ /** Inline height. */
26
+ height?: string | number;
27
+ /** Theme overrides for the chat UI. */
28
+ styleTokens?: StyleTokens;
29
+ /** Optional. If not provided, resolved internally (e.g. from /__lo/frontdoor in dev, window.location.origin in prod). */
30
+ salesforceOrigin?: string;
31
+ /** Optional. If not provided, resolved internally in dev via /__lo/frontdoor. */
32
+ frontdoorUrl?: string;
33
+ }
@@ -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" />