@zwayam/apply-experience-library 0.1.2 → 0.1.3
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/README.md +365 -83
- package/package.json +1 -1
- package/packages/web/README.md +29 -178
- package/packages/web/dist/index.js +9 -9
- package/packages/web/dist/styles.css +1 -1
package/README.md
CHANGED
|
@@ -1,94 +1,291 @@
|
|
|
1
|
-
# Apply Experience
|
|
1
|
+
# Apply Experience Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@zwayam/apply-experience-library` is a host-facing SDK for launching **Add Profile** and **Edit Profile** popups inside recruiter or admin web applications.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It is designed for hosts that already own authentication, API orchestration, and business rules, but want a reusable profile collection UI.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## What the SDK Handles
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
- resume upload and resume parsing flow
|
|
10
|
+
- manual profile creation flow
|
|
11
|
+
- add profile and edit profile popup UI
|
|
12
|
+
- dynamic field rendering from backend `applyFields`
|
|
13
|
+
- required validation, pattern validation, dependent field visibility
|
|
14
|
+
- country code + phone input handling
|
|
15
|
+
- source / source type / sub-source flow
|
|
16
|
+
- optional location autosuggestions
|
|
17
|
+
- final submit flow and success callbacks
|
|
18
|
+
|
|
19
|
+
## What the Host App Handles
|
|
20
|
+
|
|
21
|
+
- authentication and session
|
|
22
|
+
- current job and user context
|
|
23
|
+
- backend API calls
|
|
24
|
+
- route-level state management
|
|
25
|
+
- refresh / navigation after success
|
|
12
26
|
|
|
13
|
-
|
|
27
|
+
## Install
|
|
14
28
|
|
|
15
29
|
```bash
|
|
16
|
-
npm install
|
|
30
|
+
npm install @zwayam/apply-experience-library
|
|
17
31
|
```
|
|
18
32
|
|
|
19
|
-
Import styles once:
|
|
33
|
+
Import styles once in your host app:
|
|
20
34
|
|
|
21
|
-
```
|
|
35
|
+
```ts
|
|
22
36
|
import "@zwayam/apply-experience-library/styles.css";
|
|
23
37
|
```
|
|
24
38
|
|
|
25
|
-
##
|
|
39
|
+
## Exported API
|
|
26
40
|
|
|
27
|
-
The
|
|
41
|
+
The package exposes:
|
|
28
42
|
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
43
|
+
- `mountAddProfile(options)`
|
|
44
|
+
- `mountEditProfile(options)`
|
|
45
|
+
- `registerAddProfileElement(options)`
|
|
46
|
+
- `registerEditProfileElement(options)`
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
Use `mount*` for direct framework integration. Use `register*Element` if you prefer custom elements.
|
|
49
|
+
|
|
50
|
+
## How It Is Built
|
|
51
|
+
|
|
52
|
+
The SDK is made in two layers:
|
|
53
|
+
|
|
54
|
+
1. `packages/react`
|
|
55
|
+
- React implementation of Add Profile / Edit Profile
|
|
56
|
+
- field rendering, validations, workflow logic
|
|
57
|
+
|
|
58
|
+
2. `packages/web`
|
|
59
|
+
- bundled host-facing package
|
|
60
|
+
- exports mount helpers that work in Angular, React, Vue, and vanilla JS
|
|
61
|
+
|
|
62
|
+
This means host apps do not need to know the internal React implementation details. They only provide `context`, `api`, and a DOM `container`.
|
|
63
|
+
|
|
64
|
+
## Add Profile Context
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
type AddProfileHostContext = {
|
|
68
|
+
job: {
|
|
69
|
+
id: string;
|
|
70
|
+
companyId: number | string;
|
|
71
|
+
departmentId?: number | string;
|
|
72
|
+
jobTitle: string;
|
|
73
|
+
jobCode: string;
|
|
74
|
+
location?: string;
|
|
75
|
+
minYearOfExperience?: number;
|
|
76
|
+
maxYearOfExperience?: number;
|
|
77
|
+
departmentName?: string;
|
|
78
|
+
};
|
|
79
|
+
user: {
|
|
80
|
+
id: string | number;
|
|
81
|
+
email: string;
|
|
82
|
+
name: string;
|
|
83
|
+
uuid?: string;
|
|
84
|
+
};
|
|
85
|
+
session: {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
};
|
|
88
|
+
environment?: Record<string, string>;
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Example
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const addProfileContext = {
|
|
35
96
|
job: {
|
|
36
97
|
id: "263294",
|
|
37
98
|
companyId: 16136,
|
|
99
|
+
departmentId: 12,
|
|
38
100
|
jobTitle: "Java Developer",
|
|
39
|
-
jobCode: "
|
|
40
|
-
location: "
|
|
41
|
-
|
|
101
|
+
jobCode: "16667",
|
|
102
|
+
location: "Pune, Maharashtra, India",
|
|
103
|
+
minYearOfExperience: 2,
|
|
104
|
+
maxYearOfExperience: 5,
|
|
105
|
+
departmentName: "Engineering",
|
|
42
106
|
},
|
|
43
107
|
user: {
|
|
44
108
|
id: 286813,
|
|
45
|
-
email: "
|
|
109
|
+
email: "recruiter@example.com",
|
|
46
110
|
name: "Recruiter Name",
|
|
111
|
+
uuid: "host-user-uuid",
|
|
47
112
|
},
|
|
48
113
|
session: {
|
|
49
114
|
sessionId: "SESSION_ID",
|
|
50
115
|
},
|
|
116
|
+
environment: {
|
|
117
|
+
tenant: "prod",
|
|
118
|
+
},
|
|
51
119
|
};
|
|
120
|
+
```
|
|
52
121
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
122
|
+
## Edit Profile Context
|
|
123
|
+
|
|
124
|
+
Edit Profile uses everything from Add Profile, plus:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
type EditProfileHostContext = AddProfileHostContext & {
|
|
128
|
+
applyId: string | number;
|
|
129
|
+
application?: Record<string, unknown>;
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Example
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const editProfileContext = {
|
|
137
|
+
...addProfileContext,
|
|
138
|
+
applyId: 987654,
|
|
139
|
+
application: {
|
|
140
|
+
applyId: 987654,
|
|
141
|
+
candidateId: 12345,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Add Profile API Contract
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
type AddProfileApi = {
|
|
150
|
+
loadInitialData(
|
|
151
|
+
context: AddProfileHostContext,
|
|
152
|
+
): Promise<{
|
|
153
|
+
applyFields: AddProfileFieldDefinition[];
|
|
56
154
|
supportedFiles: {
|
|
57
|
-
addProfile
|
|
58
|
-
fileFormat: [
|
|
59
|
-
fileSize
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
profileSources:
|
|
155
|
+
addProfile?: {
|
|
156
|
+
fileFormat: string[];
|
|
157
|
+
fileSize?: number;
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
profileSources: Array<{
|
|
161
|
+
sourceOfProfile?: string;
|
|
162
|
+
profileSource: string;
|
|
163
|
+
subSourceValues?: string | null;
|
|
164
|
+
}>;
|
|
63
165
|
phoneConfiguration: {
|
|
64
|
-
countryIsoCode:
|
|
65
|
-
whatsAppEnabled:
|
|
66
|
-
maxCount:
|
|
67
|
-
}
|
|
68
|
-
|
|
166
|
+
countryIsoCode: string | null;
|
|
167
|
+
whatsAppEnabled: boolean;
|
|
168
|
+
maxCount: number;
|
|
169
|
+
};
|
|
170
|
+
resumeOptional?: boolean;
|
|
171
|
+
}>;
|
|
172
|
+
|
|
173
|
+
parseResume(
|
|
174
|
+
file: File,
|
|
175
|
+
context: AddProfileHostContext,
|
|
176
|
+
): Promise<{
|
|
177
|
+
responseCode?: number;
|
|
178
|
+
reponseObject?: {
|
|
179
|
+
parsedObjectId?: string;
|
|
180
|
+
parsedResult?: {
|
|
181
|
+
profile?: Record<string, unknown>;
|
|
182
|
+
};
|
|
183
|
+
jobApplication?: Record<string, unknown>;
|
|
184
|
+
};
|
|
185
|
+
}>;
|
|
186
|
+
|
|
187
|
+
submitProfile(
|
|
188
|
+
payload: FormData,
|
|
189
|
+
context: AddProfileHostContext,
|
|
190
|
+
): Promise<{
|
|
191
|
+
code?: number;
|
|
192
|
+
message?: string;
|
|
193
|
+
[key: string]: unknown;
|
|
194
|
+
}>;
|
|
195
|
+
|
|
196
|
+
getLocationSuggestions?(
|
|
197
|
+
query: string,
|
|
198
|
+
context: AddProfileHostContext,
|
|
199
|
+
): Promise<string[]>;
|
|
200
|
+
};
|
|
201
|
+
```
|
|
69
202
|
|
|
70
|
-
|
|
71
|
-
const formData = new FormData();
|
|
72
|
-
formData.append("file", file);
|
|
73
|
-
return fetch("/parse-resume", { method: "POST", body: formData }).then((res) => res.json());
|
|
74
|
-
},
|
|
203
|
+
## Edit Profile API Contract
|
|
75
204
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
205
|
+
```ts
|
|
206
|
+
type EditProfileApi = {
|
|
207
|
+
loadInitialData(
|
|
208
|
+
context: EditProfileHostContext,
|
|
209
|
+
): Promise<{
|
|
210
|
+
jobApplication: Record<string, unknown>;
|
|
211
|
+
applyFieldsObject: Record<string, unknown>;
|
|
212
|
+
applyFields: AddProfileFieldDefinition[];
|
|
213
|
+
phoneConfiguration: {
|
|
214
|
+
countryIsoCode: string | null;
|
|
215
|
+
whatsAppEnabled: boolean;
|
|
216
|
+
maxCount: number;
|
|
217
|
+
};
|
|
218
|
+
phoneNumbers?: Array<Record<string, unknown>>;
|
|
219
|
+
supportedFiles?: {
|
|
220
|
+
addProfile?: {
|
|
221
|
+
fileFormat: string[];
|
|
222
|
+
fileSize?: number;
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
}>;
|
|
226
|
+
|
|
227
|
+
submitProfile(
|
|
228
|
+
payload: Record<string, unknown>,
|
|
229
|
+
context: EditProfileHostContext,
|
|
230
|
+
): Promise<{
|
|
231
|
+
code?: number;
|
|
232
|
+
message?: string;
|
|
233
|
+
[key: string]: unknown;
|
|
234
|
+
}>;
|
|
235
|
+
|
|
236
|
+
getLocationSuggestions?(
|
|
237
|
+
query: string,
|
|
238
|
+
context: EditProfileHostContext,
|
|
239
|
+
): Promise<string[]>;
|
|
79
240
|
};
|
|
80
241
|
```
|
|
81
242
|
|
|
82
|
-
##
|
|
243
|
+
## Field Definitions
|
|
244
|
+
|
|
245
|
+
The SDK renders fields from backend `applyFields`.
|
|
246
|
+
|
|
247
|
+
Common field types include:
|
|
248
|
+
|
|
249
|
+
- `T` for text-like inputs
|
|
250
|
+
- `D` for searchable dropdowns
|
|
251
|
+
- `date`
|
|
252
|
+
- `phone`
|
|
253
|
+
- `checkbox`
|
|
254
|
+
- `location`
|
|
255
|
+
- `email`
|
|
256
|
+
- `consent`
|
|
257
|
+
|
|
258
|
+
Notes:
|
|
259
|
+
|
|
260
|
+
- `field.errorMessage` is used as first priority for validation messages when present.
|
|
261
|
+
- Generated SDK validation messages are used as fallback when API does not provide one.
|
|
262
|
+
- Currency formatting follows field metadata type such as `INRCURRENCY` / `CURRENCY`, matching host behavior.
|
|
263
|
+
|
|
264
|
+
## React Usage
|
|
83
265
|
|
|
84
|
-
```
|
|
266
|
+
```tsx
|
|
85
267
|
import { useEffect, useRef } from "react";
|
|
86
|
-
import {
|
|
268
|
+
import {
|
|
269
|
+
mountAddProfile,
|
|
270
|
+
type MountedAddProfile,
|
|
271
|
+
type AddProfileApi,
|
|
272
|
+
type AddProfileHostContext,
|
|
273
|
+
} from "@zwayam/apply-experience-library";
|
|
87
274
|
import "@zwayam/apply-experience-library/styles.css";
|
|
88
275
|
|
|
89
|
-
export function
|
|
90
|
-
|
|
91
|
-
|
|
276
|
+
export function AddProfileLauncher({
|
|
277
|
+
open,
|
|
278
|
+
context,
|
|
279
|
+
api,
|
|
280
|
+
onClose,
|
|
281
|
+
}: {
|
|
282
|
+
open: boolean;
|
|
283
|
+
context: AddProfileHostContext;
|
|
284
|
+
api: AddProfileApi;
|
|
285
|
+
onClose(): void;
|
|
286
|
+
}) {
|
|
287
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
288
|
+
const mountedRef = useRef<MountedAddProfile | null>(null);
|
|
92
289
|
|
|
93
290
|
useEffect(() => {
|
|
94
291
|
if (!open || !rootRef.current) return;
|
|
@@ -97,8 +294,12 @@ export function AddProfile({ open, context, api, onClose }) {
|
|
|
97
294
|
container: rootRef.current,
|
|
98
295
|
context,
|
|
99
296
|
api,
|
|
297
|
+
title: "Add Profile",
|
|
100
298
|
onClose,
|
|
101
|
-
onSuccess:
|
|
299
|
+
onSuccess: () => {
|
|
300
|
+
mountedRef.current?.unmount();
|
|
301
|
+
onClose();
|
|
302
|
+
},
|
|
102
303
|
});
|
|
103
304
|
|
|
104
305
|
return () => mountedRef.current?.unmount();
|
|
@@ -108,55 +309,72 @@ export function AddProfile({ open, context, api, onClose }) {
|
|
|
108
309
|
}
|
|
109
310
|
```
|
|
110
311
|
|
|
111
|
-
## Angular
|
|
312
|
+
## Angular Usage
|
|
112
313
|
|
|
113
|
-
Import styles in a global stylesheet:
|
|
314
|
+
Import styles once in a global stylesheet:
|
|
114
315
|
|
|
115
316
|
```scss
|
|
116
317
|
@import "@zwayam/apply-experience-library/styles.css";
|
|
117
318
|
```
|
|
118
319
|
|
|
119
|
-
Mount from a component:
|
|
320
|
+
Mount from a component or service:
|
|
120
321
|
|
|
121
322
|
```ts
|
|
122
|
-
import {
|
|
323
|
+
import {
|
|
324
|
+
mountAddProfile,
|
|
325
|
+
type MountedAddProfile,
|
|
326
|
+
type AddProfileApi,
|
|
327
|
+
type AddProfileHostContext,
|
|
328
|
+
} from "@zwayam/apply-experience-library";
|
|
123
329
|
|
|
124
330
|
private mountedAddProfile?: MountedAddProfile;
|
|
125
331
|
|
|
126
|
-
openAddProfile(container: Element, context:
|
|
332
|
+
openAddProfile(container: Element, context: AddProfileHostContext, api: AddProfileApi) {
|
|
127
333
|
this.mountedAddProfile = mountAddProfile({
|
|
128
334
|
container,
|
|
129
335
|
context,
|
|
130
336
|
api,
|
|
337
|
+
title: "Add Profile",
|
|
131
338
|
onClose: () => this.mountedAddProfile?.unmount(),
|
|
132
339
|
onSuccess: () => {
|
|
133
340
|
this.mountedAddProfile?.unmount();
|
|
134
|
-
// refresh host data
|
|
341
|
+
// refresh host data here
|
|
135
342
|
},
|
|
136
343
|
});
|
|
137
344
|
}
|
|
138
345
|
```
|
|
139
346
|
|
|
140
|
-
## Vue
|
|
347
|
+
## Vue Usage
|
|
141
348
|
|
|
142
349
|
```vue
|
|
143
|
-
<script setup>
|
|
144
|
-
import { onBeforeUnmount,
|
|
350
|
+
<script setup lang="ts">
|
|
351
|
+
import { onBeforeUnmount, watch, ref } from "vue";
|
|
145
352
|
import { mountAddProfile } from "@zwayam/apply-experience-library";
|
|
146
353
|
import "@zwayam/apply-experience-library/styles.css";
|
|
147
354
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
355
|
+
const props = defineProps<{
|
|
356
|
+
open: boolean;
|
|
357
|
+
context: any;
|
|
358
|
+
api: any;
|
|
359
|
+
}>();
|
|
360
|
+
|
|
361
|
+
const root = ref<HTMLElement | null>(null);
|
|
362
|
+
let mounted: ReturnType<typeof mountAddProfile> | null = null;
|
|
363
|
+
|
|
364
|
+
watch(
|
|
365
|
+
() => props.open,
|
|
366
|
+
(open) => {
|
|
367
|
+
if (!open || !root.value) return;
|
|
368
|
+
|
|
369
|
+
mounted = mountAddProfile({
|
|
370
|
+
container: root.value,
|
|
371
|
+
context: props.context,
|
|
372
|
+
api: props.api,
|
|
373
|
+
onClose: () => mounted?.unmount(),
|
|
374
|
+
onSuccess: () => mounted?.unmount(),
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
);
|
|
160
378
|
|
|
161
379
|
onBeforeUnmount(() => mounted?.unmount());
|
|
162
380
|
</script>
|
|
@@ -166,7 +384,7 @@ onBeforeUnmount(() => mounted?.unmount());
|
|
|
166
384
|
</template>
|
|
167
385
|
```
|
|
168
386
|
|
|
169
|
-
## Vanilla JavaScript
|
|
387
|
+
## Vanilla JavaScript Usage
|
|
170
388
|
|
|
171
389
|
```html
|
|
172
390
|
<button id="open-add-profile">Add Profile</button>
|
|
@@ -177,28 +395,92 @@ onBeforeUnmount(() => mounted?.unmount());
|
|
|
177
395
|
import { mountAddProfile } from "@zwayam/apply-experience-library";
|
|
178
396
|
import "@zwayam/apply-experience-library/styles.css";
|
|
179
397
|
|
|
180
|
-
document.
|
|
398
|
+
document.getElementById("open-add-profile").addEventListener("click", () => {
|
|
181
399
|
const mounted = mountAddProfile({
|
|
182
|
-
container: document.
|
|
183
|
-
context,
|
|
184
|
-
api,
|
|
400
|
+
container: document.getElementById("add-profile-root"),
|
|
401
|
+
context: addProfileContext,
|
|
402
|
+
api: addProfileApi,
|
|
185
403
|
onClose: () => mounted.unmount(),
|
|
186
404
|
onSuccess: () => mounted.unmount(),
|
|
187
405
|
});
|
|
188
406
|
});
|
|
189
407
|
```
|
|
190
408
|
|
|
409
|
+
## Custom Element Usage
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import {
|
|
413
|
+
registerAddProfileElement,
|
|
414
|
+
registerEditProfileElement,
|
|
415
|
+
} from "@zwayam/apply-experience-library";
|
|
416
|
+
|
|
417
|
+
registerAddProfileElement({
|
|
418
|
+
getApi: () => addProfileApi,
|
|
419
|
+
getContext: () => addProfileContext,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
registerEditProfileElement({
|
|
423
|
+
getApi: () => editProfileApi,
|
|
424
|
+
getContext: () => editProfileContext,
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Then use:
|
|
429
|
+
|
|
430
|
+
```html
|
|
431
|
+
<apply-experience-add-profile></apply-experience-add-profile>
|
|
432
|
+
<apply-experience-edit-profile></apply-experience-edit-profile>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Example API Object
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
const addProfileApi = {
|
|
439
|
+
loadInitialData: async (context) => {
|
|
440
|
+
const response = await fetch(`/api/apply-fields?jobId=${context.job.id}`);
|
|
441
|
+
return response.json();
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
parseResume: async (file, context) => {
|
|
445
|
+
const formData = new FormData();
|
|
446
|
+
formData.append("file", file);
|
|
447
|
+
formData.append("sessionId", context.session.sessionId);
|
|
448
|
+
const response = await fetch("/api/parse-resume", {
|
|
449
|
+
method: "POST",
|
|
450
|
+
body: formData,
|
|
451
|
+
});
|
|
452
|
+
return response.json();
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
submitProfile: async (payload, context) => {
|
|
456
|
+
payload.append("sessionId", context.session.sessionId);
|
|
457
|
+
const response = await fetch("/api/add-profile", {
|
|
458
|
+
method: "POST",
|
|
459
|
+
body: payload,
|
|
460
|
+
});
|
|
461
|
+
return response.json();
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
getLocationSuggestions: async (query) => {
|
|
465
|
+
if (!query.trim()) return [];
|
|
466
|
+
const response = await fetch(`/api/location-suggestions?q=${encodeURIComponent(query)}`);
|
|
467
|
+
const data = await response.json();
|
|
468
|
+
return data.suggestions || [];
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
```
|
|
472
|
+
|
|
191
473
|
## Development
|
|
192
474
|
|
|
193
475
|
```bash
|
|
194
476
|
npm install
|
|
195
477
|
npm run build
|
|
478
|
+
npm run playground
|
|
196
479
|
```
|
|
197
480
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
## Notes
|
|
481
|
+
## Publish
|
|
201
482
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
483
|
+
```bash
|
|
484
|
+
npm run build
|
|
485
|
+
npm publish --access public
|
|
486
|
+
```
|
package/package.json
CHANGED