chore: sync React B2E (1.116.6) & B2X (1.116.6) samples from npm (#114)
46
package-lock.json
generated
@ -9,8 +9,8 @@
|
||||
"version": "1.5.0",
|
||||
"license": "CC-BY-NC-4.0",
|
||||
"devDependencies": {
|
||||
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.112.10",
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.112.10",
|
||||
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.116.6",
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.116.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.1",
|
||||
"tsx": "^4.21.0"
|
||||
@ -678,9 +678,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/kit": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/kit/-/kit-3.2.5.tgz",
|
||||
"integrity": "sha512-Ad3H423ebBvi+q09BQL4pZC2ffwVUPoeuxwos+Sbr53TamkC3g5YT+ASB4MqqqAhMxFgyalFWIjzx9230tts7g==",
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/kit/-/kit-3.2.6.tgz",
|
||||
"integrity": "sha512-O8S4LWerHa9Zosqh+IoQjgLtpxMOfObRxaRnUdRV4MLtFUi+bQxQiyFvve6eEaBaMP1b1xVDQpvSvQ+PXEDGFQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -688,22 +688,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/sdk-core": {
|
||||
"version": "1.112.10",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.112.10.tgz",
|
||||
"integrity": "sha512-JUbzrDIg5PMuxGlABkNyMmXmQdlXIqdKrstR/lZGhrqektrI7TWc/owun2iXALLsODS3nh3nNV5p87KqYltpGA==",
|
||||
"version": "1.116.6",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.116.6.tgz",
|
||||
"integrity": "sha512-5IWfcnQRuaOxtWBbGiW00rk6f4ImNrDDnVhqZOphJhRePOvSLqWLirfIIhRK4j00Tr8w0tgn5H+hIAWCRZvIHw==",
|
||||
"dev": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt"
|
||||
},
|
||||
"node_modules/@salesforce/sdk-data": {
|
||||
"version": "1.112.10",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/sdk-data/-/sdk-data-1.112.10.tgz",
|
||||
"integrity": "sha512-YcQQUiPmdbq4wak9iRjY0FV3Xs+RRwZB0UEQ3ovWxy11rN3ZQBghdxNORYuYkjb4TCiiS6cWVy4/a+o4NspFHQ==",
|
||||
"version": "1.116.6",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/sdk-data/-/sdk-data-1.116.6.tgz",
|
||||
"integrity": "sha512-yjc1CQvBYAXWrouMmD3buu35u4DwgUGTaDZKU4//F5vIJJitsmPGrJCLDd0HdBzyu/J+77RuVGZBgsal7+zyZQ==",
|
||||
"dev": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"dependencies": {
|
||||
"@conduit-client/service-fetch-network": "3.17.0",
|
||||
"@conduit-client/utils": "3.17.0",
|
||||
"@salesforce/sdk-core": "^1.112.10"
|
||||
"@salesforce/sdk-core": "^1.116.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/ts-types": {
|
||||
@ -717,14 +717,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/webapp-experimental": {
|
||||
"version": "1.112.10",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/webapp-experimental/-/webapp-experimental-1.112.10.tgz",
|
||||
"integrity": "sha512-QwV0j4XIkYt75W78m362H+bJEejX78y1TUvhdcQZLRt5zJWi5BdNg8IhSV+aVSsCM0k6cGuAxgfixgCOX8yHfg==",
|
||||
"version": "1.116.6",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/webapp-experimental/-/webapp-experimental-1.116.6.tgz",
|
||||
"integrity": "sha512-bbWVK70rv5Y+e26NLyTB2McTZ3MCQFe+NEYZDW/T4FCaAZ3Y57u1HYzgr8p4iWKlu7DU4V1sNTUGjt01mrN0Dg==",
|
||||
"dev": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"dependencies": {
|
||||
"@salesforce/core": "^8.23.4",
|
||||
"@salesforce/sdk-data": "^1.112.10",
|
||||
"@salesforce/sdk-data": "^1.116.6",
|
||||
"axios": "^1.7.7",
|
||||
"micromatch": "^4.0.8",
|
||||
"path-to-regexp": "^8.3.0"
|
||||
@ -734,20 +734,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/webapp-template-app-react-sample-b2e-experimental": {
|
||||
"version": "1.112.10",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/webapp-template-app-react-sample-b2e-experimental/-/webapp-template-app-react-sample-b2e-experimental-1.112.10.tgz",
|
||||
"integrity": "sha512-LZCC61D3HCiGH3kxdQNaF/Z/lwj8s+vbPM/sHGC16amxfByRaxYnuIVJwITxmiKFxbrm19gqSvf9BQ6aH8Cnfg==",
|
||||
"version": "1.116.6",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/webapp-template-app-react-sample-b2e-experimental/-/webapp-template-app-react-sample-b2e-experimental-1.116.6.tgz",
|
||||
"integrity": "sha512-RctkFfBfK+7HLAnP2ERmXG3Nn8apFF4ywh5LlSyOJVUjbQI48kd7PeiCX5v5G4C5Kvqp7wyhhucY0Mk4k8PLEQ==",
|
||||
"dev": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"dependencies": {
|
||||
"@salesforce/webapp-experimental": "^1.112.10",
|
||||
"@salesforce/webapp-experimental": "^1.116.6",
|
||||
"sonner": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@salesforce/webapp-template-app-react-sample-b2x-experimental": {
|
||||
"version": "1.112.10",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/webapp-template-app-react-sample-b2x-experimental/-/webapp-template-app-react-sample-b2x-experimental-1.112.10.tgz",
|
||||
"integrity": "sha512-/VPf79iRzyiRjvKn7XzNRnYwhR6sBNRW9qV2wSvgLQhDQ7BIVU8nW5TPKcQOsTUtbgp2GxrV8I1B3XBkgx4TOw==",
|
||||
"version": "1.116.6",
|
||||
"resolved": "https://registry.npmjs.org/@salesforce/webapp-template-app-react-sample-b2x-experimental/-/webapp-template-app-react-sample-b2x-experimental-1.116.6.tgz",
|
||||
"integrity": "sha512-Ql5l+WRvAIQ0iNTutj3k0GDjHRI7wwAbXjzBRKeCR3Tp0pHEXeoKuqj1AACxCv1APAvTOne4cvzb11eJblDOLA==",
|
||||
"dev": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt"
|
||||
},
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
"registry": "https://registry.npmjs.org"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.112.10",
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.112.10",
|
||||
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.116.6",
|
||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.116.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.1",
|
||||
"tsx": "^4.21.0"
|
||||
|
||||
@ -1,353 +0,0 @@
|
||||
# Salesforce Data Access
|
||||
|
||||
- **Fetch or display Salesforce data** — Query records (Account, Contact, Opportunity, custom objects) to show in a component
|
||||
- **Create, update, or delete records** — Perform mutations on Salesforce data
|
||||
- **Add data fetching to a component** — Wire up a React component to Salesforce data
|
||||
- **Call REST APIs** — Use Connect REST, Apex REST, or UI API endpoints
|
||||
- **Explore the org schema** — Discover available objects, fields, or relationships
|
||||
|
||||
## Data SDK Requirement
|
||||
|
||||
> **All Salesforce data access MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK handles authentication, CSRF, and base URL resolution. Never use `fetch()` or `axios` directly.
|
||||
|
||||
```typescript
|
||||
import { createDataSDK, gql } from "@salesforce/sdk-data";
|
||||
|
||||
const sdk = await createDataSDK();
|
||||
|
||||
// GraphQL for record queries/mutations (PREFERRED)
|
||||
const response = await sdk.graphql?.<ResponseType>(query, variables);
|
||||
|
||||
// REST for Connect REST, Apex REST, UI API (when GraphQL insufficient)
|
||||
const res = await sdk.fetch?.("/services/apexrest/my-resource");
|
||||
```
|
||||
|
||||
**Always use optional chaining** (`sdk.graphql?.()`, `sdk.fetch?.()`) — these methods may be undefined in some surfaces.
|
||||
|
||||
## Supported APIs
|
||||
|
||||
**Only the following APIs are permitted.** Any endpoint not listed here must not be used.
|
||||
|
||||
| API | Method | Endpoints / Use Case |
|
||||
|-----|--------|----------------------|
|
||||
| GraphQL | `sdk.graphql` | All record queries and mutations via `uiapi { }` namespace |
|
||||
| UI API REST | `sdk.fetch` | `/services/data/v{ver}/ui-api/records/{id}` — record metadata when GraphQL is insufficient |
|
||||
| Apex REST | `sdk.fetch` | `/services/apexrest/{resource}` — custom server-side logic, aggregates, multi-step transactions |
|
||||
| Connect REST | `sdk.fetch` | `/services/data/v{ver}/connect/file/upload/config` — file upload config |
|
||||
| Einstein LLM | `sdk.fetch` | `/services/data/v{ver}/einstein/llm/prompt/generations` — AI text generation |
|
||||
|
||||
**Not supported:**
|
||||
|
||||
- **Enterprise REST query endpoint** (`/services/data/v*/query` with SOQL) — blocked at the proxy level. Use GraphQL for record reads; use Apex REST if server-side SOQL aggregates are required.
|
||||
- **Aura-enabled Apex** (`@AuraEnabled`) — an LWC/Aura pattern with no invocation path from React webapps.
|
||||
- **Chatter API** (`/chatter/users/me`) — use `uiapi { currentUser { ... } }` in a GraphQL query instead.
|
||||
- **Any other Salesforce REST endpoint** not listed in the supported table above.
|
||||
|
||||
## Decision: GraphQL vs REST
|
||||
|
||||
| Need | Method | Example |
|
||||
|------|--------|---------|
|
||||
| Query/mutate records | `sdk.graphql` | Account, Contact, custom objects |
|
||||
| Current user info | `sdk.graphql` | `uiapi { currentUser { Id Name { value } } }` |
|
||||
| UI API record metadata | `sdk.fetch` | `/ui-api/records/{id}` |
|
||||
| Connect REST | `sdk.fetch` | `/connect/file/upload/config` |
|
||||
| Apex REST | `sdk.fetch` | `/services/apexrest/auth/login` |
|
||||
| Einstein LLM | `sdk.fetch` | `/einstein/llm/prompt/generations` |
|
||||
|
||||
**GraphQL is preferred** for record operations. Use REST only when GraphQL doesn't cover the use case.
|
||||
|
||||
---
|
||||
|
||||
## GraphQL Workflow
|
||||
|
||||
### Step 1: Acquire Schema
|
||||
|
||||
The `schema.graphql` file (265K+ lines) is the source of truth. **Never open or parse it directly.**
|
||||
|
||||
1. Check if `schema.graphql` exists at the SFDX project root
|
||||
2. If missing, run from the **webapp dir**: `npm run graphql:schema`
|
||||
3. Custom objects appear only after metadata is deployed
|
||||
|
||||
### Step 2: Look Up Entity Schema
|
||||
|
||||
Map user intent to PascalCase names ("accounts" → `Account`), then **run the search script from the project root**:
|
||||
|
||||
```bash
|
||||
# From project root — look up all relevant schema info for one or more entities
|
||||
bash scripts/graphql-search.sh Account
|
||||
|
||||
# Multiple entities at once
|
||||
bash scripts/graphql-search.sh Account Contact Opportunity
|
||||
```
|
||||
|
||||
The script outputs five sections per entity:
|
||||
1. **Type definition** — all queryable fields and relationships
|
||||
2. **Filter options** — available fields for `where:` conditions
|
||||
3. **Sort options** — available fields for `orderBy:`
|
||||
4. **Create input** — fields accepted by create mutations
|
||||
5. **Update input** — fields accepted by update mutations
|
||||
|
||||
Use this output to determine exact field names before writing any query or mutation. **Maximum 2 script runs.** If the entity still can't be found, ask the user — the object may not be deployed.
|
||||
|
||||
### Step 3: Generate Query
|
||||
|
||||
Use the templates below. Every field name **must** be verified from the script output in Step 2.
|
||||
|
||||
#### Read Query Template
|
||||
|
||||
```graphql
|
||||
query GetAccounts {
|
||||
uiapi {
|
||||
query {
|
||||
Account(where: { Industry: { eq: "Technology" } }, first: 10) {
|
||||
edges {
|
||||
node {
|
||||
Id
|
||||
Name @optional { value }
|
||||
Industry @optional { value }
|
||||
# Parent relationship
|
||||
Owner @optional { Name { value } }
|
||||
# Child relationship
|
||||
Contacts @optional {
|
||||
edges { node { Name @optional { value } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**FLS Resilience**: Apply `@optional` to all record fields. The server omits inaccessible fields instead of failing. Consuming code must use optional chaining:
|
||||
|
||||
```typescript
|
||||
const name = node.Name?.value ?? "";
|
||||
```
|
||||
|
||||
#### Mutation Template
|
||||
|
||||
```graphql
|
||||
mutation CreateAccount($input: AccountCreateInput!) {
|
||||
uiapi {
|
||||
AccountCreate(input: $input) {
|
||||
Record { Id Name { value } }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Mutation constraints:**
|
||||
- Create: Include required fields, only `createable` fields, no child relationships
|
||||
- Update: Include `Id`, only `updateable` fields
|
||||
- Delete: Include `Id` only
|
||||
|
||||
#### Object Metadata & Picklist Values
|
||||
|
||||
Use `uiapi { objectInfos(...) }` to fetch field metadata or picklist values. Pass **either** `apiNames` or `objectInfoInputs` — never both in the same query.
|
||||
|
||||
**Object metadata** (field labels, data types, CRUD flags):
|
||||
|
||||
```typescript
|
||||
const GET_OBJECT_INFO = gql`
|
||||
query GetObjectInfo($apiNames: [String!]!) {
|
||||
uiapi {
|
||||
objectInfos(apiNames: $apiNames) {
|
||||
ApiName
|
||||
label
|
||||
labelPlural
|
||||
fields {
|
||||
ApiName
|
||||
label
|
||||
dataType
|
||||
updateable
|
||||
createable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const sdk = await createDataSDK();
|
||||
const response = await sdk.graphql?.(GET_OBJECT_INFO, { apiNames: ["Account"] });
|
||||
const objectInfos = response?.data?.uiapi?.objectInfos ?? [];
|
||||
```
|
||||
|
||||
**Picklist values** (use `objectInfoInputs` + `... on PicklistField` inline fragment):
|
||||
|
||||
```typescript
|
||||
const GET_PICKLIST_VALUES = gql`
|
||||
query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
|
||||
uiapi {
|
||||
objectInfos(objectInfoInputs: $objectInfoInputs) {
|
||||
ApiName
|
||||
fields {
|
||||
ApiName
|
||||
... on PicklistField {
|
||||
picklistValuesByRecordTypeIDs {
|
||||
recordTypeID
|
||||
picklistValues {
|
||||
label
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await sdk.graphql?.(GET_PICKLIST_VALUES, {
|
||||
objectInfoInputs: [{ objectApiName: "Account" }],
|
||||
});
|
||||
const fields = response?.data?.uiapi?.objectInfos?.[0]?.fields ?? [];
|
||||
```
|
||||
|
||||
### Step 4: Validate & Test
|
||||
|
||||
1. **Lint**: `npx eslint <file>` from webapp dir
|
||||
2. **Test**: Ask user before testing. For mutations, request input values — never fabricate data.
|
||||
|
||||
**If ESLint reports a GraphQL error** (e.g. `Cannot query field`, `Unknown type`, `Unknown argument`), the field or type name is wrong. Re-run the schema search script to find the correct name — do not guess:
|
||||
|
||||
```bash
|
||||
# From project root — re-check the entity that caused the error
|
||||
bash scripts/graphql-search.sh <EntityName>
|
||||
```
|
||||
|
||||
Then fix the query using the exact names from the script output.
|
||||
|
||||
---
|
||||
|
||||
## Webapp Integration (React)
|
||||
|
||||
```typescript
|
||||
import { createDataSDK, gql } from "@salesforce/sdk-data";
|
||||
|
||||
const GET_ACCOUNTS = gql`
|
||||
query GetAccounts {
|
||||
uiapi {
|
||||
query {
|
||||
Account(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
Id
|
||||
Name @optional { value }
|
||||
Industry @optional { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const sdk = await createDataSDK();
|
||||
const response = await sdk.graphql?.(GET_ACCOUNTS);
|
||||
|
||||
if (response?.errors?.length) {
|
||||
throw new Error(response.errors.map(e => e.message).join("; "));
|
||||
}
|
||||
|
||||
const accounts = response?.data?.uiapi?.query?.Account?.edges?.map(e => e.node) ?? [];
|
||||
|
||||
---
|
||||
|
||||
## REST API Patterns
|
||||
|
||||
Use `sdk.fetch` when GraphQL is insufficient. See the [Supported APIs](#supported-apis) table for the full allowlist.
|
||||
|
||||
```typescript
|
||||
declare const __SF_API_VERSION__: string;
|
||||
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
|
||||
|
||||
// Connect — file upload config
|
||||
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`);
|
||||
|
||||
// Apex REST (no version in path)
|
||||
const res = await sdk.fetch?.("/services/apexrest/auth/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ email, password }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
// UI API — record with metadata (prefer GraphQL for simple reads)
|
||||
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);
|
||||
|
||||
// Einstein LLM
|
||||
const res = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ promptTextorId: prompt }),
|
||||
});
|
||||
```
|
||||
|
||||
**Current user**: Do not use Chatter (`/chatter/users/me`). Use GraphQL instead:
|
||||
|
||||
```typescript
|
||||
const GET_CURRENT_USER = gql`
|
||||
query CurrentUser {
|
||||
uiapi { currentUser { Id Name { value } } }
|
||||
}
|
||||
`;
|
||||
const response = await sdk.graphql?.(GET_CURRENT_USER);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
<project-root>/ ← SFDX project root
|
||||
├── schema.graphql ← grep target (lives here)
|
||||
├── sfdx-project.json
|
||||
└── force-app/main/default/webapplications/<app-name>/ ← webapp dir
|
||||
├── package.json ← npm scripts
|
||||
└── src/
|
||||
```
|
||||
|
||||
| Command | Run From | Why |
|
||||
|---------|----------|-----|
|
||||
| `npm run graphql:schema` | webapp dir | Script in webapp's package.json |
|
||||
| `npx eslint <file>` | webapp dir | Reads eslint.config.js |
|
||||
| `bash scripts/graphql-search.sh <Entity>` | project root | Schema lookup |
|
||||
| `sf api request rest` | project root | Needs sfdx-project.json |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Schema Lookup (from project root)
|
||||
|
||||
Run the search script to get all relevant schema info in one step:
|
||||
|
||||
```bash
|
||||
bash scripts/graphql-search.sh <EntityName>
|
||||
```
|
||||
|
||||
| Script Output Section | Used For |
|
||||
|-----------------------|----------|
|
||||
| Type definition | Field names, parent/child relationships |
|
||||
| Filter options | `where:` conditions |
|
||||
| Sort options | `orderBy:` |
|
||||
| CreateRepresentation | Create mutation field list |
|
||||
| UpdateRepresentation | Update mutation field list |
|
||||
|
||||
### Error Categories
|
||||
|
||||
| Error Contains | Resolution |
|
||||
|----------------|------------|
|
||||
| `Cannot query field` | Field name is wrong — run `graphql-search.sh <Entity>` and use the exact name from the Type definition section |
|
||||
| `Unknown type` | Type name is wrong — run `graphql-search.sh <Entity>` to confirm the correct PascalCase entity name |
|
||||
| `Unknown argument` | Argument name is wrong — run `graphql-search.sh <Entity>` and check Filter or OrderBy sections |
|
||||
| `invalid syntax` | Fix syntax per error message |
|
||||
| `validation error` | Field name is wrong — run `graphql-search.sh <Entity>` to verify |
|
||||
| `VariableTypeMismatch` | Correct argument type from schema |
|
||||
| `invalid cross reference id` | Entity deleted — ask for valid Id |
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] All field names verified via search script (Step 2)
|
||||
- [ ] `@optional` applied to record fields (reads)
|
||||
- [ ] Optional chaining in consuming code
|
||||
- [ ] Lint passes: `npx eslint <file>`
|
||||
@ -1,16 +0,0 @@
|
||||
# UI Platform Rule
|
||||
|
||||
**All new UI must be built as a Salesforce Web Application.**
|
||||
|
||||
When any task involves creating a new UI, frontend, page, dashboard, form, or user-facing feature:
|
||||
|
||||
1. Use `sf webapp generate` to scaffold the web app inside the SFDX project — do not use `create-react-app`, standalone Vite, or any other scaffold.
|
||||
2. The app must live under `<sfdx-source>/webapplications/<AppName>/` as a WebApplication bundle.
|
||||
3. Do not build new UIs as LWC components, Aura components, or Visualforce pages.
|
||||
|
||||
For setup from the project root, use `node scripts/setup-cli.mjs --help` and the options documented there.
|
||||
|
||||
## Data Access (MUST FOLLOW)
|
||||
|
||||
- **Never hardcode data in the app.** All data displayed in the UI must come from live Salesforce data fetching — no static arrays, mock objects, or placeholder values in production code.
|
||||
- **Follow `.a4drules/webapp-data.md`** before writing any data access code. All implementation must match those rules (Data SDK, supported APIs, GraphQL workflow).
|
||||
@ -1 +1 @@
|
||||
1.112.10
|
||||
1.116.6
|
||||
|
||||
@ -3,6 +3,89 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.116.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.116.5...v1.116.6) (2026-03-26)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.116.5](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.116.4...v1.116.5) (2026-03-26)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.116.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.116.3...v1.116.4) (2026-03-26)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.116.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.116.2...v1.116.3) (2026-03-26)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.116.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.116.1...v1.116.2) (2026-03-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use public npm registry in sf-project-setup @W-21746844@ ([#355](https://github.com/salesforce-experience-platform-emu/webapps/issues/355)) ([4047d1e](https://github.com/salesforce-experience-platform-emu/webapps/commit/4047d1e0b79fcda14badc5a1826da2ae7ef68a68))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.116.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.116.0...v1.116.1) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.116.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.115.0...v1.116.0) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.115.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.114.0...v1.115.0) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.114.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.113.0...v1.114.0) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.113.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.112.10...v1.113.0) (2026-03-25)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.112.10](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.112.9...v1.112.10) (2026-03-24)
|
||||
|
||||
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
# App React Sample B2E
|
||||
# Property Management App
|
||||
|
||||
A B2E (Business-to-Employee) sample React web app for the Salesforce platform. Demonstrates property management, maintenance requests, tenant applications, a dashboard, and an Agentforce conversation client. Built with React, Vite, TypeScript, and Tailwind/shadcn.
|
||||
A property management sample React web app for the Salesforce platform. Demonstrates property management, maintenance requests, tenant applications, a dashboard, and an Agentforce conversation client. Built with React, Vite, TypeScript, and Tailwind/shadcn.
|
||||
|
||||
## What's included
|
||||
|
||||
| Path | Description |
|
||||
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `force-app/main/default/webapplications/appreactsampleb2e/` | React web app (source, config, tests) |
|
||||
| `force-app/main/default/objects/` | 17 custom objects — Agent\_\_c, Application\_\_c, KPI_Snapshot\_\_c, Lease\_\_c, Maintenance_Request\_\_c, Maintenance_Worker\_\_c, Notification\_\_c, Payment\_\_c, Property\_\_c, Property_Cost\_\_c, Property_Feature\_\_c, Property_Image\_\_c, Property_Listing\_\_c, Property_Management_Company\_\_c, Property_Owner\_\_c, Property_Sale\_\_c, Tenant\_\_c |
|
||||
| `force-app/main/default/layouts/` | Page layouts for each custom object |
|
||||
| `force-app/main/default/permissionsets/` | `Property_Management_Access` permission set |
|
||||
| `force-app/main/default/data/` | Sample data (JSON) for all objects, importable via `sf data import tree` |
|
||||
| Path | Description |
|
||||
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `force-app/main/default/webapplications/propertymanagementapp/` | React web app (source, config, tests) |
|
||||
| `force-app/main/default/objects/` | 17 custom objects — Agent\_\_c, Application\_\_c, KPI_Snapshot\_\_c, Lease\_\_c, Maintenance_Request\_\_c, Maintenance_Worker\_\_c, Notification\_\_c, Payment\_\_c, Property\_\_c, Property_Cost\_\_c, Property_Feature\_\_c, Property_Image\_\_c, Property_Listing\_\_c, Property_Management_Company\_\_c, Property_Owner\_\_c, Property_Sale\_\_c, Tenant\_\_c |
|
||||
| `force-app/main/default/layouts/` | Page layouts for each custom object |
|
||||
| `force-app/main/default/permissionsets/` | `Property_Management_Access` permission set |
|
||||
| `force-app/main/default/data/` | Sample data (JSON) for all objects, importable via `sf data import tree` |
|
||||
|
||||
## Getting started
|
||||
|
||||
Navigate to the web app and install dependencies:
|
||||
|
||||
```bash
|
||||
cd force-app/main/default/webapplications/appreactsampleb2e
|
||||
cd force-app/main/default/webapplications/propertymanagementapp
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Opens at http://localhost:5173 by default. For build and test instructions, see the [web app README](force-app/main/default/webapplications/appreactsampleb2e/README.md).
|
||||
Opens at http://localhost:5173 by default. For build and test instructions, see the [web app README](force-app/main/default/webapplications/propertymanagementapp/README.md).
|
||||
|
||||
## Deploy
|
||||
|
||||
### Deploy everything (metadata + web app)
|
||||
|
||||
```bash
|
||||
cd force-app/main/default/webapplications/appreactsampleb2e && npm install && npm run build && cd -
|
||||
cd force-app/main/default/webapplications/propertymanagementapp && npm install && npm run build && cd -
|
||||
sf project deploy start --source-dir force-app --target-org <alias>
|
||||
```
|
||||
|
||||
### Deploy the web app only
|
||||
|
||||
```bash
|
||||
cd force-app/main/default/webapplications/appreactsampleb2e && npm install && npm run build && cd -
|
||||
cd force-app/main/default/webapplications/propertymanagementapp && npm install && npm run build && cd -
|
||||
sf project deploy start --source-dir force-app/main/default/webapplications --target-org <alias>
|
||||
```
|
||||
|
||||
|
||||
@ -24,7 +24,9 @@
|
||||
"Description__c": "Modern 2-bedroom apartment with stunning city views. Features include balcony, in-unit washer/dryer, and central AC. Great location near restaurants and shopping.",
|
||||
"Tour_URL__c": "https://example.com/tour/sunset-4b",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 34.1007222,
|
||||
"Coordinates__Longitude__s": -118.329413
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -50,7 +52,9 @@
|
||||
"Description__c": "Spacious downtown loft with high ceilings and modern finishes. Building amenities include gym, pool, and secured parking.",
|
||||
"Tour_URL__c": "https://example.com/tour/downtown-loft",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1554995207-c18c203602cb?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 34.052235,
|
||||
"Coordinates__Longitude__s": -118.243683
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -76,7 +80,9 @@
|
||||
"Description__c": "Cozy garden studio in quiet neighborhood. Features private balcony and additional storage unit. No pets allowed.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800",
|
||||
"Agent__c": "@AgentRef3"
|
||||
"Agent__c": "@AgentRef3",
|
||||
"Coordinates__Latitude__s": 34.18045,
|
||||
"Coordinates__Longitude__s": -118.3101988
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -102,7 +108,9 @@
|
||||
"Description__c": "Elegant 3-bedroom penthouse in gated community with luxury amenities. Currently under maintenance, available mid-December.",
|
||||
"Tour_URL__c": "https://example.com/tour/luxury-penthouse",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=800",
|
||||
"Agent__c": "@AgentRef4"
|
||||
"Agent__c": "@AgentRef4",
|
||||
"Coordinates__Latitude__s": 33.7777777,
|
||||
"Coordinates__Longitude__s": -84.39
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -128,7 +136,9 @@
|
||||
"Description__c": "Modern high-rise apartment with floor-to-ceiling windows and stunning sunset views. Building features 24/7 doorman.",
|
||||
"Tour_URL__c": "https://example.com/tour/sunset-towers",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800",
|
||||
"Agent__c": "@AgentRef5"
|
||||
"Agent__c": "@AgentRef5",
|
||||
"Coordinates__Latitude__s": 34.0914289,
|
||||
"Coordinates__Longitude__s": -118.3617537
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -154,7 +164,9 @@
|
||||
"Description__c": "Stunning beachfront home with direct ocean access, modern kitchen, panoramic views, and private pool. Premium property.",
|
||||
"Tour_URL__c": "https://example.com/tour/ocean-villa",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800",
|
||||
"Agent__c": "@AgentRef3"
|
||||
"Agent__c": "@AgentRef3",
|
||||
"Coordinates__Latitude__s": 34.022352,
|
||||
"Coordinates__Longitude__s": -118.4911912
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -180,7 +192,9 @@
|
||||
"Description__c": "Modern condo in Silicon Valley with smart home features, rooftop deck, and premium amenities. Perfect for tech professionals.",
|
||||
"Tour_URL__c": "https://example.com/tour/tech-hub",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1580587771525-78b9dba3b914?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 37.4446046,
|
||||
"Coordinates__Longitude__s": -122.1595598
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -206,7 +220,9 @@
|
||||
"Description__c": "Charming historic brownstone with original hardwood floors, exposed brick, and modern updates. Rare Telegraph Hill location.",
|
||||
"Tour_URL__c": "https://example.com/tour/brownstone",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1568605114967-8130f3a36994?w=800",
|
||||
"Agent__c": "@AgentRef3"
|
||||
"Agent__c": "@AgentRef3",
|
||||
"Coordinates__Latitude__s": 37.8022765,
|
||||
"Coordinates__Longitude__s": -122.4062978
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -232,7 +248,9 @@
|
||||
"Description__c": "Bright studio in trendy Marina District with city views and modern amenities. Walking distance to shops and restaurants.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1502672023488-70e25813eb80?w=800",
|
||||
"Agent__c": "@AgentRef4"
|
||||
"Agent__c": "@AgentRef4",
|
||||
"Coordinates__Latitude__s": 37.7990915,
|
||||
"Coordinates__Longitude__s": -122.4319382
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -258,7 +276,9 @@
|
||||
"Description__c": "Magnificent estate with pool, tennis court, wine cellar, and breathtaking city views. Ultimate luxury living.",
|
||||
"Tour_URL__c": "https://example.com/tour/beverly-estate",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1613490493576-7fde63acd811?w=800",
|
||||
"Agent__c": "@AgentRef5"
|
||||
"Agent__c": "@AgentRef5",
|
||||
"Coordinates__Latitude__s": 34.0897964,
|
||||
"Coordinates__Longitude__s": -118.392847
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -284,7 +304,9 @@
|
||||
"Description__c": "Modern office space with open floor plan, conference rooms, and panoramic bay views. Premium downtown location.",
|
||||
"Tour_URL__c": "https://example.com/tour/market-office",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 37.7886951,
|
||||
"Coordinates__Longitude__s": -122.397125
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -310,7 +332,9 @@
|
||||
"Description__c": "Beautiful wine country home with vineyard views, gourmet kitchen, and outdoor entertainment area. Perfect for wine enthusiasts.",
|
||||
"Tour_URL__c": "https://example.com/tour/napa-retreat",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800",
|
||||
"Agent__c": "@AgentRef4"
|
||||
"Agent__c": "@AgentRef4",
|
||||
"Coordinates__Latitude__s": 38.3134748,
|
||||
"Coordinates__Longitude__s": -122.2863822
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -336,7 +360,9 @@
|
||||
"Description__c": "Contemporary townhome near tech companies with private garage and community amenities. Ideal for families.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800",
|
||||
"Agent__c": "@AgentRef5"
|
||||
"Agent__c": "@AgentRef5",
|
||||
"Coordinates__Latitude__s": 37.407126,
|
||||
"Coordinates__Longitude__s": -122.0883981
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -362,7 +388,9 @@
|
||||
"Description__c": "Converted warehouse loft with soaring ceilings, exposed beams, and artistic neighborhood vibe.",
|
||||
"Tour_URL__c": "https://example.com/tour/artistic-loft",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800",
|
||||
"Agent__c": "@AgentRef1"
|
||||
"Agent__c": "@AgentRef1",
|
||||
"Coordinates__Latitude__s": 37.8034013,
|
||||
"Coordinates__Longitude__s": -122.2798006
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -388,7 +416,9 @@
|
||||
"Description__c": "Ultra-luxury penthouse with concierge service, gym, and incredible bay and city views. Currently under maintenance.",
|
||||
"Tour_URL__c": "https://example.com/tour/luxury-highrise",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1737898378296-94dc316cd443?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 37.7886951,
|
||||
"Coordinates__Longitude__s": -122.397125
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -414,7 +444,9 @@
|
||||
"Description__c": "Charming family home with large backyard, updated kitchen, and excellent school district. Perfect for families.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?w=800",
|
||||
"Agent__c": "@AgentRef3"
|
||||
"Agent__c": "@AgentRef3",
|
||||
"Coordinates__Latitude__s": 37.4863302,
|
||||
"Coordinates__Longitude__s": -122.2247151
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -440,7 +472,9 @@
|
||||
"Description__c": "Brand new studio with smart home features, in-unit laundry, and rooftop terrace access. Modern living at its best.",
|
||||
"Tour_URL__c": "https://example.com/tour/modern-studio",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600585154084-4e5fe7c39198?w=800",
|
||||
"Agent__c": "@AgentRef4"
|
||||
"Agent__c": "@AgentRef4",
|
||||
"Coordinates__Latitude__s": 37.7873686,
|
||||
"Coordinates__Longitude__s": -122.4055017
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -466,7 +500,9 @@
|
||||
"Description__c": "Sophisticated executive condo with marble finishes, city views, and luxury building amenities. Premium Nob Hill location.",
|
||||
"Tour_URL__c": "https://example.com/tour/executive-suite",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600607687920-4e2a09cf159d?w=800",
|
||||
"Agent__c": "@AgentRef5"
|
||||
"Agent__c": "@AgentRef5",
|
||||
"Coordinates__Latitude__s": 37.7907054,
|
||||
"Coordinates__Longitude__s": -122.406344
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -492,7 +528,9 @@
|
||||
"Description__c": "Fully restored Victorian mansion with period details, modern conveniences, and beautiful garden. Rare Pacific Heights gem.",
|
||||
"Tour_URL__c": "https://example.com/tour/victorian",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600566753151-384129cf4e3e?w=800",
|
||||
"Agent__c": "@AgentRef1"
|
||||
"Agent__c": "@AgentRef1",
|
||||
"Coordinates__Latitude__s": 37.7933344,
|
||||
"Coordinates__Longitude__s": -122.4200908
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -518,7 +556,9 @@
|
||||
"Description__c": "Bright apartment with bay views, hardwood floors, and walking distance to Marina Green. Great neighborhood.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600585154526-990dced4db0d?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 37.7990915,
|
||||
"Coordinates__Longitude__s": -122.4319382
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -544,7 +584,9 @@
|
||||
"Description__c": "Comfortable townhouse with private patio, attached garage, and close to UC Berkeley. Great for students or faculty.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1603661764782-a3c9812afada?w=800",
|
||||
"Agent__c": "@AgentRef3"
|
||||
"Agent__c": "@AgentRef3",
|
||||
"Coordinates__Latitude__s": 37.8774835,
|
||||
"Coordinates__Longitude__s": -122.2660899
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -570,7 +612,9 @@
|
||||
"Description__c": "Modern open office space perfect for tech startups with high-speed internet and flexible layout. Prime SOMA location.",
|
||||
"Tour_URL__c": "https://example.com/tour/startup-hub",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=800",
|
||||
"Agent__c": "@AgentRef4"
|
||||
"Agent__c": "@AgentRef4",
|
||||
"Coordinates__Latitude__s": 37.7751904,
|
||||
"Coordinates__Longitude__s": -122.4107713
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -596,7 +640,9 @@
|
||||
"Description__c": "Spacious family home with modern kitchen, large basement, and proximity to Golden Gate Park. Perfect for families.",
|
||||
"Tour_URL__c": null,
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?w=800",
|
||||
"Agent__c": "@AgentRef5"
|
||||
"Agent__c": "@AgentRef5",
|
||||
"Coordinates__Latitude__s": 37.7463285,
|
||||
"Coordinates__Longitude__s": -122.4732595
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -622,7 +668,9 @@
|
||||
"Description__c": "Contemporary loft in vibrant Mission Bay with waterfront access and modern amenities. Great location near ballpark.",
|
||||
"Tour_URL__c": "https://example.com/tour/mission-loft",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600607687920-4e2a09cf159d?w=800",
|
||||
"Agent__c": "@AgentRef1"
|
||||
"Agent__c": "@AgentRef1",
|
||||
"Coordinates__Latitude__s": 37.7433669,
|
||||
"Coordinates__Longitude__s": -122.4666667
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
@ -648,7 +696,9 @@
|
||||
"Description__c": "Elegant apartment in prestigious Pacific Heights with hardwood floors, in-unit laundry, and 24/7 doorman service.",
|
||||
"Tour_URL__c": "https://example.com/tour/pac-heights",
|
||||
"Hero_Image__c": "https://images.unsplash.com/photo-1600585152220-90363fe7e115?w=800",
|
||||
"Agent__c": "@AgentRef2"
|
||||
"Agent__c": "@AgentRef2",
|
||||
"Coordinates__Latitude__s": 37.7851601,
|
||||
"Coordinates__Longitude__s": -122.4351546
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Coordinates__c</fullName>
|
||||
<description>Location coordinates</description>
|
||||
<displayLocationInDecimal>true</displayLocationInDecimal>
|
||||
<label>Coordinates</label>
|
||||
<required>false</required>
|
||||
<scale>6</scale>
|
||||
<trackHistory>false</trackHistory>
|
||||
<trackTrending>false</trackTrending>
|
||||
<type>Location</type>
|
||||
</CustomField>
|
||||
@ -416,6 +416,11 @@
|
||||
<field>Property__c.Address__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Property__c.Coordinates__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>true</editable>
|
||||
<field>Property__c.Type__c</field>
|
||||
|
||||
@ -97,6 +97,11 @@
|
||||
<field>Property__c.Address__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
<fieldPermissions>
|
||||
<editable>false</editable>
|
||||
<field>Property__c.Coordinates__c</field>
|
||||
<readable>true</readable>
|
||||
</fieldPermissions>
|
||||
|
||||
<!-- Field Permissions: Tenant__c -->
|
||||
<fieldPermissions>
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Application-wide constants.
|
||||
*/
|
||||
|
||||
import type { PaginationConfig } from "../features/object-search/hooks/useObjectSearchParams";
|
||||
|
||||
export const PAGE_SIZE_LIST = 12;
|
||||
export const DASHBOARD_MAINTENANCE_LIMIT = 5;
|
||||
|
||||
export const PROPERTY_FILTER_EXCLUDED_FIELD_PATHS = new Set([
|
||||
"CreatedDate",
|
||||
"Hero_Image__c",
|
||||
"Year_Built__c",
|
||||
"Available_Date__c",
|
||||
]);
|
||||
|
||||
export const MAINTENANCE_WORKER_FILTER_EXCLUDED_FIELD_PATHS = new Set<string>([]);
|
||||
export const MAINTENANCE_FILTER_EXCLUDED_FIELD_PATHS = new Set(["Scheduled__c"]);
|
||||
export const APPLICATION_FILTER_EXCLUDED_FIELD_PATHS = new Set<string>([]);
|
||||
|
||||
export const MAINTENANCE_WORKER_OBJECT_API_NAME = "Maintenance_Worker__c" as const;
|
||||
|
||||
export const FALLBACK_LABEL_PROPERTIES_PLURAL = "Properties";
|
||||
export const FALLBACK_LABEL_MAINTENANCE_PLURAL = "Maintenance Requests";
|
||||
export const FALLBACK_LABEL_MAINTENANCE_WORKERS_PLURAL = "Maintenance Workers";
|
||||
export const FALLBACK_LABEL_APPLICATIONS_PLURAL = "Applications";
|
||||
|
||||
export const PROPERTY_OBJECT_API_NAME = "Property__c" as const;
|
||||
export const MAINTENANCE_OBJECT_API_NAME = "Maintenance_Request__c" as const;
|
||||
export const APPLICATION_OBJECT_API_NAME = "Application__c" as const;
|
||||
|
||||
export const SEARCHABLE_OBJECTS = [
|
||||
{
|
||||
objectApiName: "Property__c" as const,
|
||||
path: "/properties",
|
||||
fallbackLabelPlural: FALLBACK_LABEL_PROPERTIES_PLURAL,
|
||||
},
|
||||
{
|
||||
objectApiName: "Maintenance_Request__c" as const,
|
||||
path: "/maintenance/requests",
|
||||
fallbackLabelPlural: FALLBACK_LABEL_MAINTENANCE_PLURAL,
|
||||
},
|
||||
{
|
||||
objectApiName: MAINTENANCE_WORKER_OBJECT_API_NAME,
|
||||
path: "/maintenance/workers",
|
||||
fallbackLabelPlural: FALLBACK_LABEL_MAINTENANCE_WORKERS_PLURAL,
|
||||
},
|
||||
{
|
||||
objectApiName: "Application__c" as const,
|
||||
path: "/applications",
|
||||
fallbackLabelPlural: FALLBACK_LABEL_APPLICATIONS_PLURAL,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type SearchableObjectConfig = (typeof SEARCHABLE_OBJECTS)[number];
|
||||
|
||||
export const PAGINATION_CONFIG: PaginationConfig = {
|
||||
defaultPageSize: 6,
|
||||
validPageSizes: [6, 12, 24, 48],
|
||||
};
|
||||
@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Central route configuration for list pages and navigation.
|
||||
* Use these paths for links and search redirects to avoid duplication.
|
||||
*/
|
||||
export const PATHS = {
|
||||
HOME: "/",
|
||||
PROPERTIES: "/properties",
|
||||
MAINTENANCE_REQUESTS: "/maintenance/requests",
|
||||
MAINTENANCE_WORKERS: "/maintenance/workers",
|
||||
APPLICATIONS: "/applications",
|
||||
} as const;
|
||||
|
||||
export interface ListPageRoute {
|
||||
path: string;
|
||||
label: string;
|
||||
searchParamKey?: string;
|
||||
}
|
||||
|
||||
/** List pages that appear in the Home search object dropdown and in nav */
|
||||
export const LIST_PAGE_ROUTES: Record<string, ListPageRoute> = {
|
||||
properties: { path: PATHS.PROPERTIES, label: "Properties", searchParamKey: "q" },
|
||||
maintenance_requests: {
|
||||
path: PATHS.MAINTENANCE_REQUESTS,
|
||||
label: "Maintenance Requests",
|
||||
searchParamKey: "q",
|
||||
},
|
||||
maintenance_workers: {
|
||||
path: PATHS.MAINTENANCE_WORKERS,
|
||||
label: "Maintenance Workers",
|
||||
searchParamKey: "q",
|
||||
},
|
||||
applications: { path: PATHS.APPLICATIONS, label: "Applications", searchParamKey: "q" },
|
||||
} as const;
|
||||
|
||||
export type ListPageKey = keyof typeof LIST_PAGE_ROUTES;
|
||||
@ -1,6 +0,0 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
# App React Sample B2E
|
||||
# Property Management App
|
||||
|
||||
A sample B2E (Business-to-Employee) React web app on the Salesforce platform. Includes property management, maintenance requests, tenant applications, a dashboard, and an Agentforce conversation client. Built with React, Vite, TypeScript, and Tailwind/shadcn.
|
||||
Property management sample React web app on the Salesforce platform. Includes property management, maintenance requests, tenant applications, a dashboard, and an Agentforce conversation client. Built with React, Vite, TypeScript, and Tailwind/shadcn.
|
||||
|
||||
For project-level details (metadata, deploy, sample data), see the [project README](../../../../../../README.md).
|
||||
|
||||
@ -15,8 +15,8 @@
|
||||
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@salesforce/sdk-data": "^1.112.10",
|
||||
"@salesforce/webapp-experimental": "^1.112.10",
|
||||
"@salesforce/sdk-data": "^1.116.6",
|
||||
"@salesforce/webapp-experimental": "^1.116.6",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@ -43,7 +43,7 @@
|
||||
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@salesforce/vite-plugin-webapp-experimental": "^1.112.10",
|
||||
"@salesforce/vite-plugin-webapp-experimental": "^1.116.6",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<WebApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<masterLabel>appreactsampleb2e</masterLabel>
|
||||
<masterLabel>propertymanagementapp</masterLabel>
|
||||
<description>A Salesforce web application.</description>
|
||||
<isActive>true</isActive>
|
||||
<version>1</version>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 873 B After Width: | Height: | Size: 873 B |
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 408 B |
|
Before Width: | Height: | Size: 857 B After Width: | Height: | Size: 857 B |
|
Before Width: | Height: | Size: 853 B After Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 906 B |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 916 B After Width: | Height: | Size: 916 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 853 B After Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 419 KiB After Width: | Height: | Size: 419 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |