@salesforce/webapp-template-app-react-template-b2x-experimental 1.81.0 → 1.83.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/webapp-csp-trusted-sites/SKILL.md +90 -0
- package/dist/.a4drules/skills/webapp-csp-trusted-sites/implementation/metadata-format.md +281 -0
- package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/SKILL.md +1 -1
- package/dist/.a4drules/skills/webapp-react-data-visualization/SKILL.md +72 -0
- package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/dashboard-layout.md +189 -0
- package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/donut-chart.md +181 -0
- package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/stat-card.md +150 -0
- package/dist/.a4drules/skills/webapp-react-interactive-map/SKILL.md +92 -0
- package/dist/.a4drules/skills/webapp-react-interactive-map/implementation/geocoding.md +245 -0
- package/dist/.a4drules/skills/webapp-react-interactive-map/implementation/leaflet-map.md +279 -0
- package/dist/.a4drules/skills/webapp-react-weather-widget/SKILL.md +65 -0
- package/dist/.a4drules/skills/webapp-react-weather-widget/implementation/weather-hook.md +258 -0
- package/dist/.a4drules/skills/webapp-react-weather-widget/implementation/weather-ui.md +216 -0
- package/dist/.a4drules/skills/webapp-ui-ux/SKILL.md +268 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/charts.csv +26 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/colors.csv +97 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/icons.csv +101 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/landing.csv +31 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/products.csv +97 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/react-performance.csv +45 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/html-tailwind.csv +56 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/react.csv +54 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/shadcn.csv +61 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/styles.csv +68 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/typography.csv +58 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/ui-reasoning.csv +101 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/ux-guidelines.csv +100 -0
- package/dist/.a4drules/skills/webapp-ui-ux/data/web-interface.csv +31 -0
- package/dist/.a4drules/skills/webapp-ui-ux/scripts/core.js +255 -0
- package/dist/.a4drules/skills/webapp-ui-ux/scripts/design_system.js +861 -0
- package/dist/.a4drules/skills/webapp-ui-ux/scripts/search.js +98 -0
- package/dist/.a4drules/skills/webapp-unsplash-images/SKILL.md +71 -0
- package/dist/.a4drules/skills/webapp-unsplash-images/implementation/usage.md +159 -0
- package/dist/.a4drules/webapp-no-node-e.md +54 -15
- package/dist/.a4drules/webapp-react.md +9 -10
- package/dist/.a4drules/webapp-skills-first.md +26 -0
- package/dist/.a4drules/webapp.md +8 -0
- package/dist/CHANGELOG.md +19 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2x/package.json +3 -3
- package/dist/package.json +1 -1
- package/package.json +1 -1
- package/dist/.a4drules/webapp-images.md +0 -15
- /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/component.md +0 -0
- /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/header-footer.md +0 -0
- /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/page.md +0 -0
- /package/dist/.a4drules/{webapp-code-quality.md → webapp-react-code-quality.md} +0 -0
- /package/dist/.a4drules/{webapp-typescript.md → webapp-react-typescript.md} +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webapp-csp-trusted-sites
|
|
3
|
+
description: Creates Salesforce CSP Trusted Site metadata when adding external domains. Use when the user adds an external API, CDN, image host, font provider, map tile server, or any third-party URL that the web application needs to load resources from — or when a browser console shows a CSP violation error.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CSP Trusted Sites
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill whenever the application references a new external domain that is not already registered as a CSP Trusted Site. This includes:
|
|
11
|
+
|
|
12
|
+
- Adding images from a new CDN (Unsplash, Pexels, Cloudinary, etc.)
|
|
13
|
+
- Loading fonts from an external provider (Google Fonts, Adobe Fonts)
|
|
14
|
+
- Calling a third-party API (Open-Meteo, Nominatim, Mapbox, etc.)
|
|
15
|
+
- Loading map tiles from a tile server (OpenStreetMap, Mapbox)
|
|
16
|
+
- Embedding iframes from external services (YouTube, Vimeo)
|
|
17
|
+
- Loading external stylesheets or scripts
|
|
18
|
+
|
|
19
|
+
Salesforce enforces Content Security Policy (CSP) headers on all web applications. Any external domain not registered as a CSP Trusted Site will be blocked by the browser, causing images to not load, API calls to fail, or fonts to be missing.
|
|
20
|
+
|
|
21
|
+
**Reference:** [Salesforce CspTrustedSite Object Reference](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_csptrustedsite.htm)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Step 1 — Identify external domains
|
|
26
|
+
|
|
27
|
+
Scan the code for any URLs pointing to external domains. Common patterns:
|
|
28
|
+
|
|
29
|
+
- `fetch("https://api.example.com/...")` — API calls
|
|
30
|
+
- `<img src="https://images.example.com/..." />` — images
|
|
31
|
+
- `<link href="https://fonts.example.com/..." />` — stylesheets
|
|
32
|
+
- `url="https://tiles.example.com/{z}/{x}/{y}.png"` — map tiles
|
|
33
|
+
- `@import url("https://cdn.example.com/...")` — CSS imports
|
|
34
|
+
|
|
35
|
+
Extract the **origin** (scheme + host) from each URL. For example:
|
|
36
|
+
- `https://api.open-meteo.com/v1/forecast?lat=...` → `https://api.open-meteo.com`
|
|
37
|
+
- `https://images.unsplash.com/photo-123?w=800` → `https://images.unsplash.com`
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Step 2 — Check existing CSP Trusted Sites
|
|
42
|
+
|
|
43
|
+
Before creating a new file, check if the domain already has a CSP Trusted Site:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ls force-app/main/default/cspTrustedSites/
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the domain is already registered, no action is needed.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Step 3 — Determine the CSP directive(s)
|
|
54
|
+
|
|
55
|
+
Map the resource type to the correct CSP `isApplicableTo*Src` fields. Read `implementation/metadata-format.md` for the full reference.
|
|
56
|
+
|
|
57
|
+
Quick reference:
|
|
58
|
+
|
|
59
|
+
| Resource type | CSP directive field(s) to set `true` |
|
|
60
|
+
|--------------|--------------------------------------|
|
|
61
|
+
| Images (img, background-image) | `isApplicableToImgSrc` |
|
|
62
|
+
| API calls (fetch, XMLHttpRequest) | `isApplicableToConnectSrc` |
|
|
63
|
+
| Fonts (.woff, .woff2, .ttf) | `isApplicableToFontSrc` |
|
|
64
|
+
| Stylesheets (CSS) | `isApplicableToStyleSrc` |
|
|
65
|
+
| Video / audio | `isApplicableToMediaSrc` |
|
|
66
|
+
| Iframes | `isApplicableToFrameSrc` |
|
|
67
|
+
|
|
68
|
+
**Always also set `isApplicableToConnectSrc` to `true`** — most resources also require connect-src for preflight/redirect handling.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Step 4 — Create the metadata file
|
|
73
|
+
|
|
74
|
+
Read `implementation/metadata-format.md` and follow the instructions to create the `.cspTrustedSite-meta.xml` file.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Step 5 — Verify
|
|
79
|
+
|
|
80
|
+
1. Confirm the file is valid XML and matches the expected schema.
|
|
81
|
+
2. Confirm the file is placed in `force-app/main/default/cspTrustedSites/`.
|
|
82
|
+
3. Confirm only the necessary `isApplicableTo*Src` fields are set to `true`.
|
|
83
|
+
4. Run from the web app directory:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **Lint:** MUST result in 0 errors.
|
|
90
|
+
- **Build:** MUST succeed.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# CSP Trusted Site Metadata — Implementation Guide
|
|
2
|
+
|
|
3
|
+
## File location
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
force-app/main/default/cspTrustedSites/{Name}.cspTrustedSite-meta.xml
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The `cspTrustedSites/` directory must be a direct child of `force-app/main/default/`. Create it if it does not exist.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File naming convention
|
|
14
|
+
|
|
15
|
+
The file name must match the `<fullName>` value inside the XML, with `.cspTrustedSite-meta.xml` appended.
|
|
16
|
+
|
|
17
|
+
| Domain | fullName | File name |
|
|
18
|
+
|--------|----------|-----------|
|
|
19
|
+
| `https://images.unsplash.com` | `Unsplash_Images` | `Unsplash_Images.cspTrustedSite-meta.xml` |
|
|
20
|
+
| `https://api.open-meteo.com` | `Open_Meteo_API` | `Open_Meteo_API.cspTrustedSite-meta.xml` |
|
|
21
|
+
| `https://tile.openstreetmap.org` | `OpenStreetMap_Tiles` | `OpenStreetMap_Tiles.cspTrustedSite-meta.xml` |
|
|
22
|
+
|
|
23
|
+
**Naming rules:**
|
|
24
|
+
- Use PascalCase with underscores separating words (e.g. `Google_Fonts_Static`)
|
|
25
|
+
- Name should describe the provider and resource type (e.g. `Pexels_Videos`, not just `Pexels`)
|
|
26
|
+
- Must be unique across the org
|
|
27
|
+
- Maximum 80 characters
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## XML template
|
|
32
|
+
|
|
33
|
+
```xml
|
|
34
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
35
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
36
|
+
<fullName>{UNIQUE_NAME}</fullName>
|
|
37
|
+
<description>{DESCRIPTION}</description>
|
|
38
|
+
<endpointUrl>{HTTPS_ORIGIN}</endpointUrl>
|
|
39
|
+
<isActive>true</isActive>
|
|
40
|
+
<context>All</context>
|
|
41
|
+
<isApplicableToConnectSrc>{true|false}</isApplicableToConnectSrc>
|
|
42
|
+
<isApplicableToFontSrc>{true|false}</isApplicableToFontSrc>
|
|
43
|
+
<isApplicableToFrameSrc>{true|false}</isApplicableToFrameSrc>
|
|
44
|
+
<isApplicableToImgSrc>{true|false}</isApplicableToImgSrc>
|
|
45
|
+
<isApplicableToMediaSrc>{true|false}</isApplicableToMediaSrc>
|
|
46
|
+
<isApplicableToStyleSrc>{true|false}</isApplicableToStyleSrc>
|
|
47
|
+
</CspTrustedSite>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Field reference
|
|
53
|
+
|
|
54
|
+
| Field | Required | Description |
|
|
55
|
+
|-------|----------|-------------|
|
|
56
|
+
| `fullName` | Yes | Unique API name. Must match the file name (before `.cspTrustedSite-meta.xml`). |
|
|
57
|
+
| `description` | Yes | Human-readable purpose. Start with "Allow access to..." |
|
|
58
|
+
| `endpointUrl` | Yes | The external origin (scheme + host). Must start with `https://`. No trailing slash. No path. |
|
|
59
|
+
| `isActive` | Yes | Always `true` for new entries. Set `false` to disable without deleting. |
|
|
60
|
+
| `context` | Yes | `All` (applies to all contexts). Other values: `LEX` (Lightning Experience only), `Communities` (Experience Cloud only), `VisualForce`. Use `All` unless there is a specific reason to restrict. |
|
|
61
|
+
| `isApplicableToConnectSrc` | Yes | `true` if the domain is called via `fetch()`, `XMLHttpRequest`, or WebSocket. |
|
|
62
|
+
| `isApplicableToFontSrc` | Yes | `true` if the domain serves font files (`.woff`, `.woff2`, `.ttf`, `.otf`). |
|
|
63
|
+
| `isApplicableToFrameSrc` | Yes | `true` if the domain is loaded in an `<iframe>` or `<object>`. |
|
|
64
|
+
| `isApplicableToImgSrc` | Yes | `true` if the domain serves images (`<img>`, CSS `background-image`, `<svg>`). |
|
|
65
|
+
| `isApplicableToMediaSrc` | Yes | `true` if the domain serves audio or video (`<audio>`, `<video>`). |
|
|
66
|
+
| `isApplicableToStyleSrc` | Yes | `true` if the domain serves CSS stylesheets (`<link rel="stylesheet">`). |
|
|
67
|
+
|
|
68
|
+
**Reference:** [CspTrustedSite — Salesforce Object Reference](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_csptrustedsite.htm)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## CSP directive mapping
|
|
73
|
+
|
|
74
|
+
| CSP header directive | Metadata field | What it allows |
|
|
75
|
+
|---------------------|----------------|----------------|
|
|
76
|
+
| `connect-src` | `isApplicableToConnectSrc` | `fetch()`, `XMLHttpRequest`, WebSocket, `EventSource` |
|
|
77
|
+
| `font-src` | `isApplicableToFontSrc` | `@font-face` sources |
|
|
78
|
+
| `frame-src` | `isApplicableToFrameSrc` | `<iframe>`, `<frame>`, `<object>`, `<embed>` |
|
|
79
|
+
| `img-src` | `isApplicableToImgSrc` | `<img>`, `background-image`, `favicon`, `<picture>` |
|
|
80
|
+
| `media-src` | `isApplicableToMediaSrc` | `<audio>`, `<video>`, `<source>`, `<track>` |
|
|
81
|
+
| `style-src` | `isApplicableToStyleSrc` | `<link rel="stylesheet">`, `@import` in CSS |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Common external domains and their directives
|
|
86
|
+
|
|
87
|
+
Use this table as a quick reference when adding new domains:
|
|
88
|
+
|
|
89
|
+
| Domain | connect-src | font-src | frame-src | img-src | media-src | style-src |
|
|
90
|
+
|--------|:-----------:|:--------:|:---------:|:-------:|:---------:|:---------:|
|
|
91
|
+
| `https://images.unsplash.com` | true | false | false | true | false | false |
|
|
92
|
+
| `https://images.pexels.com` | true | false | false | true | false | false |
|
|
93
|
+
| `https://videos.pexels.com` | true | false | false | false | true | false |
|
|
94
|
+
| `https://fonts.googleapis.com` | true | false | false | false | false | true |
|
|
95
|
+
| `https://fonts.gstatic.com` | true | true | false | false | false | false |
|
|
96
|
+
| `https://avatars.githubusercontent.com` | true | false | false | true | false | false |
|
|
97
|
+
| `https://api.open-meteo.com` | true | false | false | false | false | false |
|
|
98
|
+
| `https://nominatim.openstreetmap.org` | true | false | false | false | false | false |
|
|
99
|
+
| `https://tile.openstreetmap.org` | true | false | false | true | false | false |
|
|
100
|
+
| `https://api.mapbox.com` | true | false | false | true | false | false |
|
|
101
|
+
| `https://cdn.jsdelivr.net` | true | false | false | false | false | true |
|
|
102
|
+
| `https://www.youtube.com` | false | false | true | true | false | false |
|
|
103
|
+
| `https://player.vimeo.com` | false | false | true | false | false | false |
|
|
104
|
+
| `https://res.cloudinary.com` | true | false | false | true | false | false |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Complete examples
|
|
109
|
+
|
|
110
|
+
### Image CDN (Unsplash)
|
|
111
|
+
|
|
112
|
+
```xml
|
|
113
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
114
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
115
|
+
<fullName>Unsplash_Images</fullName>
|
|
116
|
+
<description>Allow access to Unsplash image content for static app media</description>
|
|
117
|
+
<endpointUrl>https://images.unsplash.com</endpointUrl>
|
|
118
|
+
<isActive>true</isActive>
|
|
119
|
+
<context>All</context>
|
|
120
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
121
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
122
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
123
|
+
<isApplicableToImgSrc>true</isApplicableToImgSrc>
|
|
124
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
125
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
126
|
+
</CspTrustedSite>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### REST API (Open-Meteo weather)
|
|
130
|
+
|
|
131
|
+
```xml
|
|
132
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
133
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
134
|
+
<fullName>Open_Meteo_API</fullName>
|
|
135
|
+
<description>Allow access to Open-Meteo weather forecast API</description>
|
|
136
|
+
<endpointUrl>https://api.open-meteo.com</endpointUrl>
|
|
137
|
+
<isActive>true</isActive>
|
|
138
|
+
<context>All</context>
|
|
139
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
140
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
141
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
142
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
143
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
144
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
145
|
+
</CspTrustedSite>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Font provider (Google Fonts — requires two entries)
|
|
149
|
+
|
|
150
|
+
Google Fonts needs two CSP entries because CSS is served from `fonts.googleapis.com` and font files from `fonts.gstatic.com`:
|
|
151
|
+
|
|
152
|
+
**Entry 1: Stylesheets**
|
|
153
|
+
```xml
|
|
154
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
155
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
156
|
+
<fullName>Google_Fonts</fullName>
|
|
157
|
+
<description>Allow access to Google Fonts stylesheets for custom typography</description>
|
|
158
|
+
<endpointUrl>https://fonts.googleapis.com</endpointUrl>
|
|
159
|
+
<isActive>true</isActive>
|
|
160
|
+
<context>All</context>
|
|
161
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
162
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
163
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
164
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
165
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
166
|
+
<isApplicableToStyleSrc>true</isApplicableToStyleSrc>
|
|
167
|
+
</CspTrustedSite>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Entry 2: Font files**
|
|
171
|
+
```xml
|
|
172
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
173
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
174
|
+
<fullName>Google_Fonts_Static</fullName>
|
|
175
|
+
<description>Allow access to Google Fonts static files for font loading</description>
|
|
176
|
+
<endpointUrl>https://fonts.gstatic.com</endpointUrl>
|
|
177
|
+
<isActive>true</isActive>
|
|
178
|
+
<context>All</context>
|
|
179
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
180
|
+
<isApplicableToFontSrc>true</isApplicableToFontSrc>
|
|
181
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
182
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
183
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
184
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
185
|
+
</CspTrustedSite>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Map tiles (OpenStreetMap)
|
|
189
|
+
|
|
190
|
+
```xml
|
|
191
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
192
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
193
|
+
<fullName>OpenStreetMap_Tiles</fullName>
|
|
194
|
+
<description>Allow access to OpenStreetMap tile images for map rendering</description>
|
|
195
|
+
<endpointUrl>https://tile.openstreetmap.org</endpointUrl>
|
|
196
|
+
<isActive>true</isActive>
|
|
197
|
+
<context>All</context>
|
|
198
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
199
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
200
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
201
|
+
<isApplicableToImgSrc>true</isApplicableToImgSrc>
|
|
202
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
203
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
204
|
+
</CspTrustedSite>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Geocoding API (Nominatim)
|
|
208
|
+
|
|
209
|
+
```xml
|
|
210
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
211
|
+
<CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
212
|
+
<fullName>OpenStreetMap_Nominatim</fullName>
|
|
213
|
+
<description>Allow access to OpenStreetMap Nominatim geocoding API</description>
|
|
214
|
+
<endpointUrl>https://nominatim.openstreetmap.org</endpointUrl>
|
|
215
|
+
<isActive>true</isActive>
|
|
216
|
+
<context>All</context>
|
|
217
|
+
<isApplicableToConnectSrc>true</isApplicableToConnectSrc>
|
|
218
|
+
<isApplicableToFontSrc>false</isApplicableToFontSrc>
|
|
219
|
+
<isApplicableToFrameSrc>false</isApplicableToFrameSrc>
|
|
220
|
+
<isApplicableToImgSrc>false</isApplicableToImgSrc>
|
|
221
|
+
<isApplicableToMediaSrc>false</isApplicableToMediaSrc>
|
|
222
|
+
<isApplicableToStyleSrc>false</isApplicableToStyleSrc>
|
|
223
|
+
</CspTrustedSite>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Endpoint URL rules
|
|
229
|
+
|
|
230
|
+
| Rule | Correct | Incorrect |
|
|
231
|
+
|------|---------|-----------|
|
|
232
|
+
| Must be HTTPS | `https://api.example.com` | `http://api.example.com` |
|
|
233
|
+
| No trailing slash | `https://api.example.com` | `https://api.example.com/` |
|
|
234
|
+
| No path | `https://api.example.com` | `https://api.example.com/v1/forecast` |
|
|
235
|
+
| No port (unless non-standard) | `https://api.example.com` | `https://api.example.com:443` |
|
|
236
|
+
| No wildcards | `https://api.example.com` | `https://*.example.com` |
|
|
237
|
+
|
|
238
|
+
Each subdomain needs its own entry. For example, `fonts.googleapis.com` and `fonts.gstatic.com` are separate entries.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## When a service requires multiple domains
|
|
243
|
+
|
|
244
|
+
Some services split resources across multiple subdomains. Create one CSP Trusted Site per domain:
|
|
245
|
+
|
|
246
|
+
| Service | Domains needed |
|
|
247
|
+
|---------|---------------|
|
|
248
|
+
| Google Fonts | `fonts.googleapis.com` (CSS) + `fonts.gstatic.com` (font files) |
|
|
249
|
+
| Mapbox | `api.mapbox.com` (tiles/API) + `events.mapbox.com` (telemetry) |
|
|
250
|
+
| YouTube embed | `www.youtube.com` (iframe) + `i.ytimg.com` (thumbnails) |
|
|
251
|
+
| Cloudflare CDN | `cdnjs.cloudflare.com` (scripts/CSS) |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Troubleshooting CSP violations
|
|
256
|
+
|
|
257
|
+
If the browser console shows a CSP error like:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
Refused to load the image 'https://example.com/image.png' because it violates
|
|
261
|
+
the following Content Security Policy directive: "img-src 'self' ..."
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
1. Extract the **blocked origin** from the URL (e.g. `https://example.com`).
|
|
265
|
+
2. Identify the **directive** from the error message (e.g. `img-src` → `isApplicableToImgSrc`).
|
|
266
|
+
3. Check if a CSP Trusted Site already exists for that origin.
|
|
267
|
+
4. If not, create one using this skill.
|
|
268
|
+
5. Deploy the metadata and refresh the page.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Common mistakes
|
|
273
|
+
|
|
274
|
+
| Mistake | Fix |
|
|
275
|
+
|---------|-----|
|
|
276
|
+
| Including a path in `endpointUrl` | Use only the origin: `https://api.example.com` |
|
|
277
|
+
| Adding trailing slash | Remove it: `https://api.example.com` not `https://api.example.com/` |
|
|
278
|
+
| Using HTTP instead of HTTPS | Salesforce requires HTTPS. If the service only supports HTTP, it cannot be added. |
|
|
279
|
+
| Forgetting `isApplicableToConnectSrc` | Most resources also need connect-src for redirects/preflight. Set to `true` by default. |
|
|
280
|
+
| One entry for multiple subdomains | Each subdomain needs its own file (e.g. `api.example.com` and `cdn.example.com` are separate) |
|
|
281
|
+
| File name doesn't match `fullName` | They must be identical (excluding the `.cspTrustedSite-meta.xml` extension) |
|
package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/SKILL.md
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: webapp-add-
|
|
2
|
+
name: webapp-react-add-component
|
|
3
3
|
description: Creates React components, pages, headers, and footers using shadcn UI and Tailwind CSS. Use when the user asks to create a component, add a widget, build a UI element, add a page, create a new page, add a section (e.g. "add a contacts page"), add a header, add a footer, add a navigation bar, or add anything to the web application.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webapp-react-data-visualization
|
|
3
|
+
description: Adds data visualization components (charts, stat cards, KPI metrics) to React pages using Recharts. Use when the user asks to add a chart, graph, donut chart, pie chart, bar chart, stat card, KPI metric, dashboard visualization, or analytics component to the web application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Data Visualization
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill when:
|
|
11
|
+
- Adding charts (donut, pie, bar, line, area) to a dashboard or analytics page
|
|
12
|
+
- Displaying KPI/metric stat cards with trend indicators
|
|
13
|
+
- Building a dashboard layout with mixed chart types and summary cards
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 1 — Determine the visualization type
|
|
18
|
+
|
|
19
|
+
Identify what the user needs:
|
|
20
|
+
|
|
21
|
+
- **Donut / pie chart** — categorical breakdown (e.g. issue types, status distribution)
|
|
22
|
+
- **Bar chart** — comparison across categories or time periods
|
|
23
|
+
- **Line / area chart** — trends over time
|
|
24
|
+
- **Stat card** — single KPI metric with optional trend indicator
|
|
25
|
+
- **Combined dashboard** — stat cards + one or more charts
|
|
26
|
+
|
|
27
|
+
If unclear, ask:
|
|
28
|
+
|
|
29
|
+
> "What data should the chart display, and would a donut chart, bar chart, line chart, or stat cards work best?"
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Step 2 — Install dependencies
|
|
34
|
+
|
|
35
|
+
All chart types in this skill use **recharts**. Install once from the web app directory:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install recharts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Recharts is built on D3 and provides declarative React components. No additional CSS is needed.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Step 3 — Choose implementation path
|
|
46
|
+
|
|
47
|
+
Read the corresponding guide:
|
|
48
|
+
|
|
49
|
+
- **Bar chart** — use the **`analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for categorical data).
|
|
50
|
+
- **Line / area chart** — use the **`analytics-charts`** skill in `feature-react-chart` (AnalyticsChart component for time-series data).
|
|
51
|
+
- **Donut / pie chart** — read `implementation/donut-chart.md`
|
|
52
|
+
- **Stat card with trend** — read `implementation/stat-card.md`
|
|
53
|
+
- **Dashboard layout** — read `implementation/dashboard-layout.md`
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Verification
|
|
58
|
+
|
|
59
|
+
Before completing:
|
|
60
|
+
|
|
61
|
+
1. Chart renders with correct data and colors.
|
|
62
|
+
2. Chart is responsive (resizes with container).
|
|
63
|
+
3. Legend labels match the data categories.
|
|
64
|
+
4. Stat card trends display correct positive/negative indicators.
|
|
65
|
+
5. Run from the web app directory:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **Lint:** MUST result in 0 errors.
|
|
72
|
+
- **Build:** MUST succeed.
|
package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/dashboard-layout.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Dashboard Layout — Implementation Guide
|
|
2
|
+
|
|
3
|
+
## Anatomy of a dashboard page
|
|
4
|
+
|
|
5
|
+
A typical dashboard combines stat cards, charts, and data tables:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌────────────────────────────────────────────────────┐
|
|
9
|
+
│ Search / global action bar │
|
|
10
|
+
├──────────┬──────────┬──────────────────────────────┤
|
|
11
|
+
│ Stat 1 │ Stat 2 │ Stat 3 │
|
|
12
|
+
├──────────┴──────────┴──────┬───────────────────────┤
|
|
13
|
+
│ │ │
|
|
14
|
+
│ Data table / list │ Donut chart │
|
|
15
|
+
│ (70% width) │ (30% width) │
|
|
16
|
+
│ │ │
|
|
17
|
+
└────────────────────────────┴───────────────────────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Layout implementation
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { PageContainer } from "@/components/layout/PageContainer";
|
|
26
|
+
import { StatCard } from "@/components/StatCard";
|
|
27
|
+
import { DonutChart } from "@/components/DonutChart";
|
|
28
|
+
|
|
29
|
+
export default function Dashboard() {
|
|
30
|
+
return (
|
|
31
|
+
<PageContainer>
|
|
32
|
+
<div className="max-w-7xl mx-auto space-y-6">
|
|
33
|
+
{/* Search bar */}
|
|
34
|
+
<div>{/* global search component */}</div>
|
|
35
|
+
|
|
36
|
+
{/* Main content: 70/30 split */}
|
|
37
|
+
<div className="grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6">
|
|
38
|
+
<div className="space-y-6">
|
|
39
|
+
{/* Stat cards row */}
|
|
40
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
41
|
+
<StatCard title="Metric A" value={42} />
|
|
42
|
+
<StatCard title="Metric B" value={18} />
|
|
43
|
+
<StatCard title="Metric C" value={7} />
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* Data table */}
|
|
47
|
+
<div>{/* table component */}</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{/* Sidebar chart */}
|
|
51
|
+
<div>
|
|
52
|
+
<DonutChart title="Distribution" data={chartData} />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</PageContainer>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Responsive behavior
|
|
64
|
+
|
|
65
|
+
| Breakpoint | Layout |
|
|
66
|
+
|------------|--------|
|
|
67
|
+
| Mobile (`< 768px`) | Single column, everything stacked |
|
|
68
|
+
| Tablet (`md`) | Stat cards in 3-col grid, rest stacked |
|
|
69
|
+
| Desktop (`lg`) | 70/30 split for table + chart |
|
|
70
|
+
|
|
71
|
+
Key Tailwind classes:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6
|
|
75
|
+
grid grid-cols-1 md:grid-cols-3 gap-6
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Loading state
|
|
81
|
+
|
|
82
|
+
Show a full-page loading state while dashboard data is being fetched:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
if (loading) {
|
|
86
|
+
return (
|
|
87
|
+
<PageContainer>
|
|
88
|
+
<div className="flex items-center justify-center min-h-[400px]">
|
|
89
|
+
<p className="text-muted-foreground">Loading dashboard…</p>
|
|
90
|
+
</div>
|
|
91
|
+
</PageContainer>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or use a skeleton layout:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
if (loading) {
|
|
100
|
+
return (
|
|
101
|
+
<PageContainer>
|
|
102
|
+
<div className="max-w-7xl mx-auto space-y-6">
|
|
103
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
104
|
+
{[1, 2, 3].map((i) => (
|
|
105
|
+
<div key={i} className="h-28 animate-pulse rounded-xl bg-muted" />
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
<div className="grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6">
|
|
109
|
+
<div className="h-64 animate-pulse rounded-xl bg-muted" />
|
|
110
|
+
<div className="h-64 animate-pulse rounded-xl bg-muted" />
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</PageContainer>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Data fetching pattern
|
|
121
|
+
|
|
122
|
+
Use `useEffect` with cancellation for dashboard metrics:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const [metrics, setMetrics] = useState<Metrics | null>(null);
|
|
126
|
+
const [loading, setLoading] = useState(true);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
let cancelled = false;
|
|
130
|
+
(async () => {
|
|
131
|
+
try {
|
|
132
|
+
setLoading(true);
|
|
133
|
+
const data = await fetchDashboardMetrics();
|
|
134
|
+
if (!cancelled) setMetrics(data);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!cancelled) console.error("Error loading metrics:", error);
|
|
137
|
+
} finally {
|
|
138
|
+
if (!cancelled) setLoading(false);
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
return () => { cancelled = true; };
|
|
142
|
+
}, []);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Combining multiple data sources
|
|
148
|
+
|
|
149
|
+
Dashboards often aggregate data from several APIs. Load them in parallel:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const [metrics, setMetrics] = useState<Metrics | null>(null);
|
|
153
|
+
const [requests, setRequests] = useState<Request[]>([]);
|
|
154
|
+
const [loading, setLoading] = useState(true);
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
let cancelled = false;
|
|
158
|
+
Promise.all([fetchMetrics(), fetchRecentRequests()])
|
|
159
|
+
.then(([metricsData, requestsData]) => {
|
|
160
|
+
if (!cancelled) {
|
|
161
|
+
setMetrics(metricsData);
|
|
162
|
+
setRequests(requestsData);
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
.catch((err) => {
|
|
166
|
+
if (!cancelled) console.error(err);
|
|
167
|
+
})
|
|
168
|
+
.finally(() => {
|
|
169
|
+
if (!cancelled) setLoading(false);
|
|
170
|
+
});
|
|
171
|
+
return () => { cancelled = true; };
|
|
172
|
+
}, []);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## PageContainer wrapper
|
|
178
|
+
|
|
179
|
+
A simple wrapper for consistent page padding:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
interface PageContainerProps {
|
|
183
|
+
children: React.ReactNode;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function PageContainer({ children }: PageContainerProps) {
|
|
187
|
+
return <div className="p-6">{children}</div>;
|
|
188
|
+
}
|
|
189
|
+
```
|