@shellapps/experience-react 1.0.1 → 1.2.0

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default from 'react';
4
+ import * as _shellapps_experience from '@shellapps/experience';
4
5
  import { ExperienceConfig, Experience } from '@shellapps/experience';
5
6
 
6
7
  interface ExperienceProviderProps {
@@ -34,16 +35,37 @@ declare class ErrorBoundary extends React__default.Component<ErrorBoundaryProps,
34
35
  render(): string | number | boolean | Iterable<React__default.ReactNode> | react_jsx_runtime.JSX.Element | null | undefined;
35
36
  }
36
37
 
38
+ declare function useExperience(): _shellapps_experience.Experience;
37
39
  declare function useTrack(): {
38
- track: (eventName: string, metadata?: Record<string, unknown>) => void;
40
+ track: (eventName: string, metadata?: Record<string, string>) => void;
39
41
  trackPageView: () => void;
40
42
  };
41
43
  declare function useFlag<T>(flagName: string, defaultValue: T): T;
42
- declare function useTranslation(): {
43
- t: (key: string) => string;
44
- locale: "en";
45
- };
44
+ interface TranslationResult {
45
+ t: (key: string, params?: Record<string, string | number>) => string;
46
+ locale: string;
47
+ setLocale: (locale: string) => void;
48
+ locales: Array<{
49
+ code: string;
50
+ name: string;
51
+ progress?: number;
52
+ }>;
53
+ isLoading: boolean;
54
+ }
55
+ /**
56
+ * Translation hook for the Experience Platform.
57
+ *
58
+ * Fetches translations from the Experience API, caches in memory + localStorage,
59
+ * and provides `t()` for interpolation and `setLocale()` for switching.
60
+ *
61
+ * Usage:
62
+ * ```tsx
63
+ * const { t, locale, setLocale, locales } = useTranslation();
64
+ * return <h1>{t('greeting', { name: 'Alex' })}</h1>;
65
+ * ```
66
+ */
67
+ declare function useTranslation(): TranslationResult;
46
68
 
47
69
  declare const ExperienceContext: React.Context<Experience | null>;
48
70
 
49
- export { ErrorBoundary, ExperienceContext, ExperienceProvider, type ExperienceProviderProps, useFlag, useTrack, useTranslation };
71
+ export { ErrorBoundary, ExperienceContext, ExperienceProvider, type ExperienceProviderProps, useExperience, useFlag, useTrack, useTranslation };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default from 'react';
4
+ import * as _shellapps_experience from '@shellapps/experience';
4
5
  import { ExperienceConfig, Experience } from '@shellapps/experience';
5
6
 
6
7
  interface ExperienceProviderProps {
@@ -34,16 +35,37 @@ declare class ErrorBoundary extends React__default.Component<ErrorBoundaryProps,
34
35
  render(): string | number | boolean | Iterable<React__default.ReactNode> | react_jsx_runtime.JSX.Element | null | undefined;
35
36
  }
36
37
 
38
+ declare function useExperience(): _shellapps_experience.Experience;
37
39
  declare function useTrack(): {
38
- track: (eventName: string, metadata?: Record<string, unknown>) => void;
40
+ track: (eventName: string, metadata?: Record<string, string>) => void;
39
41
  trackPageView: () => void;
40
42
  };
41
43
  declare function useFlag<T>(flagName: string, defaultValue: T): T;
42
- declare function useTranslation(): {
43
- t: (key: string) => string;
44
- locale: "en";
45
- };
44
+ interface TranslationResult {
45
+ t: (key: string, params?: Record<string, string | number>) => string;
46
+ locale: string;
47
+ setLocale: (locale: string) => void;
48
+ locales: Array<{
49
+ code: string;
50
+ name: string;
51
+ progress?: number;
52
+ }>;
53
+ isLoading: boolean;
54
+ }
55
+ /**
56
+ * Translation hook for the Experience Platform.
57
+ *
58
+ * Fetches translations from the Experience API, caches in memory + localStorage,
59
+ * and provides `t()` for interpolation and `setLocale()` for switching.
60
+ *
61
+ * Usage:
62
+ * ```tsx
63
+ * const { t, locale, setLocale, locales } = useTranslation();
64
+ * return <h1>{t('greeting', { name: 'Alex' })}</h1>;
65
+ * ```
66
+ */
67
+ declare function useTranslation(): TranslationResult;
46
68
 
47
69
  declare const ExperienceContext: React.Context<Experience | null>;
48
70
 
49
- export { ErrorBoundary, ExperienceContext, ExperienceProvider, type ExperienceProviderProps, useFlag, useTrack, useTranslation };
71
+ export { ErrorBoundary, ExperienceContext, ExperienceProvider, type ExperienceProviderProps, useExperience, useFlag, useTrack, useTranslation };
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  ErrorBoundary: () => ErrorBoundary,
34
34
  ExperienceContext: () => ExperienceContext,
35
35
  ExperienceProvider: () => ExperienceProvider,
36
+ useExperience: () => useExperience,
36
37
  useFlag: () => useFlag,
37
38
  useTrack: () => useTrack,
38
39
  useTranslation: () => useTranslation
@@ -111,7 +112,8 @@ var ErrorBoundary = class extends import_react3.default.Component {
111
112
  }
112
113
  componentDidCatch(error, errorInfo) {
113
114
  this.context?.captureError(error, {
114
- componentStack: errorInfo.componentStack || ""
115
+ extra: { componentStack: errorInfo.componentStack || "" },
116
+ severity: "fatal"
115
117
  });
116
118
  this.props.onError?.(error, errorInfo);
117
119
  }
@@ -152,26 +154,117 @@ function useExperience() {
152
154
  }
153
155
  function useTrack() {
154
156
  const experience = useExperience();
155
- return {
157
+ return (0, import_react4.useMemo)(() => ({
156
158
  track: (eventName, metadata) => experience.track(eventName, metadata),
157
159
  trackPageView: () => experience.trackPageView()
158
- };
160
+ }), [experience]);
159
161
  }
160
162
  function useFlag(flagName, defaultValue) {
161
163
  const experience = useExperience();
162
164
  return experience.getFlag(flagName, defaultValue);
163
165
  }
166
+ var translationCache = {};
167
+ var fetchPromises = /* @__PURE__ */ new Map();
164
168
  function useTranslation() {
165
- return {
166
- t: (key) => key,
167
- locale: "en"
168
- };
169
+ const experience = useExperience();
170
+ const config = experience.config || {};
171
+ const appId = config.appId || "";
172
+ const apiBaseUrl = config.apiUrl || config.baseUrl || "https://experience-api.shellapps.com";
173
+ const [locale, setLocaleState] = (0, import_react4.useState)(() => {
174
+ if (typeof window === "undefined") return "en";
175
+ const saved = localStorage.getItem(`exp_locale_${appId}`);
176
+ if (saved) return saved;
177
+ return navigator.language?.split("-")[0] || "en";
178
+ });
179
+ const [translations, setTranslations] = (0, import_react4.useState)(() => {
180
+ if (translationCache[locale]) return translationCache[locale];
181
+ if (typeof window !== "undefined") {
182
+ try {
183
+ const cached = localStorage.getItem(`exp_translations_${appId}_${locale}`);
184
+ if (cached) {
185
+ const parsed = JSON.parse(cached);
186
+ translationCache[locale] = parsed;
187
+ return parsed;
188
+ }
189
+ } catch {
190
+ }
191
+ }
192
+ return {};
193
+ });
194
+ const [locales, setLocales] = (0, import_react4.useState)([]);
195
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
196
+ const fetchTranslations = (0, import_react4.useCallback)(async (loc) => {
197
+ if (!appId) return {};
198
+ const cacheKey = `${appId}_${loc}`;
199
+ const existing = fetchPromises.get(cacheKey);
200
+ if (existing) return existing;
201
+ const promise = (async () => {
202
+ try {
203
+ const res = await fetch(`${apiBaseUrl}/api/v1/translations/${appId}/${loc}`);
204
+ if (!res.ok) return {};
205
+ const data = await res.json();
206
+ translationCache[loc] = data;
207
+ if (typeof window !== "undefined") {
208
+ try {
209
+ localStorage.setItem(`exp_translations_${appId}_${loc}`, JSON.stringify(data));
210
+ } catch {
211
+ }
212
+ }
213
+ return data;
214
+ } catch {
215
+ return {};
216
+ } finally {
217
+ fetchPromises.delete(cacheKey);
218
+ }
219
+ })();
220
+ fetchPromises.set(cacheKey, promise);
221
+ return promise;
222
+ }, [appId, apiBaseUrl]);
223
+ (0, import_react4.useEffect)(() => {
224
+ if (!appId) return;
225
+ fetch(`${apiBaseUrl}/api/v1/translations/${appId}/locales`).then((res) => res.ok ? res.json() : { locales: [] }).then((data) => setLocales(data.locales || [])).catch(() => {
226
+ });
227
+ }, [appId, apiBaseUrl]);
228
+ (0, import_react4.useEffect)(() => {
229
+ if (!appId) return;
230
+ if (translationCache[locale]) {
231
+ setTranslations(translationCache[locale]);
232
+ return;
233
+ }
234
+ setIsLoading(true);
235
+ fetchTranslations(locale).then((data) => {
236
+ setTranslations(data);
237
+ setIsLoading(false);
238
+ });
239
+ }, [locale, appId, fetchTranslations]);
240
+ const t = (0, import_react4.useCallback)((key, params) => {
241
+ let result = translations[key] ?? key;
242
+ if (result.includes("||") && params && "count" in params) {
243
+ const parts = result.split("||");
244
+ const count = Number(params.count);
245
+ result = count === 1 ? parts[0] ?? result : parts[1] ?? result;
246
+ }
247
+ if (params) {
248
+ for (const [k, v] of Object.entries(params)) {
249
+ result = result.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
250
+ }
251
+ }
252
+ return result;
253
+ }, [translations]);
254
+ const setLocale = (0, import_react4.useCallback)((newLocale) => {
255
+ setLocaleState(newLocale);
256
+ if (typeof window !== "undefined") {
257
+ localStorage.setItem(`exp_locale_${appId}`, newLocale);
258
+ }
259
+ }, [appId]);
260
+ return { t, locale, setLocale, locales, isLoading };
169
261
  }
170
262
  // Annotate the CommonJS export names for ESM import in node:
171
263
  0 && (module.exports = {
172
264
  ErrorBoundary,
173
265
  ExperienceContext,
174
266
  ExperienceProvider,
267
+ useExperience,
175
268
  useFlag,
176
269
  useTrack,
177
270
  useTranslation
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ExperienceProvider.tsx","../src/context.ts","../src/ErrorBoundary.tsx","../src/hooks.ts"],"sourcesContent":["export { ExperienceProvider } from './ExperienceProvider';\nexport type { ExperienceProviderProps } from './ExperienceProvider';\nexport { ErrorBoundary } from './ErrorBoundary';\nexport { useTrack, useFlag, useTranslation } from './hooks';\nexport { ExperienceContext } from './context';\n","import React, { useEffect, useRef } from 'react';\nimport { Experience } from '@shellapps/experience';\nimport type { ExperienceConfig } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\nexport interface ExperienceProviderProps {\n appId: string;\n apiKey: string;\n profileId?: string;\n options?: Partial<Omit<ExperienceConfig, 'appId' | 'apiKey'>>;\n children: React.ReactNode;\n}\n\nexport function ExperienceProvider({ appId, apiKey, profileId, options, children }: ExperienceProviderProps) {\n const instanceRef = useRef<Experience | null>(null);\n\n if (!instanceRef.current) {\n const instance = Experience.init({ appId, apiKey, ...options });\n if (profileId) instance.identify(profileId);\n instanceRef.current = instance;\n }\n\n useEffect(() => {\n if (profileId && instanceRef.current) {\n instanceRef.current.identify(profileId);\n }\n }, [profileId]);\n\n // data-t auto-tracking\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n let el = e.target as HTMLElement | null;\n while (el) {\n const tid = el.getAttribute?.('data-t');\n if (tid) {\n instanceRef.current?.track('element_click', { elementTid: tid });\n break;\n }\n el = el.parentElement;\n }\n };\n document.addEventListener('click', handler, true);\n return () => document.removeEventListener('click', handler, true);\n }, []);\n\n useEffect(() => {\n return () => {\n instanceRef.current?.shutdown();\n };\n }, []);\n\n return (\n <ExperienceContext.Provider value={instanceRef.current}>\n {children}\n </ExperienceContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { Experience } from '@shellapps/experience';\n\nexport const ExperienceContext = createContext<Experience | null>(null);\n","import React from 'react';\nimport type { Experience } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\ninterface ErrorBoundaryProps {\n fallback?: React.ReactNode;\n showCommentForm?: boolean;\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n children: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n comment: string;\n submitted: boolean;\n}\n\nexport class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n static contextType = ExperienceContext;\n declare context: Experience | null;\n\n state: ErrorBoundaryState = {\n hasError: false,\n error: null,\n comment: '',\n submitted: false,\n };\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.context?.captureError(error, {\n componentStack: errorInfo.componentStack || '',\n });\n this.props.onError?.(error, errorInfo);\n }\n\n private handleSubmitComment = () => {\n if (this.state.comment.trim() && this.state.error) {\n this.context?.captureMessage(\n `User feedback: ${this.state.comment}`,\n 'info',\n );\n this.setState({ submitted: true });\n }\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div style={{ padding: 20, textAlign: 'center' }}>\n <h2>Something went wrong</h2>\n <p>{this.state.error?.message}</p>\n {this.props.showCommentForm && !this.state.submitted && (\n <div>\n <textarea\n placeholder=\"Tell us what happened...\"\n value={this.state.comment}\n onChange={(e) => this.setState({ comment: e.target.value })}\n style={{ width: '100%', minHeight: 80, marginTop: 10 }}\n />\n <button onClick={this.handleSubmitComment} style={{ marginTop: 8 }}>\n Submit\n </button>\n </div>\n )}\n {this.state.submitted && <p>Thank you for your feedback!</p>}\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","import { useContext } from 'react';\nimport { ExperienceContext } from './context';\n\nfunction useExperience() {\n const ctx = useContext(ExperienceContext);\n if (!ctx) throw new Error('useExperience must be used within ExperienceProvider');\n return ctx;\n}\n\nexport function useTrack() {\n const experience = useExperience();\n return {\n track: (eventName: string, metadata?: Record<string, unknown>) => experience.track(eventName, metadata),\n trackPageView: () => experience.trackPageView(),\n };\n}\n\nexport function useFlag<T>(flagName: string, defaultValue: T): T {\n const experience = useExperience();\n return experience.getFlag(flagName, defaultValue);\n}\n\nexport function useTranslation() {\n return {\n t: (key: string) => key,\n locale: 'en' as const,\n };\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","React","import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ExperienceProvider.tsx","../src/context.ts","../src/ErrorBoundary.tsx","../src/hooks.ts"],"sourcesContent":["export { ExperienceProvider } from './ExperienceProvider';\nexport type { ExperienceProviderProps } from './ExperienceProvider';\nexport { ErrorBoundary } from './ErrorBoundary';\nexport { useExperience, useTrack, useFlag, useTranslation } from './hooks';\nexport { ExperienceContext } from './context';\n","import React, { useEffect, useRef } from 'react';\nimport { Experience } from '@shellapps/experience';\nimport type { ExperienceConfig } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\nexport interface ExperienceProviderProps {\n appId: string;\n apiKey: string;\n profileId?: string;\n options?: Partial<Omit<ExperienceConfig, 'appId' | 'apiKey'>>;\n children: React.ReactNode;\n}\n\nexport function ExperienceProvider({ appId, apiKey, profileId, options, children }: ExperienceProviderProps) {\n const instanceRef = useRef<Experience | null>(null);\n\n if (!instanceRef.current) {\n const instance = Experience.init({ appId, apiKey, ...options });\n if (profileId) instance.identify(profileId);\n instanceRef.current = instance;\n }\n\n useEffect(() => {\n if (profileId && instanceRef.current) {\n instanceRef.current.identify(profileId);\n }\n }, [profileId]);\n\n // data-t auto-tracking\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n let el = e.target as HTMLElement | null;\n while (el) {\n const tid = el.getAttribute?.('data-t');\n if (tid) {\n instanceRef.current?.track('element_click', { elementTid: tid });\n break;\n }\n el = el.parentElement;\n }\n };\n document.addEventListener('click', handler, true);\n return () => document.removeEventListener('click', handler, true);\n }, []);\n\n useEffect(() => {\n return () => {\n instanceRef.current?.shutdown();\n };\n }, []);\n\n return (\n <ExperienceContext.Provider value={instanceRef.current}>\n {children}\n </ExperienceContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { Experience } from '@shellapps/experience';\n\nexport const ExperienceContext = createContext<Experience | null>(null);\n","import React from 'react';\nimport type { Experience } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\ninterface ErrorBoundaryProps {\n fallback?: React.ReactNode;\n showCommentForm?: boolean;\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n children: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n comment: string;\n submitted: boolean;\n}\n\nexport class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n static contextType = ExperienceContext;\n declare context: Experience | null;\n\n state: ErrorBoundaryState = {\n hasError: false,\n error: null,\n comment: '',\n submitted: false,\n };\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.context?.captureError(error, {\n extra: { componentStack: errorInfo.componentStack || '' },\n severity: 'fatal' as any,\n });\n this.props.onError?.(error, errorInfo);\n }\n\n private handleSubmitComment = () => {\n if (this.state.comment.trim() && this.state.error) {\n this.context?.captureMessage(\n `User feedback: ${this.state.comment}`,\n 'info',\n );\n this.setState({ submitted: true });\n }\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div style={{ padding: 20, textAlign: 'center' }}>\n <h2>Something went wrong</h2>\n <p>{this.state.error?.message}</p>\n {this.props.showCommentForm && !this.state.submitted && (\n <div>\n <textarea\n placeholder=\"Tell us what happened...\"\n value={this.state.comment}\n onChange={(e) => this.setState({ comment: e.target.value })}\n style={{ width: '100%', minHeight: 80, marginTop: 10 }}\n />\n <button onClick={this.handleSubmitComment} style={{ marginTop: 8 }}>\n Submit\n </button>\n </div>\n )}\n {this.state.submitted && <p>Thank you for your feedback!</p>}\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","import { useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport { ExperienceContext } from './context';\n\nexport function useExperience() {\n const ctx = useContext(ExperienceContext);\n if (!ctx) throw new Error('useExperience must be used within ExperienceProvider');\n return ctx;\n}\n\nexport function useTrack() {\n const experience = useExperience();\n return useMemo(() => ({\n track: (eventName: string, metadata?: Record<string, string>) => experience.track(eventName, metadata),\n trackPageView: () => experience.trackPageView(),\n }), [experience]);\n}\n\nexport function useFlag<T>(flagName: string, defaultValue: T): T {\n const experience = useExperience();\n return experience.getFlag(flagName, defaultValue);\n}\n\n// ─── Translation Types ──────────────────────────────────────────\n\ninterface TranslationResult {\n t: (key: string, params?: Record<string, string | number>) => string;\n locale: string;\n setLocale: (locale: string) => void;\n locales: Array<{ code: string; name: string; progress?: number }>;\n isLoading: boolean;\n}\n\ninterface TranslationCache {\n [locale: string]: Record<string, string>;\n}\n\n// Module-level cache shared across hook instances\nconst translationCache: TranslationCache = {};\nconst fetchPromises = new Map<string, Promise<Record<string, string>>>();\n\n/**\n * Translation hook for the Experience Platform.\n *\n * Fetches translations from the Experience API, caches in memory + localStorage,\n * and provides `t()` for interpolation and `setLocale()` for switching.\n *\n * Usage:\n * ```tsx\n * const { t, locale, setLocale, locales } = useTranslation();\n * return <h1>{t('greeting', { name: 'Alex' })}</h1>;\n * ```\n */\nexport function useTranslation(): TranslationResult {\n const experience = useExperience();\n const config = (experience as any).config || {};\n const appId: string = config.appId || '';\n const apiBaseUrl: string = config.apiUrl || config.baseUrl || 'https://experience-api.shellapps.com';\n\n const [locale, setLocaleState] = useState(() => {\n if (typeof window === 'undefined') return 'en';\n // Check localStorage for persisted locale preference\n const saved = localStorage.getItem(`exp_locale_${appId}`);\n if (saved) return saved;\n return navigator.language?.split('-')[0] || 'en';\n });\n\n const [translations, setTranslations] = useState<Record<string, string>>(() => {\n // Try memory cache first\n if (translationCache[locale]) return translationCache[locale]!;\n // Try localStorage\n if (typeof window !== 'undefined') {\n try {\n const cached = localStorage.getItem(`exp_translations_${appId}_${locale}`);\n if (cached) {\n const parsed = JSON.parse(cached);\n translationCache[locale] = parsed;\n return parsed;\n }\n } catch { /* ignore */ }\n }\n return {};\n });\n\n const [locales, setLocales] = useState<Array<{ code: string; name: string; progress?: number }>>([]);\n const [isLoading, setIsLoading] = useState(false);\n\n // Fetch translations for a locale\n const fetchTranslations = useCallback(async (loc: string): Promise<Record<string, string>> => {\n if (!appId) return {};\n\n // Deduplicate in-flight requests\n const cacheKey = `${appId}_${loc}`;\n const existing = fetchPromises.get(cacheKey);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const res = await fetch(`${apiBaseUrl}/api/v1/translations/${appId}/${loc}`);\n if (!res.ok) return {};\n const data = await res.json();\n // Cache in memory and localStorage\n translationCache[loc] = data;\n if (typeof window !== 'undefined') {\n try {\n localStorage.setItem(`exp_translations_${appId}_${loc}`, JSON.stringify(data));\n } catch { /* quota exceeded, ignore */ }\n }\n return data as Record<string, string>;\n } catch {\n return {};\n } finally {\n fetchPromises.delete(cacheKey);\n }\n })();\n\n fetchPromises.set(cacheKey, promise);\n return promise;\n }, [appId, apiBaseUrl]);\n\n // Fetch available locales\n useEffect(() => {\n if (!appId) return;\n fetch(`${apiBaseUrl}/api/v1/translations/${appId}/locales`)\n .then(res => res.ok ? res.json() : { locales: [] })\n .then(data => setLocales(data.locales || []))\n .catch(() => {});\n }, [appId, apiBaseUrl]);\n\n // Fetch translations when locale changes\n useEffect(() => {\n if (!appId) return;\n\n // Use cached if available\n if (translationCache[locale]) {\n setTranslations(translationCache[locale]!);\n return;\n }\n\n setIsLoading(true);\n fetchTranslations(locale).then(data => {\n setTranslations(data);\n setIsLoading(false);\n });\n }, [locale, appId, fetchTranslations]);\n\n // t() function with interpolation and plural support\n const t = useCallback((key: string, params?: Record<string, string | number>): string => {\n let result = translations[key] ?? key;\n\n // Handle plurals: \"singular||plural\" with count param\n if (result.includes('||') && params && 'count' in params) {\n const parts = result.split('||');\n const count = Number(params.count);\n result = count === 1 ? (parts[0] ?? result) : (parts[1] ?? result);\n }\n\n // Handle interpolation: {{variable}}\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n result = result.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v));\n }\n }\n\n return result;\n }, [translations]);\n\n // setLocale with persistence\n const setLocale = useCallback((newLocale: string) => {\n setLocaleState(newLocale);\n if (typeof window !== 'undefined') {\n localStorage.setItem(`exp_locale_${appId}`, newLocale);\n }\n }, [appId]);\n\n return { t, locale, setLocale, locales, isLoading };\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","React","import_react"]}
package/dist/index.mjs CHANGED
@@ -70,7 +70,8 @@ var ErrorBoundary = class extends React2.Component {
70
70
  }
71
71
  componentDidCatch(error, errorInfo) {
72
72
  this.context?.captureError(error, {
73
- componentStack: errorInfo.componentStack || ""
73
+ extra: { componentStack: errorInfo.componentStack || "" },
74
+ severity: "fatal"
74
75
  });
75
76
  this.props.onError?.(error, errorInfo);
76
77
  }
@@ -103,7 +104,7 @@ var ErrorBoundary = class extends React2.Component {
103
104
  ErrorBoundary.contextType = ExperienceContext;
104
105
 
105
106
  // src/hooks.ts
106
- import { useContext } from "react";
107
+ import { useContext, useState, useCallback, useMemo, useEffect as useEffect2 } from "react";
107
108
  function useExperience() {
108
109
  const ctx = useContext(ExperienceContext);
109
110
  if (!ctx) throw new Error("useExperience must be used within ExperienceProvider");
@@ -111,25 +112,116 @@ function useExperience() {
111
112
  }
112
113
  function useTrack() {
113
114
  const experience = useExperience();
114
- return {
115
+ return useMemo(() => ({
115
116
  track: (eventName, metadata) => experience.track(eventName, metadata),
116
117
  trackPageView: () => experience.trackPageView()
117
- };
118
+ }), [experience]);
118
119
  }
119
120
  function useFlag(flagName, defaultValue) {
120
121
  const experience = useExperience();
121
122
  return experience.getFlag(flagName, defaultValue);
122
123
  }
124
+ var translationCache = {};
125
+ var fetchPromises = /* @__PURE__ */ new Map();
123
126
  function useTranslation() {
124
- return {
125
- t: (key) => key,
126
- locale: "en"
127
- };
127
+ const experience = useExperience();
128
+ const config = experience.config || {};
129
+ const appId = config.appId || "";
130
+ const apiBaseUrl = config.apiUrl || config.baseUrl || "https://experience-api.shellapps.com";
131
+ const [locale, setLocaleState] = useState(() => {
132
+ if (typeof window === "undefined") return "en";
133
+ const saved = localStorage.getItem(`exp_locale_${appId}`);
134
+ if (saved) return saved;
135
+ return navigator.language?.split("-")[0] || "en";
136
+ });
137
+ const [translations, setTranslations] = useState(() => {
138
+ if (translationCache[locale]) return translationCache[locale];
139
+ if (typeof window !== "undefined") {
140
+ try {
141
+ const cached = localStorage.getItem(`exp_translations_${appId}_${locale}`);
142
+ if (cached) {
143
+ const parsed = JSON.parse(cached);
144
+ translationCache[locale] = parsed;
145
+ return parsed;
146
+ }
147
+ } catch {
148
+ }
149
+ }
150
+ return {};
151
+ });
152
+ const [locales, setLocales] = useState([]);
153
+ const [isLoading, setIsLoading] = useState(false);
154
+ const fetchTranslations = useCallback(async (loc) => {
155
+ if (!appId) return {};
156
+ const cacheKey = `${appId}_${loc}`;
157
+ const existing = fetchPromises.get(cacheKey);
158
+ if (existing) return existing;
159
+ const promise = (async () => {
160
+ try {
161
+ const res = await fetch(`${apiBaseUrl}/api/v1/translations/${appId}/${loc}`);
162
+ if (!res.ok) return {};
163
+ const data = await res.json();
164
+ translationCache[loc] = data;
165
+ if (typeof window !== "undefined") {
166
+ try {
167
+ localStorage.setItem(`exp_translations_${appId}_${loc}`, JSON.stringify(data));
168
+ } catch {
169
+ }
170
+ }
171
+ return data;
172
+ } catch {
173
+ return {};
174
+ } finally {
175
+ fetchPromises.delete(cacheKey);
176
+ }
177
+ })();
178
+ fetchPromises.set(cacheKey, promise);
179
+ return promise;
180
+ }, [appId, apiBaseUrl]);
181
+ useEffect2(() => {
182
+ if (!appId) return;
183
+ fetch(`${apiBaseUrl}/api/v1/translations/${appId}/locales`).then((res) => res.ok ? res.json() : { locales: [] }).then((data) => setLocales(data.locales || [])).catch(() => {
184
+ });
185
+ }, [appId, apiBaseUrl]);
186
+ useEffect2(() => {
187
+ if (!appId) return;
188
+ if (translationCache[locale]) {
189
+ setTranslations(translationCache[locale]);
190
+ return;
191
+ }
192
+ setIsLoading(true);
193
+ fetchTranslations(locale).then((data) => {
194
+ setTranslations(data);
195
+ setIsLoading(false);
196
+ });
197
+ }, [locale, appId, fetchTranslations]);
198
+ const t = useCallback((key, params) => {
199
+ let result = translations[key] ?? key;
200
+ if (result.includes("||") && params && "count" in params) {
201
+ const parts = result.split("||");
202
+ const count = Number(params.count);
203
+ result = count === 1 ? parts[0] ?? result : parts[1] ?? result;
204
+ }
205
+ if (params) {
206
+ for (const [k, v] of Object.entries(params)) {
207
+ result = result.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
208
+ }
209
+ }
210
+ return result;
211
+ }, [translations]);
212
+ const setLocale = useCallback((newLocale) => {
213
+ setLocaleState(newLocale);
214
+ if (typeof window !== "undefined") {
215
+ localStorage.setItem(`exp_locale_${appId}`, newLocale);
216
+ }
217
+ }, [appId]);
218
+ return { t, locale, setLocale, locales, isLoading };
128
219
  }
129
220
  export {
130
221
  ErrorBoundary,
131
222
  ExperienceContext,
132
223
  ExperienceProvider,
224
+ useExperience,
133
225
  useFlag,
134
226
  useTrack,
135
227
  useTranslation
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ExperienceProvider.tsx","../src/context.ts","../src/ErrorBoundary.tsx","../src/hooks.ts"],"sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport { Experience } from '@shellapps/experience';\nimport type { ExperienceConfig } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\nexport interface ExperienceProviderProps {\n appId: string;\n apiKey: string;\n profileId?: string;\n options?: Partial<Omit<ExperienceConfig, 'appId' | 'apiKey'>>;\n children: React.ReactNode;\n}\n\nexport function ExperienceProvider({ appId, apiKey, profileId, options, children }: ExperienceProviderProps) {\n const instanceRef = useRef<Experience | null>(null);\n\n if (!instanceRef.current) {\n const instance = Experience.init({ appId, apiKey, ...options });\n if (profileId) instance.identify(profileId);\n instanceRef.current = instance;\n }\n\n useEffect(() => {\n if (profileId && instanceRef.current) {\n instanceRef.current.identify(profileId);\n }\n }, [profileId]);\n\n // data-t auto-tracking\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n let el = e.target as HTMLElement | null;\n while (el) {\n const tid = el.getAttribute?.('data-t');\n if (tid) {\n instanceRef.current?.track('element_click', { elementTid: tid });\n break;\n }\n el = el.parentElement;\n }\n };\n document.addEventListener('click', handler, true);\n return () => document.removeEventListener('click', handler, true);\n }, []);\n\n useEffect(() => {\n return () => {\n instanceRef.current?.shutdown();\n };\n }, []);\n\n return (\n <ExperienceContext.Provider value={instanceRef.current}>\n {children}\n </ExperienceContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { Experience } from '@shellapps/experience';\n\nexport const ExperienceContext = createContext<Experience | null>(null);\n","import React from 'react';\nimport type { Experience } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\ninterface ErrorBoundaryProps {\n fallback?: React.ReactNode;\n showCommentForm?: boolean;\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n children: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n comment: string;\n submitted: boolean;\n}\n\nexport class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n static contextType = ExperienceContext;\n declare context: Experience | null;\n\n state: ErrorBoundaryState = {\n hasError: false,\n error: null,\n comment: '',\n submitted: false,\n };\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.context?.captureError(error, {\n componentStack: errorInfo.componentStack || '',\n });\n this.props.onError?.(error, errorInfo);\n }\n\n private handleSubmitComment = () => {\n if (this.state.comment.trim() && this.state.error) {\n this.context?.captureMessage(\n `User feedback: ${this.state.comment}`,\n 'info',\n );\n this.setState({ submitted: true });\n }\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div style={{ padding: 20, textAlign: 'center' }}>\n <h2>Something went wrong</h2>\n <p>{this.state.error?.message}</p>\n {this.props.showCommentForm && !this.state.submitted && (\n <div>\n <textarea\n placeholder=\"Tell us what happened...\"\n value={this.state.comment}\n onChange={(e) => this.setState({ comment: e.target.value })}\n style={{ width: '100%', minHeight: 80, marginTop: 10 }}\n />\n <button onClick={this.handleSubmitComment} style={{ marginTop: 8 }}>\n Submit\n </button>\n </div>\n )}\n {this.state.submitted && <p>Thank you for your feedback!</p>}\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","import { useContext } from 'react';\nimport { ExperienceContext } from './context';\n\nfunction useExperience() {\n const ctx = useContext(ExperienceContext);\n if (!ctx) throw new Error('useExperience must be used within ExperienceProvider');\n return ctx;\n}\n\nexport function useTrack() {\n const experience = useExperience();\n return {\n track: (eventName: string, metadata?: Record<string, unknown>) => experience.track(eventName, metadata),\n trackPageView: () => experience.trackPageView(),\n };\n}\n\nexport function useFlag<T>(flagName: string, defaultValue: T): T {\n const experience = useExperience();\n return experience.getFlag(flagName, defaultValue);\n}\n\nexport function useTranslation() {\n return {\n t: (key: string) => key,\n locale: 'en' as const,\n };\n}\n"],"mappings":"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","names":["React","jsx","React"]}
1
+ {"version":3,"sources":["../src/ExperienceProvider.tsx","../src/context.ts","../src/ErrorBoundary.tsx","../src/hooks.ts"],"sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport { Experience } from '@shellapps/experience';\nimport type { ExperienceConfig } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\nexport interface ExperienceProviderProps {\n appId: string;\n apiKey: string;\n profileId?: string;\n options?: Partial<Omit<ExperienceConfig, 'appId' | 'apiKey'>>;\n children: React.ReactNode;\n}\n\nexport function ExperienceProvider({ appId, apiKey, profileId, options, children }: ExperienceProviderProps) {\n const instanceRef = useRef<Experience | null>(null);\n\n if (!instanceRef.current) {\n const instance = Experience.init({ appId, apiKey, ...options });\n if (profileId) instance.identify(profileId);\n instanceRef.current = instance;\n }\n\n useEffect(() => {\n if (profileId && instanceRef.current) {\n instanceRef.current.identify(profileId);\n }\n }, [profileId]);\n\n // data-t auto-tracking\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n let el = e.target as HTMLElement | null;\n while (el) {\n const tid = el.getAttribute?.('data-t');\n if (tid) {\n instanceRef.current?.track('element_click', { elementTid: tid });\n break;\n }\n el = el.parentElement;\n }\n };\n document.addEventListener('click', handler, true);\n return () => document.removeEventListener('click', handler, true);\n }, []);\n\n useEffect(() => {\n return () => {\n instanceRef.current?.shutdown();\n };\n }, []);\n\n return (\n <ExperienceContext.Provider value={instanceRef.current}>\n {children}\n </ExperienceContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { Experience } from '@shellapps/experience';\n\nexport const ExperienceContext = createContext<Experience | null>(null);\n","import React from 'react';\nimport type { Experience } from '@shellapps/experience';\nimport { ExperienceContext } from './context';\n\ninterface ErrorBoundaryProps {\n fallback?: React.ReactNode;\n showCommentForm?: boolean;\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n children: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n comment: string;\n submitted: boolean;\n}\n\nexport class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n static contextType = ExperienceContext;\n declare context: Experience | null;\n\n state: ErrorBoundaryState = {\n hasError: false,\n error: null,\n comment: '',\n submitted: false,\n };\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.context?.captureError(error, {\n extra: { componentStack: errorInfo.componentStack || '' },\n severity: 'fatal' as any,\n });\n this.props.onError?.(error, errorInfo);\n }\n\n private handleSubmitComment = () => {\n if (this.state.comment.trim() && this.state.error) {\n this.context?.captureMessage(\n `User feedback: ${this.state.comment}`,\n 'info',\n );\n this.setState({ submitted: true });\n }\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div style={{ padding: 20, textAlign: 'center' }}>\n <h2>Something went wrong</h2>\n <p>{this.state.error?.message}</p>\n {this.props.showCommentForm && !this.state.submitted && (\n <div>\n <textarea\n placeholder=\"Tell us what happened...\"\n value={this.state.comment}\n onChange={(e) => this.setState({ comment: e.target.value })}\n style={{ width: '100%', minHeight: 80, marginTop: 10 }}\n />\n <button onClick={this.handleSubmitComment} style={{ marginTop: 8 }}>\n Submit\n </button>\n </div>\n )}\n {this.state.submitted && <p>Thank you for your feedback!</p>}\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","import { useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport { ExperienceContext } from './context';\n\nexport function useExperience() {\n const ctx = useContext(ExperienceContext);\n if (!ctx) throw new Error('useExperience must be used within ExperienceProvider');\n return ctx;\n}\n\nexport function useTrack() {\n const experience = useExperience();\n return useMemo(() => ({\n track: (eventName: string, metadata?: Record<string, string>) => experience.track(eventName, metadata),\n trackPageView: () => experience.trackPageView(),\n }), [experience]);\n}\n\nexport function useFlag<T>(flagName: string, defaultValue: T): T {\n const experience = useExperience();\n return experience.getFlag(flagName, defaultValue);\n}\n\n// ─── Translation Types ──────────────────────────────────────────\n\ninterface TranslationResult {\n t: (key: string, params?: Record<string, string | number>) => string;\n locale: string;\n setLocale: (locale: string) => void;\n locales: Array<{ code: string; name: string; progress?: number }>;\n isLoading: boolean;\n}\n\ninterface TranslationCache {\n [locale: string]: Record<string, string>;\n}\n\n// Module-level cache shared across hook instances\nconst translationCache: TranslationCache = {};\nconst fetchPromises = new Map<string, Promise<Record<string, string>>>();\n\n/**\n * Translation hook for the Experience Platform.\n *\n * Fetches translations from the Experience API, caches in memory + localStorage,\n * and provides `t()` for interpolation and `setLocale()` for switching.\n *\n * Usage:\n * ```tsx\n * const { t, locale, setLocale, locales } = useTranslation();\n * return <h1>{t('greeting', { name: 'Alex' })}</h1>;\n * ```\n */\nexport function useTranslation(): TranslationResult {\n const experience = useExperience();\n const config = (experience as any).config || {};\n const appId: string = config.appId || '';\n const apiBaseUrl: string = config.apiUrl || config.baseUrl || 'https://experience-api.shellapps.com';\n\n const [locale, setLocaleState] = useState(() => {\n if (typeof window === 'undefined') return 'en';\n // Check localStorage for persisted locale preference\n const saved = localStorage.getItem(`exp_locale_${appId}`);\n if (saved) return saved;\n return navigator.language?.split('-')[0] || 'en';\n });\n\n const [translations, setTranslations] = useState<Record<string, string>>(() => {\n // Try memory cache first\n if (translationCache[locale]) return translationCache[locale]!;\n // Try localStorage\n if (typeof window !== 'undefined') {\n try {\n const cached = localStorage.getItem(`exp_translations_${appId}_${locale}`);\n if (cached) {\n const parsed = JSON.parse(cached);\n translationCache[locale] = parsed;\n return parsed;\n }\n } catch { /* ignore */ }\n }\n return {};\n });\n\n const [locales, setLocales] = useState<Array<{ code: string; name: string; progress?: number }>>([]);\n const [isLoading, setIsLoading] = useState(false);\n\n // Fetch translations for a locale\n const fetchTranslations = useCallback(async (loc: string): Promise<Record<string, string>> => {\n if (!appId) return {};\n\n // Deduplicate in-flight requests\n const cacheKey = `${appId}_${loc}`;\n const existing = fetchPromises.get(cacheKey);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const res = await fetch(`${apiBaseUrl}/api/v1/translations/${appId}/${loc}`);\n if (!res.ok) return {};\n const data = await res.json();\n // Cache in memory and localStorage\n translationCache[loc] = data;\n if (typeof window !== 'undefined') {\n try {\n localStorage.setItem(`exp_translations_${appId}_${loc}`, JSON.stringify(data));\n } catch { /* quota exceeded, ignore */ }\n }\n return data as Record<string, string>;\n } catch {\n return {};\n } finally {\n fetchPromises.delete(cacheKey);\n }\n })();\n\n fetchPromises.set(cacheKey, promise);\n return promise;\n }, [appId, apiBaseUrl]);\n\n // Fetch available locales\n useEffect(() => {\n if (!appId) return;\n fetch(`${apiBaseUrl}/api/v1/translations/${appId}/locales`)\n .then(res => res.ok ? res.json() : { locales: [] })\n .then(data => setLocales(data.locales || []))\n .catch(() => {});\n }, [appId, apiBaseUrl]);\n\n // Fetch translations when locale changes\n useEffect(() => {\n if (!appId) return;\n\n // Use cached if available\n if (translationCache[locale]) {\n setTranslations(translationCache[locale]!);\n return;\n }\n\n setIsLoading(true);\n fetchTranslations(locale).then(data => {\n setTranslations(data);\n setIsLoading(false);\n });\n }, [locale, appId, fetchTranslations]);\n\n // t() function with interpolation and plural support\n const t = useCallback((key: string, params?: Record<string, string | number>): string => {\n let result = translations[key] ?? key;\n\n // Handle plurals: \"singular||plural\" with count param\n if (result.includes('||') && params && 'count' in params) {\n const parts = result.split('||');\n const count = Number(params.count);\n result = count === 1 ? (parts[0] ?? result) : (parts[1] ?? result);\n }\n\n // Handle interpolation: {{variable}}\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n result = result.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v));\n }\n }\n\n return result;\n }, [translations]);\n\n // setLocale with persistence\n const setLocale = useCallback((newLocale: string) => {\n setLocaleState(newLocale);\n if (typeof window !== 'undefined') {\n localStorage.setItem(`exp_locale_${appId}`, newLocale);\n }\n }, [appId]);\n\n return { t, locale, setLocale, locales, isLoading };\n}\n"],"mappings":"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","names":["React","jsx","React","useEffect","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shellapps/experience-react",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "React SDK for @shellapps/experience",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -43,5 +43,5 @@
43
43
  "url": "git+https://github.com/ShellTechnology/shellapps-js.git",
44
44
  "directory": "packages/experience-react"
45
45
  },
46
- "gitHead": "b7b3b29f4722dc1284df8239ea4ca7ec6afd0783"
46
+ "gitHead": "71abb6526f26e71e7b991e1bf8ace11c7c4bd3c2"
47
47
  }