- react-native-platform-sdk: add tsconfig.json + 13 source files (core, auth, telemetry, feature-flags, kill-switch, broadcasts, surveys) - react-native-platform-sdk: React hooks + providers wrapping platform-service APIs via fetch - admin-web: fix ThemeEditor.tsx + themes/active/route.ts lysnrai token type access - tracker-web: product-context import path fix (pre-existing)
109 lines
2.8 KiB
TypeScript
109 lines
2.8 KiB
TypeScript
/**
|
|
* Surveys module — React context + hook for in-app surveys in React Native apps.
|
|
*/
|
|
|
|
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
|
|
import type { PlatformSDK } from '../core.js';
|
|
|
|
export interface Question {
|
|
id: string;
|
|
text: string;
|
|
type: 'rating' | 'text' | 'choice';
|
|
options?: string[];
|
|
required: boolean;
|
|
}
|
|
|
|
export interface ActiveSurvey {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
questions: Question[];
|
|
expiresAt?: string;
|
|
}
|
|
|
|
interface SurveyContextType {
|
|
activeSurvey: ActiveSurvey | null;
|
|
submit: (surveyId: string, answers: Record<string, unknown>) => Promise<void>;
|
|
dismiss: (surveyId: string) => void;
|
|
refresh: () => Promise<void>;
|
|
}
|
|
|
|
const SurveyContext = createContext<SurveyContextType | null>(null);
|
|
|
|
export function useSurveys(): SurveyContextType {
|
|
const ctx = useContext(SurveyContext);
|
|
if (!ctx) throw new Error('useSurveys must be used within a SurveyProvider');
|
|
return ctx;
|
|
}
|
|
|
|
interface SurveyProviderProps {
|
|
sdk: PlatformSDK;
|
|
/** Poll interval in ms (default: 600000 = 10 min) */
|
|
pollInterval?: number;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function SurveyProvider({
|
|
sdk,
|
|
pollInterval = 600_000,
|
|
children,
|
|
}: SurveyProviderProps): React.JSX.Element {
|
|
const [activeSurvey, setActiveSurvey] = useState<ActiveSurvey | null>(null);
|
|
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
const res = await sdk.fetch('/api/surveys/active');
|
|
if (res.ok) {
|
|
const data = (await res.json()) as ActiveSurvey | null;
|
|
setActiveSurvey(data ?? null);
|
|
}
|
|
} catch {
|
|
/* silent */
|
|
}
|
|
}, [sdk]);
|
|
|
|
const submit = useCallback(
|
|
async (surveyId: string, answers: Record<string, unknown>) => {
|
|
await sdk.fetch(`/api/surveys/${surveyId}/respond`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ answers }),
|
|
});
|
|
setActiveSurvey(null);
|
|
},
|
|
[sdk]
|
|
);
|
|
|
|
const dismiss = useCallback(
|
|
(surveyId: string) => {
|
|
setActiveSurvey(null);
|
|
sdk.fetch(`/api/surveys/${surveyId}/dismiss`, { method: 'POST' }).catch(() => {});
|
|
},
|
|
[sdk]
|
|
);
|
|
|
|
useEffect(() => {
|
|
refresh();
|
|
const id = setInterval(refresh, pollInterval);
|
|
return () => clearInterval(id);
|
|
}, [refresh, pollInterval]);
|
|
|
|
const value: SurveyContextType = { activeSurvey, submit, dismiss, refresh };
|
|
return React.createElement(SurveyContext.Provider, { value }, children);
|
|
}
|
|
|
|
// MARK: - UI Components
|
|
|
|
interface SurveyModalProps {
|
|
survey: ActiveSurvey | null;
|
|
onSubmit: (answers: Record<string, unknown>) => void;
|
|
onDismiss: () => void;
|
|
}
|
|
|
|
/**
|
|
* Placeholder survey modal — product apps should implement their own
|
|
* styled version. Returns null.
|
|
*/
|
|
export function SurveyModal(_props: SurveyModalProps): React.JSX.Element | null {
|
|
return null;
|
|
}
|