@salesforce/ui-bundle-template-app-react-template-b2x 11.18.0 → 11.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/uiBundles/reactexternalapp/package.json +4 -4
  3. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/{features/object-search/components → components}/ObjectBreadcrumb.tsx +7 -2
  4. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/api/distinctValuesService.ts +84 -0
  5. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/api/searchService.ts +71 -0
  6. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/MergedSearchResults.tsx +263 -0
  7. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/Search.tsx +185 -0
  8. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/SearchResults.tsx +77 -0
  9. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/SourceSection.tsx +166 -0
  10. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/controls/PaginationControls.tsx +185 -0
  11. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/controls/ScopeSelector.tsx +55 -0
  12. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/controls/SearchBar.tsx +55 -0
  13. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/controls/SortControl.tsx +62 -0
  14. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/ActiveFilters.tsx +61 -0
  15. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/DefaultFilterPanel.tsx +125 -0
  16. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/{object-search/components → search/components/filters}/FilterContext.tsx +7 -20
  17. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/BooleanFilter.tsx +50 -0
  18. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
  19. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/FilterFieldWrapper.tsx +26 -0
  20. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/MultiSelectFilter.tsx +88 -0
  21. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/NumericRangeFilter.tsx +78 -0
  22. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/SelectFilter.tsx +46 -0
  23. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/filters/inputs/TextFilter.tsx +52 -0
  24. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/components/results/DefaultResultRow.tsx +109 -0
  25. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/config.json +83 -0
  26. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/{object-search → search}/hooks/useAsyncData.ts +3 -10
  27. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/hooks/useDistinctValues.ts +59 -0
  28. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/hooks/useSearch.ts +703 -0
  29. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/index.ts +90 -0
  30. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/loadConfig.ts +14 -0
  31. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/queryBuilder.ts +156 -0
  32. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/types.ts +307 -0
  33. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/utils/debounce.ts +17 -0
  34. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/utils/fieldUtils.ts +14 -0
  35. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/utils/filterUtils.ts +285 -0
  36. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/search/utils/sortUtils.ts +34 -0
  37. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountObjectDetailPage.tsx +49 -16
  38. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountSearch.tsx +21 -293
  39. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/Home.tsx +22 -7
  40. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/routes.tsx +1 -1
  41. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/{features/object-search/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
  42. package/dist/force-app/main/default/uiBundles/reactexternalapp/tsconfig.tsbuildinfo +1 -1
  43. package/dist/package-lock.json +2 -2
  44. package/dist/package.json +1 -1
  45. package/package.json +1 -1
  46. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/accountSearchService.ts +0 -46
  47. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/distinctAccountIndustries.graphql +0 -19
  48. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/distinctAccountTypes.graphql +0 -19
  49. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/searchAccounts.graphql +0 -51
  50. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/graphql-operations-types.ts +0 -11260
  51. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/accountSearchService.ts +0 -46
  52. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +0 -19
  53. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +0 -19
  54. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +0 -121
  55. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +0 -51
  56. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +0 -355
  57. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +0 -304
  58. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/Home.tsx +0 -34
  59. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/api/objectSearchService.ts +0 -82
  60. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/ActiveFilters.tsx +0 -89
  61. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/PaginationControls.tsx +0 -109
  62. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/SearchBar.tsx +0 -41
  63. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/SortControl.tsx +0 -143
  64. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/BooleanFilter.tsx +0 -78
  65. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/DateFilter.tsx +0 -128
  66. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +0 -70
  67. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +0 -43
  68. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +0 -105
  69. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +0 -171
  70. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SearchFilter.tsx +0 -52
  71. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SelectFilter.tsx +0 -97
  72. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/TextFilter.tsx +0 -93
  73. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useDebouncedCallback.ts +0 -34
  74. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useObjectSearchParams.ts +0 -254
  75. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/debounce.ts +0 -25
  76. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/filterUtils.ts +0 -404
  77. package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/sortUtils.ts +0 -38
@@ -1,297 +1,25 @@
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 { useAsyncData } from "../hooks/useAsyncData";
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: 7,
44
- validPageSizes: [7, 14, 28, 42],
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
-
1
+ import { Search, config } from "../features/search";
2
+
3
+ /**
4
+ * Account search page.
5
+ *
6
+ * Renders the config-driven `<Search>` feature (feature-react-search) locked to
7
+ * the Account source via `restrictTo`. The shared `config.json` already defines
8
+ * the Account source's searchable fields, filters, sort options, and
9
+ * `routePattern` ("/accounts/:id"), so result rows link to the Account detail
10
+ * page and the filter / sort chrome render automatically — no per-page wiring.
11
+ *
12
+ * `restrictTo` hides the scope selector and fetches only Accounts, so the page
13
+ * reads as a single-object search even though the underlying feature supports
14
+ * many sources.
15
+ */
76
16
  export default function AccountSearch() {
77
- const [filtersOpen, setFiltersOpen] = useState(true);
78
- const { data: industryOptions } = useAsyncData(fetchDistinctIndustries, []);
79
- const { data: typeOptions } = useAsyncData(fetchDistinctTypes, []);
80
-
81
- const { filters, sort, query, pagination, resetAll } = useObjectSearchParams<
82
- Account_Filter,
83
- Account_OrderBy
84
- >(FILTER_CONFIGS, ACCOUNT_SORT_CONFIGS, PAGINATION_CONFIG);
85
-
86
- const { data, loading, error } = useAsyncData(
87
- () =>
88
- searchAccounts({
89
- where: query.where,
90
- orderBy: query.orderBy,
91
- first: pagination.pageSize,
92
- after: pagination.afterCursor,
93
- }),
94
- [query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
95
- );
96
-
97
- const pageInfo = data?.pageInfo;
98
- const totalCount = data?.totalCount;
99
- const hasNextPage = pageInfo?.hasNextPage ?? false;
100
- const hasPreviousPage = pagination.pageIndex > 0;
101
-
102
- const validAccountNodes = useMemo(
103
- () =>
104
- (data?.edges ?? []).reduce<AccountNode[]>((acc, edge) => {
105
- if (edge?.node) acc.push(edge.node);
106
- return acc;
107
- }, []),
108
- [data?.edges],
109
- );
110
-
111
- return (
112
- <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
113
- <ObjectBreadcrumb listPath="/accounts" listLabel="Accounts" />
114
-
115
- <h1 className="text-2xl font-bold mb-4">Search Accounts</h1>
116
-
117
- <div className="flex flex-col lg:flex-row gap-6">
118
- {/* Sidebar — Filter Panel */}
119
- <aside className="w-full lg:w-80 shrink-0">
120
- <FilterProvider
121
- filters={filters.active}
122
- onFilterChange={filters.set}
123
- onFilterRemove={filters.remove}
124
- onReset={resetAll}
125
- >
126
- <Card>
127
- <Collapsible open={filtersOpen} onOpenChange={setFiltersOpen}>
128
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
129
- <CardTitle className="text-base font-semibold">
130
- <h2>Filters</h2>
131
- </CardTitle>
132
- <div className="flex items-center gap-1">
133
- <FilterResetButton variant="destructive" size="sm" />
134
- <CollapsibleTrigger asChild>
135
- <Button variant="ghost" size="icon">
136
- <ChevronDown
137
- className={`h-4 w-4 transition-transform ${filtersOpen ? "" : "-rotate-90"}`}
138
- />
139
- <span className="sr-only">Toggle filters</span>
140
- </Button>
141
- </CollapsibleTrigger>
142
- </div>
143
- </CardHeader>
144
- <CollapsibleContent>
145
- <CardContent className="space-y-1 pt-0">
146
- <SearchFilter
147
- field="search"
148
- label="Search"
149
- placeholder="Search by name, phone, or industry..."
150
- />
151
- <TextFilter field="Name" label="Account Name" placeholder="Search by name..." />
152
- <SelectFilter
153
- field="Industry"
154
- label="Industry"
155
- options={industryOptions ?? []}
156
- />
157
- <MultiSelectFilter field="Type" label="Type" options={typeOptions ?? []} />
158
- <NumericRangeFilter field="AnnualRevenue" label="Annual Revenue" />
159
- <DateFilter field="CreatedDate" label="Created Date" filterType="datetime" />
160
- <DateRangeFilter
161
- field="LastModifiedDate"
162
- label="Last Modified Date"
163
- filterType="datetimerange"
164
- />
165
- </CardContent>
166
- </CollapsibleContent>
167
- </Collapsible>
168
- </Card>
169
- </FilterProvider>
170
- </aside>
171
-
172
- {/* Main area — Sort + Results */}
173
- <div className="flex-1 min-w-0">
174
- {/* Sort control + active filters */}
175
- <div className="flex flex-wrap items-center gap-2 mb-4">
176
- <SortControl
177
- configs={ACCOUNT_SORT_CONFIGS}
178
- sort={sort.current}
179
- onSortChange={sort.set}
180
- />
181
- <ActiveFilters filters={filters.active} onRemove={filters.remove} />
182
- </div>
183
-
184
- <div className="min-h-132">
185
- {/* Loading state */}
186
- {loading && (
187
- <>
188
- <Skeleton className="h-5 w-30 mb-3" />
189
- <div className="divide-y">
190
- {Array.from({ length: pagination.pageSize }, (_, i) => (
191
- <div key={i} className="flex items-center justify-between py-3">
192
- <div className="space-y-2">
193
- <Skeleton className="h-5 w-40" />
194
- <Skeleton className="h-4 w-28" />
195
- </div>
196
- <div className="space-y-2 flex flex-col items-end">
197
- <Skeleton className="h-4 w-24" />
198
- <Skeleton className="h-4 w-20" />
199
- </div>
200
- </div>
201
- ))}
202
- </div>
203
- </>
204
- )}
205
-
206
- {/* Error state */}
207
- {error && (
208
- <>
209
- <p className="text-sm text-muted-foreground mb-3">0 accounts found</p>
210
- <Alert variant="destructive" role="alert">
211
- <AlertCircle />
212
- <AlertTitle>Failed to load accounts</AlertTitle>
213
- <AlertDescription>
214
- Something went wrong while loading accounts. Please try again later.
215
- </AlertDescription>
216
- </Alert>
217
- </>
218
- )}
219
-
220
- {/* Results list */}
221
- {!loading && !error && validAccountNodes.length > 0 && (
222
- <>
223
- <p className="text-sm text-muted-foreground mb-3">
224
- {totalCount != null && (hasNextPage || hasPreviousPage)
225
- ? `${totalCount} account${totalCount !== 1 ? "s" : ""} found`
226
- : `Showing ${validAccountNodes.length} account${validAccountNodes.length !== 1 ? "s" : ""}`}
227
- </p>
228
- <AccountResultsList nodes={validAccountNodes} />
229
- </>
230
- )}
231
-
232
- {/* No results state */}
233
- {!loading && !error && validAccountNodes.length === 0 && (
234
- <div className="flex flex-col items-center justify-center py-16 text-center">
235
- <SearchX className="size-12 text-muted-foreground mb-4" />
236
- <h2 className="text-lg font-semibold mb-1">No accounts found</h2>
237
- <p className="text-sm text-muted-foreground">
238
- Try adjusting your filters or search criteria.
239
- </p>
240
- </div>
241
- )}
242
- </div>
243
-
244
- {/* Pagination — always visible, disabled while loading or on error */}
245
- <PaginationControls
246
- pageIndex={pagination.pageIndex}
247
- hasNextPage={hasNextPage}
248
- hasPreviousPage={hasPreviousPage}
249
- pageSize={pagination.pageSize}
250
- pageSizeOptions={PAGINATION_CONFIG.validPageSizes}
251
- onNextPage={() => {
252
- if (pageInfo?.endCursor) pagination.goToNextPage(pageInfo.endCursor);
253
- }}
254
- onPreviousPage={pagination.goToPreviousPage}
255
- onPageSizeChange={pagination.setPageSize}
256
- disabled={loading || !!error}
257
- />
258
- </div>
259
- </div>
260
- </div>
261
- );
262
- }
263
-
264
- // -- Result Components ------------------------------------------------------
265
-
266
- function AccountResultsList({ nodes }: { nodes: AccountNode[] }) {
267
- return (
268
- <ul className="divide-y">
269
- {nodes.map((node) => (
270
- <AccountResultItem key={node.Id} node={node} />
271
- ))}
272
- </ul>
273
- );
274
- }
275
-
276
- function AccountResultItem({ node }: { node: AccountNode }) {
277
17
  return (
278
- <li>
279
- <Link
280
- to={`/accounts/${node.Id}`}
281
- className="flex items-center justify-between py-3 px-3 -mx-3 rounded-md transition-colors hover:bg-accent"
282
- >
283
- <div>
284
- <span className="font-medium">{fieldValue(node.Name) ?? "\u2014"}</span>
285
- <p className="text-sm text-muted-foreground">
286
- {[fieldValue(node.Industry), fieldValue(node.Type)].filter(Boolean).join(" \u00B7 ") ||
287
- "\u2014"}
288
- </p>
289
- </div>
290
- <div className="text-right text-sm">
291
- <p>{fieldValue(node.Phone) ?? ""}</p>
292
- <p className="text-muted-foreground">{fieldValue(node.Owner?.Name) ?? ""}</p>
293
- </div>
294
- </Link>
295
- </li>
18
+ <Search
19
+ config={config}
20
+ title="Search Accounts"
21
+ searchPlaceholder="Search by name, phone, or industry..."
22
+ restrictTo={{ kind: "sobject", key: "accounts" }}
23
+ />
296
24
  );
297
25
  }
@@ -1,6 +1,7 @@
1
1
  import { useState } from "react";
2
2
  import { useNavigate } from "react-router";
3
- import { SearchBar } from "../features/object-search/components/SearchBar";
3
+ import { Search } from "lucide-react";
4
+ import { Input } from "../components/ui/input";
4
5
  import { Button } from "../components/ui/button";
5
6
 
6
7
  export default function HomePage() {
@@ -9,6 +10,8 @@ export default function HomePage() {
9
10
 
10
11
  const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
11
12
  e.preventDefault();
13
+ // `q` matches the search feature's GLOBAL_QUERY_KEY, so the term
14
+ // pre-fills the Account search page on arrival.
12
15
  const params = text ? `?q=${encodeURIComponent(text)}` : "";
13
16
  navigate(`/accounts${params}`);
14
17
  };
@@ -16,17 +19,29 @@ export default function HomePage() {
16
19
  return (
17
20
  <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
18
21
  <div className="flex items-center gap-6 mb-6">
19
- <h1 className="text-2xl font-bold">Account Search</h1>
22
+ <h1 className="text-2xl font-bold">Search Accounts</h1>
20
23
  <Button variant="outline" size="sm" onClick={() => navigate("/accounts")}>
21
24
  Browse All Accounts
22
25
  </Button>
23
26
  </div>
24
27
  <form onSubmit={handleSubmit} className="flex gap-2">
25
- <SearchBar
26
- placeholder="Search by name, phone, or industry..."
27
- value={text}
28
- handleChange={setText}
29
- />
28
+ <div className="relative flex-1">
29
+ <label htmlFor="account-search-input" className="sr-only">
30
+ Search accounts
31
+ </label>
32
+ <Search
33
+ aria-hidden="true"
34
+ className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
35
+ />
36
+ <Input
37
+ id="account-search-input"
38
+ type="text"
39
+ value={text}
40
+ onChange={(e) => setText(e.target.value)}
41
+ placeholder="Search by name, phone, or industry..."
42
+ className="pl-9"
43
+ />
44
+ </div>
30
45
  <Button type="submit">Search</Button>
31
46
  </form>
32
47
  </div>
@@ -9,9 +9,9 @@ import Profile from "./features/authentication/pages/Profile";
9
9
  import ChangePassword from "./features/authentication/pages/ChangePassword";
10
10
  import PrivateRoute from "./features/authentication/layouts/privateRouteLayout";
11
11
  import { ROUTES } from "./features/authentication/authenticationConfig";
12
+ import AuthAppLayout from "./features/authentication/layouts/AuthAppLayout";
12
13
  import AccountSearch from "./pages/AccountSearch";
13
14
  import AccountObjectDetail from "./pages/AccountObjectDetailPage";
14
- import AuthAppLayout from "./features/authentication/layouts/AuthAppLayout";
15
15
 
16
16
  export const routes: RouteObject[] = [
17
17
  {
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Field helpers for reading uiapi GraphQL response nodes on the Account
3
+ * detail page. Previously imported from feature-react-object-search; inlined
4
+ * here so the app no longer depends on that feature.
5
+ */
6
+
7
+ /** Picks the user-facing string for a `{ value, displayValue }` field. */
1
8
  export function fieldValue(
2
9
  field: { displayValue?: string | null; value?: unknown } | null | undefined,
3
10
  ): string | null {
@@ -6,6 +13,7 @@ export function fieldValue(
6
13
  return null;
7
14
  }
8
15
 
16
+ /** Builds the display lines for a Salesforce compound address, or null if empty. */
9
17
  export function getAddressFieldLines(address: {
10
18
  street?: string | null;
11
19
  city?: string | null;
@@ -20,6 +28,7 @@ export function getAddressFieldLines(address: {
20
28
  return lines;
21
29
  }
22
30
 
31
+ /** Formats an ISO datetime string with `toLocaleString`, or null if absent. */
23
32
  export function formatDateTimeField(
24
33
  value?: string | null,
25
34
  ...args: Parameters<Date["toLocaleString"]>
@@ -1 +1 @@
1
- {"root":["./src/app.tsx","./src/appLayout.tsx","./src/navigationMenu.tsx","./src/router-utils.tsx","./src/routes.tsx","./src/api/graphql-operations-types.ts","./src/api/graphqlClient.ts","./src/api/account/accountSearchService.ts","./src/components/alerts/status-alert.tsx","./src/components/layouts/card-layout.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/datePicker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/field.tsx","./src/components/ui/index.ts","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/spinner.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/features/authentication/authHelpers.ts","./src/features/authentication/authenticationConfig.ts","./src/features/authentication/api/userProfileApi.ts","./src/features/authentication/context/AuthContext.tsx","./src/features/authentication/footers/footer-link.tsx","./src/features/authentication/forms/auth-form.tsx","./src/features/authentication/forms/submit-button.tsx","./src/features/authentication/hooks/form.tsx","./src/features/authentication/hooks/useCountdownTimer.ts","./src/features/authentication/hooks/useRetryWithBackoff.ts","./src/features/authentication/layout/centered-page-layout.tsx","./src/features/authentication/layouts/AuthAppLayout.tsx","./src/features/authentication/layouts/privateRouteLayout.tsx","./src/features/authentication/menu/AuthMenu.tsx","./src/features/authentication/pages/ChangePassword.tsx","./src/features/authentication/pages/ForgotPassword.tsx","./src/features/authentication/pages/Login.tsx","./src/features/authentication/pages/Profile.tsx","./src/features/authentication/pages/Register.tsx","./src/features/authentication/pages/ResetPassword.tsx","./src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx","./src/features/authentication/sessionTimeout/sessionTimeService.ts","./src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts","./src/features/authentication/utils/helpers.ts","./src/features/object-search/__examples__/api/accountSearchService.ts","./src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx","./src/features/object-search/__examples__/pages/AccountSearch.tsx","./src/features/object-search/__examples__/pages/Home.tsx","./src/features/object-search/api/objectSearchService.ts","./src/features/object-search/components/ActiveFilters.tsx","./src/features/object-search/components/FilterContext.tsx","./src/features/object-search/components/ObjectBreadcrumb.tsx","./src/features/object-search/components/PaginationControls.tsx","./src/features/object-search/components/SearchBar.tsx","./src/features/object-search/components/SortControl.tsx","./src/features/object-search/components/filters/BooleanFilter.tsx","./src/features/object-search/components/filters/DateFilter.tsx","./src/features/object-search/components/filters/DateRangeFilter.tsx","./src/features/object-search/components/filters/FilterFieldWrapper.tsx","./src/features/object-search/components/filters/MultiSelectFilter.tsx","./src/features/object-search/components/filters/NumericRangeFilter.tsx","./src/features/object-search/components/filters/SearchFilter.tsx","./src/features/object-search/components/filters/SelectFilter.tsx","./src/features/object-search/components/filters/TextFilter.tsx","./src/features/object-search/hooks/useAsyncData.ts","./src/features/object-search/hooks/useDebouncedCallback.ts","./src/features/object-search/hooks/useObjectSearchParams.ts","./src/features/object-search/utils/debounce.ts","./src/features/object-search/utils/fieldUtils.ts","./src/features/object-search/utils/filterUtils.ts","./src/features/object-search/utils/sortUtils.ts","./src/hooks/useAsyncData.ts","./src/lib/utils.ts","./src/pages/AccountObjectDetailPage.tsx","./src/pages/AccountSearch.tsx","./src/pages/Home.tsx","./src/pages/NotFound.tsx","./e2e/app.spec.ts","./vite-env.d.ts","./vitest-env.d.ts","./vitest.setup.ts","../../../../../../../../../sdk/platform-sdk/types/env.d.ts"],"version":"5.9.3"}
1
+ {"root":["./src/app.tsx","./src/appLayout.tsx","./src/navigationMenu.tsx","./src/router-utils.tsx","./src/routes.tsx","./src/api/graphqlClient.ts","./src/components/ObjectBreadcrumb.tsx","./src/components/alerts/status-alert.tsx","./src/components/layouts/card-layout.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/datePicker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/field.tsx","./src/components/ui/index.ts","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/spinner.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/features/authentication/authHelpers.ts","./src/features/authentication/authenticationConfig.ts","./src/features/authentication/api/userProfileApi.ts","./src/features/authentication/context/AuthContext.tsx","./src/features/authentication/footers/footer-link.tsx","./src/features/authentication/forms/auth-form.tsx","./src/features/authentication/forms/submit-button.tsx","./src/features/authentication/hooks/form.tsx","./src/features/authentication/hooks/useCountdownTimer.ts","./src/features/authentication/hooks/useRetryWithBackoff.ts","./src/features/authentication/layout/centered-page-layout.tsx","./src/features/authentication/layouts/AuthAppLayout.tsx","./src/features/authentication/layouts/privateRouteLayout.tsx","./src/features/authentication/menu/AuthMenu.tsx","./src/features/authentication/pages/ChangePassword.tsx","./src/features/authentication/pages/ForgotPassword.tsx","./src/features/authentication/pages/Login.tsx","./src/features/authentication/pages/Profile.tsx","./src/features/authentication/pages/Register.tsx","./src/features/authentication/pages/ResetPassword.tsx","./src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx","./src/features/authentication/sessionTimeout/sessionTimeService.ts","./src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts","./src/features/authentication/utils/helpers.ts","./src/features/search/index.ts","./src/features/search/loadConfig.ts","./src/features/search/queryBuilder.ts","./src/features/search/types.ts","./src/features/search/api/distinctValuesService.ts","./src/features/search/api/searchService.ts","./src/features/search/components/MergedSearchResults.tsx","./src/features/search/components/Search.tsx","./src/features/search/components/SearchResults.tsx","./src/features/search/components/SourceSection.tsx","./src/features/search/components/controls/PaginationControls.tsx","./src/features/search/components/controls/ScopeSelector.tsx","./src/features/search/components/controls/SearchBar.tsx","./src/features/search/components/controls/SortControl.tsx","./src/features/search/components/filters/ActiveFilters.tsx","./src/features/search/components/filters/DefaultFilterPanel.tsx","./src/features/search/components/filters/FilterContext.tsx","./src/features/search/components/filters/inputs/BooleanFilter.tsx","./src/features/search/components/filters/inputs/DateRangeFilter.tsx","./src/features/search/components/filters/inputs/FilterFieldWrapper.tsx","./src/features/search/components/filters/inputs/MultiSelectFilter.tsx","./src/features/search/components/filters/inputs/NumericRangeFilter.tsx","./src/features/search/components/filters/inputs/SelectFilter.tsx","./src/features/search/components/filters/inputs/TextFilter.tsx","./src/features/search/components/results/DefaultResultRow.tsx","./src/features/search/hooks/useAsyncData.ts","./src/features/search/hooks/useDistinctValues.ts","./src/features/search/hooks/useSearch.ts","./src/features/search/utils/debounce.ts","./src/features/search/utils/fieldUtils.ts","./src/features/search/utils/filterUtils.ts","./src/features/search/utils/sortUtils.ts","./src/hooks/useAsyncData.ts","./src/lib/utils.ts","./src/pages/AccountObjectDetailPage.tsx","./src/pages/AccountSearch.tsx","./src/pages/Home.tsx","./src/pages/NotFound.tsx","./src/utils/accountFields.ts","./e2e/app.spec.ts","./vite-env.d.ts","./vitest-env.d.ts","./vitest.setup.ts","../../../../../../../../../sdk/platform-sdk/types/env.d.ts"],"version":"5.9.3"}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "11.18.0",
3
+ "version": "11.20.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
9
- "version": "11.18.0",
9
+ "version": "11.20.0",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "dependencies": {
12
12
  "fast-xml-parser": "^5.9.3",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "11.18.0",
3
+ "version": "11.20.0",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-app-react-template-b2x",
3
- "version": "11.18.0",
3
+ "version": "11.20.0",
4
4
  "description": "Salesforce React external app template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -1,46 +0,0 @@
1
- import SEARCH_ACCOUNTS_QUERY from "./query/searchAccounts.graphql?raw";
2
- import DISTINCT_INDUSTRIES_QUERY from "./query/distinctAccountIndustries.graphql?raw";
3
- import DISTINCT_TYPES_QUERY from "./query/distinctAccountTypes.graphql?raw";
4
- import {
5
- searchObjects,
6
- fetchDistinctValues,
7
- type ObjectSearchOptions,
8
- type PicklistOption,
9
- } from "../../features/object-search/api/objectSearchService";
10
- import type {
11
- SearchAccountsQuery,
12
- SearchAccountsQueryVariables,
13
- DistinctAccountIndustriesQuery,
14
- DistinctAccountTypesQuery,
15
- } from "../graphql-operations-types";
16
-
17
- export type AccountSearchResult = NonNullable<SearchAccountsQuery["uiapi"]["query"]["Account"]>;
18
-
19
- export type AccountSearchOptions = ObjectSearchOptions<
20
- SearchAccountsQueryVariables["where"],
21
- SearchAccountsQueryVariables["orderBy"]
22
- >;
23
-
24
- export type { PicklistOption };
25
-
26
- export async function searchAccounts(
27
- options: AccountSearchOptions = {},
28
- ): Promise<AccountSearchResult> {
29
- return searchObjects<AccountSearchResult, SearchAccountsQuery, SearchAccountsQueryVariables>(
30
- SEARCH_ACCOUNTS_QUERY,
31
- "Account",
32
- options,
33
- );
34
- }
35
-
36
- export async function fetchDistinctIndustries(): Promise<PicklistOption[]> {
37
- return fetchDistinctValues<DistinctAccountIndustriesQuery>(
38
- DISTINCT_INDUSTRIES_QUERY,
39
- "Account",
40
- "Industry",
41
- );
42
- }
43
-
44
- export async function fetchDistinctTypes(): Promise<PicklistOption[]> {
45
- return fetchDistinctValues<DistinctAccountTypesQuery>(DISTINCT_TYPES_QUERY, "Account", "Type");
46
- }
@@ -1,19 +0,0 @@
1
- query DistinctAccountIndustries {
2
- uiapi {
3
- aggregate {
4
- Account(groupBy: { Industry: { group: true } }) {
5
- edges {
6
- node {
7
- aggregate @optional {
8
- Industry @optional {
9
- value
10
- displayValue
11
- label
12
- }
13
- }
14
- }
15
- }
16
- }
17
- }
18
- }
19
- }
@@ -1,19 +0,0 @@
1
- query DistinctAccountTypes {
2
- uiapi {
3
- aggregate {
4
- Account(groupBy: { Type: { group: true } }) {
5
- edges {
6
- node {
7
- aggregate @optional {
8
- Type @optional {
9
- value
10
- displayValue
11
- label
12
- }
13
- }
14
- }
15
- }
16
- }
17
- }
18
- }
19
- }
@@ -1,51 +0,0 @@
1
- query SearchAccounts(
2
- $first: Int
3
- $after: String
4
- $where: Account_Filter
5
- $orderBy: Account_OrderBy
6
- ) {
7
- uiapi {
8
- query {
9
- Account(first: $first, after: $after, where: $where, orderBy: $orderBy) {
10
- edges {
11
- node {
12
- Id
13
- Name @optional {
14
- value
15
- displayValue
16
- }
17
- Industry @optional {
18
- value
19
- displayValue
20
- }
21
- Type @optional {
22
- value
23
- displayValue
24
- }
25
- Phone @optional {
26
- value
27
- displayValue
28
- }
29
- Owner @optional {
30
- Name @optional {
31
- value
32
- displayValue
33
- }
34
- }
35
- AnnualRevenue @optional {
36
- value
37
- displayValue
38
- }
39
- }
40
- }
41
- pageInfo {
42
- hasNextPage
43
- hasPreviousPage
44
- endCursor
45
- startCursor
46
- }
47
- totalCount
48
- }
49
- }
50
- }
51
- }