@salesforce/webapp-template-app-react-sample-b2x-experimental 1.84.1 → 1.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/README.md +24 -0
  3. package/dist/force-app/main/default/data/Property_Image__c.json +1 -1
  4. package/dist/force-app/main/default/data/Property_Listing__c.json +1 -1
  5. package/dist/force-app/main/default/data/prepare-import-unique-fields.js +85 -0
  6. package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +0 -7
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2x/index.html +6 -0
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +3 -3
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/applicationApi.ts +9 -9
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphql-operations-types.ts +296 -0
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +12 -7
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/maintenanceRequestApi.ts +50 -38
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyDetailGraphQL.ts +50 -102
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyListingGraphQL.ts +211 -43
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/userApi.ts +43 -0
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +9 -208
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/appliances.svg +13 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/electrical.svg +39 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/hvac.svg +78 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/pest.svg +5 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/plumbing.svg +7 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/zen-logo.svg +5 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/MaintenanceRequestIcon.tsx +46 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/NavMenu.tsx +53 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyListingCard.tsx +55 -58
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyMap.tsx +93 -11
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertySearchFilters.tsx +315 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/StatusBadge.tsx +36 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/TopBar.tsx +107 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +2 -2
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingAmenities.ts +55 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingPriceRange.ts +64 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +14 -5
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +54 -11
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +1 -1
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +42 -39
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Contact.tsx +10 -10
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +64 -91
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/HelpCenter.tsx +1 -1
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +19 -9
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +79 -100
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/NotFound.tsx +1 -1
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +62 -47
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +3 -3
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +230 -34
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +10 -1
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/styles/global.css +64 -0
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/geocode.ts +30 -5
  49. package/dist/package.json +1 -1
  50. package/dist/setup-cli.mjs +271 -0
  51. package/package.json +1 -1
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.86.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.85.0...v1.86.0) (2026-03-10)
7
+
8
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
9
+
10
+
11
+
12
+
13
+
14
+ # [1.85.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.84.1...v1.85.0) (2026-03-10)
15
+
16
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
17
+
18
+
19
+
20
+
21
+
6
22
  ## [1.84.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.84.0...v1.84.1) (2026-03-10)
7
23
 
8
24
  **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
package/dist/README.md CHANGED
@@ -76,6 +76,30 @@ Replace `<alias>` with your target org alias.
76
76
  sf data import tree --plan force-app/main/default/data/data-plan.json --target-org <alias>
77
77
  ```
78
78
 
79
+ ## Using setup-cli.mjs
80
+
81
+ 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.
82
+
83
+ Run from the **project root** (the directory that contains `force-app/`, `sfdx-project.json`, and `setup-cli.mjs`):
84
+
85
+ ```bash
86
+ node setup-cli.mjs --target-org <alias>
87
+ ```
88
+
89
+ Common options:
90
+
91
+ | Option | Description |
92
+ | --------------------- | ---------------------------------------------------------------- |
93
+ | `--skip-login` | Skip browser login (org already authenticated) |
94
+ | `--skip-data` | Skip data preparation and import |
95
+ | `--skip-graphql` | Skip GraphQL schema fetch and codegen |
96
+ | `--skip-webapp-build` | Skip `npm install` and web app build |
97
+ | `--skip-dev` | Do not start the dev server at the end |
98
+ | `--permset <name>` | Permission set to assign (default: `Property_Management_Access`) |
99
+ | `--app <name>` | Web app folder name when multiple exist |
100
+
101
+ For all options: `node setup-cli.mjs --help`.
102
+
79
103
  ## Configure Your Salesforce DX Project
80
104
 
81
105
  The `sfdx-project.json` file contains useful configuration information for your project. See [Salesforce DX Project Configuration](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_config.htm) in the _Salesforce DX Developer Guide_ for details about this file.
@@ -138,7 +138,7 @@
138
138
  "referenceId": "ImageRef12"
139
139
  },
140
140
  "Name": "Beverly Hills Estate Primary",
141
- "Property__c": "@PropertyRef11",
141
+ "Property__c": "@PropertyRef10",
142
142
  "Image_URL__c": "https://images.unsplash.com/photo-1613490493576-7fde63acd811?w=800&h=600&fit=crop",
143
143
  "Image_Type__c": "Primary",
144
144
  "Display_Order__c": 1,
@@ -90,7 +90,7 @@
90
90
  "referenceId": "ListingRef7"
91
91
  },
92
92
  "Name": "Beverly Hills Estate",
93
- "Property__c": "@PropertyRef11",
93
+ "Property__c": "@PropertyRef10",
94
94
  "Listing_Price__c": 9200000.0,
95
95
  "Listing_Status__c": "Active",
96
96
  "Featured__c": true,
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Updates unique fields in data JSON files so "sf data import tree" can be run
4
+ * repeatedly (e.g. after org already has data). Run before: node prepare-import-unique-fields.js
5
+ */
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const dataDir = __dirname;
9
+ const runId = Date.now();
10
+ const runSuffix2 = String(runId % 100).padStart(2, "0"); // 00-99 for Company_Code__c (10-char limit)
11
+
12
+ // Contact: Email + LastName — unique so re-runs don't hit DUPLICATES_DETECTED (matching may use name)
13
+ const contactPath = path.join(dataDir, "Contact.json");
14
+ let contact = JSON.parse(fs.readFileSync(contactPath, "utf8"));
15
+ contact.records.forEach((r, i) => {
16
+ if (r.Email && r.Email.includes("@"))
17
+ r.Email = r.Email.replace(/\+[0-9]+@/, "@").replace("@", "+" + runId + "@");
18
+ if (r.LastName)
19
+ r.LastName = r.LastName.replace(/\s*\[[0-9-]+\]$/, "") + " [" + runId + "-" + i + "]";
20
+ });
21
+ fs.writeFileSync(contactPath, JSON.stringify(contact, null, 2));
22
+
23
+ // Agent__c: License_Number__c — strip any existing suffix and add run id + index (each record must be unique)
24
+ const agentPath = path.join(dataDir, "Agent__c.json");
25
+ let agent = JSON.parse(fs.readFileSync(agentPath, "utf8"));
26
+ agent.records.forEach((r, i) => {
27
+ if (r.License_Number__c)
28
+ r.License_Number__c =
29
+ r.License_Number__c.replace(/-[0-9]+(-[0-9]+)?$/, "") + "-" + runId + "-" + i;
30
+ });
31
+ fs.writeFileSync(agentPath, JSON.stringify(agent, null, 2));
32
+
33
+ // Property_Management_Company__c: Company_Code__c — max 10 chars, use 8-char base + 2-digit suffix
34
+ const companyPath = path.join(dataDir, "Property_Management_Company__c.json");
35
+ let company = JSON.parse(fs.readFileSync(companyPath, "utf8"));
36
+ company.records.forEach((r) => {
37
+ if (r.Company_Code__c) r.Company_Code__c = r.Company_Code__c.slice(0, 8) + runSuffix2; // 8-char base + 2-digit = 10 chars max
38
+ });
39
+ fs.writeFileSync(companyPath, JSON.stringify(company, null, 2));
40
+
41
+ // Property_Owner__c: Email__c — add +runId before @
42
+ const ownerPath = path.join(dataDir, "Property_Owner__c.json");
43
+ let owner = JSON.parse(fs.readFileSync(ownerPath, "utf8"));
44
+ owner.records.forEach((r) => {
45
+ if (r.Email__c && r.Email__c.includes("@"))
46
+ r.Email__c = r.Email__c.replace(/\+[0-9]+@/, "@").replace("@", "+" + runId + "@");
47
+ });
48
+ fs.writeFileSync(ownerPath, JSON.stringify(owner, null, 2));
49
+
50
+ // Remap @TenantRefN in dependent files to only use refs that exist in Tenant__c.json (fix UnresolvableRefsError)
51
+ const tenantPath = path.join(dataDir, "Tenant__c.json");
52
+ const tenantData = JSON.parse(fs.readFileSync(tenantPath, "utf8"));
53
+ const validTenantRefs = tenantData.records.map((r) => r.attributes?.referenceId).filter(Boolean);
54
+ if (validTenantRefs.length === 0) throw new Error("Tenant__c.json has no referenceIds");
55
+
56
+ function remapTenantRef(refValue) {
57
+ if (typeof refValue !== "string" || !refValue.startsWith("@TenantRef")) return refValue;
58
+ const match = refValue.match(/^@(TenantRef)(\d+)$/);
59
+ if (!match) return refValue;
60
+ const n = parseInt(match[2], 10);
61
+ const idx = (n - 1) % validTenantRefs.length;
62
+ return "@" + validTenantRefs[idx];
63
+ }
64
+
65
+ const leasePath = path.join(dataDir, "Lease__c.json");
66
+ let lease = JSON.parse(fs.readFileSync(leasePath, "utf8"));
67
+ lease.records.forEach((r) => {
68
+ if (r.Tenant__c) r.Tenant__c = remapTenantRef(r.Tenant__c);
69
+ });
70
+ fs.writeFileSync(leasePath, JSON.stringify(lease, null, 2));
71
+
72
+ const maintPath = path.join(dataDir, "Maintenance_Request__c.json");
73
+ let maint = JSON.parse(fs.readFileSync(maintPath, "utf8"));
74
+ maint.records.forEach((r) => {
75
+ if (r.User__c && String(r.User__c).startsWith("@TenantRef"))
76
+ r.User__c = remapTenantRef(r.User__c);
77
+ });
78
+ fs.writeFileSync(maintPath, JSON.stringify(maint, null, 2));
79
+
80
+ console.log(
81
+ "Unique fields updated: runId=%s companySuffix=%s validTenants=%s",
82
+ runId,
83
+ runSuffix2,
84
+ validTenantRefs.length,
85
+ );
@@ -367,43 +367,36 @@
367
367
  <field>Agent__c.License_Number__c</field>
368
368
  <readable>true</readable>
369
369
  </fieldPermissions>
370
-
371
370
  <fieldPermissions>
372
371
  <editable>true</editable>
373
372
  <field>Agent__c.License_Expiry__c</field>
374
373
  <readable>true</readable>
375
374
  </fieldPermissions>
376
-
377
375
  <fieldPermissions>
378
376
  <editable>true</editable>
379
377
  <field>Agent__c.Agent_Type__c</field>
380
378
  <readable>true</readable>
381
379
  </fieldPermissions>
382
-
383
380
  <fieldPermissions>
384
381
  <editable>true</editable>
385
382
  <field>Agent__c.Territory__c</field>
386
383
  <readable>true</readable>
387
384
  </fieldPermissions>
388
-
389
385
  <fieldPermissions>
390
386
  <editable>true</editable>
391
387
  <field>Agent__c.Emergency_Alt__c</field>
392
388
  <readable>true</readable>
393
389
  </fieldPermissions>
394
-
395
390
  <fieldPermissions>
396
391
  <editable>true</editable>
397
392
  <field>Agent__c.Language__c</field>
398
393
  <readable>true</readable>
399
394
  </fieldPermissions>
400
-
401
395
  <fieldPermissions>
402
396
  <editable>true</editable>
403
397
  <field>Agent__c.Availability__c</field>
404
398
  <readable>true</readable>
405
399
  </fieldPermissions>
406
-
407
400
  <fieldPermissions>
408
401
  <editable>true</editable>
409
402
  <field>Agent__c.Office_Location__c</field>
@@ -4,6 +4,12 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Jost:wght@300;400;500;600;700&display=swap"
11
+ rel="stylesheet"
12
+ />
7
13
  <title>ZENLEASE</title>
8
14
  </head>
9
15
  <body>
@@ -15,8 +15,8 @@
15
15
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@salesforce/sdk-data": "^1.84.1",
19
- "@salesforce/webapp-experimental": "^1.84.1",
18
+ "@salesforce/sdk-data": "^1.86.0",
19
+ "@salesforce/webapp-experimental": "^1.86.0",
20
20
  "@tailwindcss/vite": "^4.1.17",
21
21
  "@tanstack/react-form": "^1.28.4",
22
22
  "@types/leaflet": "^1.9.21",
@@ -43,7 +43,7 @@
43
43
  "@graphql-eslint/eslint-plugin": "^4.1.0",
44
44
  "@graphql-tools/utils": "^11.0.0",
45
45
  "@playwright/test": "^1.49.0",
46
- "@salesforce/vite-plugin-webapp-experimental": "^1.84.1",
46
+ "@salesforce/vite-plugin-webapp-experimental": "^1.86.0",
47
47
  "@testing-library/jest-dom": "^6.6.3",
48
48
  "@testing-library/react": "^16.1.0",
49
49
  "@testing-library/user-event": "^14.5.2",
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Create Application__c (property application) record via Salesforce UI API.
3
- * Uses only fields that exist in all orgs (Property__c, Status__c, Start_Date__c,
4
- * Employment_Info__c, References__c). Applicant contact details are stored in
5
- * Employment_Info__c so no data is lost if custom contact fields aren’t deployed.
3
+ * Uses only fields that exist in the shared schema (Property__c, Status__c, Start_Date__c,
4
+ * Employment__c, References__c). Applicant contact details are stored in
5
+ * Employment__c so no data is lost if custom contact fields aren’t deployed.
6
6
  */
7
7
  import { createRecord } from "@salesforce/webapp-experimental/api";
8
8
 
@@ -17,11 +17,11 @@ export interface ApplicationRecordInput {
17
17
  Phone__c?: string | null;
18
18
  Start_Date__c?: string | null;
19
19
  Preferred_Term__c?: string | null;
20
- Employment_Info__c?: string | null;
20
+ Employment__c?: string | null;
21
21
  References__c?: string | null;
22
22
  }
23
23
 
24
- function buildEmploymentInfoBlob(input: ApplicationRecordInput): string {
24
+ function buildEmploymentBlob(input: ApplicationRecordInput): string {
25
25
  const lines: string[] = [];
26
26
  const contact = [
27
27
  input.First_Name__c,
@@ -33,13 +33,13 @@ function buildEmploymentInfoBlob(input: ApplicationRecordInput): string {
33
33
  .filter(Boolean)
34
34
  .join(", ");
35
35
  if (contact) lines.push(`Contact: ${contact}`);
36
- if (input.Employment_Info__c?.trim()) lines.push(input.Employment_Info__c.trim());
36
+ if (input.Employment__c?.trim()) lines.push(input.Employment__c.trim());
37
37
  return lines.join("\n\n") || "";
38
38
  }
39
39
 
40
40
  /**
41
41
  * Creates an Application__c record. Uses only core fields to avoid POST_BODY_PARSE_ERROR
42
- * when custom contact fields are not yet in the org. Contact + employment text go into Employment_Info__c.
42
+ * when custom contact fields are not yet in the org. Contact + employment text go into Employment__c.
43
43
  */
44
44
  export async function createApplicationRecord(
45
45
  input: ApplicationRecordInput,
@@ -55,9 +55,9 @@ export async function createApplicationRecord(
55
55
  if (input.Start_Date__c != null && input.Start_Date__c !== "") {
56
56
  fields.Start_Date__c = input.Start_Date__c;
57
57
  }
58
- const employmentBlob = buildEmploymentInfoBlob(input);
58
+ const employmentBlob = buildEmploymentBlob(input);
59
59
  if (employmentBlob) {
60
- fields.Employment_Info__c = employmentBlob;
60
+ fields.Employment__c = employmentBlob;
61
61
  }
62
62
  if (input.References__c != null && input.References__c !== "") {
63
63
  fields.References__c = input.References__c;
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Generated operation types for B2X GraphQL queries.
3
+ * Regenerate with codegen (e.g. npm run graphql:codegen) when schema or operations change.
4
+ */
5
+
6
+ // ---- MaintenanceRequests ----
7
+ export interface MaintenanceRequestsQueryVariables {
8
+ first: number;
9
+ after?: string | null;
10
+ }
11
+
12
+ export interface MaintenanceRequestsQuery {
13
+ uiapi?: {
14
+ query?: {
15
+ Maintenance_Request__c?: {
16
+ edges?: Array<{
17
+ node?: {
18
+ Id: string;
19
+ ApiName?: string | null;
20
+ Name?: { value?: unknown; displayValue?: string | null } | null;
21
+ Description__c?: { value?: unknown; displayValue?: string | null } | null;
22
+ Type__c?: { value?: unknown; displayValue?: string | null } | null;
23
+ Priority__c?: { value?: unknown; displayValue?: string | null } | null;
24
+ Status__c?: { value?: unknown; displayValue?: string | null } | null;
25
+ Scheduled__c?: { value?: unknown; displayValue?: string | null } | null;
26
+ User__r?: {
27
+ Name?: { value?: unknown; displayValue?: string | null } | null;
28
+ } | null;
29
+ } | null;
30
+ }> | null;
31
+ pageInfo?: {
32
+ hasNextPage?: boolean | null;
33
+ endCursor?: string | null;
34
+ } | null;
35
+ } | null;
36
+ } | null;
37
+ } | null;
38
+ }
39
+
40
+ // ---- PropertyListings ----
41
+ /** Single-field condition; Property__r filters on related Property (name, address, bedrooms). */
42
+ export type PropertyListingsWhereCondition = {
43
+ Name?: { like: string };
44
+ Listing_Status__c?: { like: string };
45
+ Listing_Price__c?: { gte?: number; lte?: number };
46
+ Property__r?: {
47
+ Name?: { like: string };
48
+ Address__c?: { like: string };
49
+ Bedrooms__c?: { gte?: number };
50
+ };
51
+ };
52
+ /** GraphQL ResultOrder values for orderBy (use ResultOrder.Asc / ResultOrder.Desc). */
53
+ export const ResultOrder = {
54
+ Asc: "ASC",
55
+ Desc: "DESC",
56
+ } as const;
57
+ export type ResultOrder = (typeof ResultOrder)[keyof typeof ResultOrder];
58
+
59
+ /** orderBy for Property_Listing__c (e.g. { Listing_Price__c: { order: ResultOrder.Asc } }). */
60
+ export type PropertyListingsOrderBy = {
61
+ Listing_Price__c?: { order: ResultOrder };
62
+ Property__r?: { Bedrooms__c?: { order: ResultOrder } };
63
+ };
64
+
65
+ /** Where clause: condition, or { and: conditions[] }, or { or: conditions[] }. */
66
+ export interface PropertyListingsQueryVariables {
67
+ where?:
68
+ | PropertyListingsWhereCondition
69
+ | { and: Array<PropertyListingsWhereCondition | { or: PropertyListingsWhereCondition[] }> }
70
+ | { or: PropertyListingsWhereCondition[] };
71
+ first: number;
72
+ after?: string | null;
73
+ orderBy?: PropertyListingsOrderBy | null;
74
+ }
75
+
76
+ export interface PropertyListingsQuery {
77
+ uiapi?: {
78
+ query?: {
79
+ Property_Listing__c?: {
80
+ edges?: Array<{
81
+ node?: {
82
+ Id: string;
83
+ ApiName?: string | null;
84
+ Name?: { value?: string | null; displayValue?: string | null } | null;
85
+ Listing_Price__c?: {
86
+ value?: number | null;
87
+ displayValue?: string | null;
88
+ } | null;
89
+ Listing_Status__c?: {
90
+ value?: string | null;
91
+ displayValue?: string | null;
92
+ } | null;
93
+ Property__c?: { value?: string | null; displayValue?: string | null } | null;
94
+ Property__r?: {
95
+ Name?: { value?: string | null; displayValue?: string | null } | null;
96
+ Address__c?: { value?: string | null; displayValue?: string | null } | null;
97
+ Bedrooms__c?: { value?: number | null; displayValue?: string | null } | null;
98
+ } | null;
99
+ } | null;
100
+ cursor?: string | null;
101
+ }> | null;
102
+ pageInfo?: {
103
+ hasNextPage?: boolean | null;
104
+ hasPreviousPage?: boolean | null;
105
+ startCursor?: string | null;
106
+ endCursor?: string | null;
107
+ } | null;
108
+ totalCount?: number | null;
109
+ } | null;
110
+ } | null;
111
+ } | null;
112
+ }
113
+
114
+ // ---- ListingById ----
115
+ export interface ListingByIdQueryVariables {
116
+ listingId: string;
117
+ }
118
+
119
+ export interface ListingByIdQuery {
120
+ uiapi?: {
121
+ query?: {
122
+ Property_Listing__c?: {
123
+ edges?: Array<{
124
+ node?: {
125
+ Id: string;
126
+ Name?: { value?: string | null; displayValue?: string | null } | null;
127
+ Listing_Price__c?: {
128
+ value?: unknown;
129
+ displayValue?: string | null;
130
+ } | null;
131
+ Listing_Status__c?: {
132
+ value?: string | null;
133
+ displayValue?: string | null;
134
+ } | null;
135
+ Property__c?: { value?: string | null; displayValue?: string | null } | null;
136
+ } | null;
137
+ }> | null;
138
+ } | null;
139
+ } | null;
140
+ } | null;
141
+ }
142
+
143
+ // ---- PropertyById ----
144
+ export interface PropertyByIdQueryVariables {
145
+ propertyId: string;
146
+ }
147
+
148
+ export interface PropertyByIdQuery {
149
+ uiapi?: {
150
+ query?: {
151
+ Property__c?: {
152
+ edges?: Array<{
153
+ node?: {
154
+ Id: string;
155
+ Name?: { value?: string | null; displayValue?: string | null } | null;
156
+ Address__c?: {
157
+ value?: string | null;
158
+ displayValue?: string | null;
159
+ } | null;
160
+ Type__c?: { value?: string | null; displayValue?: string | null } | null;
161
+ Monthly_Rent__c?: {
162
+ value?: unknown;
163
+ displayValue?: string | null;
164
+ } | null;
165
+ Bedrooms__c?: { value?: unknown; displayValue?: string | null } | null;
166
+ Bathrooms__c?: { value?: unknown; displayValue?: string | null } | null;
167
+ Sq_Ft__c?: { value?: unknown; displayValue?: string | null } | null;
168
+ Description__c?: {
169
+ value?: string | null;
170
+ displayValue?: string | null;
171
+ } | null;
172
+ } | null;
173
+ }> | null;
174
+ } | null;
175
+ } | null;
176
+ } | null;
177
+ }
178
+
179
+ // ---- PropertyImages ----
180
+ export interface PropertyImagesQueryVariables {
181
+ propertyId: string;
182
+ }
183
+
184
+ export interface PropertyImagesQuery {
185
+ uiapi?: {
186
+ query?: {
187
+ Property_Image__c?: {
188
+ edges?: Array<{
189
+ node?: {
190
+ Id: string;
191
+ Name?: { value?: string | null; displayValue?: string | null } | null;
192
+ Image_URL__c?: {
193
+ value?: string | null;
194
+ displayValue?: string | null;
195
+ } | null;
196
+ Image_Type__c?: {
197
+ value?: string | null;
198
+ displayValue?: string | null;
199
+ } | null;
200
+ Display_Order__c?: {
201
+ value?: unknown;
202
+ displayValue?: string | null;
203
+ } | null;
204
+ Alt_Text__c?: {
205
+ value?: string | null;
206
+ displayValue?: string | null;
207
+ } | null;
208
+ } | null;
209
+ }> | null;
210
+ } | null;
211
+ } | null;
212
+ } | null;
213
+ }
214
+
215
+ // ---- PropertyCosts ----
216
+ export interface PropertyCostsQueryVariables {
217
+ propertyId: string;
218
+ }
219
+
220
+ export interface PropertyCostsQuery {
221
+ uiapi?: {
222
+ query?: {
223
+ Property_Cost__c?: {
224
+ edges?: Array<{
225
+ node?: {
226
+ Id: string;
227
+ Cost_Category__c?: {
228
+ value?: string | null;
229
+ displayValue?: string | null;
230
+ } | null;
231
+ Cost_Amount__c?: {
232
+ value?: unknown;
233
+ displayValue?: string | null;
234
+ } | null;
235
+ Cost_Date__c?: {
236
+ value?: string | null;
237
+ displayValue?: string | null;
238
+ } | null;
239
+ Description__c?: {
240
+ value?: string | null;
241
+ displayValue?: string | null;
242
+ } | null;
243
+ Vendor__c?: {
244
+ value?: string | null;
245
+ displayValue?: string | null;
246
+ } | null;
247
+ } | null;
248
+ }> | null;
249
+ } | null;
250
+ } | null;
251
+ } | null;
252
+ }
253
+
254
+ // ---- PropertyFeatures ----
255
+ export interface PropertyFeaturesQueryVariables {
256
+ propertyId: string;
257
+ }
258
+
259
+ export interface PropertyFeaturesQuery {
260
+ uiapi?: {
261
+ query?: {
262
+ Property_Feature__c?: {
263
+ edges?: Array<{
264
+ node?: {
265
+ Id: string;
266
+ Name?: { value?: string | null; displayValue?: string | null } | null;
267
+ Feature_Category__c?: {
268
+ value?: string | null;
269
+ displayValue?: string | null;
270
+ } | null;
271
+ Description__c?: {
272
+ value?: string | null;
273
+ displayValue?: string | null;
274
+ } | null;
275
+ } | null;
276
+ }> | null;
277
+ } | null;
278
+ } | null;
279
+ } | null;
280
+ }
281
+
282
+ // ---- GetUserInfo ----
283
+ export interface GetUserInfoQuery {
284
+ uiapi?: {
285
+ query?: {
286
+ User?: {
287
+ edges?: Array<{
288
+ node?: {
289
+ Id: string;
290
+ Name?: { value?: string | null } | null;
291
+ } | null;
292
+ }> | null;
293
+ } | null;
294
+ } | null;
295
+ } | null;
296
+ }
@@ -1,17 +1,22 @@
1
1
  /**
2
- * GraphQL client using getDataSDK (replaces legacy executeGraphQL from webapp-experimental).
2
+ * Thin GraphQL client: getDataSDK + data.graphql with centralized error handling.
3
+ * Use with gql-tagged queries and generated operation types for type-safe calls.
3
4
  */
4
5
  import { getDataSDK } from "@salesforce/sdk-data";
5
6
 
6
- export async function executeGraphQL<T>(
7
- query: string,
8
- variables?: Record<string, unknown>,
9
- ): Promise<T> {
7
+ export async function executeGraphQL<TData, TVariables = Record<string, unknown>>(
8
+ query: string | { kind: string; definitions: unknown[] },
9
+ variables?: TVariables,
10
+ ): Promise<TData> {
10
11
  const data = await getDataSDK();
11
- const response = await data.graphql?.<T>(query, variables);
12
+ // SDK types graphql() first param as string; at runtime it may accept gql DocumentNode too
13
+ const response = await data.graphql?.<TData>(
14
+ query as unknown as string,
15
+ variables as Record<string, unknown>,
16
+ );
12
17
  if (response?.errors?.length) {
13
18
  const msg = response.errors.map((e) => e.message).join("; ");
14
19
  throw new Error(`GraphQL Error: ${msg}`);
15
20
  }
16
- return (response?.data ?? {}) as T;
21
+ return (response?.data ?? {}) as TData;
17
22
  }