learning_ai_common_plat/packages/survey-client/README.md
saravanakumardb1 80df7c1c1e docs(packages): Add comprehensive README for broadcast-client and survey-client
- Installation, quick start, API reference
- React integration examples with hooks and providers
- Offline support documentation
- Type definitions and error handling
2026-03-03 08:29:07 -08:00

350 lines
7.9 KiB
Markdown

# @bytelyst/survey-client
TypeScript client for the ByteLyst Survey platform. Provides survey discovery, question flow management, response submission, and offline caching.
## Installation
```bash
npm install @bytelyst/survey-client
# or
pnpm add @bytelyst/survey-client
```
## Quick Start
```typescript
import { createSurveyClient } from '@bytelyst/survey-client';
const client = createSurveyClient({
baseURL: 'https://api.bytelyst.io/v1',
productId: 'lysnrai',
getAuthToken: async () => {
return localStorage.getItem('token');
}
});
// Check for active survey
const [survey, error] = await client.getActiveSurvey();
if (survey) {
console.log('Survey available:', survey.title);
}
```
## API Reference
### `createSurveyClient(config)`
Creates a new survey client instance.
**Config:**
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `baseURL` | string | Yes | API base URL |
| `productId` | string | Yes | Product identifier |
| `getAuthToken` | () => Promise<string> | Yes | Function to retrieve JWT token |
| `enableOfflineCache` | boolean | No | Enable offline response caching (default: true) |
### Methods
#### `getActiveSurvey()`
Check if there's an active survey for the current user.
```typescript
const [survey, error] = await client.getActiveSurvey();
// Returns: ActiveSurvey | null
```
#### `startSurvey(surveyId: string)`
Start a survey session.
```typescript
const [state, error] = await client.startSurvey('srv_123');
// Returns: { surveyStateId: string, currentQuestionIndex: number }
```
#### `submitAnswer(surveyId: string, questionId: string, answer: SurveyAnswer)`
Submit an answer for a specific question.
```typescript
const [result, error] = await client.submitAnswer('srv_123', 'q1', {
type: 'rating',
value: { value: 8 }
});
// Returns: { currentQuestionIndex: number, nextQuestionId: string, isComplete: boolean }
```
**Answer Types:**
```typescript
// Single choice
{ type: 'single_choice', value: { value: 'option_1' } }
// Multiple choice
{ type: 'multiple_choice', value: { values: ['opt_1', 'opt_2'] } }
// Rating/NPS/Scale
{ type: 'rating', value: { value: 8 } }
// Text
{ type: 'text', value: { value: 'My feedback here' } }
// Ranking
{ type: 'ranking', value: { order: ['opt_1', 'opt_2', 'opt_3'] } }
```
#### `completeSurvey(surveyId: string)`
Mark survey as complete and claim any incentive.
```typescript
const [completion, error] = await client.completeSurvey('srv_123');
// Returns: { success: boolean, incentiveClaimed: boolean, incentiveType?: string, incentiveAmount?: number }
```
#### `dismissSurvey(surveyId: string)`
Dismiss survey without completing.
```typescript
await client.dismissSurvey('srv_123');
```
#### `startPolling(intervalMs: number, callback: (survey) => void)`
Start polling for active surveys.
```typescript
client.startPolling(60000, (survey) => {
if (survey) {
showSurveyModal(survey);
}
});
```
#### `stopPolling()`
Stop survey polling.
```typescript
client.stopPolling();
```
#### `flushOfflineQueue()`
Manually flush any cached offline responses.
```typescript
await client.flushOfflineQueue();
```
## React Integration
### Hook Usage
```typescript
import { useSurvey } from './hooks/useSurvey';
function SurveyWidget() {
const {
activeSurvey,
currentQuestion,
submitAnswer,
completeSurvey,
progress
} = useSurvey({
autoStart: true,
pollingInterval: 60000
});
if (!activeSurvey) return null;
return (
<SurveyModal
survey={activeSurvey}
currentQuestion={currentQuestion}
progress={progress}
onSubmit={submitAnswer}
onComplete={completeSurvey}
/>
);
}
```
### Complete Survey Flow Example
```typescript
function SurveyFlow() {
const client = useSurveyClient();
const [survey, setSurvey] = useState<ActiveSurvey | null>(null);
const [currentIndex, setCurrentIndex] = useState(0);
const [answers, setAnswers] = useState<Record<string, SurveyAnswer>>({});
useEffect(() => {
loadSurvey();
}, []);
async function loadSurvey() {
const [activeSurvey] = await client.getActiveSurvey();
if (activeSurvey) {
await client.startSurvey(activeSurvey.id);
setSurvey(activeSurvey);
}
}
async function handleAnswer(questionId: string, answer: SurveyAnswer) {
const [result] = await client.submitAnswer(survey!.id, questionId, answer);
setAnswers(prev => ({ ...prev, [questionId]: answer }));
setCurrentIndex(result.currentQuestionIndex);
if (result.isComplete) {
const [completion] = await client.completeSurvey(survey!.id);
if (completion.incentiveClaimed) {
showIncentiveToast(completion.incentiveAmount, completion.incentiveType);
}
}
}
if (!survey) return null;
const question = survey.questions[currentIndex];
const isLast = currentIndex === survey.questions.length - 1;
return (
<QuestionView
question={question}
onSubmit={(answer) => handleAnswer(question.id, answer)}
onSkip={question.required ? undefined : () => handleSkip(question.id)}
/>
);
}
```
## Offline Support
The client automatically caches responses when offline:
```typescript
const client = createSurveyClient({
baseURL: 'https://api.bytelyst.io/v1',
productId: 'lysnrai',
getAuthToken: () => getToken(),
enableOfflineCache: true // Enabled by default
});
// Responses are queued when offline
// Flush queue manually or on reconnect
window.addEventListener('online', () => {
client.flushOfflineQueue();
});
```
## Types
```typescript
interface ActiveSurvey {
id: string;
title: string;
description?: string;
questions: Question[];
currentQuestionIndex: number;
incentive?: {
type: 'pro_days' | 'credits';
amount: number;
};
}
interface Question {
id: string;
type: 'single_choice' | 'multiple_choice' | 'rating' | 'scale' |
'nps' | 'text_short' | 'text_long' | 'ranking' | 'dropdown';
text: string;
description?: string;
required: boolean;
options?: QuestionOption[];
minValue?: number;
maxValue?: number;
maxLength?: number;
showIf?: ShowIfCondition;
}
interface QuestionOption {
id: string;
text: string;
emoji?: string;
}
interface SurveyAnswer {
type: string;
value: Record<string, unknown>;
}
interface SurveyConfig {
baseURL: string;
productId: string;
getAuthToken: () => Promise<string>;
enableOfflineCache?: boolean;
}
```
## Question Type Reference
| Type | Answer Format | UI Component |
|------|--------------|--------------|
| `single_choice` | `{ value: string }` | Radio group |
| `multiple_choice` | `{ values: string[] }` | Checkboxes |
| `rating` | `{ value: number }` | Star rating (1-5) |
| `scale` | `{ value: number }` | Numeric slider |
| `nps` | `{ value: number }` | 0-10 buttons |
| `text_short` | `{ value: string }` | Single line input |
| `text_long` | `{ value: string }` | Textarea |
| `ranking` | `{ order: string[] }` | Drag-to-sort |
| `dropdown` | `{ value: string }` | Select dropdown |
## Conditional Logic
Questions can be shown/hidden based on previous answers:
```typescript
// Question only shows if q1 answer is NOT 9 or 10
{
id: 'q2',
type: 'text_long',
text: 'What could we improve?',
showIf: {
questionId: 'q1',
operator: 'not_equals',
value: ['9', '10']
}
}
```
**Operators:** `equals`, `not_equals`, `greater_than`, `less_than`, `contains`
## Error Handling
```typescript
const [data, error] = await client.submitAnswer('srv_123', 'q1', answer);
if (error) {
switch (error.code) {
case 'SURVEY_NOT_FOUND':
console.error('Survey expired or unavailable');
break;
case 'ALREADY_COMPLETED':
console.error('User already completed this survey');
break;
case 'VALIDATION_ERROR':
console.error('Invalid answer format');
break;
default:
console.error('Survey error:', error.message);
}
}
```
## Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
## License
MIT © ByteLyst