@salesforce/webapp-template-feature-react-global-search-experimental 1.75.1 → 1.76.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -322
- package/dist/.a4drules/skills/webapp-add-react-component/SKILL.md +78 -0
- package/dist/.a4drules/skills/webapp-add-react-component/implementation/component.md +78 -0
- package/dist/.a4drules/skills/webapp-add-react-component/implementation/header-footer.md +124 -0
- package/dist/.a4drules/skills/webapp-add-react-component/implementation/page.md +92 -0
- package/dist/CHANGELOG.md +19 -0
- package/dist/force-app/main/default/webapplications/feature-react-global-search/package.json +3 -3
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +3 -26
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +0 -9
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +5 -104
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +3 -69
- package/dist/force-app/main/default/webapplications/feature-react-global-search/src/index.ts +125 -0
- package/dist/package.json +1 -1
- package/package.json +3 -3
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectDetailService.ts +3 -26
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoGraphQLService.ts +0 -9
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/api/objectInfoService.ts +5 -104
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useObjectSearchData.ts +7 -228
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/hooks/useRecordDetailLayout.ts +1 -20
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/utils/apiUtils.ts +3 -69
- package/src/force-app/main/default/webapplications/feature-react-global-search/src/index.ts +125 -0
package/README.md
CHANGED
|
@@ -1,403 +1,250 @@
|
|
|
1
|
-
# Feature: Global Search
|
|
1
|
+
# Feature: React Global Search (Single Object)
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package is a **reference implementation** for adding global search (keyword search, filters, results list, and record detail) to a Salesforce web app. It provides **API services, hooks, types/schemas, and utilities** so you can **build your own UI** and import the data layer from the package—or optionally **copy** the reference UI from the package and adapt it.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Intended use (vibing model):**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **Do not copy the feature source** (api, hooks, types, utils).
|
|
8
|
+
- **Build your own UI** (recommended), or **copy** the reference pages/components from the package and adapt.
|
|
9
|
+
- **Import** data-layer pieces from the package: services, hooks, types, utilities.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
2. **Before building or running the app**, build the library bundle from this package:
|
|
11
|
-
```bash
|
|
12
|
-
cd packages/template/feature/feature-react-global-search && npm run build:lib
|
|
13
|
-
```
|
|
14
|
-
This produces `dist/index.js`, which is the package entry point.
|
|
15
|
-
3. Import what you need (e.g. `useObjectListMetadata`, `parseFilterValue`, `sanitizeFilterValue`, `FilterCriteria`, `objectInfoService`). Use your own UI for filters and list pages.
|
|
11
|
+
UI components and feature constants are deliberately **not exported** from the public API.
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
- **Type Definitions** - Complete TypeScript types with Zod schema validation
|
|
19
|
-
- **Utility Functions** - Helper functions for debouncing and data extraction
|
|
20
|
-
- **Custom Hooks** - React hooks for managing search data and API calls
|
|
21
|
-
|
|
22
|
-
## Features
|
|
23
|
-
|
|
24
|
-
- **Multi-Object Search API** - Search across multiple Salesforce objects simultaneously
|
|
25
|
-
- **Object Metadata API** - Fetch object information, column definitions, and filter definitions
|
|
26
|
-
- **Picklist Values API** - Retrieve picklist values for dynamic filter options
|
|
27
|
-
- **Type Safety** - Full TypeScript support with Zod schema validation
|
|
28
|
-
- **Race Condition Protection** - AbortSignal support to cancel stale requests
|
|
29
|
-
- **Error Handling** - Consistent error handling across all API calls
|
|
30
|
-
|
|
31
|
-
## Architecture
|
|
32
|
-
|
|
33
|
-
### Data Layer (GraphQL vs REST)
|
|
34
|
-
|
|
35
|
-
- **GraphQL (uiapi)**
|
|
36
|
-
- **Object metadata** – `getObjectInfoBatch` and `getPicklistValues` are implemented via `getObjectInfosGraphQL` (uiapi.objectInfos). Responses are adapted to the existing REST-shaped types.
|
|
37
|
-
- **Records** – A single query path is used for both list and detail: `getRecordsGraphQL` with optional `recordId` for single-record fetch. `getRecordByIdGraphQL` is a thin wrapper that calls `getRecordsGraphQL` with `recordId` and `first: 1`.
|
|
38
|
-
|
|
39
|
-
- **REST (uiApiClient)**
|
|
40
|
-
- **Layout** – Detail view layout comes from `GET /layout/{object}` (used by `objectDetailService.getRecordDetail`).
|
|
41
|
-
- **List/search** – Columns are derived from `getObjectListFilters` (e.g. `filters.map(f => ({ fieldApiName: f.targetFieldPath, label: f.label }))`); filters and results from `getObjectListFilters` / `searchResults` (keyword search) or from `getRecordsGraphQL` for list views.
|
|
42
|
-
|
|
43
|
-
The detail page uses `useRecordDetailLayout` → `objectDetailService.getRecordDetail` (layout + object metadata + single record by Id). List/search uses `useRecordListGraphQL` (columns derived from Filters API, records from GraphQL) or `useObjectSearchData` (REST search).
|
|
44
|
-
|
|
45
|
-
### API Service Layer
|
|
46
|
-
|
|
47
|
-
The feature includes a state-agnostic service layer (`objectInfoService`, `objectDetailService`, record GraphQL helpers) that provides pure functions for API interactions. All API calls:
|
|
48
|
-
|
|
49
|
-
- Use `fetchAndValidate` utility for consistent error handling
|
|
50
|
-
- Support `AbortSignal` for request cancellation
|
|
51
|
-
- Validate responses with Zod schemas
|
|
52
|
-
- Provide type-safe return values
|
|
53
|
-
|
|
54
|
-
### API Flow Diagram
|
|
55
|
-
|
|
56
|
-
The following flowchart explains the API architecture and how each API is consumed:
|
|
57
|
-
|
|
58
|
-
```mermaid
|
|
59
|
-
flowchart TD
|
|
60
|
-
Start([Application Initialization]) --> BatchAPI[getObjectInfoBatch API]
|
|
61
|
-
BatchAPI -->|Comma-separated Object Names| ObjectMetadata[Object Metadata]
|
|
62
|
-
ObjectMetadata --> LoadObjectData[Load Data for Each Object]
|
|
13
|
+
---
|
|
63
14
|
|
|
64
|
-
|
|
65
|
-
|
|
15
|
+
## What you get (public API)
|
|
16
|
+
|
|
17
|
+
Import from the package root:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
objectInfoService,
|
|
22
|
+
objectDetailService,
|
|
23
|
+
useObjectListMetadata,
|
|
24
|
+
useRecordListGraphQL,
|
|
25
|
+
useRecordDetailLayout,
|
|
26
|
+
useObjectColumns,
|
|
27
|
+
useObjectFilters,
|
|
28
|
+
useObjectInfoBatch,
|
|
29
|
+
type FilterCriteria,
|
|
30
|
+
parseFilterValue,
|
|
31
|
+
sanitizeFilterValue,
|
|
32
|
+
} from "@salesforce/webapp-template-feature-react-global-search-experimental";
|
|
33
|
+
```
|
|
66
34
|
|
|
67
|
-
|
|
68
|
-
ParallelAPIs --> FiltersAPI[getObjectListFilters API]
|
|
69
|
-
FiltersAPI -->|Object Name| Filters[Filter Definitions]
|
|
70
|
-
Filters --> Columns[Column Definitions]
|
|
35
|
+
### API services
|
|
71
36
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
37
|
+
- **`objectInfoService`**
|
|
38
|
+
- `getObjectInfoBatch(objectApiNames)` (GraphQL uiapi.objectInfos)
|
|
39
|
+
- `getPicklistValues(objectApiName, fieldName, recordTypeId?)` (GraphQL uiapi.objectInfos)
|
|
40
|
+
- `getObjectListFilters(objectApiName)` (REST search-info filters)
|
|
41
|
+
- **`objectDetailService`**
|
|
42
|
+
- `getLayout(objectApiName, recordTypeId?)` (REST layout)
|
|
43
|
+
- `getRecordDetail(objectApiName, recordId, recordTypeId?)` (layout + object info + record)
|
|
44
|
+
- **GraphQL record helpers**
|
|
45
|
+
- `getRecordsGraphQL(...)` (list query, `first + after`)
|
|
46
|
+
- `getRecordByIdGraphQL(...)` (single record by Id)
|
|
47
|
+
- `buildWhereFromCriteria(...)`, `buildOrderByFromSort(...)`, `buildGetRecordsQuery(...)`
|
|
75
48
|
|
|
76
|
-
|
|
77
|
-
Filters --> PicklistCheck{Has Picklist Fields?}
|
|
78
|
-
PicklistCheck -->|Yes| PicklistAPI[getPicklistValues API]
|
|
79
|
-
PicklistAPI -->|Object Name + Field Name| PicklistValues[Picklist Options]
|
|
80
|
-
PicklistCheck -->|No| SkipPicklist[Skip Picklist Fetch]
|
|
49
|
+
### Hooks
|
|
81
50
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
51
|
+
- **List metadata**: `useObjectListMetadata(objectApiName)` — filters, columns (derived from filters), picklistValues.
|
|
52
|
+
- **List columns (legacy)**: `useObjectColumns(objectApiName)` — columns, columnsLoading, columnsError.
|
|
53
|
+
- **List filters**: `useObjectFilters(objectApiName)` — filtersData (filters + picklist values per object).
|
|
54
|
+
- **List records**: `useRecordListGraphQL({ objectApiName, first, after, filters, sortBy, searchQuery, columns? })` — GraphQL list with cursor pagination.
|
|
55
|
+
- **Detail**: `useRecordDetailLayout({ objectApiName, recordId, recordTypeId?, initialData? })` — layout (REST) + object metadata + record (GraphQL).
|
|
56
|
+
- **Batch object info**: `useObjectInfoBatch(objectApiNames)` — objectInfos, loading, error.
|
|
85
57
|
|
|
86
|
-
|
|
87
|
-
PageChange([Pagination Change]) --> SearchAPI
|
|
88
|
-
PageSizeChange([Page Size Change]) --> SearchAPI
|
|
58
|
+
### Types, schemas, utils
|
|
89
59
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
SearchAPI -.->|Error| ErrorHandler
|
|
94
|
-
PicklistAPI -.->|Error| ErrorHandler
|
|
95
|
-
ErrorHandler --> HandleError[Handle Error Gracefully]
|
|
60
|
+
- **Types**: `Filter`, `FilterCriteria`, `Column`, `SearchResultRecord`, `LayoutResponse`, `ObjectInfoResult`, …
|
|
61
|
+
- **Zod schemas**: `LayoutResponseSchema`, `ObjectInfoResultSchema`, `SearchResultsResponseSchema`, …
|
|
62
|
+
- **Utilities**: `parseFilterValue`, `sanitizeFilterValue`, `createFiltersKey`, `getGraphQLNodeValue`, `graphQLNodeToSearchResultRecordData`, `fetchAndValidate`, `safeEncodePath`, `getNestedFieldValue`, `getRecordDisplayName`, layout transform and form helpers, etc.
|
|
96
63
|
|
|
97
|
-
|
|
98
|
-
SearchAPI -.->|AbortSignal| CancelOld[Cancel Old Request]
|
|
99
|
-
CancelOld --> PreventStale[Prevent Stale Data]
|
|
64
|
+
The definitive type surface is in `index.d.ts` in this package.
|
|
100
65
|
|
|
101
|
-
|
|
102
|
-
style FiltersAPI fill:#e1f5ff
|
|
103
|
-
style SearchAPI fill:#e1f5ff
|
|
104
|
-
style PicklistAPI fill:#e1f5ff
|
|
105
|
-
style ErrorHandler fill:#ffe1e1
|
|
106
|
-
style CancelOld fill:#fff4e1
|
|
107
|
-
```
|
|
66
|
+
---
|
|
108
67
|
|
|
109
|
-
##
|
|
68
|
+
## Features
|
|
110
69
|
|
|
111
|
-
|
|
70
|
+
- **Single object browse & search**: keyword search and “browse all” mode (`browse__all`).
|
|
71
|
+
- **Filterable list**: filter definitions + picklist values, criteria → GraphQL where clause.
|
|
72
|
+
- **Sortable list**: build orderBy from sort selection.
|
|
73
|
+
- **Cursor pagination (forward-only)** with **previous-page simulation via cursor stack**.
|
|
74
|
+
- **Record detail**: layout-driven field selection (layout REST + record GraphQL).
|
|
75
|
+
- **Type safety**: Zod validation + exported TypeScript types.
|
|
112
76
|
|
|
113
|
-
|
|
77
|
+
---
|
|
114
78
|
|
|
115
|
-
|
|
79
|
+
## Architecture
|
|
116
80
|
|
|
117
|
-
|
|
118
|
-
- Needed to create tabs for each object type
|
|
119
|
-
- Provides object plural labels for tab display
|
|
81
|
+
### Data layer (GraphQL vs REST)
|
|
120
82
|
|
|
121
|
-
**
|
|
83
|
+
- **GraphQL (uiapi)**
|
|
84
|
+
Object metadata via `getObjectInfoBatch` and `getPicklistValues`. Records via `getRecordsGraphQL` and `getRecordByIdGraphQL`.
|
|
85
|
+
- **REST (uiApiClient)**
|
|
86
|
+
Layout from `GET /layout/{object}`. Search filters from `GET /search-info/{object}/filters`. List/search results are **GraphQL-only** via `useRecordListGraphQL` (no REST keyword search in the public API).
|
|
122
87
|
|
|
123
|
-
|
|
124
|
-
- `signal?`: Optional AbortSignal for cancellation
|
|
88
|
+
In the reference implementation:
|
|
125
89
|
|
|
126
|
-
**
|
|
90
|
+
- **Detail page**: `useRecordDetailLayout` → `objectDetailService.getRecordDetail` (layout REST + record GraphQL).
|
|
91
|
+
- **List page**: `useObjectListMetadata` (filters + columns + picklists) + `useRecordListGraphQL` (records).
|
|
127
92
|
|
|
128
|
-
|
|
93
|
+
### API flow (conceptual)
|
|
129
94
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
```
|
|
95
|
+
- **Initialization**: `getObjectInfoBatch` for object metadata; per object: `getObjectListFilters` (columns derived from filters), then `getPicklistValues` for picklist filters.
|
|
96
|
+
- **List/search**: `useRecordListGraphQL` (GraphQL UI API with cursor pagination; use a cursor stack for “Previous”).
|
|
97
|
+
- **Detail**: `useRecordDetailLayout` / `objectDetailService.getRecordDetail`.
|
|
134
98
|
|
|
135
|
-
|
|
99
|
+
All API calls use `fetchAndValidate` and validate with Zod.
|
|
136
100
|
|
|
137
101
|
---
|
|
138
102
|
|
|
139
|
-
|
|
103
|
+
## Install
|
|
140
104
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
105
|
+
```bash
|
|
106
|
+
npm install @salesforce/webapp-template-feature-react-global-search-experimental
|
|
107
|
+
```
|
|
144
108
|
|
|
145
|
-
|
|
146
|
-
- Provides filter metadata (field types, operators, default values, affordances)
|
|
147
|
-
- Used to understand what filters can be applied to search results
|
|
109
|
+
### Required peer environment
|
|
148
110
|
|
|
149
|
-
|
|
111
|
+
- `@salesforce/webapp-experimental` (API proxy, auth, etc.)
|
|
112
|
+
- React and React Router
|
|
150
113
|
|
|
151
|
-
|
|
152
|
-
- `signal?`: Optional AbortSignal for cancellation
|
|
114
|
+
If you are not using that stack, you can still reuse **pure** utilities and types; the API services and hooks need an equivalent UI API transport.
|
|
153
115
|
|
|
154
|
-
|
|
116
|
+
---
|
|
155
117
|
|
|
156
|
-
|
|
118
|
+
## Configuration (constants)
|
|
157
119
|
|
|
158
|
-
|
|
159
|
-
const filters = await getObjectListFilters("Account");
|
|
160
|
-
// Returns: [{ targetFieldPath: 'Name', operator: 'contains', ... }, ...]
|
|
161
|
-
```
|
|
120
|
+
Constants are **not exported**. When you build your app (or copy reference UI), define them in the app:
|
|
162
121
|
|
|
163
|
-
**API
|
|
122
|
+
- **OBJECT_API_NAMES** – Array of object API names to search. First element is the primary object (e.g. `OBJECT_API_NAMES[0]`). Default to `["Account"]` if no object is specified.
|
|
123
|
+
- **DEFAULT_PAGE_SIZE** – e.g. `20`.
|
|
124
|
+
- **DEFAULT_DETAIL_PAGE_TITLE** – e.g. `"Untitled"`.
|
|
164
125
|
|
|
165
|
-
|
|
126
|
+
**Determining the search object:** Decide from the user’s request or app context. If the user does not specify an object, use **Account**. If your repo provides a script (e.g. `scripts/parse-feature-install-context.cjs`), you can use it to parse the user prompt and get an object API name or label hint; otherwise set OBJECT_API_NAMES in the app’s constants accordingly.
|
|
166
127
|
|
|
167
128
|
---
|
|
168
129
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
**Purpose**: Fetches available picklist values for a specific field.
|
|
172
|
-
|
|
173
|
-
**When to Use**:
|
|
130
|
+
## Vibing integration (recommended: build your own UI)
|
|
174
131
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- Called for each picklist field in the filters array returned by `getObjectListFilters`
|
|
132
|
+
1. **Routes**
|
|
133
|
+
Search results: e.g. `/global-search/:query` (use `browse__all` for “show all”). Record detail: e.g. `/object/:objectApiName/:recordId`.
|
|
178
134
|
|
|
179
|
-
**
|
|
135
|
+
2. **Search entry**
|
|
136
|
+
On submit, navigate to `/global-search/${encodeURIComponent(query)}` or `/global-search/browse__all`.
|
|
180
137
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- `recordTypeId?`: Optional record type ID (defaults to master record type)
|
|
184
|
-
- `signal?`: Optional AbortSignal for cancellation
|
|
138
|
+
3. **Results page (list)**
|
|
139
|
+
Use `useObjectListMetadata(objectApiName)` for filters/columns/picklists and `useRecordListGraphQL({ objectApiName, first, after, filters, sortBy, searchQuery, columns })` for records. Implement a **cursor stack** for “Previous” (see reference `GlobalSearch` page).
|
|
185
140
|
|
|
186
|
-
**
|
|
141
|
+
4. **Detail page**
|
|
142
|
+
Use `useRecordDetailLayout({ objectApiName, recordId })` for `layout`, `record`, `objectMetadata`; render with `layoutTransformUtils` and `graphQLNodeFieldUtils` (or equivalent).
|
|
187
143
|
|
|
188
|
-
**
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
const picklistValues = await getPicklistValues("Account", "Type");
|
|
192
|
-
// Returns: [{ label: 'Customer', value: 'Customer' }, { label: 'Partner', value: 'Partner' }, ...]
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
**API Endpoint**: `GET /object-info/{objectApiName}/picklist-values/{recordTypeId}/{fieldName}`
|
|
144
|
+
5. **Search input placement**
|
|
145
|
+
Put the search entry on the **Home page** (e.g. as a card) or in the app **Header**—**not** in a root layout that wraps every page.
|
|
196
146
|
|
|
197
147
|
---
|
|
198
148
|
|
|
199
|
-
|
|
149
|
+
## Optional: copy reference UI from the package
|
|
200
150
|
|
|
201
|
-
**
|
|
151
|
+
The package **does not export** UI components. If you prefer to copy the reference UI instead of building your own:
|
|
202
152
|
|
|
203
|
-
**
|
|
153
|
+
- **What to import (do not copy):** API (`objectInfoService`, `objectDetailService`, …), hooks (`useObjectListMetadata`, `useRecordListGraphQL`, `useRecordDetailLayout`, …), types, Zod schemas, utilities.
|
|
154
|
+
- **What to copy into the app:** A single **constants** file (set `OBJECT_API_NAMES` to your chosen object(s)) and the **pages** and **components** from the package’s reference implementation.
|
|
204
155
|
|
|
205
|
-
|
|
206
|
-
- When filters are applied (query should include filter criteria)
|
|
207
|
-
- When pagination changes (use `pageToken` parameter)
|
|
208
|
-
- When page size changes (use `pageSize` parameter)
|
|
209
|
-
- Returns the actual search results with field values
|
|
156
|
+
**Path convention (reference implementation in this repo):**
|
|
210
157
|
|
|
211
|
-
**
|
|
158
|
+
All paths below are relative to the **package root** of this feature. The reference implementation lives under:
|
|
212
159
|
|
|
213
|
-
-
|
|
214
|
-
- `objectApiName`: The object API name to search (e.g., `"Account"`)
|
|
215
|
-
- `params?`: Optional pagination parameters (`pageSize`, `pageToken`)
|
|
216
|
-
- `signal?`: Optional AbortSignal for cancellation (critical for race condition prevention)
|
|
160
|
+
`src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/`
|
|
217
161
|
|
|
218
|
-
|
|
162
|
+
Example paths:
|
|
219
163
|
|
|
220
|
-
|
|
164
|
+
- Constants: `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/constants.ts`
|
|
165
|
+
- Search page: `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/pages/GlobalSearch.tsx`
|
|
166
|
+
- Components: `src/force-app/main/default/webapplications/feature-react-global-search/src/features/global-search/components/`
|
|
221
167
|
|
|
222
|
-
|
|
223
|
-
const results = await searchResults("Acme", "Account", { pageSize: 25, pageToken: "0" });
|
|
224
|
-
// Returns: [{ id: '001...', fields: { Name: 'Acme Corp', ... }, ... }, ...]
|
|
225
|
-
```
|
|
168
|
+
After copying **pages/** and **components/** into your app:
|
|
226
169
|
|
|
227
|
-
**
|
|
170
|
+
1. In the **copied files only**, replace imports from `@/api`, `@/hooks`, `@/types`, `@/utils`, `@/lib`, `@/features` with imports from **`@salesforce/webapp-template-feature-react-global-search-experimental`**.
|
|
171
|
+
2. Keep constants and component-to-component imports pointing at your app’s copied constants and components.
|
|
172
|
+
3. Adapt markup and styles to your app’s UI library (the reference uses shadcn and Tailwind).
|
|
228
173
|
|
|
229
|
-
|
|
174
|
+
---
|
|
230
175
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
176
|
+
## Example: build filters → FilterCriteria[]
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import {
|
|
180
|
+
parseFilterValue,
|
|
181
|
+
sanitizeFilterValue,
|
|
182
|
+
type FilterCriteria,
|
|
183
|
+
} from "@salesforce/webapp-template-feature-react-global-search-experimental";
|
|
184
|
+
|
|
185
|
+
function toCriteria(
|
|
186
|
+
objectApiName: string,
|
|
187
|
+
fieldPath: string,
|
|
188
|
+
operator: "eq" | "like",
|
|
189
|
+
rawValues: string[],
|
|
190
|
+
): FilterCriteria {
|
|
191
|
+
return {
|
|
192
|
+
objectApiName,
|
|
193
|
+
fieldPath,
|
|
194
|
+
operator,
|
|
195
|
+
values: rawValues
|
|
196
|
+
.map((v) => sanitizeFilterValue(v))
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
.map((v) => parseFilterValue(v)),
|
|
199
|
+
};
|
|
235
200
|
}
|
|
236
201
|
```
|
|
237
202
|
|
|
238
203
|
---
|
|
239
204
|
|
|
240
|
-
##
|
|
241
|
-
|
|
242
|
-
### Initialization
|
|
243
|
-
|
|
244
|
-
1. **`getObjectInfoBatch`** is called with all object names from `OBJECT_API_NAMES`
|
|
245
|
-
- Returns object metadata (labels, plural labels, etc.)
|
|
246
|
-
- Use this to understand which objects are available for search
|
|
247
|
-
|
|
248
|
-
2. **For each object** (can be done in parallel):
|
|
249
|
-
- **`getObjectListFilters`** - Gets filter definitions; column definitions for list/result UI are derived from filters (e.g. `filters.map(f => f.targetFieldPath)` for field names)
|
|
250
|
-
- **`searchResults`** - Gets initial search results (if query is provided)
|
|
251
|
-
|
|
252
|
-
3. **For each picklist filter** (can be done in parallel):
|
|
253
|
-
- **`getPicklistValues`** - Gets available values for picklist fields
|
|
254
|
-
|
|
255
|
-
### Search Execution
|
|
205
|
+
## Implementation steps (for an agent)
|
|
256
206
|
|
|
257
|
-
1. **
|
|
258
|
-
|
|
259
|
-
- Include pagination parameters (`pageSize`, `pageToken`)
|
|
207
|
+
1. **Install**
|
|
208
|
+
From the consumer app root: `npm install @salesforce/webapp-template-feature-react-global-search-experimental`.
|
|
260
209
|
|
|
261
|
-
2. **
|
|
262
|
-
|
|
263
|
-
- Call **`searchResults`** with updated query/filters
|
|
264
|
-
- Reset pagination to page 1 (use `pageToken: '0'`)
|
|
210
|
+
2. **Determine search object(s)**
|
|
211
|
+
From user prompt or app context. Default to **Account** if none specified. Set `OBJECT_API_NAMES` in the app’s constants to this value.
|
|
265
212
|
|
|
266
|
-
3. **
|
|
267
|
-
|
|
268
|
-
- Call **`searchResults`** with new `pageToken`
|
|
213
|
+
3. **Constants**
|
|
214
|
+
In the app, create a constants file (or copy from the package reference path above) and set `OBJECT_API_NAMES`, `DEFAULT_PAGE_SIZE`, `DEFAULT_DETAIL_PAGE_TITLE`.
|
|
269
215
|
|
|
270
|
-
4. **
|
|
271
|
-
|
|
272
|
-
- Reset to page 1 (use `pageToken: '0'`)
|
|
216
|
+
4. **API / types / utils / hooks**
|
|
217
|
+
**Import** only from the package. Do **not** create or copy `api/`, `hooks/`, `types/`, or `utils/` in the app.
|
|
273
218
|
|
|
274
|
-
|
|
219
|
+
5. **UI**
|
|
220
|
+
Either **(a)** build your own UI using the package’s hooks and types, or **(b)** copy the full **pages/** and **components/** from the package reference path into the app, then batch-replace imports in the copied files to use the package (see “Optional: copy reference UI” above).
|
|
275
221
|
|
|
276
|
-
|
|
222
|
+
6. **Routes and search input**
|
|
223
|
+
Add routes for search (e.g. `/global-search/:query`) and record detail (e.g. `/object/:objectApiName/:recordId`). Place the search input on the **Home page** or in the **Header** only—not in a root layout.
|
|
277
224
|
|
|
278
|
-
|
|
279
|
-
- When switching between objects, cancel previous object requests
|
|
280
|
-
- When component unmounts, cancel all pending requests
|
|
225
|
+
### Checklist
|
|
281
226
|
|
|
282
|
-
|
|
227
|
+
- [ ] Package installed.
|
|
228
|
+
- [ ] Search object(s) determined; `OBJECT_API_NAMES` set in app constants.
|
|
229
|
+
- [ ] API, types, utils, hooks **imported** from package only (no api/hooks/types/utils folders in app).
|
|
230
|
+
- [ ] UI: own implementation **or** full copy of reference pages/components with imports updated to the package.
|
|
231
|
+
- [ ] Routes and search entry placement done; GlobalSearchInput not in root layout.
|
|
283
232
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
### Prerequisites
|
|
233
|
+
---
|
|
287
234
|
|
|
288
|
-
|
|
289
|
-
2. **@salesforce/webapps packages** - Required for API proxy functionality (see linking instructions below)
|
|
235
|
+
## Notes / constraints
|
|
290
236
|
|
|
291
|
-
|
|
237
|
+
- **Single-object scope**: The package’s APIs and hooks are designed for one CRM object at a time (one `objectApiName` per call). There is no built-in multi-object search or tabbed results. You can still implement **multi-object CRM search** by calling the same services and hooks once per object (e.g. in parallel or per tab), then combining the results and building your own UI (tabs, merged lists, or separate sections).
|
|
238
|
+
- **Layout is REST**: record layout from UI API REST; record and object metadata are GraphQL-backed.
|
|
239
|
+
- **No UI exports**: build or copy UI in the app; import only the data layer from the package.
|
|
292
240
|
|
|
293
|
-
|
|
241
|
+
---
|
|
294
242
|
|
|
295
|
-
|
|
296
|
-
# Step 1: In the webapps repo - register packages with npm's link registry
|
|
297
|
-
cd /path/to/webapps/packages/webapps && npm link
|
|
298
|
-
cd /path/to/webapps/packages/vite-plugin-webapps && npm link
|
|
299
|
-
|
|
300
|
-
# Step 2: In this repo - link the packages
|
|
301
|
-
cd /path/to/webapps-templates/packages/feature/feature-react-global-search
|
|
302
|
-
npm link @salesforce/webapps
|
|
303
|
-
npm link @salesforce/vite-plugin-webapps
|
|
304
|
-
```
|
|
243
|
+
## Local development (this repo)
|
|
305
244
|
|
|
306
|
-
|
|
245
|
+
When developing this feature inside the webapps repo:
|
|
307
246
|
|
|
308
247
|
```bash
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
# Start the development server
|
|
313
|
-
npm run dev
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
The `npm run build` command will:
|
|
317
|
-
|
|
318
|
-
1. Apply feature patches to create the dist directory
|
|
319
|
-
2. Remove the package-lock.json (to avoid resolution conflicts)
|
|
320
|
-
3. Re-link the @salesforce/webapps packages
|
|
321
|
-
|
|
322
|
-
## Configuration
|
|
323
|
-
|
|
324
|
-
### Setting Searchable Objects
|
|
325
|
-
|
|
326
|
-
Configure which Salesforce objects to search by editing `src/constants.ts`:
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
export const OBJECT_API_NAMES = ["Account", "Property__c"] as const;
|
|
248
|
+
nx run @salesforce/webapp-template-feature-react-global-search-experimental:build:dist-app
|
|
249
|
+
nx run @salesforce/webapp-template-feature-react-global-search-experimental:dev
|
|
330
250
|
```
|
|
331
|
-
|
|
332
|
-
This configuration determines which objects will be included when calling `getObjectInfoBatch` and when executing searches.
|
|
333
|
-
|
|
334
|
-
## Usage
|
|
335
|
-
|
|
336
|
-
### Using the API Service
|
|
337
|
-
|
|
338
|
-
Import the service and use the available methods:
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
import { objectInfoService } from "@/api/objectInfoService";
|
|
342
|
-
|
|
343
|
-
// Get object metadata
|
|
344
|
-
const objectInfo = await objectInfoService.getObjectInfoBatch("Account,Property__c");
|
|
345
|
-
|
|
346
|
-
// Get filter definitions (columns for list UI are derived from filters via targetFieldPath + label)
|
|
347
|
-
const filters = await objectInfoService.getObjectListFilters("Account");
|
|
348
|
-
|
|
349
|
-
// Get picklist values
|
|
350
|
-
const picklistValues = await objectInfoService.getPicklistValues("Account", "Type");
|
|
351
|
-
|
|
352
|
-
// Execute search
|
|
353
|
-
const results = await objectInfoService.searchResults("Acme", "Account", {
|
|
354
|
-
pageSize: 25,
|
|
355
|
-
pageToken: "0",
|
|
356
|
-
});
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### Using the Custom Hook
|
|
360
|
-
|
|
361
|
-
The `useObjectSearchData` hook manages API calls and data fetching:
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
import { useObjectSearchData } from "@/hooks/useObjectSearchData";
|
|
365
|
-
|
|
366
|
-
const { tabs, loading, error, tabData, filtersData } = useObjectSearchData({
|
|
367
|
-
filteredObjectApiNames: ["Account", "Property__c"],
|
|
368
|
-
activeTab: "Account",
|
|
369
|
-
searchQuery: "Acme",
|
|
370
|
-
searchPageSize: 25,
|
|
371
|
-
searchPageToken: "0",
|
|
372
|
-
});
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
## Type Safety
|
|
376
|
-
|
|
377
|
-
All API responses are validated using Zod schemas, ensuring:
|
|
378
|
-
|
|
379
|
-
- Type safety at compile time
|
|
380
|
-
- Runtime validation of API responses
|
|
381
|
-
- Better error messages when API contracts change
|
|
382
|
-
- Protection against malformed data
|
|
383
|
-
|
|
384
|
-
Type definitions are derived from Zod schemas using `z.infer`, ensuring consistency between validation and TypeScript types.
|
|
385
|
-
|
|
386
|
-
## Error Handling
|
|
387
|
-
|
|
388
|
-
The `fetchAndValidate` utility provides consistent error handling:
|
|
389
|
-
|
|
390
|
-
- Network errors are caught and re-thrown with context
|
|
391
|
-
- HTTP errors (non-200 status) are caught and reported
|
|
392
|
-
- Zod validation errors are caught and reported (without logging sensitive details)
|
|
393
|
-
- AbortErrors are properly propagated (not treated as regular errors)
|
|
394
|
-
|
|
395
|
-
All errors are handled gracefully in the UI, preventing page refreshes and providing user-friendly error messages.
|
|
396
|
-
|
|
397
|
-
## Dependencies
|
|
398
|
-
|
|
399
|
-
This feature depends on:
|
|
400
|
-
|
|
401
|
-
- **feature-shadcn** - For all UI components (Button, Input, Select, Table, Pagination, etc.)
|
|
402
|
-
- **@salesforce/webapps** - For API client and Salesforce integration
|
|
403
|
-
- **zod** - For runtime validation and type inference
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webapp-add-react-component
|
|
3
|
+
description: Creates React components, pages, headers, and footers using shadcn UI and Tailwind CSS. Use when the user asks to create a component, add a widget, build a UI element, add a page, create a new page, add a section (e.g. "add a contacts page"), add a header, add a footer, add a navigation bar, or add anything to the web application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add React Component
|
|
7
|
+
|
|
8
|
+
## Step 1 — Identify the type of component
|
|
9
|
+
|
|
10
|
+
Determine which of these three categories the request falls into, then follow the corresponding section below:
|
|
11
|
+
|
|
12
|
+
- **Page** — user wants a new routed page (e.g. "add a contacts page", "create a dashboard page", "add a settings section")
|
|
13
|
+
- **Header / Footer** — user wants a site-wide header, footer, nav bar, or page footer that appears on every page
|
|
14
|
+
- **Component** — everything else: a widget, card, table, form, dialog, or other UI element placed within an existing page
|
|
15
|
+
|
|
16
|
+
If it is not immediately clear from the user's message, ask:
|
|
17
|
+
|
|
18
|
+
> "Are you looking to add a new page, a site-wide header or footer, or a component within an existing page?"
|
|
19
|
+
|
|
20
|
+
Then follow the matching section.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Clarifying Questions
|
|
25
|
+
|
|
26
|
+
Ask **one question at a time** and wait for the response before asking the next. Stop when you have enough to build accurately — do not guess or assume.
|
|
27
|
+
|
|
28
|
+
### For a Page
|
|
29
|
+
|
|
30
|
+
1. **What is the name and purpose of the page?** (e.g., Contacts, Dashboard, Settings)
|
|
31
|
+
2. **What URL path should it use?** (e.g., `/contacts`, `/dashboard`) — or derive from the page name?
|
|
32
|
+
3. **Should the page appear in the navigation menu?**
|
|
33
|
+
4. **Who can access it?** Public, authenticated users only (`PrivateRoute`), or unauthenticated only (e.g., login — `AuthenticationRoute`)?
|
|
34
|
+
5. **What content or sections should the page include?** (list, form, table, detail view, etc.)
|
|
35
|
+
6. **Does it need to fetch any data?** If so, from where?
|
|
36
|
+
|
|
37
|
+
### For a Header / Footer
|
|
38
|
+
|
|
39
|
+
1. **Header, footer, or both?**
|
|
40
|
+
2. **What should the header contain?** (logo/app name, nav links, user avatar, CTA button, etc.)
|
|
41
|
+
3. **What should the footer contain?** (copyright text, links, social icons, etc.)
|
|
42
|
+
4. **Should the header be sticky (fixed to top while scrolling)?**
|
|
43
|
+
5. **Is there a logo or brand name to display?** (or placeholder?)
|
|
44
|
+
6. **Any specific color scheme or style direction?** (dark background, branded primary color, minimal, etc.)
|
|
45
|
+
7. **Should navigation links appear in the header?** If so, which pages?
|
|
46
|
+
|
|
47
|
+
### For a Component
|
|
48
|
+
|
|
49
|
+
1. **What should the component do?** (display data, accept input, trigger an action, etc.)
|
|
50
|
+
2. **What page or location should it appear on?**
|
|
51
|
+
3. **Is this shared/reusable across pages, or specific to one feature?** (determines file location)
|
|
52
|
+
4. **What data or props does it need?** (static content, props, fetched data)
|
|
53
|
+
5. **Does it need internal state?** (loading, toggle, form state, etc.)
|
|
54
|
+
6. **Are there any specific shadcn components to use?** (Card, Table, Dialog, Form, etc.)
|
|
55
|
+
7. **Should it appear in a specific layout position?** (full-width, sidebar, inline, etc.)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Implementation
|
|
60
|
+
|
|
61
|
+
Once you have identified the type and gathered answers to the clarifying questions, read and follow the corresponding implementation guide:
|
|
62
|
+
|
|
63
|
+
- **Page** — read `implementation/page.md` and follow the instructions there.
|
|
64
|
+
- **Header / Footer** — read `implementation/header-footer.md` and follow the instructions there.
|
|
65
|
+
- **Component** — read `implementation/component.md` and follow the instructions there.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Verification
|
|
70
|
+
|
|
71
|
+
Before completing, run from the web app directory `force-app/main/default/webapplications/<appName>/` (use the actual app folder name):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **Lint:** MUST result in 0 errors. Fix any ESLint or TypeScript issues.
|
|
78
|
+
- **Build:** MUST succeed. Resolve any compilation or Vite build failures before finishing.
|