@salesforce/ui-bundle-template-feature-react-authentication 1.117.2
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/LICENSE.txt +82 -0
- package/README.md +77 -0
- package/dist/.forceignore +15 -0
- package/dist/.husky/pre-commit +4 -0
- package/dist/.prettierignore +11 -0
- package/dist/.prettierrc +17 -0
- package/dist/AGENT.md +193 -0
- package/dist/CHANGELOG.md +2128 -0
- package/dist/README.md +28 -0
- package/dist/config/project-scratch-def.json +13 -0
- package/dist/eslint.config.js +7 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls +68 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls +77 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls +71 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls +105 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls +162 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls-meta.xml +5 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/.forceignore +15 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/.graphqlrc.yml +2 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/.prettierignore +9 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/.prettierrc +11 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/CHANGELOG.md +10 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/README.md +75 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/codegen.yml +95 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/components.json +18 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/e2e/app.spec.ts +17 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/eslint.config.js +169 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/feature-react-authentication.uibundle-meta.xml +7 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/index.html +12 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/package.json +70 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/playwright.config.ts +24 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/scripts/get-graphql-schema.mjs +68 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/scripts/rewrite-e2e-assets.mjs +23 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/api/graphqlClient.ts +25 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/app.tsx +16 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/appLayout.tsx +83 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/icons/book.svg +3 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/icons/copy.svg +4 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/icons/rocket.svg +3 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/icons/star.svg +3 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/images/codey-1.png +0 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/images/codey-2.png +0 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/images/codey-3.png +0 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/assets/images/vibe-codey.svg +194 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/alerts/status-alert.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/layouts/card-layout.tsx +29 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/alert.tsx +76 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/badge.tsx +48 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/breadcrumb.tsx +109 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/button.tsx +67 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/calendar.tsx +232 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/card.tsx +103 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/checkbox.tsx +32 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/collapsible.tsx +33 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/datePicker.tsx +127 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/dialog.tsx +162 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/field.tsx +237 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/index.ts +84 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/input.tsx +19 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/label.tsx +22 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/pagination.tsx +132 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/popover.tsx +89 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/select.tsx +193 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/separator.tsx +26 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/skeleton.tsx +14 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/sonner.tsx +20 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/spinner.tsx +16 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/table.tsx +114 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/components/ui/tabs.tsx +88 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/api/userProfileApi.ts +95 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/authHelpers.ts +73 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/authenticationConfig.ts +61 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/context/AuthContext.tsx +95 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/footers/footer-link.tsx +36 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/forms/auth-form.tsx +81 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/forms/submit-button.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/hooks/form.tsx +120 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/hooks/useCountdownTimer.ts +266 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/hooks/useRetryWithBackoff.ts +109 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/layout/card-skeleton.tsx +38 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/layout/centered-page-layout.tsx +87 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/layouts/AuthAppLayout.tsx +12 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/layouts/authenticationRouteLayout.tsx +21 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/layouts/privateRouteLayout.tsx +44 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/pages/ChangePassword.tsx +107 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/pages/ForgotPassword.tsx +73 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/pages/Login.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/pages/Profile.tsx +161 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/pages/Register.tsx +133 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/pages/ResetPassword.tsx +107 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +602 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/sessionTimeout/sessionTimeService.ts +149 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts +77 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/features/authentication/utils/helpers.ts +121 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/lib/utils.ts +6 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/navigationMenu.tsx +80 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/pages/Home.tsx +12 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/pages/NotFound.tsx +18 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/router-utils.tsx +35 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/routes.tsx +71 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/styles/global.css +135 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/tsconfig.json +42 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/tsconfig.node.json +13 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/ui-bundle.json +7 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/vite-env.d.ts +1 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/vite.config.ts +106 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/vitest-env.d.ts +2 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/vitest.config.ts +11 -0
- package/dist/force-app/main/default/uiBundles/feature-react-authentication/vitest.setup.ts +1 -0
- package/dist/jest.config.js +6 -0
- package/dist/package-lock.json +9995 -0
- package/dist/package.json +40 -0
- package/dist/scripts/apex/hello.apex +10 -0
- package/dist/scripts/graphql-search.sh +191 -0
- package/dist/scripts/prepare-import-unique-fields.js +122 -0
- package/dist/scripts/setup-cli.mjs +563 -0
- package/dist/scripts/sf-project-setup.mjs +66 -0
- package/dist/scripts/soql/account.soql +6 -0
- package/dist/sfdx-project.json +12 -0
- package/package.json +44 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Base React App
|
|
2
|
+
|
|
3
|
+
Base React App is a template application that demonstrates how to build a React UI Bundle on the Salesforce platform with Vite, TypeScript, Tailwind, shadcn/ui, and the Salesforce UI Bundle SDK. It provides a minimal shell (home, 404), routing, and GraphQL codegen support so feature apps can extend it via the patches pipeline.
|
|
4
|
+
|
|
5
|
+
This UI Bundle lives inside an SFDX project. The project root is the directory that contains `force-app/` and `sfdx-project.json`. Run the commands in the sections below from the paths indicated.
|
|
6
|
+
|
|
7
|
+
## Table of contents
|
|
8
|
+
|
|
9
|
+
- [Run (development)](#run-development)
|
|
10
|
+
- [Build](#build)
|
|
11
|
+
- [Deploy](#deploy)
|
|
12
|
+
- [Test](#test)
|
|
13
|
+
|
|
14
|
+
## Run (development)
|
|
15
|
+
|
|
16
|
+
From the UI Bundle directory (`force-app/main/default/uiBundles/base-react-app`):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This starts the Vite dev server (e.g. http://localhost:5173). Use `npm run dev:design` to run in design mode.
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
|
|
27
|
+
From the UI Bundle directory:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The production build is written to `dist/` inside the UI Bundle folder. Deploy using the steps in [Deploy](#deploy).
|
|
35
|
+
|
|
36
|
+
## Deploy
|
|
37
|
+
|
|
38
|
+
From the **SFDX project root** (the directory that contains `force-app/`):
|
|
39
|
+
|
|
40
|
+
1. Build the UI Bundle:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd force-app/main/default/uiBundles/base-react-app && npm install && npm run build && cd -
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
2. Deploy the UI Bundle only:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
sf project deploy start --source-dir force-app/main/default/ui-bundles --target-org <alias>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or deploy all metadata:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sf project deploy start --source-dir force-app --target-org <alias>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Replace `<alias>` with your target org alias.
|
|
59
|
+
|
|
60
|
+
## Test
|
|
61
|
+
|
|
62
|
+
From the UI Bundle directory:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install
|
|
66
|
+
npm run test
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This runs the unit test suite (Vitest). For end-to-end tests from the **base-react-app package root**:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm run test:e2e
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This installs dependencies, builds with E2E asset rewrites, and runs Playwright. Ensure Chromium is installed (`npx playwright install chromium` if needed).
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
schema: '../../../../../schema.graphql'
|
|
2
|
+
documents: 'src/**/*.{graphql,ts,tsx}'
|
|
3
|
+
generates:
|
|
4
|
+
src/api/graphql-operations-types.ts:
|
|
5
|
+
plugins:
|
|
6
|
+
- 'graphql-codegen-typescript-operation-types'
|
|
7
|
+
- 'typescript-operations'
|
|
8
|
+
config:
|
|
9
|
+
onlyOperationTypes: true
|
|
10
|
+
skipTypename: true
|
|
11
|
+
preResolveTypes: true
|
|
12
|
+
scalars:
|
|
13
|
+
# String-serialized scalars
|
|
14
|
+
JSON:
|
|
15
|
+
input: 'string'
|
|
16
|
+
output: 'string'
|
|
17
|
+
Date:
|
|
18
|
+
input: 'string'
|
|
19
|
+
output: 'string'
|
|
20
|
+
DateTime:
|
|
21
|
+
input: 'string'
|
|
22
|
+
output: 'string'
|
|
23
|
+
Time:
|
|
24
|
+
input: 'string'
|
|
25
|
+
output: 'string'
|
|
26
|
+
Email:
|
|
27
|
+
input: 'string'
|
|
28
|
+
output: 'string'
|
|
29
|
+
Url:
|
|
30
|
+
input: 'string'
|
|
31
|
+
output: 'string'
|
|
32
|
+
PhoneNumber:
|
|
33
|
+
input: 'string'
|
|
34
|
+
output: 'string'
|
|
35
|
+
Picklist:
|
|
36
|
+
input: 'string'
|
|
37
|
+
output: 'string'
|
|
38
|
+
MultiPicklist:
|
|
39
|
+
input: 'string'
|
|
40
|
+
output: 'string'
|
|
41
|
+
TextArea:
|
|
42
|
+
input: 'string'
|
|
43
|
+
output: 'string'
|
|
44
|
+
LongTextArea:
|
|
45
|
+
input: 'string'
|
|
46
|
+
output: 'string'
|
|
47
|
+
RichTextArea:
|
|
48
|
+
input: 'string'
|
|
49
|
+
output: 'string'
|
|
50
|
+
EncryptedString:
|
|
51
|
+
input: 'string'
|
|
52
|
+
output: 'string'
|
|
53
|
+
Base64:
|
|
54
|
+
input: 'string'
|
|
55
|
+
output: 'string'
|
|
56
|
+
IdOrRef:
|
|
57
|
+
input: 'string'
|
|
58
|
+
output: 'string'
|
|
59
|
+
# BigDecimal-serialized scalars (accepts number or string, returns number)
|
|
60
|
+
Currency:
|
|
61
|
+
input: 'number | string'
|
|
62
|
+
output: 'number'
|
|
63
|
+
BigDecimal:
|
|
64
|
+
input: 'number | string'
|
|
65
|
+
output: 'number'
|
|
66
|
+
Double:
|
|
67
|
+
input: 'number | string'
|
|
68
|
+
output: 'number'
|
|
69
|
+
Percent:
|
|
70
|
+
input: 'number | string'
|
|
71
|
+
output: 'number'
|
|
72
|
+
Longitude:
|
|
73
|
+
input: 'number | string'
|
|
74
|
+
output: 'number'
|
|
75
|
+
Latitude:
|
|
76
|
+
input: 'number | string'
|
|
77
|
+
output: 'number'
|
|
78
|
+
# Integer-like scalars
|
|
79
|
+
Long:
|
|
80
|
+
input: 'number'
|
|
81
|
+
output: 'number'
|
|
82
|
+
BigInteger:
|
|
83
|
+
input: 'number'
|
|
84
|
+
output: 'number'
|
|
85
|
+
Short:
|
|
86
|
+
input: 'number'
|
|
87
|
+
output: 'number'
|
|
88
|
+
Byte:
|
|
89
|
+
input: 'number'
|
|
90
|
+
output: 'number'
|
|
91
|
+
Char:
|
|
92
|
+
input: 'number'
|
|
93
|
+
output: 'number'
|
|
94
|
+
overwrite: true
|
|
95
|
+
ignoreNoDocuments: true
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"style": "new-york",
|
|
3
|
+
"rsc": true,
|
|
4
|
+
"tailwind": {
|
|
5
|
+
"config": "",
|
|
6
|
+
"css": "styles/global.css",
|
|
7
|
+
"baseColor": "neutral",
|
|
8
|
+
"cssVariables": true
|
|
9
|
+
},
|
|
10
|
+
"iconLibrary": "lucide",
|
|
11
|
+
"aliases": {
|
|
12
|
+
"components": "@/components",
|
|
13
|
+
"utils": "@/lib/utils",
|
|
14
|
+
"ui": "@/components/ui",
|
|
15
|
+
"lib": "@/lib",
|
|
16
|
+
"hooks": "@/hooks"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('base-react-app', () => {
|
|
4
|
+
test('home page loads and shows welcome content', async ({ page }) => {
|
|
5
|
+
await page.goto('/');
|
|
6
|
+
await expect(page.getByRole('heading', { name: 'Home' })).toBeVisible();
|
|
7
|
+
await expect(
|
|
8
|
+
page.getByText('Welcome to your React application.')
|
|
9
|
+
).toBeVisible();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('not found route shows 404', async ({ page }) => {
|
|
13
|
+
await page.goto('/non-existent-route');
|
|
14
|
+
await expect(page.getByRole('heading', { name: '404' })).toBeVisible();
|
|
15
|
+
await expect(page.getByText('Page not found')).toBeVisible();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import js from '@eslint/js';
|
|
6
|
+
import tseslint from '@typescript-eslint/eslint-plugin';
|
|
7
|
+
import tsparser from '@typescript-eslint/parser';
|
|
8
|
+
import react from 'eslint-plugin-react';
|
|
9
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
10
|
+
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
11
|
+
import globals from 'globals';
|
|
12
|
+
import graphqlPlugin from '@graphql-eslint/eslint-plugin';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const schemaPath = resolve(__dirname, '../../../../../schema.graphql');
|
|
16
|
+
const schemaExists = existsSync(schemaPath);
|
|
17
|
+
|
|
18
|
+
const config = [
|
|
19
|
+
// Global ignores
|
|
20
|
+
{
|
|
21
|
+
ignores: [
|
|
22
|
+
'build/**/*',
|
|
23
|
+
'dist/**/*',
|
|
24
|
+
'coverage/**/*',
|
|
25
|
+
'src/api/graphql-operations-types.ts',
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
// Config files and build tools (first to avoid inheritance)
|
|
29
|
+
{
|
|
30
|
+
files: ['*.config.{js,ts}', 'vite.config.ts'],
|
|
31
|
+
languageOptions: {
|
|
32
|
+
parser: tsparser,
|
|
33
|
+
parserOptions: {
|
|
34
|
+
ecmaVersion: 'latest',
|
|
35
|
+
sourceType: 'module',
|
|
36
|
+
},
|
|
37
|
+
globals: {
|
|
38
|
+
...globals.node,
|
|
39
|
+
__dirname: 'readonly',
|
|
40
|
+
process: 'readonly',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
plugins: {
|
|
44
|
+
'@typescript-eslint': tseslint,
|
|
45
|
+
},
|
|
46
|
+
rules: {
|
|
47
|
+
'@typescript-eslint/no-var-requires': 'off',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
// Main TypeScript/React files
|
|
51
|
+
{
|
|
52
|
+
files: ['**/*.{ts,tsx}'],
|
|
53
|
+
ignores: [
|
|
54
|
+
'coverage',
|
|
55
|
+
'dist',
|
|
56
|
+
'node_modules',
|
|
57
|
+
'build',
|
|
58
|
+
'*.config.{js,ts}',
|
|
59
|
+
'vite.config.ts',
|
|
60
|
+
],
|
|
61
|
+
languageOptions: {
|
|
62
|
+
ecmaVersion: 2020,
|
|
63
|
+
sourceType: 'module',
|
|
64
|
+
parser: tsparser,
|
|
65
|
+
parserOptions: {
|
|
66
|
+
ecmaFeatures: {
|
|
67
|
+
jsx: true,
|
|
68
|
+
},
|
|
69
|
+
ecmaVersion: 'latest',
|
|
70
|
+
sourceType: 'module',
|
|
71
|
+
project: './tsconfig.json',
|
|
72
|
+
},
|
|
73
|
+
globals: {
|
|
74
|
+
...globals.browser,
|
|
75
|
+
JSX: 'readonly',
|
|
76
|
+
React: 'readonly',
|
|
77
|
+
process: 'readonly',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
plugins: {
|
|
81
|
+
react,
|
|
82
|
+
'react-hooks': reactHooks,
|
|
83
|
+
'react-refresh': reactRefresh,
|
|
84
|
+
'@typescript-eslint': tseslint,
|
|
85
|
+
},
|
|
86
|
+
rules: {
|
|
87
|
+
...js.configs.recommended.rules,
|
|
88
|
+
...tseslint.configs.recommended.rules,
|
|
89
|
+
...react.configs.recommended.rules,
|
|
90
|
+
...reactHooks.configs.recommended.rules,
|
|
91
|
+
'react/react-in-jsx-scope': 'off',
|
|
92
|
+
'react/prop-types': 'off',
|
|
93
|
+
'react/jsx-no-comment-textnodes': 'off',
|
|
94
|
+
'react/no-unescaped-entities': 'off',
|
|
95
|
+
'@typescript-eslint/no-unused-vars': [
|
|
96
|
+
'error',
|
|
97
|
+
{
|
|
98
|
+
argsIgnorePattern: '^_',
|
|
99
|
+
varsIgnorePattern: '^_',
|
|
100
|
+
caughtErrorsIgnorePattern: '^_',
|
|
101
|
+
ignoreRestSiblings: true,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
105
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
106
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
107
|
+
'react-hooks/set-state-in-effect': 'warn',
|
|
108
|
+
},
|
|
109
|
+
settings: {
|
|
110
|
+
react: {
|
|
111
|
+
version: 'detect',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
// Test files
|
|
116
|
+
{
|
|
117
|
+
files: [
|
|
118
|
+
'**/*.test.{ts,tsx}',
|
|
119
|
+
'**/test/**/*.{ts,tsx}',
|
|
120
|
+
'src/test/**/*.{ts,tsx}',
|
|
121
|
+
],
|
|
122
|
+
languageOptions: {
|
|
123
|
+
parser: tsparser,
|
|
124
|
+
globals: {
|
|
125
|
+
...globals.browser,
|
|
126
|
+
...globals.node,
|
|
127
|
+
global: 'writable',
|
|
128
|
+
JSX: 'readonly',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
rules: {
|
|
132
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
// Only add GraphQL rules when schema exists (e.g. after graphql:schema).
|
|
138
|
+
// In CI or when schema is not checked in, skip so lint succeeds.
|
|
139
|
+
if (schemaExists) {
|
|
140
|
+
config.push(
|
|
141
|
+
{
|
|
142
|
+
files: ['**/*.{ts,tsx}'],
|
|
143
|
+
processor: graphqlPlugin.processor,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
files: ['**/*.graphql'],
|
|
147
|
+
languageOptions: {
|
|
148
|
+
parser: graphqlPlugin.parser,
|
|
149
|
+
parserOptions: {
|
|
150
|
+
graphQLConfig: {
|
|
151
|
+
schema: '../../../../../schema.graphql',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
plugins: {
|
|
156
|
+
'@graphql-eslint': graphqlPlugin,
|
|
157
|
+
},
|
|
158
|
+
rules: {
|
|
159
|
+
'@graphql-eslint/no-anonymous-operations': 'error',
|
|
160
|
+
'@graphql-eslint/no-duplicate-fields': 'error',
|
|
161
|
+
'@graphql-eslint/known-fragment-names': 'error',
|
|
162
|
+
'@graphql-eslint/no-undefined-variables': 'error',
|
|
163
|
+
'@graphql-eslint/no-unused-variables': 'error',
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default config;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<UIBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<masterLabel>feature-react-authentication</masterLabel>
|
|
4
|
+
<description>A Salesforce UI Bundle.</description>
|
|
5
|
+
<isActive>true</isActive>
|
|
6
|
+
<version>1</version>
|
|
7
|
+
</UIBundle>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Welcome to React App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="src/app.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "base-react-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.59.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"dev:design": "vite --mode design",
|
|
9
|
+
"build": "tsc -b && vite build",
|
|
10
|
+
"build:e2e": "npm run build && node scripts/rewrite-e2e-assets.mjs",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"graphql:codegen": "graphql-codegen",
|
|
15
|
+
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@salesforce/sdk-data": "file:../../../../../../../../../sdk/sdk-data",
|
|
19
|
+
"@salesforce/ui-bundle": "file:../../../../../../../../../ui-bundle",
|
|
20
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
21
|
+
"class-variance-authority": "^0.7.1",
|
|
22
|
+
"clsx": "^2.1.1",
|
|
23
|
+
"date-fns": "^4.1.0",
|
|
24
|
+
"lucide-react": "^0.562.0",
|
|
25
|
+
"radix-ui": "^1.4.3",
|
|
26
|
+
"react": "^19.2.0",
|
|
27
|
+
"react-day-picker": "^9.14.0",
|
|
28
|
+
"react-dom": "^19.2.0",
|
|
29
|
+
"react-router": "^7.10.1",
|
|
30
|
+
"shadcn": "^3.8.5",
|
|
31
|
+
"sonner": "^1.7.0",
|
|
32
|
+
"tailwind-merge": "^3.5.0",
|
|
33
|
+
"tailwindcss": "^4.1.17",
|
|
34
|
+
"tw-animate-css": "^1.4.0",
|
|
35
|
+
"@tanstack/react-form": "^1.27.7",
|
|
36
|
+
"zod": "^4.3.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^9.39.1",
|
|
40
|
+
"@graphql-codegen/cli": "^6.1.0",
|
|
41
|
+
"@graphql-codegen/typescript": "^5.0.6",
|
|
42
|
+
"@graphql-codegen/typescript-operations": "^5.0.6",
|
|
43
|
+
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
44
|
+
"@graphql-tools/utils": "^11.0.0",
|
|
45
|
+
"@playwright/test": "^1.49.0",
|
|
46
|
+
"@salesforce/vite-plugin-ui-bundle": "file:../../../../../../../../../vite-plugin-ui-bundle",
|
|
47
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
48
|
+
"@testing-library/react": "^16.1.0",
|
|
49
|
+
"@testing-library/user-event": "^14.5.2",
|
|
50
|
+
"@types/node": "^24.10.1",
|
|
51
|
+
"@types/react": "^19.2.5",
|
|
52
|
+
"@types/react-dom": "^19.2.3",
|
|
53
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
54
|
+
"@vitest/ui": "^4.0.17",
|
|
55
|
+
"eslint": "^9.39.1",
|
|
56
|
+
"eslint-plugin-react": "^7.37.2",
|
|
57
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
58
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
59
|
+
"globals": "^16.5.0",
|
|
60
|
+
"graphql": "^16.11.0",
|
|
61
|
+
"graphql-codegen-typescript-operation-types": "^2.0.2",
|
|
62
|
+
"jsdom": "^25.0.1",
|
|
63
|
+
"serve": "^14.2.5",
|
|
64
|
+
"typescript": "~5.9.3",
|
|
65
|
+
"typescript-eslint": "^8.46.4",
|
|
66
|
+
"vite": "^7.2.4",
|
|
67
|
+
"vite-plugin-graphql-codegen": "^3.6.3",
|
|
68
|
+
"vitest": "^4.0.17"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/force-app/main/default/uiBundles/feature-react-authentication/playwright.config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const E2E_PORT = 5175;
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
testDir: './e2e',
|
|
7
|
+
fullyParallel: true,
|
|
8
|
+
forbidOnly: !!process.env.CI,
|
|
9
|
+
retries: process.env.CI ? 2 : 0,
|
|
10
|
+
workers: process.env.CI ? 1 : undefined,
|
|
11
|
+
reporter: 'html',
|
|
12
|
+
use: {
|
|
13
|
+
baseURL: `http://localhost:${E2E_PORT}`,
|
|
14
|
+
trace: 'on-first-retry',
|
|
15
|
+
},
|
|
16
|
+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
|
17
|
+
webServer: {
|
|
18
|
+
// Serve built dist/ with static server so e2e works in CI without SF org (vite preview runs plugin and can fail)
|
|
19
|
+
command: `npx serve dist -l ${E2E_PORT}`,
|
|
20
|
+
url: `http://localhost:${E2E_PORT}`,
|
|
21
|
+
reuseExistingServer: !process.env.CI,
|
|
22
|
+
timeout: process.env.CI ? 120_000 : 60_000,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Downloads the full GraphQL schema from a connected Salesforce org via introspection.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npm run graphql:schema
|
|
6
|
+
* node scripts/get-graphql-schema.mjs [output-path]
|
|
7
|
+
*
|
|
8
|
+
* The default output path matches the schema location expected by codegen.yml
|
|
9
|
+
* and .graphqlrc.yml so that codegen and IDE tooling resolve it automatically.
|
|
10
|
+
*/
|
|
11
|
+
import { writeFileSync } from 'node:fs';
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
import { getOrgInfo } from '@salesforce/ui-bundle/app';
|
|
14
|
+
import { buildClientSchema, getIntrospectionQuery, printSchema } from 'graphql';
|
|
15
|
+
import { pruneSchema } from '@graphql-tools/utils';
|
|
16
|
+
|
|
17
|
+
const DEFAULT_SCHEMA_PATH = '../../../../../schema.graphql';
|
|
18
|
+
|
|
19
|
+
async function executeSalesforceGraphQLQuery(query, variables, operationName) {
|
|
20
|
+
const {
|
|
21
|
+
rawInstanceUrl: instanceUrl,
|
|
22
|
+
apiVersion,
|
|
23
|
+
accessToken,
|
|
24
|
+
} = await getOrgInfo();
|
|
25
|
+
|
|
26
|
+
const targetUrl = `${instanceUrl}/services/data/v${apiVersion}/graphql`;
|
|
27
|
+
|
|
28
|
+
console.log(`Executing introspection query against ${targetUrl}`);
|
|
29
|
+
const response = await fetch(targetUrl, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${accessToken}`,
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'X-Chatter-Entity-Encoding': 'false',
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({ query, variables, operationName }),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const errorText = await response.text();
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Salesforce GraphQL request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return response.json();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const outputPath = resolve(process.argv[2] || DEFAULT_SCHEMA_PATH);
|
|
51
|
+
|
|
52
|
+
const introspectionResult = await executeSalesforceGraphQLQuery(
|
|
53
|
+
getIntrospectionQuery(),
|
|
54
|
+
{},
|
|
55
|
+
'IntrospectionQuery'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const schema = buildClientSchema(introspectionResult.data);
|
|
59
|
+
const prunedSchema = pruneSchema(schema);
|
|
60
|
+
const sdl = printSchema(prunedSchema);
|
|
61
|
+
|
|
62
|
+
writeFileSync(outputPath, sdl);
|
|
63
|
+
|
|
64
|
+
console.log(`Schema saved to ${outputPath}`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error:', error.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepares dist/ for e2e: root-relative asset paths + SPA fallback for serve.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const distDir = join(__dirname, '..', 'dist');
|
|
10
|
+
|
|
11
|
+
// Rewrite index.html so asset paths are root-relative (/assets/...)
|
|
12
|
+
const indexPath = join(distDir, 'index.html');
|
|
13
|
+
let html = readFileSync(indexPath, 'utf8');
|
|
14
|
+
html = html.replace(/(src|href)="[^"]*\/assets\//g, '$1="/assets/');
|
|
15
|
+
writeFileSync(indexPath, html);
|
|
16
|
+
|
|
17
|
+
// SPA fallback so /about, /non-existent-route etc. serve index.html
|
|
18
|
+
writeFileSync(
|
|
19
|
+
join(distDir, 'serve.json'),
|
|
20
|
+
JSON.stringify({
|
|
21
|
+
rewrites: [{ source: '**', destination: '/index.html' }],
|
|
22
|
+
})
|
|
23
|
+
);
|
package/dist/force-app/main/default/uiBundles/feature-react-authentication/src/api/graphqlClient.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin GraphQL client: createDataSDK + data.graphql with centralized error handling.
|
|
3
|
+
* Use with gql-tagged queries and generated operation types for type-safe calls.
|
|
4
|
+
*/
|
|
5
|
+
import { createDataSDK } from '@salesforce/sdk-data';
|
|
6
|
+
|
|
7
|
+
export async function executeGraphQL<TData, TVariables>(
|
|
8
|
+
query: string,
|
|
9
|
+
variables?: TVariables
|
|
10
|
+
): Promise<TData> {
|
|
11
|
+
const data = await createDataSDK();
|
|
12
|
+
// SDK types graphql() first param as string; at runtime it may accept gql DocumentNode too
|
|
13
|
+
const response = await data.graphql?.<TData, TVariables>(query, variables);
|
|
14
|
+
|
|
15
|
+
if (!response) {
|
|
16
|
+
throw new Error('GraphQL response is undefined');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (response?.errors?.length) {
|
|
20
|
+
const msg = response.errors.map(e => e.message).join('; ');
|
|
21
|
+
throw new Error(`GraphQL Error: ${msg}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createBrowserRouter, RouterProvider } from "react-router";
|
|
2
|
+
import { routes } from "./routes";
|
|
3
|
+
import { StrictMode } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import "./styles/global.css";
|
|
6
|
+
|
|
7
|
+
// Normalize basename: strip trailing slash so it matches URLs like /lwr/application/ai/c-app
|
|
8
|
+
const rawBasePath = (globalThis as any).SFDC_ENV?.basePath;
|
|
9
|
+
const basename = typeof rawBasePath === "string" ? rawBasePath.replace(/\/+$/, "") : undefined;
|
|
10
|
+
const router = createBrowserRouter(routes, { basename });
|
|
11
|
+
|
|
12
|
+
createRoot(document.getElementById("root")!).render(
|
|
13
|
+
<StrictMode>
|
|
14
|
+
<RouterProvider router={router} />
|
|
15
|
+
</StrictMode>,
|
|
16
|
+
);
|