@explorer02/cfm-survey-sdk 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +24 -26
- package/dist/index.d.ts +24 -26
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/postinstall.js +21 -26
- package/templates/AGENT.md +24 -10
- package/templates/docs/00-integration/constraints.md +54 -0
- package/templates/docs/00-integration/setup.md +80 -0
- package/templates/docs/00-integration/useSurveySDK.md +107 -0
- package/templates/docs/01-components/01-survey-page.md +91 -0
- package/templates/docs/01-components/02-question.md +105 -0
- package/templates/docs/01-components/08-file-upload-scale.md +56 -0
- package/templates/docs/{03-client-components → 01-components}/README.md +1 -1
- package/templates/docs/02-question-types/01-rating.md +0 -8
- package/templates/docs/02-reference/question-types/01-rating.md +52 -0
- package/templates/docs/02-reference/question-types/README.md +71 -0
- package/templates/docs/02-reference/routing-table.md +37 -0
- package/templates/docs/MANIFEST.json +129 -0
- package/templates/docs/index.md +100 -140
- package/templates/docs/templates/implementation_plan.md +78 -0
- package/templates/docs/01-sdk-core/01-fetch-survey.md +0 -68
- package/templates/docs/01-sdk-core/02-survey-mapper.md +0 -85
- package/templates/docs/01-sdk-core/03-question-mappers.md +0 -114
- package/templates/docs/01-sdk-core/04-pagination.md +0 -72
- package/templates/docs/01-sdk-core/05-validation.md +0 -66
- package/templates/docs/01-sdk-core/06-submit-survey.md +0 -90
- package/templates/docs/01-sdk-core/07-language-handling.md +0 -111
- package/templates/docs/01-sdk-core/08-icons-and-emojis.md +0 -56
- package/templates/docs/01-sdk-core/README.md +0 -53
- package/templates/docs/02-question-types/02-radio.md +0 -26
- package/templates/docs/02-question-types/03-text.md +0 -26
- package/templates/docs/02-question-types/12-text-and-media.md +0 -35
- package/templates/docs/02-question-types/README.md +0 -74
- package/templates/docs/03-client-components/01-survey-page.md +0 -113
- package/templates/docs/03-client-components/02-question.md +0 -74
- package/templates/docs/03-client-components/08-file-upload-scale.md +0 -37
- package/templates/docs/04-critical-rules/01-import-rules.md +0 -51
- package/templates/docs/04-critical-rules/02-action-dispatching.md +0 -56
- package/templates/docs/04-critical-rules/03-scroll-navigation.md +0 -37
- package/templates/docs/04-critical-rules/04-logo-branding.md +0 -42
- package/templates/docs/04-critical-rules/05-troubleshooting.md +0 -46
- package/templates/docs/04-critical-rules/README.md +0 -29
- /package/templates/docs/{03-client-components → 01-components}/03-rating-scale.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/04-csat-scale.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/05-csat-matrix-scale.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/06-likert-matrix-scale.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/07-slider-matrix-scale.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/09-custom-slider-track.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/10-header-footer.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/11-progress-bar.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/12-language-selector.md +0 -0
- /package/templates/docs/{03-client-components → 01-components}/13-matrix-dropdown.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/04-csat.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/05-rating-scale.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/06-slider.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/07-matrix-cfm.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/08-matrix-csat.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/09-matrix-rating.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/10-slider-matrix.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/11-file-upload.md +0 -0
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Pagination — `useSurveyPagination`
|
|
2
|
-
|
|
3
|
-
> **Source**: `packages/sdk/src/surveyPagination/useSurveyPagination.ts`
|
|
4
|
-
> **Called by**: `useSurveySDK()` internally.
|
|
5
|
-
|
|
6
|
-
## What It Does
|
|
7
|
-
|
|
8
|
-
Manages the multi-page survey state: current page index, user answers, validation errors, page navigation, and wires into submission and progress tracking.
|
|
9
|
-
|
|
10
|
-
## State Shape
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
{
|
|
14
|
-
currentPageIndex: number, // Zero-based active page
|
|
15
|
-
values: Record<string, AnswerValue>, // All answers keyed by question ID
|
|
16
|
-
validationErrors: Record<string, string>, // Error messages keyed by question ID
|
|
17
|
-
currentQuestions: SurveyQuestion[], // Questions on the active page
|
|
18
|
-
progressPercentage: number, // 0–100 completion percentage
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Navigation Actions
|
|
23
|
-
|
|
24
|
-
### `onNext()` (dispatched by `onAction({ type: 'NEXT' })`)
|
|
25
|
-
1. Validates all questions on the current page
|
|
26
|
-
2. If validation fails → populates `validationErrors` → scrolls to first error
|
|
27
|
-
3. If validation passes → clears errors → increments `currentPageIndex`
|
|
28
|
-
4. If already on the last page → auto-redirects to `onSubmit()`
|
|
29
|
-
|
|
30
|
-
### `onBack()` (dispatched by `onAction({ type: 'PREVIOUS' })`)
|
|
31
|
-
1. Decrements `currentPageIndex` (minimum 0)
|
|
32
|
-
2. Scrolls to the top of the new page
|
|
33
|
-
3. Logs backtrack analytics
|
|
34
|
-
|
|
35
|
-
### `onChange(questionId, value)` (dispatched by `onAction({ type: 'CHANGE', payload })`)
|
|
36
|
-
1. Updates `values[questionId] = value`
|
|
37
|
-
2. If the question had a validation error, clears it immediately
|
|
38
|
-
|
|
39
|
-
### `onSubmit()` (dispatched by `onAction({ type: 'SUBMIT' })` or auto-triggered)
|
|
40
|
-
1. Validates ALL pages (not just current)
|
|
41
|
-
2. If any page has errors → navigates to the error page → scrolls to first error
|
|
42
|
-
3. If all valid → calls `submit(values)` → sends response to server
|
|
43
|
-
|
|
44
|
-
## Progress Tracking
|
|
45
|
-
|
|
46
|
-
`useSurveyProgress` computes `progressPercentage`:
|
|
47
|
-
- Counts total answerable questions across ALL pages (excludes `text_and_media`)
|
|
48
|
-
- Counts questions that have a non-empty value in `values`
|
|
49
|
-
- Formula: `(answered / total) * 100`
|
|
50
|
-
|
|
51
|
-
## Internal Refs (Analytics)
|
|
52
|
-
|
|
53
|
-
The hook maintains a `useRef` for analytics data that persists across renders:
|
|
54
|
-
- `pageStartTime` — timestamp when current page was entered
|
|
55
|
-
- `sessionStartTime` — timestamp when survey was first loaded
|
|
56
|
-
- `maxVisitedPageIndex` — furthest page the user has reached
|
|
57
|
-
- `validationErrorsCount` — total validation failures during the session
|
|
58
|
-
- `backtrackCount` — number of times user navigated backwards
|
|
59
|
-
|
|
60
|
-
These are used for telemetry and do NOT affect the UI.
|
|
61
|
-
|
|
62
|
-
## Auto-Submit on Last Page
|
|
63
|
-
|
|
64
|
-
When `onAction({ type: 'NEXT' })` is dispatched on the last page, the SDK's `useSurveySDK` dispatcher automatically converts it to `{ type: 'SUBMIT' }`:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
if (action.type === 'NEXT' && currentPageIndex === survey.pages.length - 1) {
|
|
68
|
-
action = { type: 'SUBMIT' };
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
This means the client's "Next" button on the last page automatically becomes "Submit" in behaviour.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Validation — `validateQuestions` & `validateAllPages`
|
|
2
|
-
|
|
3
|
-
> **Source**: `packages/sdk/src/surveyPagination/utils/validation.ts`
|
|
4
|
-
> **Called by**: `useOnNext()` and `useOnSubmit()` internally.
|
|
5
|
-
|
|
6
|
-
## What It Does
|
|
7
|
-
|
|
8
|
-
Validates required questions before allowing page navigation or survey submission. Populates `state.validationErrors` with per-question error messages.
|
|
9
|
-
|
|
10
|
-
## Validation Rules
|
|
11
|
-
|
|
12
|
-
### Standard Questions (rating, radio, text, csat, rating_scale, slider)
|
|
13
|
-
- **Required check**: `value !== undefined && value !== ''`
|
|
14
|
-
- `null` is treated as a **valid** answer (represents the N/A option)
|
|
15
|
-
|
|
16
|
-
### Matrix Questions (matrix, rating_matrix, slider_matrix)
|
|
17
|
-
- Every row must have a value: `question.rows.every(row => answerMap[row.id] !== undefined && answerMap[row.id] !== '')`
|
|
18
|
-
- `null` per row is valid (N/A option)
|
|
19
|
-
|
|
20
|
-
### Display-Only Questions (text_and_media)
|
|
21
|
-
- **Always skipped** — `isDisplayOnly()` returns `true`
|
|
22
|
-
- Never counted in validation or progress
|
|
23
|
-
|
|
24
|
-
## Error Messages
|
|
25
|
-
|
|
26
|
-
| Source | Priority |
|
|
27
|
-
|--------|----------|
|
|
28
|
-
| `question.requiredErrorMessage` (from API) | **Highest** — custom per-question message |
|
|
29
|
-
| Language-aware default | Fallback |
|
|
30
|
-
|
|
31
|
-
Default messages:
|
|
32
|
-
- German (`de`): `"Beantworten Sie bitte diese Frage bevor Sie fortfahren."`
|
|
33
|
-
- All other: `"Please answer this question before you proceed."`
|
|
34
|
-
|
|
35
|
-
## Scroll-to-Error
|
|
36
|
-
|
|
37
|
-
When validation fails, the SDK:
|
|
38
|
-
1. Identifies the first question with an error
|
|
39
|
-
2. Calls `document.getElementById(questionId)` to find the DOM element
|
|
40
|
-
3. Calls `.scrollIntoView({ behavior: 'smooth', block: 'center' })` to scroll to it
|
|
41
|
-
|
|
42
|
-
**⚠️ CRITICAL**: This is why every question wrapper element MUST have `id={question.id}`.
|
|
43
|
-
|
|
44
|
-
## `validateQuestions(questions, values, language)`
|
|
45
|
-
|
|
46
|
-
Validates a single page's questions. Returns `Record<string, string>` of errors (empty if all valid).
|
|
47
|
-
|
|
48
|
-
## `validateAllPages(pages, values, language)`
|
|
49
|
-
|
|
50
|
-
Validates ALL pages at once (used before submission). Returns:
|
|
51
|
-
```typescript
|
|
52
|
-
{
|
|
53
|
-
errors: Record<string, string>, // All errors across all pages
|
|
54
|
-
errorPageIndex: number, // First page with errors
|
|
55
|
-
firstErrorId: string, // First question ID with error
|
|
56
|
-
} | null // null = all valid
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
When this returns non-null, the SDK:
|
|
60
|
-
1. Navigates to `errorPageIndex`
|
|
61
|
-
2. Sets all errors into `validationErrors` state
|
|
62
|
-
3. Scrolls to `firstErrorId`
|
|
63
|
-
|
|
64
|
-
## `isQuestionAnswered(question, values)`
|
|
65
|
-
|
|
66
|
-
Exported utility that checks if a single question has been answered. Used by both validation and progress tracking.
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# Submit Survey — `useSubmitResponse` & `buildSubmitRequestBody`
|
|
2
|
-
|
|
3
|
-
> **Sources**:
|
|
4
|
-
> - `packages/sdk/src/submitSurvey/useSubmitResponse.ts`
|
|
5
|
-
> - `packages/sdk/src/submitSurvey/utils/formatters.ts`
|
|
6
|
-
|
|
7
|
-
## What It Does
|
|
8
|
-
|
|
9
|
-
Formats user answers into the Sprinklr CFM API payload structure and sends them via POST.
|
|
10
|
-
|
|
11
|
-
## Submission Endpoint
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
POST https://custom-p0.feedbook.me/api/feedback/survey/cfm/response
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Payload Format — `SubmitRequestBody`
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
{
|
|
21
|
-
questionToAnswers: Record<string, string[]>, // Standard + matrix row answers
|
|
22
|
-
quesIdVsAttachmentDetails: Record<string, any[]>, // File upload attachments
|
|
23
|
-
optionLevelAnswers: Record<string, string[]>, // (reserved)
|
|
24
|
-
conjointTasks: unknown[], // (reserved — always [])
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Per-Type Serialization
|
|
29
|
-
|
|
30
|
-
### Standard Questions (rating, radio, text, csat, rating_scale, slider)
|
|
31
|
-
```
|
|
32
|
-
questionToAnswers[questionId] = [String(value)]
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Matrix Questions (matrix, rating_matrix, slider_matrix)
|
|
36
|
-
Each row becomes a separate entry with a combined key:
|
|
37
|
-
```
|
|
38
|
-
questionToAnswers[`${questionId}_${rowId}`] = [String(rowValue)]
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### File Upload
|
|
42
|
-
```
|
|
43
|
-
quesIdVsAttachmentDetails[questionId] = [
|
|
44
|
-
{ id: "...", name: "file.pdf", mediaUrl: "https://..." },
|
|
45
|
-
...
|
|
46
|
-
]
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Text and Media (display-only)
|
|
50
|
-
Completely skipped — no data submitted.
|
|
51
|
-
|
|
52
|
-
## Answer Value Types
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
type AnswerValue = string | number | null | MatrixAnswerMap | any[];
|
|
56
|
-
|
|
57
|
-
type MatrixAnswerMap = Record<string, string | number | null | Array<string | number | null>>;
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
| Question Type | Answer Shape | Example |
|
|
61
|
-
|--------------|-------------|---------|
|
|
62
|
-
| `rating` | `number` | `7` |
|
|
63
|
-
| `radio` | `string \| number` | `"option_1"` |
|
|
64
|
-
| `text` | `string` | `"Great service"` |
|
|
65
|
-
| `csat` | `number \| null` | `4` or `null` (N/A) |
|
|
66
|
-
| `rating_scale` | `number` | `3` |
|
|
67
|
-
| `slider` | `number` | `65` |
|
|
68
|
-
| `matrix` | `MatrixAnswerMap` | `{ "row1": "col2", "row2": null }` |
|
|
69
|
-
| `slider_matrix` | `MatrixAnswerMap` | `{ "row1": 75, "row2": 30 }` |
|
|
70
|
-
| `file_upload` | `UploadedFile[]` | `[{ id, name, mediaUrl }]` |
|
|
71
|
-
| `text_and_media` | (none) | Not submitted |
|
|
72
|
-
|
|
73
|
-
## Submit Result
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
type SubmitResult = {
|
|
77
|
-
success: boolean;
|
|
78
|
-
responseId: string; // Generated database ID for the response
|
|
79
|
-
};
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Accessible via `submitSurveyResults.data` in the component.
|
|
83
|
-
|
|
84
|
-
## Error Handling
|
|
85
|
-
|
|
86
|
-
| Scenario | Behaviour |
|
|
87
|
-
|----------|-----------|
|
|
88
|
-
| Network failure | `submitSurveyResults.error` populated |
|
|
89
|
-
| Server rejection | `submitSurveyResults.error` populated with status |
|
|
90
|
-
| During submission | `submitSurveyResults.isLoading = true` — disable submit button |
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# Language Handling — Translations & Placeholders
|
|
2
|
-
|
|
3
|
-
> **Source**: `packages/sdk/src/fetchSurvey/utils/extractSurveyLanguages.ts`
|
|
4
|
-
|
|
5
|
-
## How Multi-Language Works
|
|
6
|
-
|
|
7
|
-
1. The raw API response contains `translations` objects on each question and answer detail
|
|
8
|
-
2. `extractSurveyLanguages()` scans all questions to discover available language codes
|
|
9
|
-
3. `mapSurvey()` resolves text fields using the active language's translations
|
|
10
|
-
4. Language switching is **client-side only** — no API re-fetch
|
|
11
|
-
|
|
12
|
-
## Language Discovery
|
|
13
|
-
|
|
14
|
-
The SDK traverses all questions in the survey and collects unique language codes from `question.translations` keys:
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// Pseudocode
|
|
18
|
-
languagesSet.add(baseLanguage); // e.g. 'de'
|
|
19
|
-
for each question:
|
|
20
|
-
for each key in question.translations:
|
|
21
|
-
languagesSet.add(key); // e.g. 'en', 'fr'
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Display Names
|
|
25
|
-
|
|
26
|
-
Built-in language code → display name mapping:
|
|
27
|
-
|
|
28
|
-
| Code | Display Name |
|
|
29
|
-
|------|-------------|
|
|
30
|
-
| `en` | English |
|
|
31
|
-
| `de` | Deutsch |
|
|
32
|
-
| `hi` | Hindi |
|
|
33
|
-
| `es` | Español |
|
|
34
|
-
| `fr` | Français |
|
|
35
|
-
| `it` | Italiano |
|
|
36
|
-
| `ja` | 日本語 |
|
|
37
|
-
| `ko` | 한국어 |
|
|
38
|
-
| `zh` | 中文 |
|
|
39
|
-
| `bn` | Bengali |
|
|
40
|
-
| `gu` | Gujarati |
|
|
41
|
-
| `mr` | Marathi |
|
|
42
|
-
| `pa` | Punjabi |
|
|
43
|
-
| `ta` | Tamil |
|
|
44
|
-
| `te` | Telugu |
|
|
45
|
-
| `ur` | Urdu |
|
|
46
|
-
|
|
47
|
-
Unknown codes fall back to `code.toUpperCase()`.
|
|
48
|
-
|
|
49
|
-
## Translation Resolution Priority
|
|
50
|
-
|
|
51
|
-
For question text:
|
|
52
|
-
```
|
|
53
|
-
translations[language].questionRichText
|
|
54
|
-
→ translations[language].questionText
|
|
55
|
-
→ raw.questionRichText
|
|
56
|
-
→ raw.questionText
|
|
57
|
-
→ ''
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
For answer option labels:
|
|
61
|
-
```
|
|
62
|
-
translations[language].answerText → raw.answerText → ''
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
For validation error messages:
|
|
66
|
-
```
|
|
67
|
-
config.translations[language].value[0] → config.value[0] → default message
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Placeholders
|
|
71
|
-
|
|
72
|
-
Tokens like `{{FIRST_NAME}}` in question text are replaced at map time:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
const SURVEY_PLACEHOLDERS = {
|
|
76
|
-
FIRST_NAME: 'Customer',
|
|
77
|
-
LAST_NAME: 'Smith',
|
|
78
|
-
EMAIL_ADDRESS: 'customer@example.com',
|
|
79
|
-
};
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**⚠️ CRITICAL**: Define `SURVEY_PLACEHOLDERS` as a **constant outside the component** to prevent infinite re-render loops:
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
// ✅ CORRECT — stable reference
|
|
86
|
-
const SURVEY_PLACEHOLDERS = { FIRST_NAME: 'John' };
|
|
87
|
-
export default function SurveyPage() { ... }
|
|
88
|
-
|
|
89
|
-
// ❌ WRONG — new object every render → infinite re-fetch
|
|
90
|
-
export default function SurveyPage() {
|
|
91
|
-
useSurveySDK({ options: { placeholders: { FIRST_NAME: 'John' } } });
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Language Switching in UI
|
|
96
|
-
|
|
97
|
-
```tsx
|
|
98
|
-
const [selectedLanguage, setSelectedLanguage] = useState<string | undefined>("");
|
|
99
|
-
|
|
100
|
-
// Pass to SDK
|
|
101
|
-
const { ... } = useSurveySDK({ options: { language: selectedLanguage } });
|
|
102
|
-
|
|
103
|
-
// Render selector
|
|
104
|
-
<LanguageSelector
|
|
105
|
-
languages={survey.languages}
|
|
106
|
-
selectedLanguage={survey.language}
|
|
107
|
-
onChange={setSelectedLanguage}
|
|
108
|
-
/>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
When `setSelectedLanguage` fires, the SDK re-maps the cached survey data to the new language — no network request.
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Icons & Emojis Architecture
|
|
2
|
-
|
|
3
|
-
> **Source**: `apps/client/src/components/icons.tsx` (to be created by the agent)
|
|
4
|
-
|
|
5
|
-
## The CSAT Emoji System
|
|
6
|
-
|
|
7
|
-
The SDK relies on a specific sequence of sentiment faces for CSAT scales. You MUST implement this mapping in your client application using a library like `react-icons/fa` or raw SVGs.
|
|
8
|
-
|
|
9
|
-
### Default 5-Point Mapping
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
import { FaRegSmile, FaRegMeh, FaRegFrown, FaRegSmileBeam, FaRegTired } from 'react-icons/fa';
|
|
13
|
-
|
|
14
|
-
export const CsatEmojiMapping: Record<number | string, JSX.Element> = {
|
|
15
|
-
1: <FaRegTired className="text-red-500 w-full h-full" />,
|
|
16
|
-
2: <FaRegFrown className="text-orange-500 w-full h-full" />,
|
|
17
|
-
3: <FaRegMeh className="text-yellow-500 w-full h-full" />,
|
|
18
|
-
4: <FaRegSmile className="text-green-500 w-full h-full" />,
|
|
19
|
-
5: <FaRegSmileBeam className="text-green-600 w-full h-full" />,
|
|
20
|
-
};
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### The `getEmojiForIndex` Helper
|
|
24
|
-
|
|
25
|
-
Because some surveys use 3-point, 7-point, or 10-point scales instead of 5, you MUST use a helper function to proportionally map any scale length to the 5 core sentiment faces.
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
export function getEmojiForIndex(index: number, totalOptions: number): JSX.Element {
|
|
29
|
-
const percentage = index / (totalOptions - 1);
|
|
30
|
-
|
|
31
|
-
if (percentage < 0.2) return CsatEmojiMapping[1];
|
|
32
|
-
if (percentage < 0.4) return CsatEmojiMapping[2];
|
|
33
|
-
if (percentage < 0.6) return CsatEmojiMapping[3];
|
|
34
|
-
if (percentage < 0.8) return CsatEmojiMapping[4];
|
|
35
|
-
return CsatEmojiMapping[5];
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## The Star Icon System
|
|
40
|
-
|
|
41
|
-
For star ratings, you need filled and empty variants:
|
|
42
|
-
|
|
43
|
-
```tsx
|
|
44
|
-
import { FaStar, FaRegStar } from 'react-icons/fa';
|
|
45
|
-
|
|
46
|
-
export const CsatStarIcons = {
|
|
47
|
-
filled: <FaStar className="text-yellow-400 w-full h-full" />,
|
|
48
|
-
empty: <FaRegStar className="text-gray-300 w-full h-full" />
|
|
49
|
-
};
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Color Mapping System
|
|
53
|
-
For rating matrices and numbered NPS grids, colors are usually provided by the SDK in `option.color`. If not, use standard NPS coloring:
|
|
54
|
-
- **0-6 (Detractors)**: Red/Orange
|
|
55
|
-
- **7-8 (Passives)**: Yellow
|
|
56
|
-
- **9-10 (Promoters)**: Green
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# SDK Core Architecture
|
|
2
|
-
|
|
3
|
-
> This folder documents the internal architecture of `@explorer02/cfm-survey-sdk`.
|
|
4
|
-
> Read these files **in order** to understand how the SDK processes a survey from network fetch to user submission.
|
|
5
|
-
|
|
6
|
-
## Pipeline Overview
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
10
|
-
│ Fetch API │ ──→ │ Map Survey │ ──→ │ Map Questions │ ──→ │ Paginate │
|
|
11
|
-
│ (network) │ │ (language) │ │ (per-type) │ │ (pages) │
|
|
12
|
-
└─────────────┘ └──────────────┘ └──────────────────┘ └─────────────┘
|
|
13
|
-
│
|
|
14
|
-
▼
|
|
15
|
-
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
16
|
-
│ Submit │ ←── │ Format Body │ ←── │ Validate │ ←── │ Render UI │
|
|
17
|
-
│ (network) │ │ (payload) │ │ (required) │ │ (React) │
|
|
18
|
-
└─────────────┘ └──────────────┘ └──────────────────┘ └─────────────┘
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Files in This Folder
|
|
22
|
-
|
|
23
|
-
| File | Topic | When to Read |
|
|
24
|
-
|------|-------|-------------|
|
|
25
|
-
| `01-fetch-survey.md` | Network fetching, API endpoint, instanceId | **Always** — first step |
|
|
26
|
-
| `02-survey-mapper.md` | Raw API → Survey object transformation | **Always** — understand data shape |
|
|
27
|
-
| `03-question-mappers.md` | Question type dispatch, normalizeType | **Always** — core routing logic |
|
|
28
|
-
| `04-pagination.md` | Page navigation, currentPageIndex | **Always** — state management |
|
|
29
|
-
| `05-validation.md` | Required field validation, scroll-to-error | **Always** — UX critical |
|
|
30
|
-
| `06-submit-survey.md` | Response submission, payload formatting | **Always** — submission pipeline |
|
|
31
|
-
| `07-language-handling.md` | Translations, placeholders, language switching | When survey is multi-language |
|
|
32
|
-
| `08-icons-and-emojis.md` | CSAT emoji faces, star icons | When CSAT/rating_scale types present |
|
|
33
|
-
|
|
34
|
-
## The Single Hook: `useSurveySDK()`
|
|
35
|
-
|
|
36
|
-
The entire SDK is consumed through **one hook**:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { useSurveySDK } from '@explorer02/cfm-survey-sdk';
|
|
40
|
-
|
|
41
|
-
const { surveyQueryResults, submitSurveyResults, state, onAction } = useSurveySDK({
|
|
42
|
-
options: {
|
|
43
|
-
instanceId: string, // JWT token from client
|
|
44
|
-
language: string | undefined, // Active language code
|
|
45
|
-
placeholders: Record<string, string>, // Token replacements
|
|
46
|
-
debug: boolean, // Console logging
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
This hook internally composes: `useFetchSurvey` → `useSurveyPagination` → `useSubmitResponse` → `useSurveyProgress`.
|
|
52
|
-
|
|
53
|
-
The client component **never** needs to import or call these internal hooks directly.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Radio Question (MCQ)
|
|
2
|
-
|
|
3
|
-
> **Type**: `'radio'` | **API Source**: `MCQ` (and any unrecognised answerType)
|
|
4
|
-
> **Mapper**: `radioMapper.ts` | **Component**: Inline in `Question.tsx`
|
|
5
|
-
|
|
6
|
-
## TypeScript Type
|
|
7
|
-
|
|
8
|
-
```typescript
|
|
9
|
-
type RadioQuestion = QuestionBase & {
|
|
10
|
-
type: 'radio';
|
|
11
|
-
options: SurveyOption[]; // Answer choices
|
|
12
|
-
isMultiSelect?: boolean; // If true, answer is (string|number)[]
|
|
13
|
-
};
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Answer Shape: `string | number`
|
|
17
|
-
Example: `"option_1"` or `3`
|
|
18
|
-
If `isMultiSelect: true` → `(string | number)[]`
|
|
19
|
-
|
|
20
|
-
## Rendering Guidance
|
|
21
|
-
- Render as card-style option list with radio circles
|
|
22
|
-
- Each option: border card with custom radio indicator + label
|
|
23
|
-
- Selected state: brand-color border + light tint background + filled dot
|
|
24
|
-
- Use `<input type="radio" className="sr-only">` for accessibility
|
|
25
|
-
- Labels use `dangerouslySetInnerHTML` (may contain HTML)
|
|
26
|
-
- `onChange={() => onSelect(option.value)}`
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Text Question (Free Text)
|
|
2
|
-
|
|
3
|
-
> **Type**: `'text'` | **API Source**: `TEXTFIELD`
|
|
4
|
-
> **Mapper**: `textMapper.ts` | **Component**: Inline in `Question.tsx`
|
|
5
|
-
|
|
6
|
-
## TypeScript Type
|
|
7
|
-
|
|
8
|
-
```typescript
|
|
9
|
-
type TextQuestion = QuestionBase & {
|
|
10
|
-
type: 'text';
|
|
11
|
-
maxCharacterCount?: number; // Character limit
|
|
12
|
-
placeholder?: string; // Input placeholder text
|
|
13
|
-
inputVariant?: 'short' | 'long'; // Single-line vs textarea
|
|
14
|
-
};
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Answer Shape: `string`
|
|
18
|
-
Example: `"Great customer service, very satisfied"`
|
|
19
|
-
|
|
20
|
-
## Rendering Guidance
|
|
21
|
-
- `inputVariant === 'short'` → `<input type="text">`, `inputVariant === 'long'` → `<textarea rows={4}>`
|
|
22
|
-
- Default to `<textarea>` if `inputVariant` is not specified
|
|
23
|
-
- Show character counter: `maxCharacterCount - currentLength` remaining
|
|
24
|
-
- `onChange={e => onSelect(e.target.value)}`
|
|
25
|
-
- Use `question.placeholder` or fallback: `"Type your response here..."`
|
|
26
|
-
- Focus state: brand-color border + ring
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Text and Media Question (Display-Only)
|
|
2
|
-
|
|
3
|
-
> **Type**: `'text_and_media'` | **API Source**: `TEXT_AND_MEDIA`
|
|
4
|
-
> **Mapper**: `textAndMediaMapper.ts` | **Component**: Inline in `Question.tsx`
|
|
5
|
-
|
|
6
|
-
## TypeScript Type
|
|
7
|
-
|
|
8
|
-
```typescript
|
|
9
|
-
type TextAndMediaQuestion = QuestionBase & {
|
|
10
|
-
type: 'text_and_media';
|
|
11
|
-
mediaUrl?: string; // Image or video URL
|
|
12
|
-
mediaMimeType?: string; // 'image/*' or 'video/*'
|
|
13
|
-
mediaTitle?: string; // Caption/alt text
|
|
14
|
-
mediaAlignment?: string; // Layout position
|
|
15
|
-
mediaSize?: number; // Width percentage
|
|
16
|
-
};
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Answer Shape: NONE
|
|
20
|
-
This is **display-only** — no answer is collected.
|
|
21
|
-
|
|
22
|
-
## ⚠️ Critical Rules
|
|
23
|
-
- **DO NOT** dispatch a `CHANGE` action for this question type
|
|
24
|
-
- **Excluded** from validation (always passes)
|
|
25
|
-
- **Excluded** from progress percentage counting
|
|
26
|
-
- **Skipped** in `buildSubmitRequestBody()` — nothing is submitted
|
|
27
|
-
|
|
28
|
-
## Rendering Guidance
|
|
29
|
-
- Show `question.text` as the heading
|
|
30
|
-
- If `mediaUrl` exists:
|
|
31
|
-
- `mediaMimeType` starts with `'video/'` → `<video controls src={mediaUrl}>`
|
|
32
|
-
- Otherwise → `<img src={mediaUrl} alt={mediaTitle}>`
|
|
33
|
-
- Position media according to `mediaAlignment`
|
|
34
|
-
- Size media according to `mediaSize` percentage
|
|
35
|
-
- Show `mediaTitle` as caption below media
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Question Types Overview
|
|
2
|
-
|
|
3
|
-
> This folder contains one detailed doc per question type. Each doc covers: TypeScript type, mapper logic, answer shape, properties, and rendering guidance.
|
|
4
|
-
|
|
5
|
-
## The `SurveyQuestion` Discriminated Union
|
|
6
|
-
|
|
7
|
-
The SDK maps API questions into a **discriminated union** using the `type` field:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
type SurveyQuestion =
|
|
11
|
-
| RatingQuestion // type: 'rating' — NPS 0-10 numeric scale
|
|
12
|
-
| RadioQuestion // type: 'radio' — MCQ single/multi select
|
|
13
|
-
| TextQuestion // type: 'text' — Free text input
|
|
14
|
-
| CsatQuestion // type: 'csat' — 1-5 satisfaction (emoji/star)
|
|
15
|
-
| RatingScaleQuestion // type: 'rating_scale' — Star/emoji rating (1-5/1-10)
|
|
16
|
-
| SliderQuestion // type: 'slider' — Single range slider
|
|
17
|
-
| MatrixQuestion // type: 'matrix' — Grid (CFM/CSAT/RATING subTypes)
|
|
18
|
-
| RatingMatrixQuestion // type: 'rating_matrix' — NPS-style rating grid
|
|
19
|
-
| SliderMatrixQuestion // type: 'slider_matrix' — Grid of range sliders
|
|
20
|
-
| FileUploadQuestion // type: 'file_upload' — File attachment
|
|
21
|
-
| TextAndMediaQuestion; // type: 'text_and_media' — Display-only media card
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Question → Component Routing Table
|
|
25
|
-
|
|
26
|
-
| `question.type` | `question.subType` | Client Component | Answer Shape |
|
|
27
|
-
|-----------------|-------------------|-----------------|-------------|
|
|
28
|
-
| `'rating'` | — | `RatingScale` | `number` |
|
|
29
|
-
| `'radio'` | — | Inline radio cards | `string \| number` |
|
|
30
|
-
| `'text'` | — | Inline textarea | `string` |
|
|
31
|
-
| `'csat'` | — | `CsatScale` | `number \| null` |
|
|
32
|
-
| `'rating_scale'` | — | `RatingScale` (star/emoji variant) | `number` |
|
|
33
|
-
| `'slider'` | — | `CustomSliderTrack` | `number` |
|
|
34
|
-
| `'matrix'` | `'CFM_MATRIX'` | `LikertMatrixScale` | `MatrixAnswerMap` |
|
|
35
|
-
| `'matrix'` | `'CSAT_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` |
|
|
36
|
-
| `'matrix'` | `'RATING_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` |
|
|
37
|
-
| `'rating_matrix'` | — | `CsatMatrixScale` | `MatrixAnswerMap` |
|
|
38
|
-
| `'slider_matrix'` | — | `SliderMatrixScale` | `MatrixAnswerMap` |
|
|
39
|
-
| `'file_upload'` | — | `FileUploadScale` | `UploadedFile[]` |
|
|
40
|
-
| `'text_and_media'` | — | Inline media card | (no answer) |
|
|
41
|
-
|
|
42
|
-
## Shared Base Properties
|
|
43
|
-
|
|
44
|
-
All question types extend `QuestionBase`:
|
|
45
|
-
|
|
46
|
-
| Property | Type | Description |
|
|
47
|
-
|----------|------|-------------|
|
|
48
|
-
| `id` | `string` | ⚠️ MUST be DOM element id |
|
|
49
|
-
| `text` | `string` | Question text (may contain HTML) |
|
|
50
|
-
| `description` | `string?` | Supplementary instruction (HTML) |
|
|
51
|
-
| `required` | `boolean?` | Mandatory answer flag |
|
|
52
|
-
| `requiredErrorMessage` | `string?` | Custom validation error |
|
|
53
|
-
| `questionNumber` | `number?` | 1-indexed ordinal |
|
|
54
|
-
| `containerMediaUrl` | `string?` | Image/video alongside question |
|
|
55
|
-
| `containerMediaMimeType` | `string?` | MIME type for container media |
|
|
56
|
-
| `containerMediaAlignment` | `string?` | Media layout position |
|
|
57
|
-
| `containerMediaSize` | `number?` | Media width percentage |
|
|
58
|
-
|
|
59
|
-
## Files in This Folder
|
|
60
|
-
|
|
61
|
-
| File | Question Type | API Source |
|
|
62
|
-
|------|-------------|-----------|
|
|
63
|
-
| `01-rating.md` | `RatingQuestion` | `SCALE` (NPS/TEN_POINT) |
|
|
64
|
-
| `02-radio.md` | `RadioQuestion` | `MCQ` |
|
|
65
|
-
| `03-text.md` | `TextQuestion` | `TEXTFIELD` |
|
|
66
|
-
| `04-csat.md` | `CsatQuestion` | `SCALE` (CSAT, ≤5 options) |
|
|
67
|
-
| `05-rating-scale.md` | `RatingScaleQuestion` | `SCALE` (STAR/EMOJI) |
|
|
68
|
-
| `06-slider.md` | `SliderQuestion` | `SCALE` (INPUT_TYPE=SLIDER) |
|
|
69
|
-
| `07-matrix-cfm.md` | `MatrixQuestion` CFM | `CFM_MATRIX` |
|
|
70
|
-
| `08-matrix-csat.md` | `MatrixQuestion` CSAT | `CSAT_MATRIX` |
|
|
71
|
-
| `09-matrix-rating.md` | `RatingMatrixQuestion` | `RATING_MATRIX` |
|
|
72
|
-
| `10-slider-matrix.md` | `SliderMatrixQuestion` | `SLIDER_MATRIX` |
|
|
73
|
-
| `11-file-upload.md` | `FileUploadQuestion` | `FILE_UPLOAD` |
|
|
74
|
-
| `12-text-and-media.md` | `TextAndMediaQuestion` | `TEXT_AND_MEDIA` |
|