@getecho-ai/react-native-sdk 1.0.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/README.md +350 -0
- package/dist/bridge/CallbackManager.d.ts +46 -0
- package/dist/bridge/CallbackManager.js +117 -0
- package/dist/bridge/WebViewBridge.d.ts +67 -0
- package/dist/bridge/WebViewBridge.js +151 -0
- package/dist/components/EchoChat.d.ts +20 -0
- package/dist/components/EchoChat.js +45 -0
- package/dist/components/EchoChatButton.d.ts +15 -0
- package/dist/components/EchoChatButton.js +65 -0
- package/dist/components/EchoChatModal.d.ts +13 -0
- package/dist/components/EchoChatModal.js +473 -0
- package/dist/components/EchoProvider.d.ts +30 -0
- package/dist/components/EchoProvider.js +240 -0
- package/dist/hooks/useSimpleCart.d.ts +76 -0
- package/dist/hooks/useSimpleCart.js +134 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +77 -0
- package/dist/types/index.d.ts +156 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/resolveApiUrl.d.ts +42 -0
- package/dist/utils/resolveApiUrl.js +82 -0
- package/package.json +54 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* EchoChatModal - Chat interface using WebView
|
|
4
|
+
*
|
|
5
|
+
* Embeds Echo web chat via WebView with postMessage bridge
|
|
6
|
+
* Handles all communication between WebView and React Native
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EchoChatModal = void 0;
|
|
13
|
+
const react_1 = require("react");
|
|
14
|
+
const react_native_1 = require("react-native");
|
|
15
|
+
const react_native_webview_1 = require("react-native-webview");
|
|
16
|
+
const CallbackManager_1 = __importDefault(require("../bridge/CallbackManager"));
|
|
17
|
+
const WebViewBridge_1 = __importDefault(require("../bridge/WebViewBridge"));
|
|
18
|
+
const resolveApiUrl_1 = require("../utils/resolveApiUrl");
|
|
19
|
+
const EchoProvider_1 = require("./EchoProvider");
|
|
20
|
+
const ECHO_CHAT_URL = "https://get-echo.ai/embed";
|
|
21
|
+
const TRAILING_SLASHES_REGEX = /\/+$/;
|
|
22
|
+
function ensureHttpScheme(url) {
|
|
23
|
+
// WebView needs an absolute URL with a scheme.
|
|
24
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
return `http://${url}`;
|
|
28
|
+
}
|
|
29
|
+
function resolveEmbedUrl(url) {
|
|
30
|
+
const withScheme = ensureHttpScheme(url).trim();
|
|
31
|
+
// If caller already passes /embed, don't second-guess it.
|
|
32
|
+
if (withScheme.includes("/embed")) {
|
|
33
|
+
return withScheme;
|
|
34
|
+
}
|
|
35
|
+
// Treat it as a base (e.g. http://localhost:3000) and append /embed.
|
|
36
|
+
return `${withScheme.replace(TRAILING_SLASHES_REGEX, "")}/embed`;
|
|
37
|
+
}
|
|
38
|
+
const EchoChatModal = ({ visible, onClose, }) => {
|
|
39
|
+
const { config, userId, chatId, isReady, updateChatId, updateUserId, handleAddToCart, handleGetCart, handleNavigateToProduct, } = (0, EchoProvider_1.useEcho)();
|
|
40
|
+
const webViewRef = (0, react_1.useRef)(null);
|
|
41
|
+
const wasVisibleRef = (0, react_1.useRef)(false);
|
|
42
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
43
|
+
const [loadTimeout, setLoadTimeout] = (0, react_1.useState)(false);
|
|
44
|
+
const [activeResumeChatId, setActiveResumeChatId] = (0, react_1.useState)(null);
|
|
45
|
+
const [isResumeReady, setIsResumeReady] = (0, react_1.useState)(false);
|
|
46
|
+
// Set WebView ref for bridge - update whenever ref or isResumeReady changes
|
|
47
|
+
(0, react_1.useEffect)(() => {
|
|
48
|
+
if (webViewRef.current && isResumeReady) {
|
|
49
|
+
WebViewBridge_1.default.setWebViewRef(webViewRef.current);
|
|
50
|
+
console.log("[EchoChatModal] WebView ref set, ready for messaging");
|
|
51
|
+
}
|
|
52
|
+
}, [isResumeReady, webViewRef.current]);
|
|
53
|
+
// Handle messages from WebView
|
|
54
|
+
const handleMessage = (0, react_1.useCallback)(async (message) => {
|
|
55
|
+
console.log("[EchoChatModal] handleMessage", message);
|
|
56
|
+
if (message.type === "echo:request_actions") {
|
|
57
|
+
const actions = ["addToCart", "getCart"];
|
|
58
|
+
if (config.callbacks.onNavigateToCheckout) {
|
|
59
|
+
actions.push("redirectToCheckout");
|
|
60
|
+
}
|
|
61
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
62
|
+
source: "echo-widget",
|
|
63
|
+
type: "echo:available_actions",
|
|
64
|
+
actions,
|
|
65
|
+
schemas: {},
|
|
66
|
+
customerId: null,
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (message.type === "requestWidgetState") {
|
|
71
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
72
|
+
source: "echo-parent",
|
|
73
|
+
type: "widgetState",
|
|
74
|
+
isExpanded: false, // Always false in RN - sidebar controlled via showSidebar URL param
|
|
75
|
+
isOpen: visible,
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (message.type === "close") {
|
|
80
|
+
onClose();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (message.type === "chatId") {
|
|
84
|
+
const payload = message.payload;
|
|
85
|
+
if (payload?.chatId) {
|
|
86
|
+
await updateChatId(payload.chatId);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (message.type === "userId") {
|
|
91
|
+
const payload = message.payload;
|
|
92
|
+
if (payload?.userId) {
|
|
93
|
+
await updateUserId(payload.userId);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (message.type === "navigate") {
|
|
98
|
+
const payload = message.payload;
|
|
99
|
+
const navigateUrl = payload?.url;
|
|
100
|
+
const navigateProductId = payload?.productId;
|
|
101
|
+
if (navigateProductId !== undefined && navigateProductId !== null) {
|
|
102
|
+
handleNavigateToProduct(String(navigateProductId));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (navigateUrl) {
|
|
106
|
+
if (config.callbacks.onNavigateToUrl) {
|
|
107
|
+
config.callbacks.onNavigateToUrl(navigateUrl);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
try {
|
|
111
|
+
await react_native_1.Linking.openURL(navigateUrl);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error("[EchoChatModal] Failed to open URL:", error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (message.type === "addToCart") {
|
|
121
|
+
const payload = message.payload ?? {};
|
|
122
|
+
const productPayload = payload.product ||
|
|
123
|
+
payload;
|
|
124
|
+
const callbackId = message.callbackId ||
|
|
125
|
+
payload.callbackId ||
|
|
126
|
+
"";
|
|
127
|
+
const productId = productPayload.productId ??
|
|
128
|
+
productPayload.id ??
|
|
129
|
+
"";
|
|
130
|
+
const quantity = productPayload.quantity ?? undefined;
|
|
131
|
+
const product = {
|
|
132
|
+
id: String(productId),
|
|
133
|
+
title: productPayload
|
|
134
|
+
.productName ||
|
|
135
|
+
productPayload.title ||
|
|
136
|
+
String(productId),
|
|
137
|
+
priceAmount: productPayload
|
|
138
|
+
.productPrice ?? productPayload.price,
|
|
139
|
+
primaryImage: productPayload
|
|
140
|
+
.productImage ?? productPayload.image,
|
|
141
|
+
url: productPayload
|
|
142
|
+
.productUrl ?? productPayload.url,
|
|
143
|
+
};
|
|
144
|
+
const result = await handleAddToCart(product, quantity);
|
|
145
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
146
|
+
source: "echo-parent",
|
|
147
|
+
type: "addToCartResponse",
|
|
148
|
+
callbackId,
|
|
149
|
+
success: result.success,
|
|
150
|
+
error: result.error,
|
|
151
|
+
cartItemCount: result.cartItemCount,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (message.type === "getCart") {
|
|
156
|
+
const payload = message.payload ?? {};
|
|
157
|
+
const callbackId = message.callbackId ||
|
|
158
|
+
payload.callbackId ||
|
|
159
|
+
"";
|
|
160
|
+
const result = await handleGetCart();
|
|
161
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
162
|
+
source: "echo-parent",
|
|
163
|
+
type: "getCartResponse",
|
|
164
|
+
callbackId,
|
|
165
|
+
success: result.success,
|
|
166
|
+
cart: result.cart,
|
|
167
|
+
error: result.error,
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (message.type === "redirectToCheckout") {
|
|
172
|
+
const payload = message.payload ?? {};
|
|
173
|
+
const callbackId = message.callbackId ||
|
|
174
|
+
payload.callbackId ||
|
|
175
|
+
"";
|
|
176
|
+
if (!config.callbacks.onNavigateToCheckout) {
|
|
177
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
178
|
+
source: "echo-parent",
|
|
179
|
+
type: "redirectToCheckoutResponse",
|
|
180
|
+
callbackId,
|
|
181
|
+
success: false,
|
|
182
|
+
error: "CALLBACK_NOT_REGISTERED",
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
config.callbacks.onNavigateToCheckout();
|
|
188
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
189
|
+
source: "echo-parent",
|
|
190
|
+
type: "redirectToCheckoutResponse",
|
|
191
|
+
callbackId,
|
|
192
|
+
success: true,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
197
|
+
source: "echo-parent",
|
|
198
|
+
type: "redirectToCheckoutResponse",
|
|
199
|
+
callbackId,
|
|
200
|
+
success: false,
|
|
201
|
+
error: error instanceof Error ? error.message : "CHECKOUT_FAILED",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
switch (message.type) {
|
|
207
|
+
case "action": {
|
|
208
|
+
const { action, callbackId, product } = message.payload || {};
|
|
209
|
+
if (action === "addToCart" && product) {
|
|
210
|
+
// Call partner's callback
|
|
211
|
+
const result = await handleAddToCart(product);
|
|
212
|
+
// Complete the action
|
|
213
|
+
CallbackManager_1.default.completeAction(callbackId, result);
|
|
214
|
+
// Send result back to WebView
|
|
215
|
+
WebViewBridge_1.default.completeActionInWebView(callbackId, result);
|
|
216
|
+
}
|
|
217
|
+
else if (action === "getCart") {
|
|
218
|
+
const result = await handleGetCart();
|
|
219
|
+
WebViewBridge_1.default.completeActionInWebView(callbackId, {
|
|
220
|
+
success: result.success,
|
|
221
|
+
data: result.cart,
|
|
222
|
+
error: result.error,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "chatReady": {
|
|
228
|
+
// Initialize WebView with config
|
|
229
|
+
WebViewBridge_1.default.initWebView({
|
|
230
|
+
apiKey: config.apiKey,
|
|
231
|
+
userId,
|
|
232
|
+
userEmail: config.userEmail,
|
|
233
|
+
theme: config.theme,
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
default:
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}, [
|
|
241
|
+
config,
|
|
242
|
+
userId,
|
|
243
|
+
visible,
|
|
244
|
+
onClose,
|
|
245
|
+
updateChatId,
|
|
246
|
+
updateUserId,
|
|
247
|
+
handleAddToCart,
|
|
248
|
+
handleGetCart,
|
|
249
|
+
handleNavigateToProduct,
|
|
250
|
+
]);
|
|
251
|
+
// Subscribe to bridge messages
|
|
252
|
+
(0, react_1.useEffect)(() => {
|
|
253
|
+
console.log("[EchoChatModal] useEffect");
|
|
254
|
+
const unsubscribe = WebViewBridge_1.default.onMessage((message) => {
|
|
255
|
+
void handleMessage(message).catch((error) => {
|
|
256
|
+
console.error("[EchoChatModal] handleMessage error", error);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
return () => {
|
|
260
|
+
unsubscribe();
|
|
261
|
+
};
|
|
262
|
+
}, [handleMessage]);
|
|
263
|
+
// Cancel all pending actions when modal closes
|
|
264
|
+
(0, react_1.useEffect)(() => {
|
|
265
|
+
if (!visible) {
|
|
266
|
+
CallbackManager_1.default.cancelAll("MODAL_CLOSED");
|
|
267
|
+
}
|
|
268
|
+
}, [visible]);
|
|
269
|
+
// Capture resume chat id only when opening the modal
|
|
270
|
+
(0, react_1.useEffect)(() => {
|
|
271
|
+
if (!isReady) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (visible && !wasVisibleRef.current) {
|
|
275
|
+
setActiveResumeChatId(chatId || null);
|
|
276
|
+
setIsResumeReady(true);
|
|
277
|
+
setIsLoading(true);
|
|
278
|
+
setLoadTimeout(false);
|
|
279
|
+
}
|
|
280
|
+
if (!visible && wasVisibleRef.current) {
|
|
281
|
+
setIsResumeReady(false);
|
|
282
|
+
}
|
|
283
|
+
wasVisibleRef.current = visible;
|
|
284
|
+
}, [visible, chatId, isReady]);
|
|
285
|
+
// WebView loading timeout (15 seconds)
|
|
286
|
+
(0, react_1.useEffect)(() => {
|
|
287
|
+
if (visible && isResumeReady && isLoading) {
|
|
288
|
+
const timeoutId = setTimeout(() => {
|
|
289
|
+
setLoadTimeout(true);
|
|
290
|
+
setIsLoading(false);
|
|
291
|
+
}, 15000);
|
|
292
|
+
return () => clearTimeout(timeoutId);
|
|
293
|
+
}
|
|
294
|
+
}, [visible, isResumeReady, isLoading]);
|
|
295
|
+
// Build WebView URL with params.
|
|
296
|
+
// Source: EchoProvider config only (no EchoChat override), then default.
|
|
297
|
+
// resolveApiUrl handles Android emulator localhost -> 10.0.2.2 conversion
|
|
298
|
+
const resolvedApiUrl = (0, resolveApiUrl_1.resolveApiUrl)(config.apiUrl) || ECHO_CHAT_URL;
|
|
299
|
+
const embedUrl = resolveEmbedUrl(resolvedApiUrl);
|
|
300
|
+
const separator = embedUrl.includes("?") ? "&" : "?";
|
|
301
|
+
const urlParams = new URLSearchParams();
|
|
302
|
+
urlParams.set("apiKey", config.apiKey);
|
|
303
|
+
urlParams.set("userId", userId);
|
|
304
|
+
urlParams.set("embed", "true");
|
|
305
|
+
urlParams.set("skipIdentify", "true"); // Skip web embed's auto-identify (SDK handles it)
|
|
306
|
+
if (config.userEmail) {
|
|
307
|
+
urlParams.set("userEmail", config.userEmail);
|
|
308
|
+
}
|
|
309
|
+
if (activeResumeChatId) {
|
|
310
|
+
urlParams.set("resumeChatId", activeResumeChatId);
|
|
311
|
+
}
|
|
312
|
+
// Add UI settings to URL params
|
|
313
|
+
if (config.uiSettings?.showSidebar !== undefined) {
|
|
314
|
+
urlParams.set("showSidebar", String(config.uiSettings.showSidebar));
|
|
315
|
+
}
|
|
316
|
+
if (config.uiSettings?.showExpandButton !== undefined) {
|
|
317
|
+
urlParams.set("showExpandButton", String(config.uiSettings.showExpandButton));
|
|
318
|
+
}
|
|
319
|
+
if (config.uiSettings?.showCartButton !== undefined) {
|
|
320
|
+
urlParams.set("showCartButton", String(config.uiSettings.showCartButton));
|
|
321
|
+
}
|
|
322
|
+
if (config.uiSettings?.showHistoryButton !== undefined) {
|
|
323
|
+
urlParams.set("showHistoryButton", String(config.uiSettings.showHistoryButton));
|
|
324
|
+
}
|
|
325
|
+
if (config.uiSettings?.showCloseButton !== undefined) {
|
|
326
|
+
urlParams.set("showCloseButton", String(config.uiSettings.showCloseButton));
|
|
327
|
+
}
|
|
328
|
+
const chatUrl = `${embedUrl}${separator}${urlParams.toString()}`;
|
|
329
|
+
return (<react_native_1.Modal animationType="slide" onRequestClose={onClose} transparent={true} visible={visible}>
|
|
330
|
+
<react_native_1.SafeAreaView style={styles.container}>
|
|
331
|
+
<react_native_1.View style={styles.header}>
|
|
332
|
+
<react_native_1.Text style={styles.title}>AI Asistan</react_native_1.Text>
|
|
333
|
+
<react_native_1.TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
334
|
+
<react_native_1.Text style={styles.closeText}>✕</react_native_1.Text>
|
|
335
|
+
</react_native_1.TouchableOpacity>
|
|
336
|
+
</react_native_1.View>
|
|
337
|
+
|
|
338
|
+
<react_native_1.View style={styles.webviewContainer}>
|
|
339
|
+
{isLoading && (<react_native_1.View style={styles.loadingContainer}>
|
|
340
|
+
<react_native_1.ActivityIndicator color="#007AFF" size="large"/>
|
|
341
|
+
<react_native_1.Text style={styles.loadingText}>Yükleniyor...</react_native_1.Text>
|
|
342
|
+
</react_native_1.View>)}
|
|
343
|
+
|
|
344
|
+
{loadTimeout && (<react_native_1.View style={styles.errorContainer}>
|
|
345
|
+
<react_native_1.Text style={styles.errorTitle}>⏱️ Yükleme Zaman Aşımı</react_native_1.Text>
|
|
346
|
+
<react_native_1.Text style={styles.errorText}>
|
|
347
|
+
İnternet bağlantınızı kontrol edin
|
|
348
|
+
</react_native_1.Text>
|
|
349
|
+
<react_native_1.TouchableOpacity style={styles.retryButton} onPress={() => {
|
|
350
|
+
setLoadTimeout(false);
|
|
351
|
+
setIsLoading(true);
|
|
352
|
+
setIsResumeReady(false);
|
|
353
|
+
setTimeout(() => setIsResumeReady(true), 100);
|
|
354
|
+
}}>
|
|
355
|
+
<react_native_1.Text style={styles.retryButtonText}>🔄 Tekrar Dene</react_native_1.Text>
|
|
356
|
+
</react_native_1.TouchableOpacity>
|
|
357
|
+
</react_native_1.View>)}
|
|
358
|
+
|
|
359
|
+
{isResumeReady && (<react_native_webview_1.WebView domStorageEnabled={true} javaScriptEnabled={true} onLoad={() => {
|
|
360
|
+
setIsLoading(false);
|
|
361
|
+
// Broadcast available actions after WebView is loaded
|
|
362
|
+
// This ensures the web client receives actions even if it missed the initial request
|
|
363
|
+
const actions = ["addToCart", "getCart"];
|
|
364
|
+
if (config.callbacks.onNavigateToCheckout) {
|
|
365
|
+
actions.push("redirectToCheckout");
|
|
366
|
+
}
|
|
367
|
+
// Small delay to ensure WebView's message listeners are set up
|
|
368
|
+
setTimeout(() => {
|
|
369
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
370
|
+
source: "echo-widget",
|
|
371
|
+
type: "echo:available_actions",
|
|
372
|
+
actions,
|
|
373
|
+
schemas: {},
|
|
374
|
+
customerId: null,
|
|
375
|
+
});
|
|
376
|
+
}, 100);
|
|
377
|
+
}} onMessage={(event) => {
|
|
378
|
+
// console.log("[EchoChatModal] onMessage", event);
|
|
379
|
+
WebViewBridge_1.default.handleMessage(event);
|
|
380
|
+
}} ref={webViewRef} scalesPageToFit={true} source={{ uri: chatUrl }} startInLoadingState={true} style={styles.webview}/>)}
|
|
381
|
+
</react_native_1.View>
|
|
382
|
+
</react_native_1.SafeAreaView>
|
|
383
|
+
</react_native_1.Modal>);
|
|
384
|
+
};
|
|
385
|
+
exports.EchoChatModal = EchoChatModal;
|
|
386
|
+
const styles = react_native_1.StyleSheet.create({
|
|
387
|
+
container: {
|
|
388
|
+
flex: 1,
|
|
389
|
+
backgroundColor: "#fff",
|
|
390
|
+
},
|
|
391
|
+
header: {
|
|
392
|
+
flexDirection: "row",
|
|
393
|
+
alignItems: "center",
|
|
394
|
+
justifyContent: "space-between",
|
|
395
|
+
paddingHorizontal: 16,
|
|
396
|
+
paddingVertical: 12,
|
|
397
|
+
borderBottomWidth: 1,
|
|
398
|
+
borderBottomColor: "#E5E5E5",
|
|
399
|
+
backgroundColor: "#fff",
|
|
400
|
+
},
|
|
401
|
+
title: {
|
|
402
|
+
fontSize: 18,
|
|
403
|
+
fontWeight: "600",
|
|
404
|
+
color: "#000",
|
|
405
|
+
},
|
|
406
|
+
closeButton: {
|
|
407
|
+
padding: 8,
|
|
408
|
+
},
|
|
409
|
+
closeText: {
|
|
410
|
+
fontSize: 20,
|
|
411
|
+
color: "#666",
|
|
412
|
+
},
|
|
413
|
+
webviewContainer: {
|
|
414
|
+
flex: 1,
|
|
415
|
+
position: "relative",
|
|
416
|
+
},
|
|
417
|
+
webview: {
|
|
418
|
+
flex: 1,
|
|
419
|
+
},
|
|
420
|
+
loadingContainer: {
|
|
421
|
+
position: "absolute",
|
|
422
|
+
top: 0,
|
|
423
|
+
left: 0,
|
|
424
|
+
right: 0,
|
|
425
|
+
bottom: 0,
|
|
426
|
+
justifyContent: "center",
|
|
427
|
+
alignItems: "center",
|
|
428
|
+
backgroundColor: "#fff",
|
|
429
|
+
zIndex: 1,
|
|
430
|
+
},
|
|
431
|
+
loadingText: {
|
|
432
|
+
marginTop: 12,
|
|
433
|
+
fontSize: 14,
|
|
434
|
+
color: "#666",
|
|
435
|
+
},
|
|
436
|
+
errorContainer: {
|
|
437
|
+
position: "absolute",
|
|
438
|
+
top: 0,
|
|
439
|
+
left: 0,
|
|
440
|
+
right: 0,
|
|
441
|
+
bottom: 0,
|
|
442
|
+
justifyContent: "center",
|
|
443
|
+
alignItems: "center",
|
|
444
|
+
backgroundColor: "#fff",
|
|
445
|
+
zIndex: 2,
|
|
446
|
+
padding: 20,
|
|
447
|
+
},
|
|
448
|
+
errorTitle: {
|
|
449
|
+
fontSize: 18,
|
|
450
|
+
fontWeight: "600",
|
|
451
|
+
color: "#000",
|
|
452
|
+
marginBottom: 8,
|
|
453
|
+
textAlign: "center",
|
|
454
|
+
},
|
|
455
|
+
errorText: {
|
|
456
|
+
fontSize: 14,
|
|
457
|
+
color: "#666",
|
|
458
|
+
marginBottom: 20,
|
|
459
|
+
textAlign: "center",
|
|
460
|
+
},
|
|
461
|
+
retryButton: {
|
|
462
|
+
backgroundColor: "#007AFF",
|
|
463
|
+
paddingHorizontal: 24,
|
|
464
|
+
paddingVertical: 12,
|
|
465
|
+
borderRadius: 8,
|
|
466
|
+
},
|
|
467
|
+
retryButtonText: {
|
|
468
|
+
color: "#fff",
|
|
469
|
+
fontSize: 16,
|
|
470
|
+
fontWeight: "600",
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
exports.default = exports.EchoChatModal;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EchoProvider - Main context provider for Echo SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - SDK configuration (apiKey, callbacks)
|
|
6
|
+
* - User session management
|
|
7
|
+
* - Bridge initialization
|
|
8
|
+
*/
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import type { AddToCartResult, EchoConfig, GetCartResult, Product } from "../types";
|
|
11
|
+
type EchoContextType = {
|
|
12
|
+
config: EchoConfig;
|
|
13
|
+
userId: string;
|
|
14
|
+
chatId: string;
|
|
15
|
+
isReady: boolean;
|
|
16
|
+
updateUserId: (newUserId: string) => Promise<void>;
|
|
17
|
+
updateChatId: (newChatId: string) => Promise<void>;
|
|
18
|
+
handleAddToCart: (product: Product, quantity?: number) => Promise<AddToCartResult>;
|
|
19
|
+
handleGetCart: () => Promise<GetCartResult>;
|
|
20
|
+
handleNavigateToProduct: (productId: string) => void;
|
|
21
|
+
handleNavigateToCheckout: () => void;
|
|
22
|
+
handleAuthRequired: () => void;
|
|
23
|
+
};
|
|
24
|
+
export declare const useEcho: () => EchoContextType;
|
|
25
|
+
type EchoProviderProps = {
|
|
26
|
+
config: EchoConfig;
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
};
|
|
29
|
+
export declare const EchoProvider: React.FC<EchoProviderProps>;
|
|
30
|
+
export default EchoProvider;
|