central-auth-js-sdk 1.2.9 → 1.2.11
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/INTEGRATION_GUIDE.md +16 -2
- package/auth.js +8 -0
- package/core/api.js +39 -0
- package/core/flow.js +2 -0
- package/package.json +1 -1
- package/signup.js +69 -2
- package/styles.css +25 -0
- package/ui/screens/email-verify.js +43 -71
- package/ui/screens/index.js +58 -22
- package/ui/screens/org-details.js +75 -0
- package/ui/screens/org-fields.js +190 -0
package/INTEGRATION_GUIDE.md
CHANGED
|
@@ -354,9 +354,13 @@ const instance = startSignup({
|
|
|
354
354
|
|
|
355
355
|
## 5.3. Organization details (Team, Sub-team & Designation)
|
|
356
356
|
|
|
357
|
-
During
|
|
357
|
+
During registration (both email signup verification and Google/Social profile completion), the SDK renders **dynamic dropdown menus** populated with the **Teams**, **Sub-teams**, and **Designations** configured in the Central Auth Admin Interface for your entity.
|
|
358
358
|
|
|
359
|
-
|
|
359
|
+
### Dynamic Dropdown & Hierarchy Features
|
|
360
|
+
- **Dynamic Hierarchy**: The **Sub-team** dropdown dynamically updates its options according to the selected **Team** (displaying only sub-teams mapped to that parent team).
|
|
361
|
+
- **Auto-Resolution**: By default, the SDK automatically detects the entity domain from the user's email (e.g. `jane@jukshio.com` -> `jukshio.com`) and queries `GET /org/structure?entity_domain=...`.
|
|
362
|
+
- **Explicit Entity Configuration**: You can explicitly supply `entityName` (e.g. `'Juksho'`), `entityId`, or `entityDomain` in `startSignup` or `initializeAuth` to bind the signup wizard to a specific tenant.
|
|
363
|
+
- **Granular Visibility**: If your application does not need to collect organization details, or if you only want to collect a subset of them, use **`collectOrgDetails`** (or its alias **`showOrgDetails`**).
|
|
360
364
|
|
|
361
365
|
### Configuration options
|
|
362
366
|
|
|
@@ -392,6 +396,16 @@ const instance = startSignup({
|
|
|
392
396
|
});
|
|
393
397
|
```
|
|
394
398
|
|
|
399
|
+
#### 3. Bind to an entity explicitly (e.g. Juksho)
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
const instance = startSignup({
|
|
403
|
+
container: '#auth-container',
|
|
404
|
+
env: 'dev',
|
|
405
|
+
entityName: 'Juksho', // Loads teams, sub-teams, and designations for Juksho entity
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
395
409
|
#### 3. Set globally via `initializeAuth`
|
|
396
410
|
|
|
397
411
|
```javascript
|
package/auth.js
CHANGED
|
@@ -125,6 +125,10 @@ export function initializeAuth(config = {}) {
|
|
|
125
125
|
collectOrgDetails: true,
|
|
126
126
|
mobileVerification: 'india_only',
|
|
127
127
|
skipMobileOtp: false,
|
|
128
|
+
entityName: null,
|
|
129
|
+
entityId: null,
|
|
130
|
+
entityDomain: null,
|
|
131
|
+
orgStructure: null,
|
|
128
132
|
theme: null,
|
|
129
133
|
...config,
|
|
130
134
|
};
|
|
@@ -187,6 +191,10 @@ export function startSignup(config = {}) {
|
|
|
187
191
|
showOrgDetails: config.showOrgDetails ?? merged.showOrgDetails,
|
|
188
192
|
mobileVerification: config.mobileVerification ?? merged.mobileVerification,
|
|
189
193
|
skipMobileOtp: config.skipMobileOtp ?? merged.skipMobileOtp,
|
|
194
|
+
entityName: config.entityName ?? merged.entityName,
|
|
195
|
+
entityId: config.entityId ?? merged.entityId,
|
|
196
|
+
entityDomain: config.entityDomain ?? merged.entityDomain,
|
|
197
|
+
orgStructure: config.orgStructure ?? merged.orgStructure,
|
|
190
198
|
theme: merged.theme || undefined,
|
|
191
199
|
onSignupComplete: config.onSignupComplete ?? merged.onSignupComplete,
|
|
192
200
|
onContinue: config.onContinue ?? merged.onContinue,
|
package/core/api.js
CHANGED
|
@@ -82,6 +82,45 @@ export function createApi(baseUrl = DEFAULT_BASE, options = {}) {
|
|
|
82
82
|
return request('POST', '/signup/mobile/save', { email, mobile });
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
+
/** POST /signup/org/details — Save team, sub-team, designation for user (including social login) */
|
|
86
|
+
async signupSaveOrgDetails({ loginToken, email, userId, team, sub_team, designation }) {
|
|
87
|
+
const payload = {};
|
|
88
|
+
if (loginToken) payload.login_token = loginToken;
|
|
89
|
+
if (email) payload.email = email;
|
|
90
|
+
if (userId) payload.user_id = userId;
|
|
91
|
+
if (team !== undefined) payload.team = team;
|
|
92
|
+
if (sub_team !== undefined) payload.sub_team = sub_team;
|
|
93
|
+
if (designation !== undefined) payload.designation = designation;
|
|
94
|
+
return request('POST', '/signup/org/details', payload);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/** GET /org/structure — Fetch organization structure (teams, sub-teams, designations) */
|
|
98
|
+
async getOrgStructure({ entityId, entityName, entityDomain } = {}) {
|
|
99
|
+
const tryFetch = async (queryParam) => {
|
|
100
|
+
return request('GET', `/org/structure?${queryParam}`);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (entityId) {
|
|
104
|
+
try {
|
|
105
|
+
const res = await tryFetch(`entity_id=${encodeURIComponent(entityId)}`);
|
|
106
|
+
if (res && (res.teams || res.designations)) return res;
|
|
107
|
+
} catch (_) {}
|
|
108
|
+
}
|
|
109
|
+
if (entityDomain) {
|
|
110
|
+
try {
|
|
111
|
+
const res = await tryFetch(`entity_domain=${encodeURIComponent(entityDomain)}`);
|
|
112
|
+
if (res && (res.teams || res.designations)) return res;
|
|
113
|
+
} catch (_) {}
|
|
114
|
+
}
|
|
115
|
+
if (entityName) {
|
|
116
|
+
try {
|
|
117
|
+
const res = await tryFetch(`entity_name=${encodeURIComponent(entityName)}`);
|
|
118
|
+
if (res && (res.teams || res.designations)) return res;
|
|
119
|
+
} catch (_) {}
|
|
120
|
+
}
|
|
121
|
+
return { teams: [], designations: [] };
|
|
122
|
+
},
|
|
123
|
+
|
|
85
124
|
/** POST /signup/password/set — Password step */
|
|
86
125
|
async signupPasswordSet(email, password) {
|
|
87
126
|
return request('POST', '/signup/password/set', { email, password });
|
package/core/flow.js
CHANGED
|
@@ -8,6 +8,7 @@ export const STEPS = {
|
|
|
8
8
|
EMAIL_VERIFY: 'emailVerify',
|
|
9
9
|
MOBILE: 'mobile',
|
|
10
10
|
MOBILE_VERIFY: 'mobileVerify',
|
|
11
|
+
ORG_DETAILS: 'orgDetails',
|
|
11
12
|
PASSWORD: 'password',
|
|
12
13
|
FACE: 'face',
|
|
13
14
|
SUCCESS: 'success',
|
|
@@ -32,6 +33,7 @@ const STEP_ORDER = {
|
|
|
32
33
|
STEPS.EMAIL,
|
|
33
34
|
STEPS.MOBILE,
|
|
34
35
|
STEPS.MOBILE_VERIFY,
|
|
36
|
+
STEPS.ORG_DETAILS,
|
|
35
37
|
STEPS.FACE,
|
|
36
38
|
STEPS.SUCCESS,
|
|
37
39
|
],
|
package/package.json
CHANGED
package/signup.js
CHANGED
|
@@ -74,6 +74,10 @@ export function mount(container, options = {}) {
|
|
|
74
74
|
mobileVerification = 'india_only',
|
|
75
75
|
collectOrgDetails = true,
|
|
76
76
|
showOrgDetails,
|
|
77
|
+
entityName = null,
|
|
78
|
+
entityId = null,
|
|
79
|
+
entityDomain = null,
|
|
80
|
+
orgStructure = null,
|
|
77
81
|
} = options;
|
|
78
82
|
|
|
79
83
|
const flow = new FlowController();
|
|
@@ -84,6 +88,10 @@ export function mount(container, options = {}) {
|
|
|
84
88
|
if (showOrgDetails !== undefined) {
|
|
85
89
|
state.set('showOrgDetails', showOrgDetails);
|
|
86
90
|
}
|
|
91
|
+
if (entityName) state.set('entityName', entityName);
|
|
92
|
+
if (entityId) state.set('entityId', entityId);
|
|
93
|
+
if (entityDomain) state.set('entityDomain', entityDomain);
|
|
94
|
+
if (orgStructure) state.set('orgStructure', orgStructure);
|
|
87
95
|
const api = createApi(apiBaseUrl);
|
|
88
96
|
|
|
89
97
|
const layout = createLayout(el, () => {});
|
|
@@ -96,7 +104,18 @@ export function mount(container, options = {}) {
|
|
|
96
104
|
state.set('loginToken', oauth.loginToken);
|
|
97
105
|
state.set('socialProvider', oauth.provider);
|
|
98
106
|
if (oauth.email) state.set('email', oauth.email);
|
|
107
|
+
if (oauth.entityDomain) state.set('entityDomain', oauth.entityDomain);
|
|
99
108
|
clearOAuthParams();
|
|
109
|
+
const socialDomain = oauth.entityDomain || (oauth.email && oauth.email.includes('@') ? oauth.email.split('@')[1] : null);
|
|
110
|
+
api.getOrgStructure({
|
|
111
|
+
entityId: state.get('entityId'),
|
|
112
|
+
entityName: state.get('entityName'),
|
|
113
|
+
entityDomain: socialDomain,
|
|
114
|
+
}).then((res) => {
|
|
115
|
+
if (res && (res.teams || res.designations)) {
|
|
116
|
+
state.set('orgStructure', res);
|
|
117
|
+
}
|
|
118
|
+
}).catch(() => {});
|
|
100
119
|
flow.setSocialFlow();
|
|
101
120
|
if (oauth.nextStep === 'FR_ENROLLMENT') {
|
|
102
121
|
flow.setStep(STEPS.FACE);
|
|
@@ -131,6 +150,16 @@ export function mount(container, options = {}) {
|
|
|
131
150
|
if (data.lastName) state.set('lastName', data.lastName);
|
|
132
151
|
}
|
|
133
152
|
state.set('email', email);
|
|
153
|
+
const emailDomain = state.get('entityDomain') || (email && email.includes('@') ? email.split('@')[1] : null);
|
|
154
|
+
api.getOrgStructure({
|
|
155
|
+
entityId: state.get('entityId'),
|
|
156
|
+
entityName: state.get('entityName'),
|
|
157
|
+
entityDomain: emailDomain,
|
|
158
|
+
}).then((res) => {
|
|
159
|
+
if (res && (res.teams || res.designations)) {
|
|
160
|
+
state.set('orgStructure', res);
|
|
161
|
+
}
|
|
162
|
+
}).catch(() => {});
|
|
134
163
|
try {
|
|
135
164
|
const res = await api.signupStart(email);
|
|
136
165
|
const apiStep = mapNextStep(res?.next_step);
|
|
@@ -194,9 +223,14 @@ export function mount(container, options = {}) {
|
|
|
194
223
|
);
|
|
195
224
|
|
|
196
225
|
if (shouldSkipOtp) {
|
|
197
|
-
// Skip OTP verification, save mobile directly and advance
|
|
226
|
+
// Skip OTP verification, save mobile directly and advance
|
|
198
227
|
await api.signupMobileSave(state.get('email'), mobile);
|
|
199
|
-
|
|
228
|
+
if (isSocial()) {
|
|
229
|
+
const skipOrg = state.get('collectOrgDetails') === false || state.get('showOrgDetails') === false;
|
|
230
|
+
flow.setStep(skipOrg ? (enableFaceRegistration ? STEPS.FACE : STEPS.SUCCESS) : STEPS.ORG_DETAILS);
|
|
231
|
+
} else {
|
|
232
|
+
flow.setStep(STEPS.PASSWORD);
|
|
233
|
+
}
|
|
200
234
|
return;
|
|
201
235
|
}
|
|
202
236
|
|
|
@@ -246,6 +280,10 @@ export function mount(container, options = {}) {
|
|
|
246
280
|
const mobile = state.get('mobile');
|
|
247
281
|
if (provider === 'microsoft') await api.microsoftMobileVerify(token, mobile, otp, appIdentifier);
|
|
248
282
|
else await api.googleMobileVerify(token, mobile, otp, appIdentifier);
|
|
283
|
+
|
|
284
|
+
const skipOrg = state.get('collectOrgDetails') === false || state.get('showOrgDetails') === false;
|
|
285
|
+
flow.setStep(skipOrg ? (enableFaceRegistration ? STEPS.FACE : STEPS.SUCCESS) : STEPS.ORG_DETAILS);
|
|
286
|
+
return;
|
|
249
287
|
} else {
|
|
250
288
|
await api.signupMobileVerify(state.get('email'), state.get('mobile'), otp);
|
|
251
289
|
}
|
|
@@ -257,6 +295,34 @@ export function mount(container, options = {}) {
|
|
|
257
295
|
}
|
|
258
296
|
}
|
|
259
297
|
|
|
298
|
+
async function handleOrgDetailsSubmit(data) {
|
|
299
|
+
state.clearError();
|
|
300
|
+
state.setMultiple({
|
|
301
|
+
team: data.team,
|
|
302
|
+
subTeam: data.subTeam,
|
|
303
|
+
designation: data.designation,
|
|
304
|
+
});
|
|
305
|
+
try {
|
|
306
|
+
await api.signupSaveOrgDetails({
|
|
307
|
+
loginToken: state.get('loginToken'),
|
|
308
|
+
email: state.get('email'),
|
|
309
|
+
userId: state.get('userId'),
|
|
310
|
+
team: data.team,
|
|
311
|
+
sub_team: data.subTeam,
|
|
312
|
+
designation: data.designation,
|
|
313
|
+
});
|
|
314
|
+
if (enableFaceRegistration) {
|
|
315
|
+
flow.setStep(STEPS.FACE);
|
|
316
|
+
} else {
|
|
317
|
+
flow.setStep(STEPS.SUCCESS);
|
|
318
|
+
}
|
|
319
|
+
} catch (err) {
|
|
320
|
+
const msg = err?.data?.error || err?.message || 'Failed to save organization details';
|
|
321
|
+
state.set('error', msg);
|
|
322
|
+
showError(msg);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
260
326
|
async function handlePasswordSubmit(password) {
|
|
261
327
|
state.clearError();
|
|
262
328
|
state.set('password', password);
|
|
@@ -330,6 +396,7 @@ export function mount(container, options = {}) {
|
|
|
330
396
|
onMobileSubmit: handleMobileSubmit,
|
|
331
397
|
onMobileVerify: handleMobileVerify,
|
|
332
398
|
onMobileResend: handleMobileResend,
|
|
399
|
+
onOrgDetailsSubmit: handleOrgDetailsSubmit,
|
|
333
400
|
onPasswordSubmit: handlePasswordSubmit,
|
|
334
401
|
onFaceSubmit: handleFaceSubmit,
|
|
335
402
|
onBack: () => {
|
package/styles.css
CHANGED
|
@@ -557,6 +557,31 @@
|
|
|
557
557
|
box-shadow: 0 0 0 3px var(--wf-primary-glow), inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
select.wf-input,
|
|
561
|
+
.wf-select {
|
|
562
|
+
appearance: none;
|
|
563
|
+
-webkit-appearance: none;
|
|
564
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
|
565
|
+
background-repeat: no-repeat;
|
|
566
|
+
background-position: right 0.875rem center;
|
|
567
|
+
background-size: 1rem 1rem;
|
|
568
|
+
padding-right: 2.25rem;
|
|
569
|
+
cursor: pointer;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
select.wf-input option,
|
|
573
|
+
.wf-select option {
|
|
574
|
+
background: var(--wf-card);
|
|
575
|
+
color: var(--wf-fg);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
select.wf-input:disabled,
|
|
579
|
+
.wf-select:disabled {
|
|
580
|
+
opacity: 0.6;
|
|
581
|
+
cursor: not-allowed;
|
|
582
|
+
background-color: var(--wf-muted-bg);
|
|
583
|
+
}
|
|
584
|
+
|
|
560
585
|
.wf-input::placeholder {
|
|
561
586
|
color: var(--wf-muted);
|
|
562
587
|
}
|
|
@@ -1,63 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Email Verification Screen — OTP + First/Last name
|
|
2
|
+
* Email Verification Screen — OTP + First/Last name + Org Hierarchy Dropdowns
|
|
3
3
|
* v2.0 — warm, modern design
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
6
|
+
import { renderOrgFieldsHtml, attachOrgFieldsLogic } from "./org-fields.js";
|
|
7
|
+
|
|
8
|
+
const MAIL_CHECK = "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z";
|
|
9
|
+
const ARROW_LEFT = "M19 12H5m7 7l-7-7 7-7";
|
|
10
|
+
const ARROW_RIGHT = "M5 12h14m-7-7l7 7-7 7";
|
|
9
11
|
|
|
10
12
|
export function createEmailVerifyScreen(email, onNext, onBack, prefill = {}, options = {}) {
|
|
11
|
-
const wrap = document.createElement(
|
|
12
|
-
wrap.className =
|
|
13
|
+
const wrap = document.createElement("div");
|
|
14
|
+
wrap.className = "wf-screen";
|
|
13
15
|
|
|
14
16
|
const orgOpt = options.collectOrgDetails !== undefined ? options.collectOrgDetails : options.showOrgDetails;
|
|
15
|
-
const showTeam = typeof orgOpt ===
|
|
16
|
-
const showSubTeam = typeof orgOpt ===
|
|
17
|
-
const showDesignation = typeof orgOpt ===
|
|
17
|
+
const showTeam = typeof orgOpt === "object" && orgOpt !== null ? Boolean(orgOpt.team) : orgOpt !== false;
|
|
18
|
+
const showSubTeam = typeof orgOpt === "object" && orgOpt !== null ? Boolean(orgOpt.subTeam) : orgOpt !== false;
|
|
19
|
+
const showDesignation = typeof orgOpt === "object" && orgOpt !== null ? Boolean(orgOpt.designation) : orgOpt !== false;
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<input type="text" id="wf-team" placeholder="Engineering" class="wf-input" autocomplete="organization" />
|
|
26
|
-
</div>
|
|
27
|
-
<div class="wf-field">
|
|
28
|
-
<label class="wf-label" for="wf-sub-team">Sub-team</label>
|
|
29
|
-
<input type="text" id="wf-sub-team" placeholder="Backend" class="wf-input" />
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
`;
|
|
33
|
-
} else if (showTeam) {
|
|
34
|
-
orgFieldsHtml += `
|
|
35
|
-
<div class="wf-field">
|
|
36
|
-
<label class="wf-label" for="wf-team">Team</label>
|
|
37
|
-
<input type="text" id="wf-team" placeholder="Engineering" class="wf-input" autocomplete="organization" />
|
|
38
|
-
</div>
|
|
39
|
-
`;
|
|
40
|
-
} else if (showSubTeam) {
|
|
41
|
-
orgFieldsHtml += `
|
|
42
|
-
<div class="wf-field">
|
|
43
|
-
<label class="wf-label" for="wf-sub-team">Sub-team</label>
|
|
44
|
-
<input type="text" id="wf-sub-team" placeholder="Backend" class="wf-input" />
|
|
45
|
-
</div>
|
|
46
|
-
`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (showDesignation) {
|
|
50
|
-
orgFieldsHtml += `
|
|
51
|
-
<div class="wf-field">
|
|
52
|
-
<label class="wf-label" for="wf-designation">Designation</label>
|
|
53
|
-
<input type="text" id="wf-designation" placeholder="Software Engineer" class="wf-input" autocomplete="organization-title" />
|
|
54
|
-
</div>
|
|
55
|
-
`;
|
|
56
|
-
}
|
|
21
|
+
const orgFieldsHtml = renderOrgFieldsHtml({
|
|
22
|
+
showTeam,
|
|
23
|
+
showSubTeam,
|
|
24
|
+
showDesignation,
|
|
25
|
+
prefix: "wf-",
|
|
26
|
+
});
|
|
57
27
|
|
|
58
28
|
const otpInputs = Array(6).fill(0).map((_, i) => `
|
|
59
29
|
<input type="text" inputmode="numeric" maxlength="1" class="wf-otp-input" id="wf-email-otp-${i}" data-idx="${i}" autocomplete="one-time-code" />
|
|
60
|
-
`).join(
|
|
30
|
+
`).join("");
|
|
61
31
|
|
|
62
32
|
wrap.innerHTML = `
|
|
63
33
|
<div class="wf-screen-header">
|
|
@@ -98,57 +68,59 @@ export function createEmailVerifyScreen(email, onNext, onBack, prefill = {}, opt
|
|
|
98
68
|
</p>
|
|
99
69
|
`;
|
|
100
70
|
|
|
101
|
-
const form = wrap.querySelector(
|
|
102
|
-
const otpEls = wrap.querySelectorAll(
|
|
103
|
-
const firstName = wrap.querySelector(
|
|
104
|
-
const lastName = wrap.querySelector(
|
|
105
|
-
const team = wrap.querySelector('#wf-team');
|
|
106
|
-
const subTeam = wrap.querySelector('#wf-sub-team');
|
|
107
|
-
const designation = wrap.querySelector('#wf-designation');
|
|
71
|
+
const form = wrap.querySelector("#wf-email-verify-form");
|
|
72
|
+
const otpEls = wrap.querySelectorAll(".wf-otp-input");
|
|
73
|
+
const firstName = wrap.querySelector("#wf-first-name");
|
|
74
|
+
const lastName = wrap.querySelector("#wf-last-name");
|
|
108
75
|
|
|
109
76
|
if (prefill.firstName) firstName.value = prefill.firstName;
|
|
110
77
|
if (prefill.lastName) lastName.value = prefill.lastName;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
78
|
+
|
|
79
|
+
// Initialize and attach dynamic org dropdowns logic
|
|
80
|
+
const orgLogic = attachOrgFieldsLogic({
|
|
81
|
+
wrap,
|
|
82
|
+
prefix: "wf-",
|
|
83
|
+
prefill,
|
|
84
|
+
options,
|
|
85
|
+
});
|
|
114
86
|
|
|
115
87
|
function handleOtpPaste(e) {
|
|
116
88
|
e.preventDefault();
|
|
117
|
-
const pasted = (e.clipboardData || window.clipboardData).getData(
|
|
118
|
-
const digits = pasted.replace(/\D/g,
|
|
89
|
+
const pasted = (e.clipboardData || window.clipboardData).getData("text");
|
|
90
|
+
const digits = pasted.replace(/\D/g, "").slice(0, 6).split("");
|
|
119
91
|
digits.forEach((d, i) => { if (otpEls[i]) otpEls[i].value = d; });
|
|
120
92
|
const nextIdx = Math.min(digits.length, 5);
|
|
121
93
|
if (otpEls[nextIdx]) otpEls[nextIdx].focus();
|
|
122
94
|
}
|
|
123
95
|
|
|
124
96
|
otpEls.forEach((el, i) => {
|
|
125
|
-
el.addEventListener(
|
|
126
|
-
el.addEventListener(
|
|
127
|
-
const v = e.target.value.replace(/\D/g,
|
|
97
|
+
el.addEventListener("paste", handleOtpPaste);
|
|
98
|
+
el.addEventListener("input", (e) => {
|
|
99
|
+
const v = e.target.value.replace(/\D/g, "").slice(0, 1);
|
|
128
100
|
e.target.value = v;
|
|
129
101
|
if (v && i < 5) otpEls[i + 1].focus();
|
|
130
102
|
});
|
|
131
|
-
el.addEventListener(
|
|
132
|
-
if (e.key ===
|
|
103
|
+
el.addEventListener("keydown", (e) => {
|
|
104
|
+
if (e.key === "Backspace" && !e.target.value && i > 0) otpEls[i - 1].focus();
|
|
133
105
|
});
|
|
134
106
|
});
|
|
135
107
|
|
|
136
|
-
form.addEventListener(
|
|
108
|
+
form.addEventListener("submit", (e) => {
|
|
137
109
|
e.preventDefault();
|
|
138
|
-
const code = [...otpEls].map((o) => o.value).join(
|
|
110
|
+
const code = [...otpEls].map((o) => o.value).join("");
|
|
139
111
|
if (code.length === 6 && firstName.value.trim() && lastName.value.trim()) {
|
|
140
112
|
onNext({
|
|
141
113
|
otp: code,
|
|
142
114
|
firstName: firstName.value.trim(),
|
|
143
115
|
lastName: lastName.value.trim(),
|
|
144
|
-
team:
|
|
145
|
-
subTeam:
|
|
146
|
-
designation:
|
|
116
|
+
team: orgLogic.getTeam(),
|
|
117
|
+
subTeam: orgLogic.getSubTeam(),
|
|
118
|
+
designation: orgLogic.getDesignation(),
|
|
147
119
|
});
|
|
148
120
|
}
|
|
149
121
|
});
|
|
150
122
|
|
|
151
|
-
wrap.querySelector('[data-action="back"]').addEventListener(
|
|
123
|
+
wrap.querySelector('[data-action="back"]').addEventListener("click", onBack);
|
|
152
124
|
|
|
153
125
|
return wrap;
|
|
154
126
|
}
|
package/ui/screens/index.js
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
* Callbacks are async API handlers (called from index.js)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { STEPS } from
|
|
7
|
-
import { createEmailEntryScreen } from
|
|
8
|
-
import { createEmailVerifyScreen } from
|
|
9
|
-
import { createMobileEntryScreen } from
|
|
10
|
-
import { createMobileVerifyScreen } from
|
|
11
|
-
import { createPasswordSetupScreen } from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
6
|
+
import { STEPS } from "../../core/flow.js";
|
|
7
|
+
import { createEmailEntryScreen } from "./email-entry.js";
|
|
8
|
+
import { createEmailVerifyScreen } from "./email-verify.js";
|
|
9
|
+
import { createMobileEntryScreen } from "./mobile-entry.js";
|
|
10
|
+
import { createMobileVerifyScreen } from "./mobile-verify.js";
|
|
11
|
+
import { createPasswordSetupScreen } from "./password-setup.js";
|
|
12
|
+
import { createOrgDetailsScreen } from "./org-details.js";
|
|
13
|
+
import { createFaceRegistrationScreen } from "./face-registration.js";
|
|
14
|
+
import { createSuccessScreen } from "./success.js";
|
|
14
15
|
|
|
15
16
|
export function createScreen(step, state, flow, callbacks) {
|
|
16
17
|
const {
|
|
@@ -19,6 +20,7 @@ export function createScreen(step, state, flow, callbacks) {
|
|
|
19
20
|
onMobileSubmit,
|
|
20
21
|
onMobileVerify,
|
|
21
22
|
onMobileResend,
|
|
23
|
+
onOrgDetailsSubmit,
|
|
22
24
|
onPasswordSubmit,
|
|
23
25
|
onFaceSubmit,
|
|
24
26
|
onBack,
|
|
@@ -29,45 +31,79 @@ export function createScreen(step, state, flow, callbacks) {
|
|
|
29
31
|
api,
|
|
30
32
|
} = callbacks;
|
|
31
33
|
|
|
34
|
+
const email = state.get("email") || "";
|
|
35
|
+
const derivedDomain = email.includes("@") ? email.split("@")[1] : null;
|
|
36
|
+
const entityDomain = state.get("entityDomain") || derivedDomain;
|
|
37
|
+
const entityName = state.get("entityName") || null;
|
|
38
|
+
const entityId = state.get("entityId") || null;
|
|
39
|
+
const orgStructure = state.get("orgStructure") || null;
|
|
40
|
+
|
|
32
41
|
switch (step) {
|
|
33
42
|
case STEPS.EMAIL:
|
|
34
43
|
return createEmailEntryScreen(onEmailSubmit, onSocialSignUp, onSwitchToLogin, onBackToApp);
|
|
35
44
|
|
|
36
45
|
case STEPS.EMAIL_VERIFY:
|
|
37
46
|
return createEmailVerifyScreen(
|
|
38
|
-
|
|
47
|
+
email,
|
|
39
48
|
(data) => onEmailVerify(data),
|
|
40
49
|
onBack,
|
|
41
50
|
{
|
|
42
|
-
firstName: state.get(
|
|
43
|
-
lastName: state.get(
|
|
44
|
-
team: state.get(
|
|
45
|
-
subTeam: state.get(
|
|
46
|
-
designation: state.get(
|
|
51
|
+
firstName: state.get("firstName") || "",
|
|
52
|
+
lastName: state.get("lastName") || "",
|
|
53
|
+
team: state.get("team") || "",
|
|
54
|
+
subTeam: state.get("subTeam") || "",
|
|
55
|
+
designation: state.get("designation") || "",
|
|
47
56
|
},
|
|
48
57
|
{
|
|
49
|
-
collectOrgDetails: state.get(
|
|
50
|
-
showOrgDetails: state.get(
|
|
58
|
+
collectOrgDetails: state.get("collectOrgDetails"),
|
|
59
|
+
showOrgDetails: state.get("showOrgDetails"),
|
|
60
|
+
api,
|
|
61
|
+
entityId,
|
|
62
|
+
entityName,
|
|
63
|
+
entityDomain,
|
|
64
|
+
orgStructure,
|
|
65
|
+
onOrgLoaded: (data) => state.set("orgStructure", data),
|
|
51
66
|
}
|
|
52
67
|
);
|
|
53
68
|
|
|
54
69
|
case STEPS.MOBILE:
|
|
55
70
|
return createMobileEntryScreen(onMobileSubmit, onBack, {
|
|
56
|
-
skipMobileOtp: state.get(
|
|
57
|
-
mobileVerification: state.get(
|
|
71
|
+
skipMobileOtp: state.get("skipMobileOtp"),
|
|
72
|
+
mobileVerification: state.get("mobileVerification"),
|
|
58
73
|
});
|
|
59
74
|
|
|
60
75
|
case STEPS.MOBILE_VERIFY:
|
|
61
76
|
return createMobileVerifyScreen(
|
|
62
|
-
state.get(
|
|
77
|
+
state.get("mobile"),
|
|
63
78
|
(otp, channel) => onMobileVerify(otp, channel),
|
|
64
79
|
onBack,
|
|
65
80
|
{
|
|
66
|
-
channel: state.get(
|
|
81
|
+
channel: state.get("mobileChannel") || "sms",
|
|
67
82
|
onResend: onMobileResend,
|
|
68
83
|
}
|
|
69
84
|
);
|
|
70
85
|
|
|
86
|
+
case STEPS.ORG_DETAILS:
|
|
87
|
+
return createOrgDetailsScreen(
|
|
88
|
+
(data) => onOrgDetailsSubmit(data),
|
|
89
|
+
onBack,
|
|
90
|
+
{
|
|
91
|
+
team: state.get("team") || "",
|
|
92
|
+
subTeam: state.get("subTeam") || "",
|
|
93
|
+
designation: state.get("designation") || "",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
collectOrgDetails: state.get("collectOrgDetails"),
|
|
97
|
+
showOrgDetails: state.get("showOrgDetails"),
|
|
98
|
+
api,
|
|
99
|
+
entityId,
|
|
100
|
+
entityName,
|
|
101
|
+
entityDomain,
|
|
102
|
+
orgStructure,
|
|
103
|
+
onOrgLoaded: (data) => state.set("orgStructure", data),
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
71
107
|
case STEPS.PASSWORD:
|
|
72
108
|
return createPasswordSetupScreen(onPasswordSubmit, onBack);
|
|
73
109
|
|
|
@@ -79,9 +115,9 @@ export function createScreen(step, state, flow, callbacks) {
|
|
|
79
115
|
);
|
|
80
116
|
|
|
81
117
|
case STEPS.SUCCESS:
|
|
82
|
-
return createSuccessScreen(typeof onContinue ===
|
|
118
|
+
return createSuccessScreen(typeof onContinue === "function" ? onContinue : () => {});
|
|
83
119
|
|
|
84
120
|
default:
|
|
85
|
-
return document.createElement(
|
|
121
|
+
return document.createElement("div");
|
|
86
122
|
}
|
|
87
123
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organization Details Screen — Team, Sub-team & Designation Dropdowns
|
|
3
|
+
* v2.0 — warm, modern design for onboarding & social signup
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { renderOrgFieldsHtml, attachOrgFieldsLogic } from "./org-fields.js";
|
|
7
|
+
|
|
8
|
+
const BRIEFCASE_ICON = "M20 7h-4V4c0-1.1-.9-2-2-2h-4c-1.1 0-2 .9-2 2v3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zM10 4h4v3h-4V4zm10 16H4V9h16v11z";
|
|
9
|
+
const ARROW_LEFT = "M19 12H5m7 7l-7-7 7-7";
|
|
10
|
+
const ARROW_RIGHT = "M5 12h14m-7-7l7 7-7 7";
|
|
11
|
+
|
|
12
|
+
export function createOrgDetailsScreen(onNext, onBack, prefill = {}, options = {}) {
|
|
13
|
+
const wrap = document.createElement("div");
|
|
14
|
+
wrap.className = "wf-screen";
|
|
15
|
+
|
|
16
|
+
const orgOpt = options.collectOrgDetails !== undefined ? options.collectOrgDetails : options.showOrgDetails;
|
|
17
|
+
const showTeam = typeof orgOpt === "object" && orgOpt !== null ? Boolean(orgOpt.team) : orgOpt !== false;
|
|
18
|
+
const showSubTeam = typeof orgOpt === "object" && orgOpt !== null ? Boolean(orgOpt.subTeam) : orgOpt !== false;
|
|
19
|
+
const showDesignation = typeof orgOpt === "object" && orgOpt !== null ? Boolean(orgOpt.designation) : orgOpt !== false;
|
|
20
|
+
|
|
21
|
+
const fieldsHtml = renderOrgFieldsHtml({
|
|
22
|
+
showTeam,
|
|
23
|
+
showSubTeam,
|
|
24
|
+
showDesignation,
|
|
25
|
+
prefix: "wf-org-",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
wrap.innerHTML = `
|
|
29
|
+
<div class="wf-screen-header">
|
|
30
|
+
<div class="wf-icon-box">
|
|
31
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${BRIEFCASE_ICON}"/></svg>
|
|
32
|
+
</div>
|
|
33
|
+
<h2 class="wf-screen-title">Complete your profile</h2>
|
|
34
|
+
<p class="wf-screen-desc">Select your team and role to personalize your workspace</p>
|
|
35
|
+
</div>
|
|
36
|
+
<form class="wf-form" id="wf-org-form">
|
|
37
|
+
${fieldsHtml}
|
|
38
|
+
<div class="wf-btn-row">
|
|
39
|
+
<button type="button" class="wf-btn wf-btn-outline wf-btn-lg" data-action="back" aria-label="Go back">
|
|
40
|
+
<svg class="wf-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ARROW_LEFT}"/></svg>
|
|
41
|
+
</button>
|
|
42
|
+
<button type="submit" class="wf-btn wf-btn-primary wf-btn-lg wf-btn-flex" id="wf-btn-org-submit">
|
|
43
|
+
Continue
|
|
44
|
+
<svg class="wf-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ARROW_RIGHT}"/></svg>
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</form>
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const form = wrap.querySelector("#wf-org-form");
|
|
51
|
+
|
|
52
|
+
// Initialize and attach dynamic org dropdowns logic
|
|
53
|
+
const orgLogic = attachOrgFieldsLogic({
|
|
54
|
+
wrap,
|
|
55
|
+
prefix: "wf-org-",
|
|
56
|
+
prefill,
|
|
57
|
+
options,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
form.addEventListener("submit", (e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
onNext({
|
|
63
|
+
team: orgLogic.getTeam(),
|
|
64
|
+
subTeam: orgLogic.getSubTeam(),
|
|
65
|
+
designation: orgLogic.getDesignation(),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const backBtn = wrap.querySelector('button[data-action="back"]');
|
|
70
|
+
if (backBtn && typeof onBack === "function") {
|
|
71
|
+
backBtn.addEventListener("click", onBack);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return wrap;
|
|
75
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Org Fields (Team, Sub-team, Designation) Dropdowns Component
|
|
3
|
+
* Handles dynamic fetching from /org/structure and cascaded sub-team filtering.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function escapeHtml(str) {
|
|
7
|
+
if (!str) return "";
|
|
8
|
+
return String(str)
|
|
9
|
+
.replace(/&/g, "&")
|
|
10
|
+
.replace(/</g, "<")
|
|
11
|
+
.replace(/>/g, ">")
|
|
12
|
+
.replace(/"/g, """);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function renderOrgFieldsHtml({
|
|
16
|
+
showTeam = true,
|
|
17
|
+
showSubTeam = true,
|
|
18
|
+
showDesignation = true,
|
|
19
|
+
prefix = "wf-",
|
|
20
|
+
} = {}) {
|
|
21
|
+
let html = "";
|
|
22
|
+
|
|
23
|
+
if (showTeam && showSubTeam) {
|
|
24
|
+
html += `
|
|
25
|
+
<div class="wf-row-2">
|
|
26
|
+
<div class="wf-field">
|
|
27
|
+
<label class="wf-label" for="${prefix}team">Team</label>
|
|
28
|
+
<select id="${prefix}team" class="wf-input wf-select" autocomplete="organization">
|
|
29
|
+
<option value="">Select Team</option>
|
|
30
|
+
</select>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="wf-field">
|
|
33
|
+
<label class="wf-label" for="${prefix}sub-team">Sub-team</label>
|
|
34
|
+
<select id="${prefix}sub-team" class="wf-input wf-select" disabled>
|
|
35
|
+
<option value="">Select a team first</option>
|
|
36
|
+
</select>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
`;
|
|
40
|
+
} else if (showTeam) {
|
|
41
|
+
html += `
|
|
42
|
+
<div class="wf-field">
|
|
43
|
+
<label class="wf-label" for="${prefix}team">Team</label>
|
|
44
|
+
<select id="${prefix}team" class="wf-input wf-select" autocomplete="organization">
|
|
45
|
+
<option value="">Select Team</option>
|
|
46
|
+
</select>
|
|
47
|
+
</div>
|
|
48
|
+
`;
|
|
49
|
+
} else if (showSubTeam) {
|
|
50
|
+
html += `
|
|
51
|
+
<div class="wf-field">
|
|
52
|
+
<label class="wf-label" for="${prefix}sub-team">Sub-team</label>
|
|
53
|
+
<select id="${prefix}sub-team" class="wf-input wf-select" disabled>
|
|
54
|
+
<option value="">Select a team first</option>
|
|
55
|
+
</select>
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (showDesignation) {
|
|
61
|
+
html += `
|
|
62
|
+
<div class="wf-field">
|
|
63
|
+
<label class="wf-label" for="${prefix}designation">Designation</label>
|
|
64
|
+
<select id="${prefix}designation" class="wf-input wf-select" autocomplete="organization-title">
|
|
65
|
+
<option value="">Select Designation</option>
|
|
66
|
+
</select>
|
|
67
|
+
</div>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return html;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function attachOrgFieldsLogic({
|
|
75
|
+
wrap,
|
|
76
|
+
prefix = "wf-",
|
|
77
|
+
prefill = {},
|
|
78
|
+
options = {},
|
|
79
|
+
}) {
|
|
80
|
+
const teamEl = wrap.querySelector(`#${prefix}team`);
|
|
81
|
+
const subTeamEl = wrap.querySelector(`#${prefix}sub-team`);
|
|
82
|
+
const designationEl = wrap.querySelector(`#${prefix}designation`);
|
|
83
|
+
|
|
84
|
+
let currentTeams = [];
|
|
85
|
+
let currentDesignations = [];
|
|
86
|
+
|
|
87
|
+
function updateSubTeams(selectedTeamName, selectedSubTeamValue = "") {
|
|
88
|
+
if (!subTeamEl) return;
|
|
89
|
+
if (!selectedTeamName) {
|
|
90
|
+
subTeamEl.innerHTML = '<option value="">Select a team first</option>';
|
|
91
|
+
subTeamEl.disabled = true;
|
|
92
|
+
subTeamEl.value = "";
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const team = currentTeams.find((t) => t.name === selectedTeamName || t.id === selectedTeamName);
|
|
97
|
+
const subTeams = team && Array.isArray(team.sub_teams) ? team.sub_teams : [];
|
|
98
|
+
|
|
99
|
+
if (subTeams.length === 0) {
|
|
100
|
+
subTeamEl.innerHTML = '<option value="">No sub-teams</option>';
|
|
101
|
+
subTeamEl.disabled = true;
|
|
102
|
+
subTeamEl.value = "";
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let opts = '<option value="">Select Sub-team</option>';
|
|
107
|
+
subTeams.forEach((st) => {
|
|
108
|
+
opts += `<option value="${escapeHtml(st.name)}">${escapeHtml(st.name)}</option>`;
|
|
109
|
+
});
|
|
110
|
+
subTeamEl.innerHTML = opts;
|
|
111
|
+
subTeamEl.disabled = false;
|
|
112
|
+
|
|
113
|
+
if (selectedSubTeamValue) {
|
|
114
|
+
subTeamEl.value = selectedSubTeamValue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function populate(orgData) {
|
|
119
|
+
if (!orgData) return;
|
|
120
|
+
currentTeams = Array.isArray(orgData.teams) ? orgData.teams : [];
|
|
121
|
+
currentDesignations = Array.isArray(orgData.designations) ? orgData.designations : [];
|
|
122
|
+
|
|
123
|
+
// Populate Teams
|
|
124
|
+
if (teamEl) {
|
|
125
|
+
const prevVal = teamEl.value || prefill.team || "";
|
|
126
|
+
let opts = '<option value="">Select Team</option>';
|
|
127
|
+
currentTeams.forEach((t) => {
|
|
128
|
+
opts += `<option value="${escapeHtml(t.name)}">${escapeHtml(t.name)}</option>`;
|
|
129
|
+
});
|
|
130
|
+
teamEl.innerHTML = opts;
|
|
131
|
+
if (prevVal) {
|
|
132
|
+
teamEl.value = prevVal;
|
|
133
|
+
updateSubTeams(prevVal, subTeamEl?.value || prefill.subTeam || "");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Populate Designations
|
|
138
|
+
if (designationEl) {
|
|
139
|
+
const prevVal = designationEl.value || prefill.designation || "";
|
|
140
|
+
let opts = '<option value="">Select Designation</option>';
|
|
141
|
+
currentDesignations.forEach((d) => {
|
|
142
|
+
opts += `<option value="${escapeHtml(d.name)}">${escapeHtml(d.name)}</option>`;
|
|
143
|
+
});
|
|
144
|
+
designationEl.innerHTML = opts;
|
|
145
|
+
if (prevVal) {
|
|
146
|
+
designationEl.value = prevVal;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle Team change -> cascade Sub-teams
|
|
152
|
+
if (teamEl) {
|
|
153
|
+
teamEl.addEventListener("change", () => {
|
|
154
|
+
updateSubTeams(teamEl.value);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// If org structure was already loaded, populate immediately
|
|
159
|
+
if (options.orgStructure) {
|
|
160
|
+
populate(options.orgStructure);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// If api is available, ensure we have the latest org structure
|
|
164
|
+
if (options.api && typeof options.api.getOrgStructure === "function") {
|
|
165
|
+
const { entityId, entityName, entityDomain, onOrgLoaded } = options;
|
|
166
|
+
if (entityId || entityName || entityDomain) {
|
|
167
|
+
options.api.getOrgStructure({ entityId, entityName, entityDomain })
|
|
168
|
+
.then((res) => {
|
|
169
|
+
if (res && (Array.isArray(res.teams) || Array.isArray(res.designations))) {
|
|
170
|
+
if (typeof onOrgLoaded === "function") onOrgLoaded(res);
|
|
171
|
+
populate(res);
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.catch(() => {});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
getTeam() {
|
|
180
|
+
return teamEl ? teamEl.value.trim() : "";
|
|
181
|
+
},
|
|
182
|
+
getSubTeam() {
|
|
183
|
+
return subTeamEl && !subTeamEl.disabled ? subTeamEl.value.trim() : "";
|
|
184
|
+
},
|
|
185
|
+
getDesignation() {
|
|
186
|
+
return designationEl ? designationEl.value.trim() : "";
|
|
187
|
+
},
|
|
188
|
+
populate,
|
|
189
|
+
};
|
|
190
|
+
}
|