@salesforce/webapp-template-feature-react-authentication-experimental 1.12.0 → 1.14.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/.a4drules/skills/install-feature/SKILL.md +58 -0
- package/dist/CHANGELOG.md +19 -0
- package/dist/force-app/main/default/classes/WebAppAuthUtils.cls +12 -0
- package/dist/force-app/main/default/classes/WebAppChangePassword.cls +77 -0
- package/dist/force-app/main/default/classes/WebAppChangePassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/webapplications/feature-react-authentication/src/components/forms/auth-form.tsx +2 -0
- package/dist/force-app/main/default/webapplications/feature-react-authentication/src/components/forms/submit-button.tsx +5 -5
- package/dist/force-app/main/default/webapplications/feature-react-authentication/src/pages/ChangePassword.tsx +5 -12
- package/dist/package.json +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: install-feature
|
|
3
|
+
description: Install a feature into the current app. Use when the user asks to add a feature, capability, or functionality like authentication, search, charts, or navigation to their React web app.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Install feature
|
|
7
|
+
|
|
8
|
+
When the user asks to add a feature to their app, follow this workflow.
|
|
9
|
+
|
|
10
|
+
## 1. Match the request to a feature
|
|
11
|
+
|
|
12
|
+
Available features (npm packages):
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
| Feature | Package | Description |
|
|
16
|
+
| ---------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
17
|
+
| **Authentication** | `@salesforce/webapp-template-feature-react-authentication-experimental` | Login, register, password reset, protected routes |
|
|
18
|
+
| **Global search** | `@salesforce/webapp-template-feature-react-global-search-experimental` | Search Salesforce objects with filters and pagination |
|
|
19
|
+
| **Navigation menu** | `@salesforce/webapp-template-feature-react-nav-menu-experimental` | App layout with responsive navigation menu |
|
|
20
|
+
| **Analytics charts** | `@salesforce/webapp-template-feature-react-chart-experimental` | Recharts line/bar charts with theming (AnalyticsChart, ChartContainer) |
|
|
21
|
+
| **Shared UI (shadcn)** | `@salesforce/webapp-template-feature-react-shadcn-experimental` | Button, Card, Input, Select, Table, Tabs, etc. |
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
If no feature matches, tell the user and offer to build it from scratch following the project's existing patterns.
|
|
25
|
+
|
|
26
|
+
## 2. Install the npm package
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install <package-name>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 3. Copy skills and rules from the package
|
|
33
|
+
|
|
34
|
+
After install, check the package in `node_modules/<package-name>/` for:
|
|
35
|
+
|
|
36
|
+
- `**skills/**` – If it exists, copy each skill folder into the current project's skills directory (e.g. `.cline/skills/` or `.cursor/skills/`).
|
|
37
|
+
- `**rules/**` – If it exists, copy each rule file into the current project's rules directory (e.g. `.clinerules/` or `.cursor/rules/`).
|
|
38
|
+
|
|
39
|
+
Not every package has skills or rules; skip this step if neither directory exists.
|
|
40
|
+
|
|
41
|
+
## 4. Read the feature's exports and components
|
|
42
|
+
|
|
43
|
+
Read the package's entry point (usually `index.ts` or the `main` field in its `package.json`) to understand what components and types it exports.
|
|
44
|
+
|
|
45
|
+
Also read the `feature.ts` file if it exists — it lists dependencies on other features (e.g. shadcn) and any npm dependencies the feature needs (e.g. `recharts`). Install any missing dependencies.
|
|
46
|
+
|
|
47
|
+
## 5. Implement the feature in the current app
|
|
48
|
+
|
|
49
|
+
- **If the package exports reusable components** (e.g. `AnalyticsChart`, `ChartContainer`): import and use them directly. Follow the package's README or skill instructions for props, data shapes, and usage patterns.
|
|
50
|
+
- **If the package is a reference implementation** (e.g. authentication pages, search pages): read its source code under `src/` as a reference, then create equivalent pages/components in the current app following the app's existing patterns and conventions.
|
|
51
|
+
|
|
52
|
+
Place new routes inside the existing app's router (e.g. `routes.tsx`). Do not replace the app shell; add the feature as new routes or sections within it.
|
|
53
|
+
|
|
54
|
+
## 6. Verify
|
|
55
|
+
|
|
56
|
+
- Confirm the app builds without errors.
|
|
57
|
+
- Confirm any new routes are accessible and render correctly.
|
|
58
|
+
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.14.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.13.0...v1.14.0) (2026-02-06)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1.13.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.12.0...v1.13.0) (2026-02-06)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* adding a sample feature package for reference ([#77](https://github.com/salesforce-experience-platform-emu/webapps/issues/77)) ([94b4d8e](https://github.com/salesforce-experience-platform-emu/webapps/commit/94b4d8e5e89c10888868b8b014c1032ff4b6177d))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
# [1.12.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.11.2...v1.12.0) (2026-02-06)
|
|
7
26
|
|
|
8
27
|
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
@@ -53,4 +53,16 @@ public without sharing class WebAppAuthUtils {
|
|
|
53
53
|
|
|
54
54
|
return defaultUrl + url;
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Logs an error message and stack trace to the system debug log.
|
|
59
|
+
* It is only captured if a Trace Flag is active for the user.
|
|
60
|
+
*
|
|
61
|
+
* @param ex The exception to log.
|
|
62
|
+
* @param level The logging level to use.
|
|
63
|
+
*/
|
|
64
|
+
public static void debugLog(Exception ex, LoggingLevel level) {
|
|
65
|
+
System.debug(level, 'Message: ' + ex.getMessage());
|
|
66
|
+
System.debug(level, 'Stack Trace: ' + ex.getStackTraceString());
|
|
67
|
+
}
|
|
56
68
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Applications REST endpoint for authenticated user password changes.
|
|
3
|
+
*
|
|
4
|
+
* Endpoint: POST /services/apexrest/auth/change-password
|
|
5
|
+
*
|
|
6
|
+
* Allows authenticated users to change their password by providing their current password
|
|
7
|
+
* and a new password. The new password is validated against org password policies.
|
|
8
|
+
*
|
|
9
|
+
* Security: Uses 'with sharing' to enforce user-level security. Only authenticated users
|
|
10
|
+
* can change their own password.
|
|
11
|
+
*/
|
|
12
|
+
@RestResource(urlMapping='/auth/change-password')
|
|
13
|
+
global with sharing class WebAppChangePassword {
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Changes the password for the currently authenticated user.
|
|
17
|
+
*
|
|
18
|
+
* @param newPassword The new password to set.
|
|
19
|
+
* @param currentPassword The user's current password for verification.
|
|
20
|
+
* @return SuccessPasswordChangeResponse on success, ErrorPasswordChangeResponse on failure.
|
|
21
|
+
*/
|
|
22
|
+
@HttpPost
|
|
23
|
+
global static PasswordChangeResponse changePassword(String newPassword, String currentPassword) {
|
|
24
|
+
Savepoint sp = Database.setSavepoint();
|
|
25
|
+
try {
|
|
26
|
+
// newPassword and confirmPassword should be validated to be the same value on the client
|
|
27
|
+
Site.changePassword(newPassword, newPassword, currentPassword);
|
|
28
|
+
return new SuccessPasswordChangeResponse();
|
|
29
|
+
} catch (Exception ex) {
|
|
30
|
+
Database.rollback(sp);
|
|
31
|
+
|
|
32
|
+
// System.SecurityException is a user error but treat others as system errors
|
|
33
|
+
if (ex instanceof System.SecurityException) {
|
|
34
|
+
RestContext.response.statusCode = 400;
|
|
35
|
+
return new ErrorPasswordChangeResponse(ex.getMessage());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Logs are only captured if a Trace Flag is active for the user
|
|
39
|
+
WebAppAuthUtils.debugLog(ex, LoggingLevel.ERROR);
|
|
40
|
+
|
|
41
|
+
RestContext.response.statusCode = 500;
|
|
42
|
+
return new ErrorPasswordChangeResponse('Password change failed');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Base response class for password change operations. */
|
|
47
|
+
global abstract class PasswordChangeResponse {
|
|
48
|
+
|
|
49
|
+
/** Success or failure of the password change operation */
|
|
50
|
+
private Boolean success;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Success response for password change. */
|
|
54
|
+
global class SuccessPasswordChangeResponse extends PasswordChangeResponse {
|
|
55
|
+
|
|
56
|
+
/** Constructs a success response. */
|
|
57
|
+
private SuccessPasswordChangeResponse() {
|
|
58
|
+
this.success = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Error response for password change. */
|
|
63
|
+
global class ErrorPasswordChangeResponse extends PasswordChangeResponse {
|
|
64
|
+
|
|
65
|
+
/** Error message describing the failure reason */
|
|
66
|
+
private String error;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Constructs an error response with the specified error message.
|
|
70
|
+
* @param error The error message to return to the client.
|
|
71
|
+
*/
|
|
72
|
+
private ErrorPasswordChangeResponse(String error) {
|
|
73
|
+
this.success = false;
|
|
74
|
+
this.error = error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -18,6 +18,7 @@ interface AuthFormProps extends Omit<React.ComponentProps<"form">, "onSubmit"> {
|
|
|
18
18
|
submit: {
|
|
19
19
|
text: string;
|
|
20
20
|
loadingText?: string;
|
|
21
|
+
disabled?: boolean;
|
|
21
22
|
};
|
|
22
23
|
footer?: {
|
|
23
24
|
text?: string;
|
|
@@ -68,6 +69,7 @@ export function AuthForm({
|
|
|
68
69
|
form={id}
|
|
69
70
|
label={submit.text}
|
|
70
71
|
loadingLabel={submit.loadingText}
|
|
72
|
+
disabled={submit.disabled}
|
|
71
73
|
className="mt-6"
|
|
72
74
|
/>
|
|
73
75
|
</form>
|
|
@@ -3,7 +3,7 @@ import { Spinner } from "../ui/spinner";
|
|
|
3
3
|
import { cn } from "../../lib/utils";
|
|
4
4
|
import { useFormContext } from "../../hooks/form";
|
|
5
5
|
|
|
6
|
-
interface SubmitButtonProps extends Omit<React.ComponentProps<typeof Button>, "type"
|
|
6
|
+
interface SubmitButtonProps extends Omit<React.ComponentProps<typeof Button>, "type"> {
|
|
7
7
|
/** Button text when not submitting */
|
|
8
8
|
label: string;
|
|
9
9
|
/** Button text while submitting */
|
|
@@ -18,15 +18,15 @@ const isSubmittingSelector = (state: { isSubmitting: boolean }) => state.isSubmi
|
|
|
18
18
|
* Submit button that subscribes to form submission state.
|
|
19
19
|
* UX Best Practice:
|
|
20
20
|
* 1. Disables interaction immediately upon click (via isLoading) to prevent
|
|
21
|
-
* accidental double-submissions
|
|
22
|
-
* 2. Provides immediate visual feedback (Spinner)
|
|
23
|
-
* generating sessions) can have variable latency.
|
|
21
|
+
* accidental double-submissions.
|
|
22
|
+
* 2. Provides immediate visual feedback (Spinner).
|
|
24
23
|
*/
|
|
25
24
|
export function SubmitButton({
|
|
26
25
|
label,
|
|
27
26
|
loadingLabel = "Submitting…",
|
|
28
27
|
className,
|
|
29
28
|
form: formId,
|
|
29
|
+
disabled,
|
|
30
30
|
...props
|
|
31
31
|
}: SubmitButtonProps) {
|
|
32
32
|
const form = useFormContext();
|
|
@@ -37,7 +37,7 @@ export function SubmitButton({
|
|
|
37
37
|
type="submit"
|
|
38
38
|
form={formId}
|
|
39
39
|
className={cn("w-full", className)}
|
|
40
|
-
disabled={isSubmitting}
|
|
40
|
+
disabled={isSubmitting || disabled}
|
|
41
41
|
{...props}
|
|
42
42
|
>
|
|
43
43
|
{isSubmitting && <Spinner className="mr-2" aria-hidden="true" />}
|
|
@@ -7,7 +7,6 @@ import { AuthForm } from "../components/forms/auth-form";
|
|
|
7
7
|
import { useAppForm } from "../hooks/form";
|
|
8
8
|
import { ROUTES, AUTH_PLACEHOLDERS } from "../utils/authenticationConfig";
|
|
9
9
|
import { newPasswordSchema, handleApiResponse, getErrorMessage } from "../utils/helpers";
|
|
10
|
-
import { getUser } from "../context/AuthContext";
|
|
11
10
|
|
|
12
11
|
const changePasswordSchema = z
|
|
13
12
|
.object({
|
|
@@ -18,25 +17,19 @@ const changePasswordSchema = z
|
|
|
18
17
|
export default function ChangePassword() {
|
|
19
18
|
const [success, setSuccess] = useState(false);
|
|
20
19
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
21
|
-
const user = getUser();
|
|
22
20
|
|
|
23
21
|
const form = useAppForm({
|
|
24
22
|
defaultValues: { currentPassword: "", newPassword: "", confirmPassword: "" },
|
|
25
23
|
validators: { onChange: changePasswordSchema, onSubmit: changePasswordSchema },
|
|
26
|
-
onSubmit: async ({ value }) => {
|
|
24
|
+
onSubmit: async ({ value: formFieldValues }) => {
|
|
27
25
|
setSubmitError(null);
|
|
28
26
|
setSuccess(false);
|
|
29
27
|
try {
|
|
30
28
|
// [Dev Note] Custom Apex Endpoint: /auth/change-password
|
|
31
|
-
// Security Critical:
|
|
32
|
-
// We must pass the `user.id` (from AuthContext) to the Apex endpoint
|
|
33
|
-
// to ensure the password change is applied to the correct user.
|
|
34
|
-
// The Apex backend should also verify that the authenticated session matches this ID.
|
|
35
29
|
// You must ensure this Apex class exists in your org
|
|
36
|
-
const response = await baseClient.post("/services/apexrest/auth/change-password", {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
newPassword: value.newPassword,
|
|
30
|
+
const response = await baseClient.post("/sfdcapi/services/apexrest/auth/change-password", {
|
|
31
|
+
currentPassword: formFieldValues.currentPassword,
|
|
32
|
+
newPassword: formFieldValues.newPassword,
|
|
40
33
|
});
|
|
41
34
|
await handleApiResponse(response, "Password change failed");
|
|
42
35
|
setSuccess(true);
|
|
@@ -65,7 +58,7 @@ export default function ChangePassword() {
|
|
|
65
58
|
</>
|
|
66
59
|
)
|
|
67
60
|
}
|
|
68
|
-
submit={{ text: "Change Password", loadingText: "Changing…" }}
|
|
61
|
+
submit={{ text: "Change Password", loadingText: "Changing…", disabled: success }}
|
|
69
62
|
footer={{ link: ROUTES.PROFILE.PATH, linkText: "Back to Profile" }}
|
|
70
63
|
>
|
|
71
64
|
<form.AppField name="currentPassword">
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/webapp-template-feature-react-authentication-experimental",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Authentication feature for web applications",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"author": "",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"watch": "npx tsx ../../cli/src/index.ts watch-patches packages/template/feature/feature-react-authentication packages/template/base-app/base-react-app packages/template/feature/feature-react-authentication/dist"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@salesforce/webapp-experimental": "^1.
|
|
22
|
+
"@salesforce/webapp-experimental": "^1.14.0",
|
|
23
23
|
"@tanstack/react-form": "^1.27.7",
|
|
24
24
|
"@types/react": "^19.2.7",
|
|
25
25
|
"@types/react-dom": "^19.2.3",
|
|
@@ -27,5 +27,5 @@
|
|
|
27
27
|
"react-router": "^7.10.1",
|
|
28
28
|
"vite": "^7.3.1"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "b7dd7bcfba3476266327ccfbe859927846abd506"
|
|
31
31
|
}
|