@salesforce/afv-skills 1.56.0 → 1.58.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/package.json +1 -1
- package/skills/commerce-b2b-open-code-components-integrate/SKILL.md +1 -1
- package/skills/commerce-b2b-open-code-components-replace/SKILL.md +16 -16
- package/skills/dx-devops-project-manage/SKILL.md +1 -1
- package/skills/experience-cms-content-generate/SKILL.md +1 -1
- package/skills/experience-cms-content-render/SKILL.md +3 -3
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +35 -7
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +13 -4
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +30 -8
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +14 -4
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +11 -1
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +1 -1
- package/skills/experience-cms-content-render/assets/shared/mediaLabels.ts +62 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +13 -6
- package/skills/experience-content-media-stock-image-search/SKILL.md +0 -1
- package/skills/experience-lwc-design-generate/SKILL.md +1 -1
- package/skills/experience-lwc-legacy-migrate/SKILL.md +212 -0
- package/skills/experience-lwc-legacy-migrate/assets/lo20-host-page-template.html +192 -0
- package/skills/experience-lwc-legacy-migrate/references/aura-to-lwc-completeness-checklist.md +188 -0
- package/skills/experience-lwc-legacy-migrate/references/lightning-out-2-system-reference.md +495 -0
- package/skills/experience-lwc-legacy-migrate/references/lightning-out-beta-to-2-migration.md +824 -0
- package/skills/experience-lwc-legacy-migrate/scripts/convert-lo-names.py +94 -0
- package/skills/experience-lwc-legacy-migrate/scripts/validate-lo20-page.py +133 -0
- package/skills/experience-ui-bundle-project-generate/SKILL.md +14 -40
- package/skills/experience-ui-bundle-project-generate/scripts/flatten-project.mjs +33 -11
- package/skills/experience-ui-bundle-project-generate/scripts/generate-project.mjs +77 -0
- package/skills/experience-ui-bundle-project-generate/scripts/install-deps.mjs +95 -0
- package/skills/integration-connectivity-connected-app-configure/SKILL.md +5 -5
- package/skills/integration-connectivity-generate/SKILL.md +10 -10
- package/skills/integration-connectivity-generate/scripts/configure-named-credential.sh +2 -2
- package/skills/platform-apex-test-generate/SKILL.md +1 -1
- package/skills/platform-apex-test-run/SKILL.md +1 -1
- package/skills/platform-custom-field-generate/SKILL.md +1 -1
- package/skills/platform-custom-lightning-type-generate/SKILL.md +1 -1
- package/skills/platform-custom-metadata-type-generate/SKILL.md +1 -1
- package/skills/platform-custom-object-generate/SKILL.md +1 -1
- package/skills/platform-custom-report-type-generate/SKILL.md +1 -1
- package/skills/platform-custom-setting-generate/SKILL.md +1 -1
- package/skills/platform-data-and-tooling-api-context-get/SKILL.md +1 -1
- package/skills/platform-data-manage/SKILL.md +10 -10
- package/skills/platform-flexipage-generate/SKILL.md +1 -1
- package/skills/platform-lightning-app-coordinate/SKILL.md +1 -1
- package/skills/platform-metadata-api-context-get/SKILL.md +1 -1
- package/skills/platform-metadata-retrieve/SKILL.md +1 -1
- package/skills/platform-permission-set-generate/SKILL.md +1 -1
- package/skills/platform-report-generate/SKILL.md +1 -1
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +4 -4
- package/skills/platform-sharing-owd-configure/SKILL.md +1 -1
- package/skills/platform-sharing-rules-generate/SKILL.md +1 -1
- package/skills/platform-soql-query/SKILL.md +1 -1
- package/skills/platform-value-set-generate/SKILL.md +1 -1
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +4 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +26 -6
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +5 -4
- package/skills/service-de-waba-integrate/SKILL.md +2 -1
- package/skills/service-digital-engagement-deployment-configure/SKILL.md +2 -0
- package/skills/service-digital-engagement-deployment-configure/scripts/check-api-version.sh +29 -0
- package/skills/service-email-to-case-configure/SKILL.md +1 -1
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +1 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +1 -0
- package/skills/service-omni-command-center-configure/SKILL.md +92 -0
- package/skills/service-omni-command-center-configure/references/api-notes.md +50 -0
- package/skills/service-omni-command-center-configure/scripts/configure-and-report.sh +257 -0
- package/skills/service-omni-command-center-configure/scripts/settings_document.py +97 -0
- package/skills/service-omni-command-center-configure/scripts/tests/test_command_center_configure_contracts.py +206 -0
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
# Lightning Out Beta → Lightning Out 2.0 Migration Guide
|
|
2
|
+
|
|
3
|
+
## Migration Approach
|
|
4
|
+
|
|
5
|
+
This guide helps you identify and transform Lightning Out Beta code to Lightning Out 2.0. **Your existing HTML structure, styling, and custom logic remain unchanged** - we only modify Lightning Out-specific patterns.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Lightning Out 2.0 Documentation
|
|
10
|
+
|
|
11
|
+
Before applying the migration steps below, internalize the complete Lightning Out 2.0 architecture, lifecycle, styling, events, and constraints documentation in the companion reference:
|
|
12
|
+
|
|
13
|
+
See [lightning-out-2-system-reference.md](lightning-out-2-system-reference.md) for the complete LO 2.0 system reference.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Phase 1: Identify Lightning Out Beta Patterns
|
|
18
|
+
|
|
19
|
+
**Search for these patterns in the customer's code:**
|
|
20
|
+
|
|
21
|
+
### Pattern A: Script Tag Loading Beta Library
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script src="https://[DOMAIN].lightning.force.com/lightning/lightning.out.js"></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**What to extract:**
|
|
28
|
+
|
|
29
|
+
- Domain (before `.lightning.force.com`)
|
|
30
|
+
|
|
31
|
+
### Pattern B: Configuration Variables
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const [ENDPOINT_VAR] = 'https://[DOMAIN].lightning.force.com';
|
|
35
|
+
const [APP_VAR] = 'c:[AuraAppName]'; // Aura app name
|
|
36
|
+
const [COMPONENT_VAR] = 'c:[componentName]'; // Component name
|
|
37
|
+
const [TOKEN_VAR] = '[AUTH_TOKEN]'; // Hardcoded token
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**What to extract:**
|
|
41
|
+
|
|
42
|
+
- Endpoint variable name and domain value
|
|
43
|
+
- Aura app variable name and value
|
|
44
|
+
- Component variable name and value (Aura format: `c:name`)
|
|
45
|
+
- Token variable name
|
|
46
|
+
|
|
47
|
+
### Pattern C: $Lightning.use() Call
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
$Lightning.use(
|
|
51
|
+
[APP_VAR], // 1st param: Aura app (could be variable or string)
|
|
52
|
+
function () {
|
|
53
|
+
// Callback function body
|
|
54
|
+
},
|
|
55
|
+
[ENDPOINT_VAR], // 3rd param: Endpoint (could be variable or string)
|
|
56
|
+
[TOKEN_VAR], // 4th param: Auth token (could be variable or string)
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**What to extract:**
|
|
61
|
+
|
|
62
|
+
- The entire `$Lightning.use()` block
|
|
63
|
+
- First parameter value (Aura app)
|
|
64
|
+
- Third parameter value (endpoint)
|
|
65
|
+
- Fourth parameter value (token)
|
|
66
|
+
|
|
67
|
+
### Pattern D: $Lightning.createComponent() Call
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
$Lightning.createComponent(
|
|
71
|
+
[COMPONENT_VAR], // 1st param: Component name (could be variable or string "c:name")
|
|
72
|
+
{[ATTRIBUTES]}, // 2nd param: Attributes object (could be empty {})
|
|
73
|
+
"[DOM_ID]", // 3rd param: DOM element ID as STRING
|
|
74
|
+
function(cmp) {
|
|
75
|
+
// Optional callback
|
|
76
|
+
[CUSTOM_LOGIC]
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**What to extract:**
|
|
82
|
+
|
|
83
|
+
- Component name/variable
|
|
84
|
+
- Attributes object (if any): `{ key: "value", anotherKey: true }`
|
|
85
|
+
- DOM element ID (3rd parameter - this is where component renders)
|
|
86
|
+
- Any custom logic inside the callback
|
|
87
|
+
|
|
88
|
+
### Pattern E: DOM Target Element
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<[ANY_TAG] id="[DOM_ID]">[CONTENT]</[ANY_TAG]>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Where `[DOM_ID]` matches the 3rd parameter in `$Lightning.createComponent()`
|
|
95
|
+
|
|
96
|
+
**What to extract:**
|
|
97
|
+
|
|
98
|
+
- Element location in HTML
|
|
99
|
+
- Element tag name and existing attributes
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Phase 2: Apply Transformations
|
|
104
|
+
|
|
105
|
+
**Note**: Examples below use generic component names like "c-lo-beta-entry-form". Replace ALL occurrences with your actual component name from Phase 1.
|
|
106
|
+
|
|
107
|
+
### Transform 1: Update Script Tag
|
|
108
|
+
|
|
109
|
+
**Find Pattern A:**
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<script src="https://[DOMAIN].lightning.force.com/lightning/lightning.out.js"></script>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Replace with:**
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<script async src="https://[DOMAIN].my.salesforce.com/lightning/lightning.out.latest/index.iife.prod.js"></script>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Rules:**
|
|
122
|
+
|
|
123
|
+
- Change domain: `.lightning.force.com` → `.my.salesforce.com`
|
|
124
|
+
- Change path: `/lightning/lightning.out.js` → `/lightning/lightning.out.latest/index.iife.prod.js`
|
|
125
|
+
- Add `async` attribute
|
|
126
|
+
|
|
127
|
+
### Transform 2: Convert Component Name
|
|
128
|
+
|
|
129
|
+
**From Pattern B or D, extract the component name:**
|
|
130
|
+
|
|
131
|
+
- If variable: `const component = "c:myComponent"` → extract `"c:myComponent"`
|
|
132
|
+
- If inline: `$Lightning.createComponent("c:myComponent", ...)` → extract `"c:myComponent"`
|
|
133
|
+
|
|
134
|
+
**Convert Aura format to kebab-case:**
|
|
135
|
+
|
|
136
|
+
- `c:myComponent` → `c-my-component`
|
|
137
|
+
- `c:loBetaEntryForm` → `c-lo-beta-entry-form`
|
|
138
|
+
- `c:userDashboard` → `c-user-dashboard`
|
|
139
|
+
|
|
140
|
+
**Conversion algorithm:**
|
|
141
|
+
|
|
142
|
+
1. Remove `c:` prefix
|
|
143
|
+
2. Insert hyphen before each capital letter
|
|
144
|
+
3. Convert to lowercase
|
|
145
|
+
4. Add `c-` prefix
|
|
146
|
+
|
|
147
|
+
### Transform 3: Add Component Tag to HTML
|
|
148
|
+
|
|
149
|
+
**At the location of Pattern E** (the DOM target element), add:
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<!-- Add these BEFORE or INSIDE the existing target element -->
|
|
153
|
+
<div id="[MOUNT_ID]"></div> <!-- Choose a mount point ID -->
|
|
154
|
+
<[KEBAB-COMPONENT-TAG] [ATTRIBUTES]></[KEBAB-COMPONENT-TAG]>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Where:**
|
|
158
|
+
|
|
159
|
+
- `[MOUNT_ID]` = Your chosen ID for the mount point (e.g., "lo2Mount", "sfAppMount")
|
|
160
|
+
- `[KEBAB-COMPONENT-TAG]` = converted component name from Transform 2
|
|
161
|
+
- `[ATTRIBUTES]` = converted attributes from Pattern D (see below)
|
|
162
|
+
|
|
163
|
+
**If Pattern D had attributes object:**
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
{ recordId: "123", isActive: true, userName: "John" }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Convert to HTML attributes (camelCase → kebab-case):**
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<c-my-component record-id="123" is-active="true" user-name="John"></c-my-component>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Transform 4: Replace Beta JavaScript Block
|
|
176
|
+
|
|
177
|
+
**Delete entire Patterns C and D block:**
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
// DELETE THIS ENTIRE SECTION
|
|
181
|
+
const [ENDPOINT_VAR] = "...";
|
|
182
|
+
const [APP_VAR] = "c:...";
|
|
183
|
+
const [COMPONENT_VAR] = "c:...";
|
|
184
|
+
const [TOKEN_VAR] = "...";
|
|
185
|
+
|
|
186
|
+
$Lightning.use([APP_VAR], function() {
|
|
187
|
+
$Lightning.createComponent([COMPONENT_VAR], {...}, "[DOM_ID]", function(cmp) {
|
|
188
|
+
[CUSTOM_LOGIC]
|
|
189
|
+
});
|
|
190
|
+
}, [ENDPOINT_VAR], [TOKEN_VAR]);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Replace with LO 2.0 pattern:**
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// Configuration (NEW - adapt names to customer's style)
|
|
197
|
+
const ECA_CONSUMER_KEY = '[GET_FROM_SALESFORCE_SETUP]';
|
|
198
|
+
const LO_APP_ID = '[GET_FROM_SALESFORCE_SETUP]'; // Optional 18-character ID (omit if not required)
|
|
199
|
+
const ORG_MYDOMAIN_URL = 'https://[DOMAIN].my.salesforce.com'; // Transform from extracted domain
|
|
200
|
+
const components = '[KEBAB-COMPONENT-NAME]'; // From Transform 2
|
|
201
|
+
|
|
202
|
+
// Helper functions (REQUIRED - copy exactly)
|
|
203
|
+
function readCachedResult() {
|
|
204
|
+
try {
|
|
205
|
+
const raw = localStorage.getItem('lo2_frontdoor_result');
|
|
206
|
+
if (!raw) return null;
|
|
207
|
+
return JSON.parse(raw);
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function clearCachedResult() {
|
|
214
|
+
try {
|
|
215
|
+
localStorage.removeItem('lo2_frontdoor_result');
|
|
216
|
+
} catch {}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Mount function (adapt element selection to customer's DOM)
|
|
220
|
+
function mountLo20(frontdoorUrl, orgUrl) {
|
|
221
|
+
const [MOUNT_ELEMENT] = document.getElementById('[MOUNT_ID]'); // Customer's mount point
|
|
222
|
+
if ([MOUNT_ELEMENT] && [MOUNT_ELEMENT].querySelector('lightning-out-application')) return;
|
|
223
|
+
|
|
224
|
+
function tryMount() {
|
|
225
|
+
if (!customElements?.get?.('lightning-out-application')) return false;
|
|
226
|
+
|
|
227
|
+
const el = document.createElement('lightning-out-application');
|
|
228
|
+
el.setAttribute('components', components);
|
|
229
|
+
if (LO_APP_ID) el.setAttribute('app-id', LO_APP_ID);
|
|
230
|
+
el.setAttribute('frontdoor-url', frontdoorUrl);
|
|
231
|
+
|
|
232
|
+
([MOUNT_ELEMENT] || document.body).appendChild(el);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const start = Date.now();
|
|
237
|
+
(function tick() {
|
|
238
|
+
if (tryMount()) return;
|
|
239
|
+
if (Date.now() - start > 15000) {
|
|
240
|
+
console.error('LO 2.0 mount timeout');
|
|
241
|
+
// Optionally show error in customer's error display
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
setTimeout(tick, 50);
|
|
245
|
+
})();
|
|
246
|
+
|
|
247
|
+
// CRITICAL: Use literal component name string (NOT variable)
|
|
248
|
+
customElements.whenDefined('[LITERAL-KEBAB-COMPONENT-NAME]').then(() => {
|
|
249
|
+
// Hide customer's loading indicator here
|
|
250
|
+
// Example: document.getElementById('[CUSTOMER_LOADING_ID]').style.display = 'none';
|
|
251
|
+
|
|
252
|
+
// If Pattern D had custom callback logic, add it here:
|
|
253
|
+
[CUSTOM_LOGIC_FROM_CALLBACK];
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Boot function (wrap all initialization)
|
|
258
|
+
function boot() {
|
|
259
|
+
try {
|
|
260
|
+
// Store OAuth config for callback page
|
|
261
|
+
localStorage.setItem('orgMyDomainURL', ORG_MYDOMAIN_URL);
|
|
262
|
+
localStorage.setItem('loAppID', LO_APP_ID);
|
|
263
|
+
localStorage.setItem('loECAKey', ECA_CONSUMER_KEY);
|
|
264
|
+
localStorage.setItem('lo2_returnTo', window.location.pathname);
|
|
265
|
+
|
|
266
|
+
// OAuth callback listeners
|
|
267
|
+
window.addEventListener('message', (evt) => {
|
|
268
|
+
const data = evt?.data;
|
|
269
|
+
if (!data || data.error || !data.frontdoorUrl) return;
|
|
270
|
+
clearCachedResult();
|
|
271
|
+
mountLo20(data.frontdoorUrl, data.orgUrl);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const bc = new BroadcastChannel('lo2auth');
|
|
276
|
+
bc.onmessage = (evt) => {
|
|
277
|
+
const data = evt?.data;
|
|
278
|
+
if (!data || data.error || !data.frontdoorUrl) return;
|
|
279
|
+
clearCachedResult();
|
|
280
|
+
mountLo20(data.frontdoorUrl, data.orgUrl);
|
|
281
|
+
};
|
|
282
|
+
} catch (e) {}
|
|
283
|
+
|
|
284
|
+
window.addEventListener('storage', (evt) => {
|
|
285
|
+
if (evt.key !== 'lo2_frontdoor_result') return;
|
|
286
|
+
const data = readCachedResult();
|
|
287
|
+
if (!data || data.error || !data.frontdoorUrl) return;
|
|
288
|
+
mountLo20(data.frontdoorUrl, data.orgUrl);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Check for cached auth
|
|
292
|
+
const cached = readCachedResult();
|
|
293
|
+
if (cached?.frontdoorUrl) {
|
|
294
|
+
mountLo20(cached.frontdoorUrl, cached.orgUrl);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Wire up customer's OAuth trigger (button/link/etc)
|
|
298
|
+
const [CUSTOMER_TRIGGER_ELEMENT] = document.getElementById('[CUSTOMER_TRIGGER_ID]');
|
|
299
|
+
if ([CUSTOMER_TRIGGER_ELEMENT]) {
|
|
300
|
+
[CUSTOMER_TRIGGER_ELEMENT].addEventListener('click', () => {
|
|
301
|
+
clearCachedResult();
|
|
302
|
+
window.location.assign('/frontdoor-url.html');
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
} catch (e) {
|
|
306
|
+
console.error('Boot error:', e);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
boot();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Phase 3: Integration with Customer Code
|
|
316
|
+
|
|
317
|
+
### Preserve Customer's:
|
|
318
|
+
|
|
319
|
+
1. **HTML structure** - Don't modify layout, styling, or element hierarchy
|
|
320
|
+
2. **Loading indicators** - Hook into existing UI, don't create new ones
|
|
321
|
+
3. **Error handling** - Use existing error display mechanisms
|
|
322
|
+
4. **Event handlers** - Preserve any custom event listeners
|
|
323
|
+
5. **Business logic** - Move callback logic from Pattern D to `customElements.whenDefined().then()`
|
|
324
|
+
|
|
325
|
+
### Adapt These Elements:
|
|
326
|
+
|
|
327
|
+
1. **Element selectors** - Use customer's actual element IDs (don't hardcode "loAppMount", "connectBtn")
|
|
328
|
+
2. **Loading indicator** - In `customElements.whenDefined()`, hide customer's loading element
|
|
329
|
+
3. **OAuth trigger** - Wire up customer's existing button/link for authentication
|
|
330
|
+
4. **Error display** - If customer has error handling, integrate with that
|
|
331
|
+
|
|
332
|
+
### Example Integration:
|
|
333
|
+
|
|
334
|
+
**Customer's Beta code had:**
|
|
335
|
+
|
|
336
|
+
```html
|
|
337
|
+
<div id="myContainer">
|
|
338
|
+
<div class="spinner">Loading...</div>
|
|
339
|
+
<div id="lightningTarget"></div>
|
|
340
|
+
</div>
|
|
341
|
+
<button onclick="doAuth()">Login</button>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**After migration:**
|
|
345
|
+
|
|
346
|
+
```html
|
|
347
|
+
<div id="myContainer">
|
|
348
|
+
<div class="spinner">Loading...</div>
|
|
349
|
+
<div id="sfMount"></div>
|
|
350
|
+
<!-- ADD: Choose your own ID -->
|
|
351
|
+
<c-my-component></c-my-component>
|
|
352
|
+
<!-- ADD -->
|
|
353
|
+
<div id="lightningTarget"></div>
|
|
354
|
+
<!-- KEEP if needed -->
|
|
355
|
+
</div>
|
|
356
|
+
<button id="authBtn">Login</button>
|
|
357
|
+
<!-- ADD id for event listener -->
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**In JavaScript, use customer's actual IDs:**
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
const sfMount = document.getElementById('sfMount'); // Use YOUR mount ID
|
|
364
|
+
|
|
365
|
+
function mountLo20(frontdoorUrl, orgUrl) {
|
|
366
|
+
if (sfMount && sfMount.querySelector('lightning-out-application')) return;
|
|
367
|
+
// ... rest of mount logic using sfMount
|
|
368
|
+
(sfMount || document.body).appendChild(el);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// In boot(), hide customer's spinner when ready
|
|
372
|
+
customElements.whenDefined('c-my-component').then(() => {
|
|
373
|
+
document.querySelector('.spinner').style.display = 'none'; // Customer's spinner
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Wire up customer's button
|
|
377
|
+
document.getElementById('authBtn').addEventListener('click', () => {
|
|
378
|
+
clearCachedResult();
|
|
379
|
+
window.location.assign('/frontdoor-url.html');
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Critical Implementation Rules
|
|
386
|
+
|
|
387
|
+
**These rules prevent runtime errors - follow exactly:**
|
|
388
|
+
|
|
389
|
+
### Rule 1: `customElements.whenDefined()` requires literal string
|
|
390
|
+
|
|
391
|
+
WRONG — Will fail:
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
const components = "c-my-component";
|
|
395
|
+
customElements.whenDefined(components).then(...); // Using variable
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
CORRECT:
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
const components = "c-my-component";
|
|
402
|
+
customElements.whenDefined('c-my-component').then(...); // Literal string
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Rule 2: `mountLo20()` must accept two parameters
|
|
406
|
+
|
|
407
|
+
CORRECT signature (even if orgUrl unused):
|
|
408
|
+
|
|
409
|
+
```javascript
|
|
410
|
+
function mountLo20(frontdoorUrl, orgUrl) {
|
|
411
|
+
// Implementation
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Rule 3: Always call `clearCachedResult()` before mounting
|
|
416
|
+
|
|
417
|
+
In every OAuth listener:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
if (data?.frontdoorUrl) {
|
|
421
|
+
clearCachedResult(); // Must call first
|
|
422
|
+
mountLo20(data.frontdoorUrl, data.orgUrl);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Rule 4: Pass both parameters when calling mountLo20
|
|
427
|
+
|
|
428
|
+
Always:
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
mountLo20(data.frontdoorUrl, data.orgUrl); // Both parameters, even if orgUrl is undefined
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Rule 5: Wrap all initialization in `boot()` function
|
|
435
|
+
|
|
436
|
+
Required structure:
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
function boot() {
|
|
440
|
+
try {
|
|
441
|
+
// All localStorage setup
|
|
442
|
+
// All event listeners
|
|
443
|
+
// All initialization
|
|
444
|
+
} catch (e) {
|
|
445
|
+
console.error('Boot error:', e);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
boot(); // Must call at end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Rule 6: Component tag has no inline styles
|
|
452
|
+
|
|
453
|
+
CORRECT:
|
|
454
|
+
|
|
455
|
+
```html
|
|
456
|
+
<c-my-component></c-my-component>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
WRONG:
|
|
460
|
+
|
|
461
|
+
```html
|
|
462
|
+
<c-my-component style="display: none"></c-my-component>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Transformation Patterns
|
|
468
|
+
|
|
469
|
+
### 1. Domain Transformation
|
|
470
|
+
|
|
471
|
+
**Pattern**: `https://[subdomain].lightning.[pod].force.com` → `https://[subdomain].my.[pod].salesforce.com`
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
// BEFORE (Beta)
|
|
475
|
+
const endpoint = 'https://orgfarm-5b43fe90c6.test1.lightning.pc-rnd.force.com';
|
|
476
|
+
|
|
477
|
+
// AFTER (LO 2.0)
|
|
478
|
+
const ORG_MYDOMAIN_URL = 'https://orgfarm-5b43fe90c6.test1.my.pc-rnd.salesforce.com';
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### 2. Library Script Tag
|
|
482
|
+
|
|
483
|
+
```html
|
|
484
|
+
<!-- BEFORE (Beta) -->
|
|
485
|
+
<script src="https://YOUR_ORG.lightning.force.com/lightning/lightning.out.js"></script>
|
|
486
|
+
|
|
487
|
+
<!-- AFTER (LO 2.0) -->
|
|
488
|
+
<script async src="https://YOUR_ORG.my.salesforce.com/lightning/lightning.out.latest/index.iife.prod.js"></script>
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 3. Component Name Conversion
|
|
492
|
+
|
|
493
|
+
```javascript
|
|
494
|
+
// BEFORE (Beta): Aura-style naming
|
|
495
|
+
const component = 'c:loBetaEntryForm';
|
|
496
|
+
|
|
497
|
+
// AFTER (LO 2.0): Kebab-case
|
|
498
|
+
const components = 'c-lo-beta-entry-form';
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### 4. Component Initialization - Complete Replacement
|
|
502
|
+
|
|
503
|
+
**REMOVE this entire Beta pattern:**
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
$Lightning.use(
|
|
507
|
+
'c:OrgFarmOut',
|
|
508
|
+
function () {
|
|
509
|
+
$Lightning.createComponent(
|
|
510
|
+
'c:loBetaEntryForm',
|
|
511
|
+
{ contactId: '003...' }, // attributes
|
|
512
|
+
'lightning-out', // DOM target
|
|
513
|
+
function (cmp) {
|
|
514
|
+
console.log('created');
|
|
515
|
+
},
|
|
516
|
+
);
|
|
517
|
+
},
|
|
518
|
+
'https://org.lightning.force.com',
|
|
519
|
+
'00D...TOKEN...', // hardcoded token
|
|
520
|
+
);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**REPLACE with LO 2.0 pattern:**
|
|
524
|
+
|
|
525
|
+
**Step A**: Add component tag directly in HTML:
|
|
526
|
+
|
|
527
|
+
```html
|
|
528
|
+
<div id="loAppMount"></div>
|
|
529
|
+
<c-lo-beta-entry-form contact-id="003..."></c-lo-beta-entry-form>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
_Note: Attributes converted from camelCase to kebab-case_
|
|
533
|
+
|
|
534
|
+
**Step B**: Create mounting function:
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
function mountLo20(frontdoorUrl) {
|
|
538
|
+
// Poll for custom element availability
|
|
539
|
+
function tryMount() {
|
|
540
|
+
if (!customElements?.get?.('lightning-out-application')) return false;
|
|
541
|
+
|
|
542
|
+
const el = document.createElement('lightning-out-application');
|
|
543
|
+
el.setAttribute('components', 'c-lo-beta-entry-form');
|
|
544
|
+
// app-id is optional — set it only if your LO 2.0 app requires it
|
|
545
|
+
// el.setAttribute('app-id', 'YOUR_18_CHAR_APP_ID');
|
|
546
|
+
el.setAttribute('frontdoor-url', frontdoorUrl);
|
|
547
|
+
|
|
548
|
+
document.getElementById('loAppMount').appendChild(el);
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Retry with timeout
|
|
553
|
+
const start = Date.now();
|
|
554
|
+
(function tick() {
|
|
555
|
+
if (tryMount()) return;
|
|
556
|
+
if (Date.now() - start > 15000) {
|
|
557
|
+
console.error('Timeout: LO 2.0 not registered');
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
setTimeout(tick, 50);
|
|
561
|
+
})();
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### 5. Authentication Replacement
|
|
566
|
+
|
|
567
|
+
**BEFORE (Beta)**: Hardcoded token
|
|
568
|
+
|
|
569
|
+
```javascript
|
|
570
|
+
const authToken = '<LEGACY_SESSION_TOKEN_PLACEHOLDER>'; // Remove this
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**AFTER (LO 2.0)**: OAuth PKCE flow
|
|
574
|
+
|
|
575
|
+
**Step A**: Store OAuth config in localStorage:
|
|
576
|
+
|
|
577
|
+
```javascript
|
|
578
|
+
localStorage.setItem('orgMyDomainURL', 'https://YOUR_ORG.my.salesforce.com');
|
|
579
|
+
localStorage.setItem('loAppID', 'YOUR_18_CHAR_APP_ID');
|
|
580
|
+
localStorage.setItem('loECAKey', 'YOUR_ECA_CONSUMER_KEY');
|
|
581
|
+
localStorage.setItem('lo2_returnTo', '/your-page.html');
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Step B**: Trigger OAuth flow (redirect to callback page):
|
|
585
|
+
|
|
586
|
+
```javascript
|
|
587
|
+
// User clicks "Connect"
|
|
588
|
+
window.location.assign('/frontdoor-url.html');
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Step C**: Listen for OAuth result via multiple channels:
|
|
592
|
+
|
|
593
|
+
```javascript
|
|
594
|
+
// postMessage
|
|
595
|
+
window.addEventListener('message', (evt) => {
|
|
596
|
+
if (evt.data?.frontdoorUrl) {
|
|
597
|
+
mountLo20(evt.data.frontdoorUrl);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// BroadcastChannel
|
|
602
|
+
const bc = new BroadcastChannel('lo2auth');
|
|
603
|
+
bc.onmessage = (evt) => {
|
|
604
|
+
if (evt.data?.frontdoorUrl) {
|
|
605
|
+
mountLo20(evt.data.frontdoorUrl);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// localStorage (cross-tab)
|
|
610
|
+
window.addEventListener('storage', (evt) => {
|
|
611
|
+
if (evt.key === 'lo2_frontdoor_result') {
|
|
612
|
+
const data = JSON.parse(localStorage.getItem('lo2_frontdoor_result'));
|
|
613
|
+
if (data?.frontdoorUrl) mountLo20(data.frontdoorUrl);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### 6. Component Attribute Mapping
|
|
619
|
+
|
|
620
|
+
```javascript
|
|
621
|
+
// BEFORE (Beta): JS object
|
|
622
|
+
$Lightning.createComponent("c:myComp", {
|
|
623
|
+
contactId: "003...",
|
|
624
|
+
recordId: "001...",
|
|
625
|
+
isActive: true,
|
|
626
|
+
count: 5
|
|
627
|
+
}, ...);
|
|
628
|
+
|
|
629
|
+
// AFTER (LO 2.0): HTML attributes (kebab-case)
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
```html
|
|
633
|
+
<c-my-comp contact-id="003..." record-id="001..." is-active="true" count="5"> </c-my-comp>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### 7. Component Readiness Detection
|
|
637
|
+
|
|
638
|
+
```javascript
|
|
639
|
+
// BEFORE (Beta): Callback
|
|
640
|
+
$Lightning.createComponent(..., function(cmp) {
|
|
641
|
+
console.log("Component ready");
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// AFTER (LO 2.0): Web Component lifecycle
|
|
645
|
+
customElements.whenDefined("c-my-component").then(() => {
|
|
646
|
+
console.log("Component ready");
|
|
647
|
+
document.getElementById("loading").style.display = "none";
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### 8. Salesforce App Mapping
|
|
652
|
+
|
|
653
|
+
```javascript
|
|
654
|
+
// BEFORE (Beta): Aura app name
|
|
655
|
+
const auraApp = 'c:OrgFarmOut'; // No longer used
|
|
656
|
+
|
|
657
|
+
// AFTER (LO 2.0): Lightning Out 2.0 app ID (optional, 18 chars)
|
|
658
|
+
const LO_APP_ID = '<LIGHTNING_OUT_APP_ID_PLACEHOLDER>'; // From Setup → Lightning Out Apps (omit if not required)
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## Required Setup in Salesforce
|
|
664
|
+
|
|
665
|
+
### 1. Create Lightning Out 2.0 App (Optional)
|
|
666
|
+
|
|
667
|
+
- Setup → Lightning Out → New Lightning Out App
|
|
668
|
+
- Copy the **18-character App ID** (only needed if your deployment requires an explicit app ID)
|
|
669
|
+
|
|
670
|
+
### 2. Create External Client App (ECA)
|
|
671
|
+
|
|
672
|
+
- Setup → App Manager → New Connected App → External Client App
|
|
673
|
+
- Enable OAuth Settings with PKCE
|
|
674
|
+
- Copy the **Consumer Key**
|
|
675
|
+
- Add your callback URL to allowed domains
|
|
676
|
+
|
|
677
|
+
### 3. Helper Files (Assumption)
|
|
678
|
+
|
|
679
|
+
This guide assumes you have:
|
|
680
|
+
|
|
681
|
+
- `frontdoor-url.html` — OAuth callback page
|
|
682
|
+
- `utils/LightningOutAuth.js` — OAuth PKCE implementation
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## Essential Code Blocks
|
|
687
|
+
|
|
688
|
+
The complete, copyable host-page implementation lives in
|
|
689
|
+
[`assets/lo20-host-page-template.html`](../assets/lo20-host-page-template.html).
|
|
690
|
+
Copy that file as your starting point and fill in the `CONFIGURATION`
|
|
691
|
+
constants, then replace `c-your-component` with the migrated component tag and
|
|
692
|
+
its kebab-case attributes (use `scripts/convert-lo-names.py` to derive them).
|
|
693
|
+
The template is a single self-consistent host page that already assembles every
|
|
694
|
+
required piece:
|
|
695
|
+
|
|
696
|
+
- **Configuration** constants (ECA consumer key, optional LO 2.0 App id, My Domain URL, `components`).
|
|
697
|
+
- **DOM references** declared once at the top.
|
|
698
|
+
- The four **helper functions**: `setLoading`, `showError`, `readCachedResult`, `clearCachedResult`.
|
|
699
|
+
- The two-parameter **`mountLo20(frontdoorUrl, orgUrl)`** function with a duplicate-mount guard and a literal-string `customElements.whenDefined(...)` gate.
|
|
700
|
+
- The **`boot()`** wrapper holding all initialization — localStorage config, the postMessage / BroadcastChannel / storage listeners, the cached-result check, and the connect-button handler — with `boot()` called once at the end.
|
|
701
|
+
|
|
702
|
+
**Do NOT** hand-reassemble these blocks into separate `initializeOAuth()` /
|
|
703
|
+
`setupCallbackListeners()` functions, run init code at script level outside
|
|
704
|
+
`boot()`, or omit the trailing `boot()` call. After filling in your copy, run
|
|
705
|
+
`python3 scripts/validate-lo20-page.py <file>` to confirm the six
|
|
706
|
+
non-negotiable rules still hold.
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## Common Pitfalls
|
|
711
|
+
|
|
712
|
+
| Issue | Problem | Solution |
|
|
713
|
+
| ------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
714
|
+
| **Missing boot() wrapper** | Init code runs at script level | Wrap ALL init logic in `function boot() { ... }` and call `boot()` at end |
|
|
715
|
+
| **Component display logic** | Adding `display:none` to component tag | Control visibility via `loadingEl.style.display`, NOT on component tag |
|
|
716
|
+
| **Missing helper functions** | Undefined `setLoading()`, `showError()`, etc. | Include all 4 helper functions: `setLoading`, `showError`, `readCachedResult`, `clearCachedResult` |
|
|
717
|
+
| **Wrong customElements.whenDefined** | Using variable: `customElements.whenDefined(components)` | Use literal string: `customElements.whenDefined('c-my-component')` |
|
|
718
|
+
| **Missing clearCachedResult()** | Not clearing cache before mount | Call `clearCachedResult()` before every `mountLo20()` call |
|
|
719
|
+
| **BroadcastChannel not wrapped** | BroadcastChannel creation fails in some browsers | Wrap in try-catch: `try { const bc = new BroadcastChannel(...) } catch (e) {}` |
|
|
720
|
+
| **Domain mismatch** | Using `.lightning.force.com` | Change to `.my.salesforce.com` |
|
|
721
|
+
| **Component not defined** | Wrong kebab-case format | `c:myComponent` → `c-my-component` |
|
|
722
|
+
| **Missing attributes** | Using camelCase in HTML | `contactId` → `contact-id` |
|
|
723
|
+
| **No component tag** | Only creating `<lightning-out-application>` | Add `<c-component>` tag to HTML |
|
|
724
|
+
|
|
725
|
+
Other pitfalls to watch for: missing defensive checks (`!data` / `data.error` /
|
|
726
|
+
`!data.frontdoorUrl`) in a listener; inline `document.getElementById()` calls
|
|
727
|
+
instead of DOM-reference constants; mount timeouts (check the `app-id` and
|
|
728
|
+
library URL); calling `mountLo20()` more than once without a duplicate-mount
|
|
729
|
+
guard; and OAuth not starting because the localStorage config was not set
|
|
730
|
+
before the redirect. `scripts/validate-lo20-page.py` catches the structural
|
|
731
|
+
ones automatically.
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Verification Checklist
|
|
736
|
+
|
|
737
|
+
After migration, verify:
|
|
738
|
+
|
|
739
|
+
**Script Structure:**
|
|
740
|
+
|
|
741
|
+
- [ ] All init logic wrapped in `function boot() { ... }`
|
|
742
|
+
- [ ] `boot()` called at the end of script
|
|
743
|
+
- [ ] DOM references declared as constants at top
|
|
744
|
+
- [ ] All 4 helper functions present: `setLoading`, `showError`, `readCachedResult`, `clearCachedResult`
|
|
745
|
+
|
|
746
|
+
**Mount Function:**
|
|
747
|
+
|
|
748
|
+
- [ ] `mountLo20(frontdoorUrl, orgUrl)` takes TWO parameters
|
|
749
|
+
- [ ] Duplicate mount check present
|
|
750
|
+
- [ ] `customElements.whenDefined()` uses literal string, not variable
|
|
751
|
+
- [ ] `(loAppMount || document.body).appendChild(el)` fallback present
|
|
752
|
+
|
|
753
|
+
**OAuth & Listeners:**
|
|
754
|
+
|
|
755
|
+
- [ ] `clearCachedResult()` called before every `mountLo20()` call
|
|
756
|
+
- [ ] All three listeners present: postMessage, BroadcastChannel, storage
|
|
757
|
+
- [ ] BroadcastChannel wrapped in try-catch
|
|
758
|
+
- [ ] Each listener checks: `!data`, `data.error`, `!data.frontdoorUrl`
|
|
759
|
+
- [ ] Cached result check present before any user interaction
|
|
760
|
+
|
|
761
|
+
**HTML:**
|
|
762
|
+
|
|
763
|
+
- [ ] Component tag has NO inline `display:none` style
|
|
764
|
+
- [ ] Both `<div id="loAppMount"></div>` and `<c-component>` tag present
|
|
765
|
+
- [ ] Loading indicator uses proper structure with nested divs
|
|
766
|
+
|
|
767
|
+
**Configuration:**
|
|
768
|
+
|
|
769
|
+
- [ ] Domain changed to `.my.salesforce.com`
|
|
770
|
+
- [ ] Library path uses `/lightning.out.latest/index.iife.prod.js`
|
|
771
|
+
- [ ] Component name is kebab-case
|
|
772
|
+
- [ ] LO 2.0 app ID is 18 characters (if used — app-id is optional)
|
|
773
|
+
- [ ] ECA consumer key is correct
|
|
774
|
+
- [ ] Component attributes are kebab-case
|
|
775
|
+
- [ ] `lo2_returnTo` matches current filename
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## Quick Migration Workflow
|
|
780
|
+
|
|
781
|
+
1. **Extract from Beta page:**
|
|
782
|
+
|
|
783
|
+
- Domain (endpoint URL)
|
|
784
|
+
- Component name (Aura format)
|
|
785
|
+
- Attributes object
|
|
786
|
+
- Aura app name
|
|
787
|
+
|
|
788
|
+
2. **Transform:**
|
|
789
|
+
|
|
790
|
+
- Domain: `.lightning.force.com` → `.my.salesforce.com`
|
|
791
|
+
- Component: `c:name` → `c-name`
|
|
792
|
+
- Attributes: `{camelCase: val}` → `kebab-case="val"`
|
|
793
|
+
|
|
794
|
+
3. **Setup in Salesforce:**
|
|
795
|
+
|
|
796
|
+
- Create LO 2.0 App → get 18-char ID (optional)
|
|
797
|
+
- Create ECA → get consumer key
|
|
798
|
+
|
|
799
|
+
4. **Update HTML:**
|
|
800
|
+
|
|
801
|
+
- Change script src
|
|
802
|
+
- Add mount point div
|
|
803
|
+
- Add component tag with attributes
|
|
804
|
+
- Remove `$Lightning` code
|
|
805
|
+
|
|
806
|
+
5. **Add JavaScript:**
|
|
807
|
+
|
|
808
|
+
- Config constants
|
|
809
|
+
- mountLo20() function
|
|
810
|
+
- localStorage setup
|
|
811
|
+
- OAuth listeners
|
|
812
|
+
- Connect button handler
|
|
813
|
+
|
|
814
|
+
6. **Verify layout:**
|
|
815
|
+
|
|
816
|
+
- Component fills the same space as the Beta version (no clipping or scrolling)
|
|
817
|
+
- No fixed height on mount point or parent containers
|
|
818
|
+
- All form fields visible without scrolling within the component area
|
|
819
|
+
|
|
820
|
+
7. **Test:**
|
|
821
|
+
- Click "Connect to Salesforce"
|
|
822
|
+
- Complete OAuth
|
|
823
|
+
- Verify component renders with full content visible
|
|
824
|
+
- Check browser console for errors
|