@explorer02/cfm-survey-sdk 0.1.3 → 0.1.5

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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/postinstall.js +30 -18
  3. package/templates/AGENT.md +10 -1906
  4. package/templates/docs/01-sdk-core/01-fetch-survey.md +68 -0
  5. package/templates/docs/01-sdk-core/02-survey-mapper.md +85 -0
  6. package/templates/docs/01-sdk-core/03-question-mappers.md +114 -0
  7. package/templates/docs/01-sdk-core/04-pagination.md +72 -0
  8. package/templates/docs/01-sdk-core/05-validation.md +66 -0
  9. package/templates/docs/01-sdk-core/06-submit-survey.md +90 -0
  10. package/templates/docs/01-sdk-core/07-language-handling.md +111 -0
  11. package/templates/docs/01-sdk-core/08-icons-and-emojis.md +88 -0
  12. package/templates/docs/01-sdk-core/README.md +53 -0
  13. package/templates/docs/02-question-types/01-rating.md +52 -0
  14. package/templates/docs/02-question-types/02-radio.md +26 -0
  15. package/templates/docs/02-question-types/03-text.md +26 -0
  16. package/templates/docs/02-question-types/04-csat.md +54 -0
  17. package/templates/docs/02-question-types/05-rating-scale.md +26 -0
  18. package/templates/docs/02-question-types/06-slider.md +30 -0
  19. package/templates/docs/02-question-types/07-matrix-cfm.md +43 -0
  20. package/templates/docs/02-question-types/08-matrix-csat.md +29 -0
  21. package/templates/docs/02-question-types/09-matrix-rating.md +28 -0
  22. package/templates/docs/02-question-types/10-slider-matrix.md +40 -0
  23. package/templates/docs/02-question-types/11-file-upload.md +34 -0
  24. package/templates/docs/02-question-types/12-text-and-media.md +35 -0
  25. package/templates/docs/02-question-types/README.md +74 -0
  26. package/templates/docs/03-client-components/01-survey-page.md +113 -0
  27. package/templates/docs/03-client-components/02-question.md +57 -0
  28. package/templates/docs/03-client-components/03-rating-scale.md +38 -0
  29. package/templates/docs/03-client-components/04-csat-scale.md +40 -0
  30. package/templates/docs/03-client-components/05-csat-matrix-scale.md +43 -0
  31. package/templates/docs/03-client-components/06-likert-matrix-scale.md +38 -0
  32. package/templates/docs/03-client-components/07-slider-matrix-scale.md +44 -0
  33. package/templates/docs/03-client-components/08-file-upload-scale.md +33 -0
  34. package/templates/docs/03-client-components/09-custom-slider-track.md +27 -0
  35. package/templates/docs/03-client-components/10-header-footer.md +46 -0
  36. package/templates/docs/03-client-components/11-progress-bar.md +31 -0
  37. package/templates/docs/03-client-components/12-language-selector.md +41 -0
  38. package/templates/docs/03-client-components/13-matrix-dropdown.md +42 -0
  39. package/templates/docs/03-client-components/README.md +63 -0
  40. package/templates/docs/04-critical-rules/01-import-rules.md +51 -0
  41. package/templates/docs/04-critical-rules/02-action-dispatching.md +56 -0
  42. package/templates/docs/04-critical-rules/03-scroll-navigation.md +37 -0
  43. package/templates/docs/04-critical-rules/04-logo-branding.md +42 -0
  44. package/templates/docs/04-critical-rules/05-troubleshooting.md +46 -0
  45. package/templates/docs/04-critical-rules/README.md +29 -0
  46. package/templates/docs/index.md +129 -0
@@ -0,0 +1,68 @@
1
+ # Fetch Survey — `useFetchSurvey`
2
+
3
+ > **Source**: `packages/sdk/src/fetchSurvey/useFetchSurvey.ts`
4
+ > **Called by**: `useSurveySDK()` internally — the client never calls this directly.
5
+
6
+ ## What It Does
7
+
8
+ 1. Builds the API URL from the client's `instanceId` (JWT token)
9
+ 2. Makes a `GET` request to the Sprinklr CFM API endpoint
10
+ 3. Caches the raw response using `@tanstack/react-query` (singleton QueryClient — no provider needed)
11
+ 4. Re-maps the cached response whenever the target language changes (client-side, no re-fetch)
12
+
13
+ ## API Endpoint
14
+
15
+ ```
16
+ https://custom-p0.feedbook.me/api/feedback/survey/cfm?instanceId={JWT_TOKEN}
17
+ ```
18
+
19
+ The URL is constructed by `buildSurveyUrl()` in `fetchSurvey/utils/url.ts`.
20
+
21
+ ## Instance ID
22
+
23
+ The `instanceId` is a **JWT token** string that encodes:
24
+ - `surveyId` — Which survey to load
25
+ - `distributionEntityId` — Which distribution channel
26
+ - `surveyLanguage` — Default language (e.g. `'de'`)
27
+ - `partnerId` — Tenant identifier
28
+
29
+ **⚠️ CRITICAL**: The instanceId comes from the **client's chat prompt**. The agent must ask the client for it before building the survey. Without it, `useFetchSurvey` returns `null` and the survey cannot load.
30
+
31
+ ## Return Shape
32
+
33
+ ```typescript
34
+ {
35
+ survey: Survey | null, // Fully mapped survey object (see 02-survey-mapper.md)
36
+ session: SurveySession | null, // { instanceId, language } — passed to submit hook
37
+ fetching: boolean, // True while network request is in flight
38
+ fetchError: Error | null, // Network/API error
39
+ }
40
+ ```
41
+
42
+ ## Caching Behaviour
43
+
44
+ - Keyed by `['survey', url]` — fetched **once** per instanceId
45
+ - Language changes do NOT re-fetch — they re-map the cached raw response via `useMemo`
46
+ - Uses `cache: 'force-cache'` on the fetch request for browser-level HTTP caching
47
+
48
+ ## QueryClient
49
+
50
+ The SDK creates its own internal singleton `QueryClient` in `src/queryClient.ts`.
51
+ The client app does **NOT** need to wrap anything in `<QueryClientProvider>`.
52
+
53
+ ## Logging & Telemetry
54
+
55
+ The fetch lifecycle emits structured logs (when `debug: true`):
56
+ - `logFetchStart(url)` — before the request
57
+ - `logFetchPerfSuccess(durationMs, status, url, payloadSize)` — on success
58
+ - `logFetchPerfFailure(durationMs, status, url)` — on failure
59
+ - `trackFetchLatency(...)` — performance telemetry
60
+
61
+ ## Error Handling
62
+
63
+ | Scenario | Behaviour |
64
+ |----------|-----------|
65
+ | Network offline | `fetchError` populated, `survey` is `null` |
66
+ | HTTP 4xx/5xx | `fetchError` populated with status message |
67
+ | Invalid instanceId | API returns empty/malformed → `survey` maps to `null` |
68
+ | Successful but empty | `survey` is `null`, no error |
@@ -0,0 +1,85 @@
1
+ # Survey Mapper — `mapSurvey`
2
+
3
+ > **Source**: `packages/sdk/src/fetchSurvey/utils/mappers/surveyMapper.ts`
4
+ > **Called by**: `useFetchSurvey()` inside `useMemo` — re-runs when language changes.
5
+
6
+ ## What It Does
7
+
8
+ Transforms the raw API response (`ApiResponse`) into a clean, typed `Survey` object:
9
+
10
+ ```
11
+ ApiResponse (raw network JSON)
12
+
13
+ ├── extractQuestions() → Flat list of ApiQuestion[]
14
+ ├── resolveSurveyLanguage() → Determine active language code
15
+ ├── extractSurveyLanguages()→ Build SurveyLanguage[] for language switcher
16
+ └── mapSurveyPages() → Pages → Questions → per-type mappers
17
+
18
+ └── mapQuestion() per question (see 03-question-mappers.md)
19
+ ```
20
+
21
+ ## Output: `Survey` Type
22
+
23
+ ```typescript
24
+ type Survey = {
25
+ id: string; // Survey identifier
26
+ language: string; // Active language code (e.g. 'en', 'de')
27
+ languages: SurveyLanguage[]; // All available languages for the switcher
28
+ pages: SurveyPageData[]; // Ordered list of pages
29
+ };
30
+
31
+ type SurveyPageData = {
32
+ id: string; // Page identifier
33
+ title?: string; // Optional page heading
34
+ questions: SurveyQuestion[]; // Questions on this page
35
+ };
36
+
37
+ type SurveyLanguage = {
38
+ code: string; // BCP-47 code (e.g. 'de')
39
+ name: string; // Display name (e.g. 'Deutsch')
40
+ };
41
+ ```
42
+
43
+ ## Key Functions
44
+
45
+ ### `extractQuestions(api)`
46
+ Traverses `api.cfmSurveyDTO.surveyPages[].questionHolders[].question` to produce a flat array of raw API questions.
47
+
48
+ ### `resolveSurveyLanguage(api, language?)`
49
+ Priority: `language` parameter → `api.surveyLanguage` → `api.cfmSurveyDTO.baseLanguage` → `'en'`
50
+
51
+ ### `extractSession(api)`
52
+ Returns `{ instanceId, language }` or `null` if no valid instanceId exists. This session object is passed to the submit hook.
53
+
54
+ ### `mapSurveyPages(api, language, placeholders)`
55
+ Iterates over `api.cfmSurveyDTO.surveyPages`, calling `mapQuestion()` for each question within each page's `questionHolders`. Returns `SurveyPageData[]`.
56
+
57
+ ## API Response Structure (Simplified)
58
+
59
+ ```typescript
60
+ type ApiResponse = {
61
+ instanceId: string;
62
+ surveyLanguage: string;
63
+ cfmSurveyDTO: {
64
+ id: string;
65
+ baseLanguage: string;
66
+ surveyPages: Array<{
67
+ id: string;
68
+ pageTitle?: string;
69
+ questionHolders: Array<{
70
+ question: ApiQuestion;
71
+ }>;
72
+ }>;
73
+ };
74
+ };
75
+ ```
76
+
77
+ ## Language Re-mapping
78
+
79
+ When the user switches language via the `LanguageSelector`:
80
+ 1. `selectedLanguage` state changes in `SurveyPage.tsx`
81
+ 2. This flows into `useSurveySDK({ options: { language: selectedLanguage } })`
82
+ 3. `useFetchSurvey` does **NOT re-fetch** — the raw `ApiResponse` is cached
83
+ 4. `useMemo` re-runs `mapSurvey(apiResponse, newLanguage, placeholders)`
84
+ 5. Each question's text, options, and labels are resolved from `translations[language]`
85
+ 6. The entire UI re-renders with translated content
@@ -0,0 +1,114 @@
1
+ # Question Mappers — `mapQuestion`
2
+
3
+ > **Source**: `packages/sdk/src/fetchSurvey/utils/mappers/questionMappers/index.ts`
4
+ > **Called by**: `mapSurveyPages()` for each question in each page.
5
+
6
+ ## What It Does
7
+
8
+ Takes a raw `ApiQuestion` and produces a fully typed `SurveyQuestion` variant. This is the core routing logic that determines what question type the client component will render.
9
+
10
+ ## Dispatch Pipeline
11
+
12
+ ```
13
+ ApiQuestion
14
+
15
+ ├── resolveBase(raw, configs, language, placeholders)
16
+ │ → { id, text, description, required, requiredErrorMessage, containerMedia* }
17
+
18
+ ├── normalizeType(raw.answerType)
19
+ │ → 'rating' | 'text' | 'matrix' | 'rating_matrix' | 'slider_matrix'
20
+ │ | 'text_and_media' | 'file_upload' | 'radio' (default)
21
+
22
+ └── switch(type) → per-type mapper
23
+ ├── 'rating' → mapRatingQuestion() [handles NPS, CSAT, Slider, RatingScale]
24
+ ├── 'text' → mapTextQuestion()
25
+ ├── 'matrix' → mapMatrixQuestion() [handles CFM, CSAT, RATING matrix]
26
+ ├── 'rating_matrix' → mapMatrixQuestion() [same handler, different subType]
27
+ ├── 'slider_matrix' → mapSliderMatrixQuestion()
28
+ ├── 'text_and_media' → mapTextAndMediaQuestion()
29
+ ├── 'file_upload' → mapFileUploadQuestion()
30
+ └── default → mapRadioQuestion() [MCQ and any unrecognised type]
31
+ ```
32
+
33
+ ## `normalizeType(answerType)` — Type Resolution
34
+
35
+ | API `answerType` | Normalized Type | Notes |
36
+ |-----------------|----------------|-------|
37
+ | `'SCALE'` | `'rating'` | Further sub-typed inside ratingMapper |
38
+ | `'RATING'` | `'rating'` | Legacy alias |
39
+ | `'RATING_MATRIX'` | `'rating_matrix'` | Grid with NPS-style columns |
40
+ | `'TEXTFIELD'` | `'text'` | Short or long text input |
41
+ | `'MCQ'` | `'radio'` | Multiple choice (single select) |
42
+ | `'CFM_MATRIX'` | `'matrix'` | Likert-style grid |
43
+ | `'CSAT_MATRIX'` | `'matrix'` | Satisfaction grid with emoji/star |
44
+ | `'SLIDER_MATRIX'` | `'slider_matrix'` | Grid of range sliders |
45
+ | `'TEXT_AND_MEDIA'` | `'text_and_media'` | Display-only media card |
46
+ | `'FILE_UPLOAD'` | `'file_upload'` | File attachment question |
47
+ | Any other | `'radio'` | Safe fallback |
48
+
49
+ ## `resolveBase()` — Shared Fields
50
+
51
+ Every question variant inherits these base fields:
52
+
53
+ ```typescript
54
+ type QuestionBase = {
55
+ id: string; // ⚠️ MUST be used as DOM element id
56
+ text: string; // May contain HTML — use dangerouslySetInnerHTML
57
+ description?: string; // Supplementary instruction text (HTML)
58
+ required?: boolean; // Whether answer is mandatory
59
+ requiredErrorMessage?: string; // Custom validation error text
60
+ questionNumber?: number; // 1-indexed ordinal position
61
+ containerMediaUrl?: string; // Image/video shown alongside question
62
+ containerMediaMimeType?: string; // MIME type for container media
63
+ containerMediaAlignment?: string;// Layout position of container media
64
+ containerMediaSize?: number; // Width percentage of container media
65
+ };
66
+ ```
67
+
68
+ ### Text Resolution Priority
69
+ ```
70
+ language translation (richText) → language translation (plainText)
71
+ → default richText → default plainText → ''
72
+ ```
73
+
74
+ ### Placeholder Replacement
75
+ The `resolveBase` function replaces `{{TOKEN}}` patterns in question text and description:
76
+ ```typescript
77
+ text.replace(/\{\{(\w+)\}\}/g, (match, key) =>
78
+ placeholders[key] !== undefined ? placeholders[key] : match
79
+ );
80
+ ```
81
+
82
+ ## Rating Mapper Sub-Dispatch
83
+
84
+ The `mapRatingQuestion()` handler further sub-types based on `questionConfigs`:
85
+
86
+ ```
87
+ SCALE answerType
88
+
89
+ ├── SCALE_TYPE=CSAT (or ≤5 options) → type: 'csat'
90
+ ├── SCALE_TYPE=STAR/EMOJI/FIVE_POINT → type: 'rating_scale'
91
+ ├── INPUT_TYPE=SLIDER → type: 'slider'
92
+ └── default (NPS/TEN_POINT) → type: 'rating'
93
+ ```
94
+
95
+ This means the client component's `Question.tsx` needs to handle **all these refined types**, not just the raw `'rating'` type.
96
+
97
+ ## Final Output: `SurveyQuestion` Union
98
+
99
+ ```typescript
100
+ type SurveyQuestion =
101
+ | RatingQuestion // type: 'rating'
102
+ | RadioQuestion // type: 'radio'
103
+ | TextQuestion // type: 'text'
104
+ | MatrixQuestion // type: 'matrix'
105
+ | RatingMatrixQuestion // type: 'rating_matrix'
106
+ | SliderMatrixQuestion // type: 'slider_matrix'
107
+ | TextAndMediaQuestion // type: 'text_and_media'
108
+ | CsatQuestion // type: 'csat'
109
+ | SliderQuestion // type: 'slider'
110
+ | RatingScaleQuestion // type: 'rating_scale'
111
+ | FileUploadQuestion; // type: 'file_upload'
112
+ ```
113
+
114
+ Each type is documented in detail in `02-question-types/`.
@@ -0,0 +1,72 @@
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.
@@ -0,0 +1,66 @@
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.
@@ -0,0 +1,90 @@
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 |
@@ -0,0 +1,111 @@
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.