chore: sync React b2e and b2x samples from npm @1.117.3
@ -1 +1 @@
|
|||||||
1.116.6
|
1.117.3
|
||||||
|
|||||||
@ -1,87 +1,193 @@
|
|||||||
# Agent guide: SFDX project with React web app
|
# Agent guide: Salesforce UI Bundle development
|
||||||
|
|
||||||
This project is a **Salesforce DX (SFDX) project** containing a **React web application**. The SFDX source path is defined in `sfdx-project.json` (`packageDirectories[].path`); the web app lives under `<sfdx-source>/webapplications/<appName>/`. Use this file when working in this directory.
|
This project is a **Salesforce DX (SFDX) project** containing a **React UI Bundle**. The UI Bundle is a standalone Vite + React SPA that runs inside the Salesforce platform. Use this file when working in this project.
|
||||||
|
|
||||||
## SFDX Source Path
|
## Resolving paths
|
||||||
|
|
||||||
The source path prefix is **not** always `force-app`. Read `sfdx-project.json` at the project root, take the first `packageDirectories[].path` value, and append `/main/default` to get `<sfdx-source>`. All paths below use this placeholder.
|
Read `sfdx-project.json` at the project root. Take the first `packageDirectories[].path` value and append `/main/default` to get `<sfdx-source>`. The UI Bundle directory is:
|
||||||
|
|
||||||
|
```
|
||||||
|
<sfdx-source>/uiBundles/<appName>/
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<appName>` with the actual folder name found under `uiBundles/`. The source path is **not** always `force-app` — always resolve it from `sfdx-project.json`.
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
- **Project root**: this directory — SFDX project root. Contains `sfdx-project.json`, the SFDX source directory, and (optionally) LWC/Aura.
|
```
|
||||||
- **React web app**: `<sfdx-source>/webapplications/<appName>/`
|
<project-root>/
|
||||||
- Replace `<appName>` with the actual app folder name (e.g. `base-react-app`, or the name chosen when the app was generated).
|
├── sfdx-project.json
|
||||||
- Entry: `src/App.tsx`
|
├── package.json # SFDX root scripts
|
||||||
- Routes: `src/routes.tsx`
|
├── scripts/
|
||||||
- API/GraphQL: `src/api/` (e.g. `graphql.ts`, `graphql-operations-types.ts`, `utils/`)
|
│ ├── setup-cli.mjs # One-command setup (deploy, schema, build)
|
||||||
|
│ └── graphql-search.sh # Schema entity lookup
|
||||||
|
├── config/
|
||||||
|
│ └── project-scratch-def.json
|
||||||
|
│
|
||||||
|
└── <sfdx-source>/
|
||||||
|
├── uiBundles/
|
||||||
|
│ └── <appName>/ # ← React UI Bundle (primary workspace)
|
||||||
|
│ ├── <appName>.uibundle-meta.xml
|
||||||
|
│ ├── ui-bundle.json
|
||||||
|
│ ├── index.html
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── vite.config.ts / tsconfig.json
|
||||||
|
│ ├── vitest.config.ts / playwright.config.ts
|
||||||
|
│ ├── codegen.yml / .graphqlrc.yml
|
||||||
|
│ └── src/ # All application code lives here
|
||||||
|
│
|
||||||
|
├── classes/ # Apex classes (optional)
|
||||||
|
├── objects/ # Custom objects and fields (optional)
|
||||||
|
├── permissionsets/ # Permission sets (optional)
|
||||||
|
├── cspTrustedSites/ # CSP trusted site definitions (optional)
|
||||||
|
├── layouts/ # Object layouts (optional)
|
||||||
|
├── triggers/ # Apex triggers (optional)
|
||||||
|
└── data/ # Sample data for import (optional)
|
||||||
|
```
|
||||||
|
|
||||||
Path convention: **webapplications** (lowercase).
|
## Web application source structure
|
||||||
|
|
||||||
|
All application code lives inside the UI Bundle's `src/` directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app.tsx # Entry point — creates the browser router
|
||||||
|
├── appLayout.tsx # Shell layout (header, navigation, Outlet, footer)
|
||||||
|
├── routes.tsx # Single route registry for the entire app
|
||||||
|
├── navigationMenu.tsx # Navigation component
|
||||||
|
├── router-utils.tsx # Router helpers
|
||||||
|
├── lib/utils.ts # Utility functions (cn, etc.)
|
||||||
|
├── styles/global.css # Tailwind global styles
|
||||||
|
├── api/ # GraphQL operations, clients, data services
|
||||||
|
├── assets/ # Static SVGs, images
|
||||||
|
├── components/
|
||||||
|
│ ├── ui/ # Shared primitives (shadcn-style: button, card, input, etc.)
|
||||||
|
│ ├── layout/ # Layout components (header, footer, sidebar)
|
||||||
|
│ └── <feature>/ # Feature-specific components
|
||||||
|
├── features/ # Feature modules (auth, search, etc.)
|
||||||
|
├── hooks/ # Custom React hooks
|
||||||
|
├── pages/ # Page components (one per route)
|
||||||
|
├── public/ # Static assets served as-is
|
||||||
|
└── utils/ # Shared utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `app.tsx` | Creates `BrowserRouter`; do not add UI here |
|
||||||
|
| `appLayout.tsx` | Source of truth for navigation, header, footer, and page shell |
|
||||||
|
| `routes.tsx` | Single route registry; all pages are children of the layout route |
|
||||||
|
| `<appName>.uibundle-meta.xml` | Salesforce deploy descriptor (`masterLabel`, `version`, `isActive`) |
|
||||||
|
| `ui-bundle.json` | Runtime config (`outputDir`, routing) |
|
||||||
|
|
||||||
## Two package.json contexts
|
## Two package.json contexts
|
||||||
|
|
||||||
### 1. Project root (this directory)
|
### 1. Project root
|
||||||
|
|
||||||
Used for SFDX metadata (LWC, Aura, etc.). Scripts here are for the base SFDX template:
|
Used for SFDX metadata tooling. Scripts here target LWC/Aura, not the React app.
|
||||||
|
|
||||||
| Command | Purpose |
|
| Command | Purpose |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
| `npm run lint` | ESLint for `aura/` and `lwc/` |
|
|
||||||
| `npm run test` | LWC Jest (passWithNoTests) |
|
| `npm run test` | LWC Jest (passWithNoTests) |
|
||||||
| `npm run prettier` | Format supported metadata files |
|
| `npm run prettier` | Format metadata files |
|
||||||
| `npm run prettier:verify` | Check Prettier |
|
| `npm run prettier:verify` | Check Prettier |
|
||||||
|
|
||||||
**One-command setup:** From project root run `node scripts/setup-cli.mjs --target-org <alias>` to run login (if needed), deploy, optional permset/data import, GraphQL schema/codegen, web app build, and optionally the dev server. Use `node scripts/setup-cli.mjs --help` for options (e.g. `--skip-login`, `--skip-data`, `--webapp-name`).
|
**One-command setup:** `node scripts/setup-cli.mjs --target-org <alias>` runs login, deploy, permset assignment, data import, GraphQL schema/codegen, UI Bundle build, and optionally the dev server. Use `--help` for all flags.
|
||||||
|
|
||||||
Root **does not** run the React app. The root `npm run build` is a no-op for the base SFDX project.
|
### 2. Web app directory (primary workspace)
|
||||||
|
|
||||||
### 2. React web app (where you do most work)
|
**Always `cd` into the UI Bundle directory for dev/build/lint/test:**
|
||||||
|
|
||||||
**Always `cd` into the web app directory for dev/build/lint/test:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd <sfdx-source>/webapplications/<appName>
|
|
||||||
```
|
|
||||||
|
|
||||||
| Command | Purpose |
|
| Command | Purpose |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
| `npm run dev` | Start Vite dev server |
|
| `npm run dev` | Start Vite dev server |
|
||||||
| `npm run build` | TypeScript (`tsc -b`) + Vite build |
|
| `npm run build` | TypeScript check + Vite production build |
|
||||||
| `npm run lint` | ESLint for the React app |
|
| `npm run lint` | ESLint for the React app |
|
||||||
| `npm run test` | Vitest |
|
| `npm run test` | Vitest unit tests |
|
||||||
| `npm run preview` | Preview production build |
|
| `npm run preview` | Preview production build |
|
||||||
| `npm run graphql:codegen` | Generate GraphQL types |
|
| `npm run graphql:codegen` | Generate GraphQL types from schema |
|
||||||
| `npm run graphql:schema` | Fetch GraphQL schema |
|
| `npm run graphql:schema` | Fetch GraphQL schema from org |
|
||||||
|
|
||||||
**Before finishing changes:** run `npm run build` and `npm run lint` from the web app directory; both must succeed.
|
**Before completing any change:** run `npm run build` and `npm run lint` from the UI Bundle directory. Both must pass with zero errors.
|
||||||
|
|
||||||
## Agent rules (.a4drules/)
|
## Development conventions
|
||||||
|
|
||||||
Markdown rules at the project root under **.a4drules/** define platform constraints:
|
### UI
|
||||||
|
|
||||||
- **`.a4drules/webapp-ui.md`** — Salesforce Web Application UI (scaffold with `sf webapp generate`, no LWC/Aura for new UI).
|
- **Component library:** shadcn/ui primitives in `src/components/ui/`. Always use these over raw HTML equivalents.
|
||||||
- **`.a4drules/webapp-data.md`** — Salesforce data access (Data SDK only, supported APIs, GraphQL workflow, `scripts/graphql-search.sh` for schema lookup).
|
- **Styling:** Tailwind CSS only. No inline `style={{}}`. Use `cn()` from `@/lib/utils` for conditional classes.
|
||||||
|
- **Icons:** Lucide React.
|
||||||
|
- **Path alias:** `@/*` maps to `src/*`. Use it for all imports.
|
||||||
|
- **TypeScript:** No `any`. Use proper types, generics, or `unknown`.
|
||||||
|
- **Components:** Accept `className?: string` prop. Extract shared state to custom hooks in `src/hooks/`.
|
||||||
|
- **React apps must not** import Salesforce platform modules (`lightning/*`, `@wire`, LWC APIs).
|
||||||
|
|
||||||
When rules refer to "web app directory" or `<sfdx-source>/webapplications/<appName>/`, resolve `<sfdx-source>` from `sfdx-project.json` and use the **actual app folder name** for this project.
|
### Routing
|
||||||
|
|
||||||
|
- React Router with `createBrowserRouter`. Route definitions live exclusively in `routes.tsx`.
|
||||||
|
- All page routes are children of the layout route (which renders `appLayout.tsx`).
|
||||||
|
- Default-export one component per page file.
|
||||||
|
- The catch-all `path: '*'` route must always be last.
|
||||||
|
- Navigation uses absolute paths (`/dashboard`). Non-router imports use dot-relative paths (`./utils`).
|
||||||
|
- Navigation visibility is driven by `handle.showInNavigation` on route definitions.
|
||||||
|
|
||||||
|
### Layout and navigation
|
||||||
|
|
||||||
|
- `appLayout.tsx` owns the header, navigation menu, footer, and `<Outlet />`.
|
||||||
|
- To modify header or footer, edit `appLayout.tsx` and create components in `src/components/layout/`.
|
||||||
|
- To add a page, add a route in `routes.tsx` and create the page component — do not modify `appLayout.tsx` or `app.tsx` for page additions.
|
||||||
|
|
||||||
|
### Data access (Salesforce)
|
||||||
|
|
||||||
|
- **All data access uses the Data SDK** (`@salesforce/sdk-data`) via `createDataSDK()`.
|
||||||
|
- **Never** use `fetch()` or `axios` directly for Salesforce data.
|
||||||
|
- **GraphQL is preferred** for record operations (`sdk.graphql`). Use `sdk.fetch` only when GraphQL cannot cover the case (UI API REST, Apex REST, Connect REST, Einstein LLM).
|
||||||
|
- Use optional chaining: `sdk.graphql?.()`, `sdk.fetch?.()`.
|
||||||
|
- Apply the `@optional` directive to all record fields for field-level security resilience.
|
||||||
|
- Verify field and object names via `scripts/graphql-search.sh` before writing queries.
|
||||||
|
- Use `__SF_API_VERSION__` global for API version in REST calls.
|
||||||
|
- **Blocked APIs:** Enterprise REST query endpoint (`/query` with SOQL), `@AuraEnabled` Apex, Chatter API.
|
||||||
|
|
||||||
|
### CSP trusted sites
|
||||||
|
|
||||||
|
Any external domain the app calls (APIs, CDNs, fonts) must have a `.cspTrustedSite-meta.xml` file under `<sfdx-source>/cspTrustedSites/`. Unregistered domains are blocked at runtime. Each subdomain needs its own entry. URLs must be HTTPS with no trailing slash, no path, and no wildcards.
|
||||||
|
|
||||||
## Deploying
|
## Deploying
|
||||||
|
|
||||||
**Deployment order:** Metadata (objects, permission sets) must be deployed before GraphQL schema fetch. After any metadata deployment, re-run `npm run graphql:schema` and `npm run graphql:codegen` from the webapp dir. **One-command setup:** `node scripts/setup-cli.mjs --target-org <alias>` runs deploy → permset → schema → codegen in the correct order.
|
**Deployment order matters.** Metadata (objects, permission sets) must be deployed before fetching the GraphQL schema. After any metadata deployment that changes objects, fields, or permissions, re-run schema fetch and codegen.
|
||||||
|
|
||||||
From **this project root** (resolve the actual SFDX source path from `sfdx-project.json`):
|
**Recommended sequence:**
|
||||||
|
|
||||||
|
1. Authenticate to the target org
|
||||||
|
2. Build the UI Bundle (`npm run build` in the UI Bundle directory)
|
||||||
|
3. Deploy metadata (`sf project deploy start --source-dir <packageDir> --target-org <alias>`)
|
||||||
|
4. Assign permission sets
|
||||||
|
5. Import data (only with user confirmation)
|
||||||
|
6. Fetch GraphQL schema + run codegen (`npm run graphql:schema && npm run graphql:codegen`)
|
||||||
|
7. Rebuild the UI Bundle (schema changes may affect generated types)
|
||||||
|
|
||||||
|
**Or use the one-command setup:** `node scripts/setup-cli.mjs --target-org <alias>`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build the React app first (replace <sfdx-source> and <appName> with actual values)
|
# Deploy UI Bundle only
|
||||||
cd <sfdx-source>/webapplications/<appName> && npm i && npm run build && cd -
|
sf project deploy start --source-dir <sfdx-source>/ui-bundles --target-org <alias>
|
||||||
|
|
||||||
# Deploy web app only (replace <sfdx-source> with actual path, e.g. force-app/main/default)
|
# Deploy all metadata
|
||||||
sf project deploy start --source-dir <sfdx-source>/webapplications --target-org <alias>
|
|
||||||
|
|
||||||
# Deploy all metadata (use the top-level package directory, e.g. force-app)
|
|
||||||
sf project deploy start --source-dir <packageDir> --target-org <alias>
|
sf project deploy start --source-dir <packageDir> --target-org <alias>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Conventions (quick reference)
|
## Skills
|
||||||
|
|
||||||
- **UI**: shadcn/ui + Tailwind. Import from `@/components/ui/...`.
|
Check for available skills before implementing any of the following:
|
||||||
- **Entry**: Keep `App.tsx` and routes in `src/`; add features as new routes or sections, don't replace the app shell but you may modify it to match the requested design.
|
|
||||||
- **Data (Salesforce)**: Follow `.a4drules/webapp-data.md` for all Salesforce data access. Use the Data SDK (`createDataSDK()` + `sdk.graphql` or `sdk.fetch`) — never use `fetch` or `axios` directly. GraphQL is preferred; use `sdk.fetch` when GraphQL is not sufficient.
|
| Area | When to consult |
|
||||||
|
|------|----------------|
|
||||||
|
| UI generation | Building pages, components, modifying header/footer/layout |
|
||||||
|
| Salesforce data access | Reading/writing records, GraphQL queries, REST calls |
|
||||||
|
| Metadata and deployment | Scaffolding apps, configuring CSP, deployment sequencing |
|
||||||
|
| Feature installation | Before building something from scratch — check if a pre-built feature exists |
|
||||||
|
| File upload | Adding file upload with Salesforce ContentVersion |
|
||||||
|
| Agentforce conversation | Adding or modifying the Agentforce chat widget |
|
||||||
|
|
||||||
|
Skills are the authoritative source for detailed patterns, constraints, and code examples in each area. This file provides project-level orientation; skills provide implementation depth.
|
||||||
|
|||||||
@ -1,43 +1,43 @@
|
|||||||
# Property Management App
|
# Property Management App
|
||||||
|
|
||||||
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.
|
A property management sample React UI Bundle 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
|
## What's included
|
||||||
|
|
||||||
| Path | Description |
|
| Path | Description |
|
||||||
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `force-app/main/default/webapplications/propertymanagementapp/` | React web app (source, config, tests) |
|
| `force-app/main/default/uiBundles/propertymanagementapp/` | React UI Bundle (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/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/layouts/` | Page layouts for each custom object |
|
||||||
| `force-app/main/default/permissionsets/` | `Property_Management_Access` permission set |
|
| `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` |
|
| `force-app/main/default/data/` | Sample data (JSON) for all objects, importable via `sf data import tree` |
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Navigate to the web app and install dependencies:
|
Navigate to the UI Bundle and install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd force-app/main/default/webapplications/propertymanagementapp
|
cd force-app/main/default/uiBundles/propertymanagementapp
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
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/propertymanagementapp/README.md).
|
Opens at http://localhost:5173 by default. For build and test instructions, see the [UI Bundle README](force-app/main/default/uiBundles/propertymanagementapp/README.md).
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
### Deploy everything (metadata + web app)
|
### Deploy everything (metadata + UI Bundle)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd force-app/main/default/webapplications/propertymanagementapp && npm install && npm run build && cd -
|
cd force-app/main/default/uiBundles/propertymanagementapp && npm install && npm run build && cd -
|
||||||
sf project deploy start --source-dir force-app --target-org <alias>
|
sf project deploy start --source-dir force-app --target-org <alias>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy the web app only
|
### Deploy the UI Bundle only
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd force-app/main/default/webapplications/propertymanagementapp && npm install && npm run build && cd -
|
cd force-app/main/default/uiBundles/propertymanagementapp && npm install && npm run build && cd -
|
||||||
sf project deploy start --source-dir force-app/main/default/webapplications --target-org <alias>
|
sf project deploy start --source-dir force-app/main/default/ui-bundles --target-org <alias>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy metadata only (objects, layouts, permission sets)
|
### Deploy metadata only (objects, layouts, permission sets)
|
||||||
@ -70,7 +70,7 @@ sf data import tree --plan force-app/main/default/data/data-plan.json --target-o
|
|||||||
|
|
||||||
## Using setup-cli.mjs
|
## Using setup-cli.mjs
|
||||||
|
|
||||||
When this app is built (e.g. from the monorepo or from a published package), a `setup-cli.mjs` script is included at the project root. It runs the full setup in one go: login (if needed), deploy metadata, assign the `Property_Management_Access` permission set, prepare and import sample data, fetch GraphQL schema and run codegen, build the web app, and optionally start the dev server.
|
When this app is built (e.g. from the monorepo or from a published package), a `setup-cli.mjs` script is included at the project root. It runs the full setup in one go: login (if needed), deploy metadata, assign the `Property_Management_Access` permission set, prepare and import sample data, fetch GraphQL schema and run codegen, build the UI Bundle, and optionally start the dev server.
|
||||||
|
|
||||||
Run from the **project root** (the directory that contains `force-app/`, `sfdx-project.json`, and `setup-cli.mjs`):
|
Run from the **project root** (the directory that contains `force-app/`, `sfdx-project.json`, and `setup-cli.mjs`):
|
||||||
|
|
||||||
@ -80,15 +80,15 @@ node setup-cli.mjs --target-org <alias>
|
|||||||
|
|
||||||
Common options:
|
Common options:
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| --------------------- | ---------------------------------------------------------------- |
|
| ------------------------ | ---------------------------------------------------------------- |
|
||||||
| `--skip-login` | Skip browser login (org already authenticated) |
|
| `--skip-login` | Skip browser login (org already authenticated) |
|
||||||
| `--skip-data` | Skip data preparation and import |
|
| `--skip-data` | Skip data preparation and import |
|
||||||
| `--skip-graphql` | Skip GraphQL schema fetch and codegen |
|
| `--skip-graphql` | Skip GraphQL schema fetch and codegen |
|
||||||
| `--skip-webapp-build` | Skip `npm install` and web app build |
|
| `--skip-ui-bundle-build` | Skip `npm install` and UI Bundle build |
|
||||||
| `--skip-dev` | Do not start the dev server at the end |
|
| `--skip-dev` | Do not start the dev server at the end |
|
||||||
| `--permset <name>` | Permission set to assign (default: `Property_Management_Access`) |
|
| `--permset <name>` | Permission set to assign (default: `Property_Management_Access`) |
|
||||||
| `--app <name>` | Web app folder name when multiple exist |
|
| `--app <name>` | Web app folder name when multiple exist |
|
||||||
|
|
||||||
For all options: `node setup-cli.mjs --help`.
|
For all options: `node setup-cli.mjs --help`.
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
public with sharing class MaintenanceRequestTriggerHandler {
|
public without sharing class MaintenanceRequestTriggerHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles before insert logic for Maintenance Request records
|
* Handles before insert logic for Maintenance Request records
|
||||||
@ -7,11 +7,14 @@ public with sharing class MaintenanceRequestTriggerHandler {
|
|||||||
public static void handleBeforeInsert(List<Maintenance_Request__c> newRequests) {
|
public static void handleBeforeInsert(List<Maintenance_Request__c> newRequests) {
|
||||||
// Map to store request type to worker type mappings
|
// Map to store request type to worker type mappings
|
||||||
Map<String, String> requestTypeToWorkerType = new Map<String, String>{
|
Map<String, String> requestTypeToWorkerType = new Map<String, String>{
|
||||||
'Plumbing' => 'Plumbing',
|
'Plumbing' => 'Plumbing',
|
||||||
'Electrical' => 'Electrical',
|
'Electrical' => 'Electrical',
|
||||||
'HVAC' => 'HVAC (Heating & Cooling)',
|
'HVAC' => 'HVAC (Heating & Cooling)',
|
||||||
'Appliance' => 'Appliance Repair',
|
'Appliance' => 'Appliance Repair',
|
||||||
'Pest' => 'Pest Control'
|
'Carpentry' => 'General Carpentry',
|
||||||
|
'Landscaping' => 'Landscaping / Grounds',
|
||||||
|
'Cleaning' => 'Janitorial / Cleaning',
|
||||||
|
'Pest' => 'Pest Control'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Collect unique worker types needed
|
// Collect unique worker types needed
|
||||||
|
|||||||
@ -29,7 +29,15 @@ public with sharing class TenantTriggerHandler {
|
|||||||
if (userIds.isEmpty()) {
|
if (userIds.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
assignTenantMaintenanceAccessAsync(new List<Id>(userIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
@future
|
||||||
|
private static void assignTenantMaintenanceAccessAsync(List<Id> userIdsList) {
|
||||||
|
Set<Id> userIds = new Set<Id>(userIdsList);
|
||||||
|
if (userIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
List<PermissionSet> permSets = [
|
List<PermissionSet> permSets = [
|
||||||
SELECT Id
|
SELECT Id
|
||||||
FROM PermissionSet
|
FROM PermissionSet
|
||||||
|
|||||||
@ -73,7 +73,9 @@ private class TenantTriggerHandler_Test {
|
|||||||
static void testNoDuplicateAssign() {
|
static void testNoDuplicateAssign() {
|
||||||
Id runAsUserId = UserInfo.getUserId();
|
Id runAsUserId = UserInfo.getUserId();
|
||||||
Tenant__c tenant = new Tenant__c(User__c = runAsUserId);
|
Tenant__c tenant = new Tenant__c(User__c = runAsUserId);
|
||||||
|
Test.startTest();
|
||||||
insert tenant;
|
insert tenant;
|
||||||
|
Test.stopTest();
|
||||||
|
|
||||||
Integer countBefore = [
|
Integer countBefore = [
|
||||||
SELECT COUNT()
|
SELECT COUNT()
|
||||||
@ -82,8 +84,10 @@ private class TenantTriggerHandler_Test {
|
|||||||
AND PermissionSet.Name = 'Tenant_Maintenance_Access'
|
AND PermissionSet.Name = 'Tenant_Maintenance_Access'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Test.startTest();
|
||||||
tenant.Status__c = 'Active';
|
tenant.Status__c = 'Active';
|
||||||
update tenant;
|
update tenant;
|
||||||
|
Test.stopTest();
|
||||||
|
|
||||||
Integer countAfter = [
|
Integer countAfter = [
|
||||||
SELECT COUNT()
|
SELECT COUNT()
|
||||||
|
|||||||
@ -32,11 +32,31 @@
|
|||||||
<default>false</default>
|
<default>false</default>
|
||||||
<label>Appliance</label>
|
<label>Appliance</label>
|
||||||
</value>
|
</value>
|
||||||
|
<value>
|
||||||
|
<fullName>Carpentry</fullName>
|
||||||
|
<default>false</default>
|
||||||
|
<label>Carpentry</label>
|
||||||
|
</value>
|
||||||
|
<value>
|
||||||
|
<fullName>Landscaping</fullName>
|
||||||
|
<default>false</default>
|
||||||
|
<label>Landscaping</label>
|
||||||
|
</value>
|
||||||
|
<value>
|
||||||
|
<fullName>Cleaning</fullName>
|
||||||
|
<default>false</default>
|
||||||
|
<label>Cleaning</label>
|
||||||
|
</value>
|
||||||
<value>
|
<value>
|
||||||
<fullName>Pest</fullName>
|
<fullName>Pest</fullName>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
<label>Pest Control</label>
|
<label>Pest Control</label>
|
||||||
</value>
|
</value>
|
||||||
|
<value>
|
||||||
|
<fullName>Other</fullName>
|
||||||
|
<default>false</default>
|
||||||
|
<label>Other</label>
|
||||||
|
</value>
|
||||||
</valueSetDefinition>
|
</valueSetDefinition>
|
||||||
</valueSet>
|
</valueSet>
|
||||||
</CustomField>
|
</CustomField>
|
||||||
|
|||||||
@ -7,4 +7,4 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- auto bump base react app versions and fix issue with base webapplication json ([#175](https://github.com/salesforce-experience-platform-emu/webapps/issues/175)) ([048b5a8](https://github.com/salesforce-experience-platform-emu/webapps/commit/048b5a8449c899fc923aeebc3c76bc5bf1c5e0d4))
|
- auto bump base react app versions and fix issue with base ui-bundle json ([#175](https://github.com/salesforce-experience-platform-emu/webapps/issues/175)) ([048b5a8](https://github.com/salesforce-experience-platform-emu/webapps/commit/048b5a8449c899fc923aeebc3c76bc5bf1c5e0d4))
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Property Management App
|
# Property Management App
|
||||||
|
|
||||||
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.
|
Property management sample React UI Bundle 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).
|
For project-level details (metadata, deploy, sample data), see the [project README](../../../../../../README.md).
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ Starts the Vite dev server (default: http://localhost:5173).
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Writes the production bundle to `dist/` inside the web app folder.
|
Writes the production bundle to `dist/` inside the UI Bundle folder.
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
@ -18,7 +18,12 @@ const schemaExists = existsSync(schemaPath);
|
|||||||
const config = [
|
const config = [
|
||||||
// Global ignores
|
// Global ignores
|
||||||
{
|
{
|
||||||
ignores: ['build/**/*', 'dist/**/*', 'coverage/**/*'],
|
ignores: [
|
||||||
|
'build/**/*',
|
||||||
|
'dist/**/*',
|
||||||
|
'coverage/**/*',
|
||||||
|
'src/api/graphql-operations-types.ts',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// Config files and build tools (first to avoid inheritance)
|
// Config files and build tools (first to avoid inheritance)
|
||||||
{
|
{
|
||||||
@ -89,11 +94,17 @@ const config = [
|
|||||||
'react/no-unescaped-entities': 'off',
|
'react/no-unescaped-entities': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'error',
|
'error',
|
||||||
{ argsIgnorePattern: '^_' },
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'react-hooks/set-state-in-effect': 'warn',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
@ -15,8 +15,8 @@
|
|||||||
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@salesforce/sdk-data": "^1.116.6",
|
"@salesforce/sdk-data": "file:../../../../../../../../../sdk/sdk-data",
|
||||||
"@salesforce/webapp-experimental": "^1.116.6",
|
"@salesforce/ui-bundle": "file:../../../../../../../../../ui-bundle",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"@salesforce/agentforce-conversation-client": "^1.102.0",
|
"@salesforce/agentforce-conversation-client": "^1.116.9",
|
||||||
"recharts": "^2.15.0"
|
"recharts": "^2.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
||||||
"@graphql-tools/utils": "^11.0.0",
|
"@graphql-tools/utils": "^11.0.0",
|
||||||
"@playwright/test": "^1.49.0",
|
"@playwright/test": "^1.49.0",
|
||||||
"@salesforce/vite-plugin-webapp-experimental": "^1.116.6",
|
"@salesforce/vite-plugin-ui-bundle": "file:../../../../../../../../../vite-plugin-ui-bundle",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.1.0",
|
"@testing-library/react": "^16.1.0",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<WebApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
<UIBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
<masterLabel>propertymanagementapp</masterLabel>
|
<masterLabel>propertymanagementapp</masterLabel>
|
||||||
<description>A Salesforce web application.</description>
|
<description>A Salesforce UI Bundle.</description>
|
||||||
<isActive>true</isActive>
|
<isActive>true</isActive>
|
||||||
<version>1</version>
|
<version>1</version>
|
||||||
</WebApplication>
|
</UIBundle>
|
||||||
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
import { writeFileSync } from 'node:fs';
|
import { writeFileSync } from 'node:fs';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { getOrgInfo } from '@salesforce/webapp-experimental/app';
|
import { getOrgInfo } from '@salesforce/ui-bundle/app';
|
||||||
import { buildClientSchema, getIntrospectionQuery, printSchema } from 'graphql';
|
import { buildClientSchema, getIntrospectionQuery, printSchema } from 'graphql';
|
||||||
import { pruneSchema } from '@graphql-tools/utils';
|
import { pruneSchema } from '@graphql-tools/utils';
|
||||||
|
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
import GET_APPLICATIONS from "./query/getApplications.graphql?raw";
|
||||||
|
import UPDATE_APPLICATION_STATUS from "./query/updateApplicationStatus.graphql?raw";
|
||||||
|
import APPLICATION_FOR_APPROVAL_QUERY from "./query/applicationForApproval.graphql?raw";
|
||||||
|
import USER_BY_CONTACT_QUERY from "./query/userByContact.graphql?raw";
|
||||||
|
import EXISTING_TENANT_QUERY from "./query/existingTenant.graphql?raw";
|
||||||
|
import { createRecord } from "@salesforce/ui-bundle/api";
|
||||||
|
import type {
|
||||||
|
GetApplicationsQuery,
|
||||||
|
GetApplicationsQueryVariables,
|
||||||
|
UpdateApplicationStatusMutation,
|
||||||
|
UpdateApplicationStatusMutationVariables,
|
||||||
|
ApplicationForApprovalQuery,
|
||||||
|
ApplicationForApprovalQueryVariables,
|
||||||
|
UserByContactQuery,
|
||||||
|
UserByContactQueryVariables,
|
||||||
|
ExistingTenantQuery,
|
||||||
|
ExistingTenantQueryVariables,
|
||||||
|
} from "../graphql-operations-types.js";
|
||||||
|
import { executeGraphQL } from "../graphqlClient.js";
|
||||||
|
|
||||||
|
export type ApplicationNode = NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<GetApplicationsQuery["uiapi"]["query"]["Application__c"]>["edges"]
|
||||||
|
>[number]
|
||||||
|
>["node"];
|
||||||
|
|
||||||
|
const TENANT_OBJECT_API_NAME = "Tenant__c";
|
||||||
|
|
||||||
|
export async function getApplications(): Promise<NonNullable<ApplicationNode>[]> {
|
||||||
|
try {
|
||||||
|
const data = await executeGraphQL<GetApplicationsQuery, GetApplicationsQueryVariables>(
|
||||||
|
GET_APPLICATIONS,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const edges = data?.uiapi?.query?.Application__c?.edges || [];
|
||||||
|
return edges
|
||||||
|
.map((edge) => edge?.node)
|
||||||
|
.filter((node): node is NonNullable<ApplicationNode> => node != null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching applications:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateApplicationStatus(
|
||||||
|
applicationId: string,
|
||||||
|
status: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const normalizedStatus = status.trim().toLowerCase();
|
||||||
|
if (normalizedStatus === "approved") {
|
||||||
|
await ensureTenantForApprovedApplication(applicationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const variables: UpdateApplicationStatusMutationVariables = {
|
||||||
|
input: {
|
||||||
|
Id: applicationId,
|
||||||
|
Application__c: {
|
||||||
|
Status__c: status,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const data = await executeGraphQL<
|
||||||
|
UpdateApplicationStatusMutation,
|
||||||
|
UpdateApplicationStatusMutationVariables
|
||||||
|
>(UPDATE_APPLICATION_STATUS, variables);
|
||||||
|
return !!data?.uiapi?.Application__cUpdate?.success;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating application status:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureTenantForApprovedApplication(applicationId: string): Promise<void> {
|
||||||
|
const appData = await executeGraphQL<
|
||||||
|
ApplicationForApprovalQuery,
|
||||||
|
ApplicationForApprovalQueryVariables
|
||||||
|
>(APPLICATION_FOR_APPROVAL_QUERY, { applicationId });
|
||||||
|
const applicationNode = appData.uiapi?.query?.Application__c?.edges?.[0]?.node;
|
||||||
|
if (!applicationNode) {
|
||||||
|
throw new Error("Application record not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const contactId = applicationNode.User__c?.value ?? null;
|
||||||
|
const propertyId = applicationNode.Property__c?.value ?? null;
|
||||||
|
const startDate = applicationNode.Start_Date__c?.value ?? null;
|
||||||
|
|
||||||
|
if (!contactId || !propertyId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userData = await executeGraphQL<UserByContactQuery, UserByContactQueryVariables>(
|
||||||
|
USER_BY_CONTACT_QUERY,
|
||||||
|
{ contactId },
|
||||||
|
);
|
||||||
|
const userId = userData.uiapi?.query?.User?.edges?.[0]?.node?.Id ?? null;
|
||||||
|
if (!userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingTenantData = await executeGraphQL<
|
||||||
|
ExistingTenantQuery,
|
||||||
|
ExistingTenantQueryVariables
|
||||||
|
>(EXISTING_TENANT_QUERY, {
|
||||||
|
userId,
|
||||||
|
propertyId,
|
||||||
|
});
|
||||||
|
const existingTenantId = existingTenantData.uiapi?.query?.Tenant__c?.edges?.[0]?.node?.Id ?? null;
|
||||||
|
if (existingTenantId) return;
|
||||||
|
|
||||||
|
const tenantFields: Record<string, unknown> = {
|
||||||
|
User__c: userId,
|
||||||
|
Property__c: propertyId,
|
||||||
|
User_Status__c: "Tenant",
|
||||||
|
Status__c: "Active",
|
||||||
|
};
|
||||||
|
if (startDate) {
|
||||||
|
tenantFields.Start_Date__c = String(startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
await createRecord(TENANT_OBJECT_API_NAME, tenantFields);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
query ApplicationForApproval($applicationId: ID!) {
|
||||||
|
uiapi {
|
||||||
|
query {
|
||||||
|
Application__c(where: { Id: { eq: $applicationId } }, first: 1) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
Id
|
||||||
|
User__c @optional {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
Property__c @optional {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
Start_Date__c @optional {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
query ExistingTenant($userId: ID!, $propertyId: ID!) {
|
||||||
|
uiapi {
|
||||||
|
query {
|
||||||
|
Tenant__c(
|
||||||
|
where: { and: [{ User__c: { eq: $userId } }, { Property__c: { eq: $propertyId } }] }
|
||||||
|
first: 1
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
query UserByContact($contactId: ID!) {
|
||||||
|
uiapi {
|
||||||
|
query {
|
||||||
|
User(where: { ContactId: { eq: $contactId } }, first: 1) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 |
@ -57,6 +57,7 @@ export function AgentforceConversationClient({
|
|||||||
agentId,
|
agentId,
|
||||||
inline: inlineProp,
|
inline: inlineProp,
|
||||||
headerEnabled,
|
headerEnabled,
|
||||||
|
showHeaderIcon,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
styleTokens,
|
styleTokens,
|
||||||
@ -68,6 +69,8 @@ export function AgentforceConversationClient({
|
|||||||
const renderingConfig: NonNullable<AgentforceClientConfig["renderingConfig"]> = {
|
const renderingConfig: NonNullable<AgentforceClientConfig["renderingConfig"]> = {
|
||||||
mode: inlineProp ? "inline" : "floating",
|
mode: inlineProp ? "inline" : "floating",
|
||||||
...(headerEnabled !== undefined && { headerEnabled }),
|
...(headerEnabled !== undefined && { headerEnabled }),
|
||||||
|
...(showHeaderIcon !== undefined && { showHeaderIcon }),
|
||||||
|
...{ showAvatar: false },
|
||||||
...(width !== undefined && { width }),
|
...(width !== undefined && { width }),
|
||||||
...(height !== undefined && { height }),
|
...(height !== undefined && { height }),
|
||||||
};
|
};
|
||||||
@ -77,7 +80,7 @@ export function AgentforceConversationClient({
|
|||||||
...(styleTokens !== undefined && { styleTokens }),
|
...(styleTokens !== undefined && { styleTokens }),
|
||||||
renderingConfig,
|
renderingConfig,
|
||||||
};
|
};
|
||||||
}, [agentId, inlineProp, headerEnabled, width, height, styleTokens]);
|
}, [agentId, inlineProp, headerEnabled, showHeaderIcon, width, height, styleTokens]);
|
||||||
|
|
||||||
const inline = normalizedAgentforceClientConfig?.renderingConfig?.mode === "inline";
|
const inline = normalizedAgentforceClientConfig?.renderingConfig?.mode === "inline";
|
||||||
|
|
||||||
@ -85,7 +88,7 @@ export function AgentforceConversationClient({
|
|||||||
if (!normalizedAgentforceClientConfig?.agentId) {
|
if (!normalizedAgentforceClientConfig?.agentId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"AgentforceConversationClient requires agentId. " +
|
"AgentforceConversationClient requires agentId. " +
|
||||||
"Pass flat props only (agentId, inline, headerEnabled, width, height, styleTokens).",
|
"Pass flat props only (agentId, inline, headerEnabled, showHeaderIcon, width, height, styleTokens).",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ export function FilterRow({
|
|||||||
}: FilterRowProps) {
|
}: FilterRowProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("bg-white rounded-lg shadow-sm border border-gray-200 p-4", className)}
|
className={cn("bg-white rounded-lg shadow-sm border border-gray-200 p-4 pb-2", className)}
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
{...props}
|
{...props}
|
||||||