@salesforce/webapp-template-app-react-sample-b2x-experimental 1.79.2 → 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.
Files changed (44) hide show
  1. package/dist/CHANGELOG.md +8 -0
  2. package/dist/force-app/main/default/classes/WebAppAuthUtils.cls +68 -0
  3. package/dist/force-app/main/default/classes/WebAppAuthUtils.cls-meta.xml +5 -0
  4. package/dist/force-app/main/default/classes/WebAppChangePassword.cls +77 -0
  5. package/dist/force-app/main/default/classes/WebAppChangePassword.cls-meta.xml +5 -0
  6. package/dist/force-app/main/default/classes/WebAppForgotPassword.cls +71 -0
  7. package/dist/force-app/main/default/classes/WebAppForgotPassword.cls-meta.xml +5 -0
  8. package/dist/force-app/main/default/classes/WebAppLogin.cls +105 -0
  9. package/dist/force-app/main/default/classes/WebAppLogin.cls-meta.xml +5 -0
  10. package/dist/force-app/main/default/classes/WebAppRegistration.cls +162 -0
  11. package/dist/force-app/main/default/classes/WebAppRegistration.cls-meta.xml +5 -0
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +3 -3
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/leadApi.ts +15 -6
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/app.tsx +4 -1
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +155 -88
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/api/userProfileApi.ts +81 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/authHelpers.ts +73 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/authenticationConfig.ts +61 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/context/AuthContext.tsx +95 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/footers/footer-link.tsx +36 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/forms/auth-form.tsx +81 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/forms/submit-button.tsx +49 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/hooks/form.tsx +120 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/hooks/useCountdownTimer.ts +266 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/hooks/useRetryWithBackoff.ts +109 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/layout/card-skeleton.tsx +38 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/layout/centered-page-layout.tsx +87 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/layouts/AuthAppLayout.tsx +12 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/layouts/authenticationRouteLayout.tsx +21 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/layouts/privateRouteLayout.tsx +36 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/pages/ChangePassword.tsx +107 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/pages/ForgotPassword.tsx +73 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/pages/Login.tsx +97 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/pages/Profile.tsx +139 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/pages/Register.tsx +133 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/pages/ResetPassword.tsx +107 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +616 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/sessionTimeout/sessionTimeService.ts +161 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts +77 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/authentication/utils/helpers.ts +121 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Contact.tsx +201 -114
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +67 -13
  43. package/dist/package.json +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1,133 @@
1
+ import { useState } from "react";
2
+ import { useNavigate, useSearchParams } from "react-router";
3
+ import { z } from "zod";
4
+ import { CenteredPageLayout } from "../layout/centered-page-layout";
5
+ import { AuthForm } from "../forms/auth-form";
6
+ import { useAppForm } from "../hooks/form";
7
+ import { getDataSDK } from "@salesforce/sdk-data";
8
+ import { ROUTES, AUTH_PLACEHOLDERS } from "../authenticationConfig";
9
+ import { emailSchema, passwordSchema, getStartUrl, type AuthResponse } from "../authHelpers";
10
+ import { handleApiResponse, getErrorMessage } from "../utils/helpers";
11
+
12
+ const registerSchema = z
13
+ .object({
14
+ firstName: z.string().trim().min(1, "First name is required"),
15
+ lastName: z.string().trim().min(1, "Last name is required"),
16
+ email: emailSchema,
17
+ password: passwordSchema,
18
+ confirmPassword: z.string().min(1, "Please confirm your password"),
19
+ startUrl: z.string(),
20
+ })
21
+ .refine((data) => data.password === data.confirmPassword, {
22
+ message: "Passwords do not match",
23
+ path: ["confirmPassword"],
24
+ });
25
+
26
+ export default function Register() {
27
+ const navigate = useNavigate();
28
+ const [searchParams] = useSearchParams();
29
+ const [submitError, setSubmitError] = useState<string | null>(null);
30
+
31
+ const form = useAppForm({
32
+ defaultValues: {
33
+ firstName: "",
34
+ lastName: "",
35
+ email: "",
36
+ password: "",
37
+ confirmPassword: "",
38
+ startUrl: getStartUrl(searchParams) || "",
39
+ },
40
+ validators: { onChange: registerSchema, onSubmit: registerSchema },
41
+ onSubmit: async ({ value: formFieldValues }) => {
42
+ setSubmitError(null);
43
+ try {
44
+ // [Dev Note] Salesforce Integration:
45
+ // We use the Data SDK fetch to make an authenticated (or guest) call to Salesforce.
46
+ // "/sfdcapi/services/apexrest/auth/register" refers to a custom Apex Class exposed as a REST resource.
47
+ // You must ensure this Apex class exists in your org and handles registration
48
+ // (e.g., duplicate checks and user creation such as Site.createExternalUser).
49
+ const { confirmPassword, ...request } = formFieldValues;
50
+ const sdk = await getDataSDK();
51
+ const response = await sdk.fetch!("/sfdcapi/services/apexrest/auth/register", {
52
+ method: "POST",
53
+ body: JSON.stringify({ request }),
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ Accept: "application/json",
57
+ },
58
+ });
59
+ const result = await handleApiResponse<AuthResponse>(response, "Registration failed");
60
+ if (result?.redirectUrl) {
61
+ // Hard navigate to the URL which logs the new user in
62
+ window.location.replace(result.redirectUrl);
63
+ } else {
64
+ // In case redirectUrl is null, redirect to the login page
65
+ navigate(ROUTES.LOGIN.PATH, { replace: true });
66
+ }
67
+ } catch (err) {
68
+ setSubmitError(getErrorMessage(err, "Registration failed"));
69
+ }
70
+ },
71
+ onSubmitInvalid: () => {},
72
+ });
73
+
74
+ return (
75
+ <CenteredPageLayout title={ROUTES.REGISTER.TITLE}>
76
+ <form.AppForm>
77
+ <AuthForm
78
+ title="Sign Up"
79
+ description="Enter your information to create an account"
80
+ error={submitError}
81
+ submit={{ text: "Create an account", loadingText: "Creating account…" }}
82
+ footer={{
83
+ text: "Already have an account?",
84
+ link: ROUTES.LOGIN.PATH,
85
+ linkText: "Sign in",
86
+ }}
87
+ >
88
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 items-start">
89
+ <form.AppField name="firstName">
90
+ {(field) => (
91
+ <field.TextField
92
+ label="First name"
93
+ placeholder={AUTH_PLACEHOLDERS.FIRST_NAME}
94
+ autoComplete="given-name"
95
+ />
96
+ )}
97
+ </form.AppField>
98
+ <form.AppField name="lastName">
99
+ {(field) => (
100
+ <field.TextField
101
+ label="Last name"
102
+ placeholder={AUTH_PLACEHOLDERS.LAST_NAME}
103
+ autoComplete="family-name"
104
+ />
105
+ )}
106
+ </form.AppField>
107
+ </div>
108
+ <form.AppField name="email">
109
+ {(field) => <field.EmailField label="Email" />}
110
+ </form.AppField>
111
+ <form.AppField name="password">
112
+ {(field) => (
113
+ <field.PasswordField
114
+ label="Password"
115
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_CREATE}
116
+ autoComplete="new-password"
117
+ />
118
+ )}
119
+ </form.AppField>
120
+ <form.AppField name="confirmPassword">
121
+ {(field) => (
122
+ <field.PasswordField
123
+ label="Confirm Password"
124
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_CONFIRM}
125
+ autoComplete="new-password"
126
+ />
127
+ )}
128
+ </form.AppField>
129
+ </AuthForm>
130
+ </form.AppForm>
131
+ </CenteredPageLayout>
132
+ );
133
+ }
@@ -0,0 +1,107 @@
1
+ import { useState } from "react";
2
+ import { Link, useSearchParams } from "react-router";
3
+ import { CardLayout } from "../../../components/layouts/card-layout";
4
+ import { CenteredPageLayout } from "../layout/centered-page-layout";
5
+ import { AuthForm } from "../forms/auth-form";
6
+ import { StatusAlert } from "../../../components/alerts/status-alert";
7
+ import { useAppForm } from "../hooks/form";
8
+ import { getDataSDK } from "@salesforce/sdk-data";
9
+ import { ROUTES, AUTH_PLACEHOLDERS } from "../authenticationConfig";
10
+ import { newPasswordSchema } from "../authHelpers";
11
+ import { handleApiResponse, getErrorMessage } from "../utils/helpers";
12
+
13
+ export default function ResetPassword() {
14
+ const [searchParams] = useSearchParams();
15
+ const token = searchParams.get("token");
16
+ const [success, setSuccess] = useState(false);
17
+ const [submitError, setSubmitError] = useState<string | null>(null);
18
+
19
+ const form = useAppForm({
20
+ defaultValues: { newPassword: "", confirmPassword: "" },
21
+ validators: { onChange: newPasswordSchema, onSubmit: newPasswordSchema },
22
+ onSubmit: async ({ value }) => {
23
+ setSubmitError(null);
24
+ setSuccess(false);
25
+ try {
26
+ // [Dev Note] Custom Apex Endpoint: /auth/reset-password
27
+ // You must ensure this Apex class exists in your org
28
+ const sdk = await getDataSDK();
29
+ const response = await sdk.fetch!("/sfdcapi/services/apexrest/auth/reset-password", {
30
+ method: "POST",
31
+ body: JSON.stringify({ token, newPassword: value.newPassword }),
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ Accept: "application/json",
35
+ },
36
+ });
37
+ await handleApiResponse(response, "Password reset failed");
38
+ setSuccess(true);
39
+ // Scroll to top of page after successful submission so user sees it
40
+ window.scrollTo({ top: 0, behavior: "smooth" });
41
+ } catch (err) {
42
+ setSubmitError(getErrorMessage(err, "Password reset failed"));
43
+ }
44
+ },
45
+ onSubmitInvalid: () => {},
46
+ });
47
+
48
+ if (!token) {
49
+ return (
50
+ <CenteredPageLayout title={ROUTES.RESET_PASSWORD.TITLE}>
51
+ <CardLayout title="Reset Password">
52
+ <StatusAlert>
53
+ Reset token is invalid or expired.{" "}
54
+ <Link to={ROUTES.FORGOT_PASSWORD.PATH} className="underline underline-offset-4">
55
+ Request a new password reset link.
56
+ </Link>
57
+ </StatusAlert>
58
+ </CardLayout>
59
+ </CenteredPageLayout>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <CenteredPageLayout title={ROUTES.RESET_PASSWORD.TITLE}>
65
+ <form.AppForm>
66
+ <AuthForm
67
+ title="Reset Password"
68
+ description="Enter your new password below"
69
+ error={submitError}
70
+ success={
71
+ success && (
72
+ <>
73
+ Password reset successfully!{" "}
74
+ <Link to={ROUTES.LOGIN.PATH} className="underline">
75
+ Sign in
76
+ </Link>
77
+ </>
78
+ )
79
+ }
80
+ submit={{ text: "Reset Password", loadingText: "Resetting…" }}
81
+ footer={{ text: "Remember your password?", link: ROUTES.LOGIN.PATH, linkText: "Sign in" }}
82
+ >
83
+ <form.AppField name="newPassword">
84
+ {(field) => (
85
+ <field.PasswordField
86
+ label="New Password"
87
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_NEW}
88
+ autoComplete="new-password"
89
+ disabled={success}
90
+ />
91
+ )}
92
+ </form.AppField>
93
+ <form.AppField name="confirmPassword">
94
+ {(field) => (
95
+ <field.PasswordField
96
+ label="Confirm Password"
97
+ placeholder={AUTH_PLACEHOLDERS.PASSWORD_NEW_CONFIRM}
98
+ autoComplete="new-password"
99
+ disabled={success}
100
+ />
101
+ )}
102
+ </form.AppField>
103
+ </AuthForm>
104
+ </form.AppForm>
105
+ </CenteredPageLayout>
106
+ );
107
+ }