@salesforce/webapp-template-app-react-template-b2x-experimental 1.79.1 → 1.80.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 +16 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2x/package.json +3 -3
- package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/authentication/api/userProfileApi.ts +81 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/authentication/layout/centered-page-layout.tsx +17 -2
- package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/authentication/pages/Profile.tsx +7 -55
- package/dist/package.json +1 -1
- 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.80.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.79.2...v1.80.0) (2026-03-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [1.79.2](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.79.1...v1.79.2) (2026-03-06)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
## [1.79.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.79.0...v1.79.1) (2026-03-06)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@salesforce/sdk-data": "^1.
|
|
19
|
-
"@salesforce/webapp-experimental": "^1.
|
|
18
|
+
"@salesforce/sdk-data": "^1.80.0",
|
|
19
|
+
"@salesforce/webapp-experimental": "^1.80.0",
|
|
20
20
|
"@tailwindcss/vite": "^4.1.17",
|
|
21
21
|
"@tanstack/react-form": "^1.28.4",
|
|
22
22
|
"class-variance-authority": "^0.7.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
41
41
|
"@graphql-tools/utils": "^11.0.0",
|
|
42
42
|
"@playwright/test": "^1.49.0",
|
|
43
|
-
"@salesforce/vite-plugin-webapp-experimental": "^1.
|
|
43
|
+
"@salesforce/vite-plugin-webapp-experimental": "^1.80.0",
|
|
44
44
|
"@testing-library/jest-dom": "^6.6.3",
|
|
45
45
|
"@testing-library/react": "^16.1.0",
|
|
46
46
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extensible user profile fetching and updating via UI API GraphQL.
|
|
3
|
+
*/
|
|
4
|
+
import { getDataSDK } from "@salesforce/sdk-data";
|
|
5
|
+
import { flattenGraphQLRecord } from "../utils/helpers";
|
|
6
|
+
|
|
7
|
+
const USER_PROFILE_FIELDS_FULL = `
|
|
8
|
+
Id
|
|
9
|
+
FirstName { value }
|
|
10
|
+
LastName { value }
|
|
11
|
+
Email { value }
|
|
12
|
+
Phone { value }
|
|
13
|
+
Street { value }
|
|
14
|
+
City { value }
|
|
15
|
+
State { value }
|
|
16
|
+
PostalCode { value }
|
|
17
|
+
Country { value }`;
|
|
18
|
+
|
|
19
|
+
function getUserProfileQuery(fields: string): string {
|
|
20
|
+
return `
|
|
21
|
+
query GetUserProfile($userId: ID) {
|
|
22
|
+
uiapi {
|
|
23
|
+
query {
|
|
24
|
+
User(where: { Id: { eq: $userId } }) {
|
|
25
|
+
edges {
|
|
26
|
+
node {${fields}}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getUserProfileMutation(fields: string): string {
|
|
35
|
+
return `
|
|
36
|
+
mutation UpdateUserProfile($input: UserUpdateInput!) {
|
|
37
|
+
uiapi {
|
|
38
|
+
UserUpdate(input: $input) {
|
|
39
|
+
Record {${fields}}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function throwOnGraphQLErrors(response: any): void {
|
|
46
|
+
if (response?.errors?.length) {
|
|
47
|
+
throw new Error(response.errors.map((e: any) => e.message).join("; "));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetches the user profile via GraphQL and returns a flattened record.
|
|
53
|
+
* @param userId - The Salesforce User Id.
|
|
54
|
+
* @param fields - GraphQL field selection (defaults to USER_PROFILE_FIELDS_FULL).
|
|
55
|
+
*/
|
|
56
|
+
export async function fetchUserProfile<T>(
|
|
57
|
+
userId: string,
|
|
58
|
+
fields: string = USER_PROFILE_FIELDS_FULL,
|
|
59
|
+
): Promise<T> {
|
|
60
|
+
const data = await getDataSDK();
|
|
61
|
+
const response: any = await data.graphql?.(getUserProfileQuery(fields), { userId });
|
|
62
|
+
throwOnGraphQLErrors(response);
|
|
63
|
+
return flattenGraphQLRecord<T>(response?.data?.uiapi?.query?.User?.edges?.[0]?.node);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Updates the user profile via GraphQL and returns the flattened updated record.
|
|
68
|
+
* @param userId - The Salesforce User Id.
|
|
69
|
+
* @param values - The field values to update.
|
|
70
|
+
*/
|
|
71
|
+
export async function updateUserProfile<T>(
|
|
72
|
+
userId: string,
|
|
73
|
+
values: Record<string, unknown>,
|
|
74
|
+
): Promise<T> {
|
|
75
|
+
const data = await getDataSDK();
|
|
76
|
+
const response: any = await data.graphql?.(getUserProfileMutation(USER_PROFILE_FIELDS_FULL), {
|
|
77
|
+
input: { Id: userId, User: { ...values } },
|
|
78
|
+
});
|
|
79
|
+
throwOnGraphQLErrors(response);
|
|
80
|
+
return flattenGraphQLRecord<T>(response?.data?.uiapi?.UserUpdate?.Record);
|
|
81
|
+
}
|
|
@@ -34,13 +34,19 @@ interface CenteredPageLayoutProps
|
|
|
34
34
|
* Optional page title. If provided, will render a <title> component that React will place in the document head.
|
|
35
35
|
*/
|
|
36
36
|
title?: string;
|
|
37
|
+
/**
|
|
38
|
+
* When true, content is aligned to the top instead of being vertically centered.
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
topAligned?: boolean;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* CenteredPageLayout component that provides consistent page structure and spacing.
|
|
41
46
|
*
|
|
42
47
|
* This component creates a full-viewport-height container that centers its content
|
|
43
|
-
*
|
|
48
|
+
* horizontally. By default, content is top-aligned; set `topAligned={false}` to
|
|
49
|
+
* vertically center instead. The inner content area has a configurable maximum width
|
|
44
50
|
* to prevent content from becoming too wide on large screens.
|
|
45
51
|
*
|
|
46
52
|
* @example
|
|
@@ -48,6 +54,10 @@ interface CenteredPageLayoutProps
|
|
|
48
54
|
* <CenteredPageLayout contentMaxWidth="md">
|
|
49
55
|
* <YourPageContent />
|
|
50
56
|
* </CenteredPageLayout>
|
|
57
|
+
*
|
|
58
|
+
* <CenteredPageLayout contentMaxWidth="md" topAligned={false}>
|
|
59
|
+
* <VerticallyCenteredContent />
|
|
60
|
+
* </CenteredPageLayout>
|
|
51
61
|
* ```
|
|
52
62
|
*/
|
|
53
63
|
export function CenteredPageLayout({
|
|
@@ -55,13 +65,18 @@ export function CenteredPageLayout({
|
|
|
55
65
|
className,
|
|
56
66
|
children,
|
|
57
67
|
title,
|
|
68
|
+
topAligned = true,
|
|
58
69
|
...props
|
|
59
70
|
}: CenteredPageLayoutProps) {
|
|
60
71
|
return (
|
|
61
72
|
<>
|
|
62
73
|
{title && <title>{title}</title>}
|
|
63
74
|
<main
|
|
64
|
-
className={cn(
|
|
75
|
+
className={cn(
|
|
76
|
+
"flex min-h-svh w-full justify-center p-6 md:p-10",
|
|
77
|
+
topAligned ? "items-start" : "items-center",
|
|
78
|
+
className,
|
|
79
|
+
)}
|
|
65
80
|
data-slot="page-layout"
|
|
66
81
|
{...props}
|
|
67
82
|
>
|
|
@@ -1,50 +1,15 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
import { getDataSDK } from "@salesforce/sdk-data";
|
|
5
|
-
|
|
6
4
|
import { CenteredPageLayout } from "../layout/centered-page-layout";
|
|
7
5
|
import { CardSkeleton } from "../layout/card-skeleton";
|
|
8
6
|
import { AuthForm } from "../forms/auth-form";
|
|
9
7
|
import { useAppForm } from "../hooks/form";
|
|
10
8
|
import { ROUTES } from "../authenticationConfig";
|
|
11
9
|
import { emailSchema } from "../authHelpers";
|
|
12
|
-
import {
|
|
10
|
+
import { getErrorMessage } from "../utils/helpers";
|
|
13
11
|
import { getUser } from "../context/AuthContext";
|
|
14
|
-
|
|
15
|
-
const GRAPHQL_USER_PROFILE_FIELDS = `
|
|
16
|
-
Id
|
|
17
|
-
FirstName { value }
|
|
18
|
-
LastName { value }
|
|
19
|
-
Email { value }
|
|
20
|
-
Phone { value }
|
|
21
|
-
Street { value }
|
|
22
|
-
City { value }
|
|
23
|
-
State { value }
|
|
24
|
-
PostalCode { value }
|
|
25
|
-
Country { value }`;
|
|
26
|
-
|
|
27
|
-
const QUERY_PROFILE_GRAPHQL = `
|
|
28
|
-
query GetUserProfile($userId: ID) {
|
|
29
|
-
uiapi {
|
|
30
|
-
query {
|
|
31
|
-
User(where: { Id: { eq: $userId } }) {
|
|
32
|
-
edges {
|
|
33
|
-
node {${GRAPHQL_USER_PROFILE_FIELDS}}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}`;
|
|
39
|
-
|
|
40
|
-
const MUTATE_PROFILE_GRAPHQL = `
|
|
41
|
-
mutation UpdateUserProfile($input: UserUpdateInput!) {
|
|
42
|
-
uiapi {
|
|
43
|
-
UserUpdate(input: $input) {
|
|
44
|
-
Record {${GRAPHQL_USER_PROFILE_FIELDS}}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}`;
|
|
12
|
+
import { fetchUserProfile, updateUserProfile } from "../api/userProfileApi";
|
|
48
13
|
|
|
49
14
|
const optionalString = z
|
|
50
15
|
.string()
|
|
@@ -81,17 +46,9 @@ export default function Profile() {
|
|
|
81
46
|
setSubmitError(null);
|
|
82
47
|
setSuccess(false);
|
|
83
48
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
input: { Id: user.id, User: { ...value } },
|
|
87
|
-
});
|
|
88
|
-
if (response?.errors?.length) {
|
|
89
|
-
throw new Error(response.errors.map((e: any) => e.message).join("; "));
|
|
90
|
-
}
|
|
91
|
-
setProfile(flattenGraphQLRecord(response?.data?.uiapi?.UserUpdate?.Record));
|
|
92
|
-
|
|
49
|
+
const updated = await updateUserProfile<ProfileFormValues>(user.id, value);
|
|
50
|
+
setProfile(updated);
|
|
93
51
|
setSuccess(true);
|
|
94
|
-
// Scroll to top of page after successful update so user sees it
|
|
95
52
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
96
53
|
} catch (err) {
|
|
97
54
|
setSubmitError(getErrorMessage(err, "Failed to update profile"));
|
|
@@ -102,15 +59,10 @@ export default function Profile() {
|
|
|
102
59
|
|
|
103
60
|
useEffect(() => {
|
|
104
61
|
let mounted = true;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.then((data) => data.graphql?.(QUERY_PROFILE_GRAPHQL, { userId: user.id }))
|
|
108
|
-
.then((response: any) => {
|
|
109
|
-
if (response?.errors?.length) {
|
|
110
|
-
throw new Error(response.errors.map((e: any) => e.message).join("; "));
|
|
111
|
-
}
|
|
62
|
+
fetchUserProfile<ProfileFormValues>(user.id)
|
|
63
|
+
.then((data) => {
|
|
112
64
|
if (mounted) {
|
|
113
|
-
setProfile(
|
|
65
|
+
setProfile(data);
|
|
114
66
|
}
|
|
115
67
|
})
|
|
116
68
|
.catch((err: any) => {
|
package/dist/package.json
CHANGED