@salesforce/webapp-template-app-react-sample-b2x-experimental 1.88.1 → 1.89.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.
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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.89.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.88.1...v1.89.0) (2026-03-10)
7
+
8
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
9
+
10
+
11
+
12
+
13
+
6
14
  ## [1.88.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.88.0...v1.88.1) (2026-03-10)
7
15
 
8
16
  **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
@@ -4,7 +4,7 @@
4
4
  "contentBody": {
5
5
  "authenticationType": "AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED",
6
6
  "appContainer": true,
7
- "appSpace": "c/appreactsampleb2x"
7
+ "appSpace": "c-appreactsampleb2x"
8
8
  },
9
9
  "urlName": "appreactsampleb2x"
10
10
  }
@@ -15,8 +15,8 @@
15
15
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
16
16
  },
17
17
  "dependencies": {
18
- "@salesforce/sdk-data": "^1.88.1",
19
- "@salesforce/webapp-experimental": "^1.88.1",
18
+ "@salesforce/sdk-data": "^1.89.0",
19
+ "@salesforce/webapp-experimental": "^1.89.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.88.1",
46
+ "@salesforce/vite-plugin-webapp-experimental": "^1.89.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,6 @@
1
1
  /**
2
2
  * Create Application__c (property application) record via Salesforce UI API.
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.
3
+ * Uses User__c to link the application to the authenticated user.
6
4
  */
7
5
  import { createRecord } from "@salesforce/webapp-experimental/api";
8
6
 
@@ -10,37 +8,13 @@ const OBJECT_API_NAME = "Application__c";
10
8
 
11
9
  export interface ApplicationRecordInput {
12
10
  Property__c: string | null;
11
+ User__c: string;
13
12
  Status__c?: string;
14
- First_Name__c?: string | null;
15
- Last_Name__c?: string | null;
16
- Email__c?: string | null;
17
- Phone__c?: string | null;
18
13
  Start_Date__c?: string | null;
19
- Preferred_Term__c?: string | null;
20
14
  Employment__c?: string | null;
21
15
  References__c?: string | null;
22
16
  }
23
17
 
24
- function buildEmploymentBlob(input: ApplicationRecordInput): string {
25
- const lines: string[] = [];
26
- const contact = [
27
- input.First_Name__c,
28
- input.Last_Name__c,
29
- input.Email__c,
30
- input.Phone__c,
31
- input.Preferred_Term__c,
32
- ]
33
- .filter(Boolean)
34
- .join(", ");
35
- if (contact) lines.push(`Contact: ${contact}`);
36
- if (input.Employment__c?.trim()) lines.push(input.Employment__c.trim());
37
- return lines.join("\n\n") || "";
38
- }
39
-
40
- /**
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__c.
43
- */
44
18
  export async function createApplicationRecord(
45
19
  input: ApplicationRecordInput,
46
20
  ): Promise<{ id: string }> {
@@ -49,15 +23,17 @@ export async function createApplicationRecord(
49
23
  if (input.Property__c != null && input.Property__c !== "") {
50
24
  fields.Property__c = input.Property__c;
51
25
  }
26
+ if (input.User__c) {
27
+ fields.User__c = input.User__c;
28
+ }
52
29
  if (input.Status__c != null && input.Status__c !== "") {
53
30
  fields.Status__c = input.Status__c;
54
31
  }
55
32
  if (input.Start_Date__c != null && input.Start_Date__c !== "") {
56
33
  fields.Start_Date__c = input.Start_Date__c;
57
34
  }
58
- const employmentBlob = buildEmploymentBlob(input);
59
- if (employmentBlob) {
60
- fields.Employment__c = employmentBlob;
35
+ if (input.Employment__c != null && input.Employment__c !== "") {
36
+ fields.Employment__c = input.Employment__c;
61
37
  }
62
38
  if (input.References__c != null && input.References__c !== "") {
63
39
  fields.References__c = input.References__c;
@@ -16,6 +16,10 @@ const USER_PROFILE_FIELDS_FULL = `
16
16
  PostalCode { value }
17
17
  Country { value }`;
18
18
 
19
+ const USER_CONTACT_FIELDS = `
20
+ Id
21
+ ContactId { value }`;
22
+
19
23
  function getUserProfileQuery(fields: string): string {
20
24
  return `
21
25
  query GetUserProfile($userId: ID) {
@@ -58,11 +62,21 @@ export async function fetchUserProfile<T>(
58
62
  fields: string = USER_PROFILE_FIELDS_FULL,
59
63
  ): Promise<T> {
60
64
  const data = await getDataSDK();
61
- const response: any = await data.graphql?.(getUserProfileQuery(fields), { userId });
65
+ const response: any = await data.graphql?.(getUserProfileQuery(fields), {
66
+ userId,
67
+ });
62
68
  throwOnGraphQLErrors(response);
63
69
  return flattenGraphQLRecord<T>(response?.data?.uiapi?.query?.User?.edges?.[0]?.node);
64
70
  }
65
71
 
72
+ /**
73
+ * Fetches the user's associated contact record ID via GraphQL and returns a flattened record.
74
+ * @param userId - The Salesforce User Id.
75
+ */
76
+ export async function fetchUserContact<T>(userId: string): Promise<T> {
77
+ return fetchUserProfile<T>(userId, USER_CONTACT_FIELDS);
78
+ }
79
+
66
80
  /**
67
81
  * Updates the user profile via GraphQL and returns the flattened updated record.
68
82
  * @param userId - The Salesforce User Id.
@@ -1,19 +1,20 @@
1
1
  import { useSearchParams, Link } from "react-router";
2
- import { useCallback, useEffect, useState, type ChangeEvent } from "react";
3
- import { Button } from "@/components/ui/button";
4
- import { Input } from "@/components/ui/input";
5
- import { Label } from "@/components/ui/label";
6
- import { Card, CardContent } from "@/components/ui/card";
2
+ import { useCallback, useEffect, useState, type ChangeEvent, type SubmitEvent } from "react";
3
+ import { Button } from "../components/ui/button";
4
+ import { Input } from "../components/ui/input";
5
+ import { Label } from "../components/ui/label";
6
+ import { Card, CardContent } from "../components/ui/card";
7
7
  import {
8
8
  fetchListingById,
9
9
  fetchPropertyById,
10
10
  fetchPrimaryImagesByPropertyIds,
11
11
  } from "@/api/propertyDetailGraphQL";
12
12
  import { createApplicationRecord } from "@/api/applicationApi";
13
-
14
- const PREFERRED_TERM_OPTIONS = ["", "1 month", "6 months", "12 months"];
13
+ import { useAuth } from "../features/authentication/context/AuthContext";
14
+ import { fetchUserContact } from "../features/authentication/api/userProfileApi";
15
15
 
16
16
  export default function Application() {
17
+ const { user } = useAuth();
17
18
  const [searchParams] = useSearchParams();
18
19
  const listingId = searchParams.get("listingId") ?? "";
19
20
 
@@ -21,24 +22,33 @@ export default function Application() {
21
22
  const [propertyAddress, setPropertyAddress] = useState<string | null>(null);
22
23
  const [propertyId, setPropertyId] = useState<string | null>(null);
23
24
  const [propertyImageUrl, setPropertyImageUrl] = useState<string | null>(null);
25
+ const [contactId, setContactId] = useState<string | null>(null);
24
26
  const [loading, setLoading] = useState(!!listingId);
25
27
  const [loadError, setLoadError] = useState<string | null>(null);
26
28
 
27
- // Form state – all map to Application__c fields
28
- const [firstName, setFirstName] = useState("");
29
- const [lastName, setLastName] = useState("");
30
- const [email, setEmail] = useState("");
31
- const [phone, setPhone] = useState("");
32
29
  const [moveInDate, setMoveInDate] = useState("");
33
- const [preferredTerm, setPreferredTerm] = useState("");
34
- const [employmentInfo, setEmploymentInfo] = useState("");
30
+ const [employment, setEmployment] = useState("");
35
31
  const [references, setReferences] = useState("");
36
32
 
37
33
  const [submitting, setSubmitting] = useState(false);
38
34
  const [submitError, setSubmitError] = useState<string | null>(null);
39
35
  const [submittedId, setSubmittedId] = useState<string | null>(null);
40
36
 
41
- // Load listing and property when listingId is present
37
+ useEffect(() => {
38
+ if (!user?.id) return;
39
+ let mounted = true;
40
+ fetchUserContact<{ ContactId?: string }>(user.id)
41
+ .then((contact) => {
42
+ if (mounted) setContactId(contact.ContactId ?? null);
43
+ })
44
+ .catch((err) => {
45
+ if (mounted) console.error("Failed to fetch contact ID", err);
46
+ });
47
+ return () => {
48
+ mounted = false;
49
+ };
50
+ }, [user]);
51
+
42
52
  useEffect(() => {
43
53
  if (!listingId?.trim()) {
44
54
  setLoading(false);
@@ -80,7 +90,7 @@ export default function Application() {
80
90
  }, [listingId]);
81
91
 
82
92
  const handleSubmit = useCallback(
83
- async (e: React.FormEvent) => {
93
+ async (e: SubmitEvent<HTMLFormElement>) => {
84
94
  e.preventDefault();
85
95
  setSubmitError(null);
86
96
  setSubmitting(true);
@@ -88,13 +98,9 @@ export default function Application() {
88
98
  const id = await createApplicationRecord({
89
99
  Property__c: propertyId || null,
90
100
  Status__c: "Submitted",
91
- First_Name__c: firstName.trim() || null,
92
- Last_Name__c: lastName.trim() || null,
93
- Email__c: email.trim() || null,
94
- Phone__c: phone.trim() || null,
101
+ User__c: contactId || user?.id || "",
95
102
  Start_Date__c: moveInDate.trim() || null,
96
- Preferred_Term__c: preferredTerm.trim() || null,
97
- Employment__c: employmentInfo.trim() || null,
103
+ Employment__c: employment.trim() || null,
98
104
  References__c: references.trim() || null,
99
105
  });
100
106
  setSubmittedId(id.id);
@@ -104,17 +110,7 @@ export default function Application() {
104
110
  setSubmitting(false);
105
111
  }
106
112
  },
107
- [
108
- propertyId,
109
- firstName,
110
- lastName,
111
- email,
112
- phone,
113
- moveInDate,
114
- preferredTerm,
115
- employmentInfo,
116
- references,
117
- ],
113
+ [propertyId, contactId, moveInDate, employment, references],
118
114
  );
119
115
 
120
116
  if (loading) {
@@ -186,46 +182,6 @@ export default function Application() {
186
182
  <Card className="mb-6 rounded-2xl border border-border shadow-sm">
187
183
  <CardContent className="pt-3">
188
184
  <form onSubmit={handleSubmit}>
189
- <h3 className="mb-4 text-base font-semibold text-foreground">Your info</h3>
190
- <div className="mb-4 grid grid-cols-1 gap-4 md:grid-cols-2">
191
- <div className="space-y-2">
192
- <Label htmlFor="app-first-name">First Name *</Label>
193
- <Input
194
- id="app-first-name"
195
- type="text"
196
- value={firstName}
197
- onChange={(e: ChangeEvent<HTMLInputElement>) => setFirstName(e.target.value)}
198
- />
199
- </div>
200
- <div className="space-y-2">
201
- <Label htmlFor="app-last-name">Last Name</Label>
202
- <Input
203
- id="app-last-name"
204
- type="text"
205
- value={lastName}
206
- onChange={(e: ChangeEvent<HTMLInputElement>) => setLastName(e.target.value)}
207
- />
208
- </div>
209
- </div>
210
- <div className="mb-4 space-y-2">
211
- <Label htmlFor="app-email">Email Address</Label>
212
- <Input
213
- id="app-email"
214
- type="email"
215
- value={email}
216
- onChange={(e: ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)}
217
- />
218
- </div>
219
- <div className="mb-4 space-y-2">
220
- <Label htmlFor="app-phone">Phone Number</Label>
221
- <Input
222
- id="app-phone"
223
- type="tel"
224
- value={phone}
225
- onChange={(e: ChangeEvent<HTMLInputElement>) => setPhone(e.target.value)}
226
- />
227
- </div>
228
- <h3 className="mb-4 mt-6 text-base font-semibold text-foreground">Move in</h3>
229
185
  <div className="mb-4 grid grid-cols-1 gap-4 md:grid-cols-2">
230
186
  <div className="space-y-2">
231
187
  <Label htmlFor="app-move-in">Move in date</Label>
@@ -236,21 +192,6 @@ export default function Application() {
236
192
  onChange={(e: ChangeEvent<HTMLInputElement>) => setMoveInDate(e.target.value)}
237
193
  />
238
194
  </div>
239
- <div className="space-y-2">
240
- <Label htmlFor="app-term">Preferred term</Label>
241
- <select
242
- id="app-term"
243
- className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
244
- value={preferredTerm}
245
- onChange={(e: ChangeEvent<HTMLSelectElement>) => setPreferredTerm(e.target.value)}
246
- >
247
- {PREFERRED_TERM_OPTIONS.map((opt) => (
248
- <option key={opt || "empty"} value={opt}>
249
- {opt || "Select one"}
250
- </option>
251
- ))}
252
- </select>
253
- </div>
254
195
  </div>
255
196
  <div className="mb-4 space-y-2">
256
197
  <Label htmlFor="app-employment">Employment info</Label>
@@ -258,8 +199,8 @@ export default function Application() {
258
199
  id="app-employment"
259
200
  rows={3}
260
201
  className="min-h-[80px] w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
261
- value={employmentInfo}
262
- onChange={(e) => setEmploymentInfo(e.target.value)}
202
+ value={employment}
203
+ onChange={(e) => setEmployment(e.target.value)}
263
204
  />
264
205
  </div>
265
206
  <div className="mb-4 space-y-2">
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.88.1",
3
+ "version": "1.89.0",
4
4
  "description": "Base SFDX project template",
5
5
  "private": true,
6
6
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-app-react-sample-b2x-experimental",
3
- "version": "1.88.1",
3
+ "version": "1.89.0",
4
4
  "description": "B2C sample app template with app shell",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",