turbodesk-livechat-react-native 0.1.0-alpha.3 → 0.1.0-alpha.30
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/CHANGELOG.md +50 -0
- package/dist/api/conversation-api.d.ts +9 -0
- package/dist/api/conversation-api.d.ts.map +1 -1
- package/dist/api/conversation-api.js +15 -1
- package/dist/api/conversation-api.js.map +1 -1
- package/dist/hooks/use-live-chat.d.ts +1 -0
- package/dist/hooks/use-live-chat.d.ts.map +1 -1
- package/dist/hooks/use-live-chat.js +2 -0
- package/dist/hooks/use-live-chat.js.map +1 -1
- package/dist/hooks/use-send-message.d.ts +1 -0
- package/dist/hooks/use-send-message.d.ts.map +1 -1
- package/dist/hooks/use-send-message.js +20 -14
- package/dist/hooks/use-send-message.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/navigation/LiveChatPanel.d.ts.map +1 -1
- package/dist/navigation/LiveChatPanel.js +20 -1
- package/dist/navigation/LiveChatPanel.js.map +1 -1
- package/dist/provider/LiveChatContext.d.ts.map +1 -1
- package/dist/provider/LiveChatContext.js +2 -0
- package/dist/provider/LiveChatContext.js.map +1 -1
- package/dist/provider/LiveChatProvider.d.ts +1 -1
- package/dist/provider/LiveChatProvider.d.ts.map +1 -1
- package/dist/provider/LiveChatProvider.js +17 -2
- package/dist/provider/LiveChatProvider.js.map +1 -1
- package/dist/provider/types.d.ts +4 -0
- package/dist/provider/types.d.ts.map +1 -1
- package/dist/ui/components/ConversationHeader.d.ts.map +1 -1
- package/dist/ui/components/ConversationHeader.js +7 -4
- package/dist/ui/components/ConversationHeader.js.map +1 -1
- package/dist/ui/components/ConversationListScreen.d.ts.map +1 -1
- package/dist/ui/components/ConversationListScreen.js +11 -2
- package/dist/ui/components/ConversationListScreen.js.map +1 -1
- package/dist/ui/components/ConversationScreen.d.ts.map +1 -1
- package/dist/ui/components/ConversationScreen.js +13 -3
- package/dist/ui/components/ConversationScreen.js.map +1 -1
- package/dist/ui/components/HomeScreen.d.ts.map +1 -1
- package/dist/ui/components/HomeScreen.js +14 -4
- package/dist/ui/components/HomeScreen.js.map +1 -1
- package/dist/ui/components/MessageComposer.d.ts +1 -0
- package/dist/ui/components/MessageComposer.d.ts.map +1 -1
- package/dist/ui/components/MessageComposer.js +89 -30
- package/dist/ui/components/MessageComposer.js.map +1 -1
- package/dist/ui/safe-area.d.ts +9 -0
- package/dist/ui/safe-area.d.ts.map +1 -0
- package/dist/ui/safe-area.js +28 -0
- package/dist/ui/safe-area.js.map +1 -0
- package/package.json +11 -3
- package/src/api/conversation-api.ts +33 -8
- package/src/hooks/use-live-chat.ts +3 -0
- package/src/hooks/use-send-message.ts +169 -159
- package/src/index.ts +3 -0
- package/src/navigation/LiveChatPanel.tsx +26 -3
- package/src/provider/LiveChatContext.ts +2 -0
- package/src/provider/LiveChatProvider.tsx +396 -380
- package/src/provider/types.ts +63 -57
- package/src/ui/components/ConversationHeader.tsx +7 -6
- package/src/ui/components/ConversationListScreen.tsx +18 -5
- package/src/ui/components/ConversationScreen.tsx +369 -362
- package/src/ui/components/HomeScreen.tsx +21 -6
- package/src/ui/components/MessageComposer.tsx +116 -36
- package/src/ui/safe-area.ts +34 -0
|
@@ -1,159 +1,169 @@
|
|
|
1
|
-
import { useCallback, useState } from "react";
|
|
2
|
-
import { conversationApi } from "../api/conversation-api";
|
|
3
|
-
import { useLiveChatContext } from "../provider/LiveChatContext";
|
|
4
|
-
|
|
5
|
-
function pickConversationId(raw: any): string | null {
|
|
6
|
-
if (!raw) return null;
|
|
7
|
-
const a = raw?.data !== undefined ? raw.data : raw;
|
|
8
|
-
const b = a?.data !== undefined ? a.data : a;
|
|
9
|
-
const id = b?.conversationId ?? a?.conversationId;
|
|
10
|
-
return id != null && id !== "" ? String(id) : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type AttachmentMeta = {
|
|
14
|
-
fileId?: string;
|
|
15
|
-
fileUrl: string;
|
|
16
|
-
fileName: string;
|
|
17
|
-
fileExtension?: string;
|
|
18
|
-
type: "image" | "video" | "document" | "audio";
|
|
19
|
-
caption?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type InteractiveReplyMeta = {
|
|
23
|
-
type: "button_reply" | "list_reply";
|
|
24
|
-
id: string;
|
|
25
|
-
title: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type UseSendMessageResult = {
|
|
29
|
-
sending: boolean;
|
|
30
|
-
error: string | null;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { conversationApi } from "../api/conversation-api";
|
|
3
|
+
import { useLiveChatContext } from "../provider/LiveChatContext";
|
|
4
|
+
|
|
5
|
+
function pickConversationId(raw: any): string | null {
|
|
6
|
+
if (!raw) return null;
|
|
7
|
+
const a = raw?.data !== undefined ? raw.data : raw;
|
|
8
|
+
const b = a?.data !== undefined ? a.data : a;
|
|
9
|
+
const id = b?.conversationId ?? a?.conversationId;
|
|
10
|
+
return id != null && id !== "" ? String(id) : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AttachmentMeta = {
|
|
14
|
+
fileId?: string;
|
|
15
|
+
fileUrl: string;
|
|
16
|
+
fileName: string;
|
|
17
|
+
fileExtension?: string;
|
|
18
|
+
type: "image" | "video" | "document" | "audio";
|
|
19
|
+
caption?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type InteractiveReplyMeta = {
|
|
23
|
+
type: "button_reply" | "list_reply";
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type UseSendMessageResult = {
|
|
29
|
+
sending: boolean;
|
|
30
|
+
error: string | null;
|
|
31
|
+
clearError: () => void;
|
|
32
|
+
sendText: (
|
|
33
|
+
text: string,
|
|
34
|
+
conversationId?: string | null
|
|
35
|
+
) => Promise<string | null>;
|
|
36
|
+
sendAttachment: (
|
|
37
|
+
attachment: AttachmentMeta,
|
|
38
|
+
conversationId?: string | null
|
|
39
|
+
) => Promise<string | null>;
|
|
40
|
+
sendInteractive: (
|
|
41
|
+
reply: InteractiveReplyMeta,
|
|
42
|
+
conversationId?: string | null
|
|
43
|
+
) => Promise<string | null>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function useSendMessage(): UseSendMessageResult {
|
|
47
|
+
const { visitorQueryParams } = useLiveChatContext();
|
|
48
|
+
const [sending, setSending] = useState(false);
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
|
|
51
|
+
const clearError = useCallback(() => setError(null), []);
|
|
52
|
+
|
|
53
|
+
const sendText = useCallback(
|
|
54
|
+
async (text: string, conversationId?: string | null): Promise<string | null> => {
|
|
55
|
+
const trimmed = text.trim();
|
|
56
|
+
if (!trimmed) throw new Error("Message cannot be empty");
|
|
57
|
+
if (!visitorQueryParams) throw new Error("Not connected yet. Please wait and try again.");
|
|
58
|
+
|
|
59
|
+
const localMessageId =
|
|
60
|
+
typeof crypto !== "undefined" && crypto.randomUUID
|
|
61
|
+
? crypto.randomUUID()
|
|
62
|
+
: "lc-" + Date.now();
|
|
63
|
+
const body: any = {
|
|
64
|
+
localMessageId,
|
|
65
|
+
message: { type: "text", text: { body: trimmed } },
|
|
66
|
+
};
|
|
67
|
+
if (conversationId) body.conversationId = conversationId;
|
|
68
|
+
|
|
69
|
+
setSending(true);
|
|
70
|
+
setError(null);
|
|
71
|
+
try {
|
|
72
|
+
const raw = await conversationApi.sendVisitorMessage(body, {
|
|
73
|
+
params: visitorQueryParams,
|
|
74
|
+
});
|
|
75
|
+
return pickConversationId(raw);
|
|
76
|
+
} catch (e: any) {
|
|
77
|
+
const msg = e?.response?.data?.message ?? e?.message ?? "Failed to send message";
|
|
78
|
+
setError(msg);
|
|
79
|
+
throw new Error(msg);
|
|
80
|
+
} finally {
|
|
81
|
+
setSending(false);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[visitorQueryParams]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const sendAttachment = useCallback(
|
|
88
|
+
async (
|
|
89
|
+
attachment: AttachmentMeta,
|
|
90
|
+
conversationId?: string | null
|
|
91
|
+
): Promise<string | null> => {
|
|
92
|
+
if (!visitorQueryParams) throw new Error("Not connected yet. Please wait and try again.");
|
|
93
|
+
|
|
94
|
+
const localMessageId =
|
|
95
|
+
typeof crypto !== "undefined" && crypto.randomUUID
|
|
96
|
+
? crypto.randomUUID()
|
|
97
|
+
: "lc-" + Date.now();
|
|
98
|
+
const typeObj: Record<string, any> = { link: attachment.fileUrl };
|
|
99
|
+
if (attachment.caption) typeObj.caption = attachment.caption;
|
|
100
|
+
if (attachment.type === "document") typeObj.filename = attachment.fileName;
|
|
101
|
+
|
|
102
|
+
const body: any = {
|
|
103
|
+
localMessageId,
|
|
104
|
+
fileId: attachment.fileId,
|
|
105
|
+
fileUrl: attachment.fileUrl,
|
|
106
|
+
fileName: attachment.fileName,
|
|
107
|
+
fileExtension: attachment.fileExtension,
|
|
108
|
+
message: { type: attachment.type, [attachment.type]: typeObj },
|
|
109
|
+
};
|
|
110
|
+
if (conversationId) body.conversationId = conversationId;
|
|
111
|
+
|
|
112
|
+
setSending(true);
|
|
113
|
+
setError(null);
|
|
114
|
+
try {
|
|
115
|
+
const raw = await conversationApi.sendVisitorMessage(body, {
|
|
116
|
+
params: visitorQueryParams,
|
|
117
|
+
});
|
|
118
|
+
return pickConversationId(raw);
|
|
119
|
+
} catch (e: any) {
|
|
120
|
+
const msg = e?.response?.data?.message ?? e?.message ?? "Failed to send attachment";
|
|
121
|
+
setError(msg);
|
|
122
|
+
throw new Error(msg);
|
|
123
|
+
} finally {
|
|
124
|
+
setSending(false);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
[visitorQueryParams]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const sendInteractive = useCallback(
|
|
131
|
+
async (reply: InteractiveReplyMeta, conversationId?: string | null): Promise<string | null> => {
|
|
132
|
+
if (!visitorQueryParams) throw new Error("Not connected yet. Please wait and try again.");
|
|
133
|
+
|
|
134
|
+
const localMessageId =
|
|
135
|
+
typeof crypto !== "undefined" && crypto.randomUUID
|
|
136
|
+
? crypto.randomUUID()
|
|
137
|
+
: "lc-" + Date.now();
|
|
138
|
+
const body: any = {
|
|
139
|
+
localMessageId,
|
|
140
|
+
message: {
|
|
141
|
+
type: "interactive",
|
|
142
|
+
interactive: {
|
|
143
|
+
type: reply.type,
|
|
144
|
+
[reply.type]: { id: reply.id, title: reply.title },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
if (conversationId) body.conversationId = conversationId;
|
|
149
|
+
|
|
150
|
+
setSending(true);
|
|
151
|
+
setError(null);
|
|
152
|
+
try {
|
|
153
|
+
const raw = await conversationApi.sendVisitorMessage(body, {
|
|
154
|
+
params: visitorQueryParams,
|
|
155
|
+
});
|
|
156
|
+
return pickConversationId(raw);
|
|
157
|
+
} catch (e: any) {
|
|
158
|
+
const msg = e?.response?.data?.message ?? e?.message ?? "Failed to send reply";
|
|
159
|
+
setError(msg);
|
|
160
|
+
throw new Error(msg);
|
|
161
|
+
} finally {
|
|
162
|
+
setSending(false);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
[visitorQueryParams]
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return { sending, error, clearError, sendText, sendAttachment, sendInteractive };
|
|
169
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -75,6 +75,9 @@ export { PanelRouterProvider, usePanelRouter } from "./navigation/panel-router-c
|
|
|
75
75
|
export type { PanelRouterContextValue, WidgetPanelView } from "./navigation/panel-router-context";
|
|
76
76
|
|
|
77
77
|
// ─── Theme ─────────────────────────────────────────────────────────────────────
|
|
78
|
+
export { usePanelSafeAreaInsets } from "./ui/safe-area";
|
|
79
|
+
export type { PanelSafeAreaInsets } from "./ui/safe-area";
|
|
80
|
+
|
|
78
81
|
export {
|
|
79
82
|
defaultTheme,
|
|
80
83
|
mergeTheme,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from "react";
|
|
2
|
-
import { View, StyleSheet, BackHandler, PanResponder
|
|
1
|
+
import React, { useEffect, useRef, type ComponentType, type ReactNode } from "react";
|
|
2
|
+
import { View, StyleSheet, BackHandler, PanResponder } from "react-native";
|
|
3
3
|
import { PanelRouterProvider, usePanelRouter } from "./panel-router-context";
|
|
4
4
|
import { HomeScreen } from "../ui/components/HomeScreen";
|
|
5
5
|
import { ConversationListScreen } from "../ui/components/ConversationListScreen";
|
|
@@ -8,10 +8,24 @@ import { useLiveChatContext } from "../provider/LiveChatContext";
|
|
|
8
8
|
import { getBrandColor, useEffectiveAppearance } from "../ui/theme";
|
|
9
9
|
import { WsStatusStrip } from "../ui/components/WsStatusStrip";
|
|
10
10
|
|
|
11
|
+
let KeyboardProvider: ComponentType<{ children: ReactNode }> | null = null;
|
|
12
|
+
try {
|
|
13
|
+
KeyboardProvider = require("react-native-keyboard-controller").KeyboardProvider;
|
|
14
|
+
} catch {
|
|
15
|
+
KeyboardProvider = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export type LiveChatPanelProps = {
|
|
12
19
|
onClose?: () => void;
|
|
13
20
|
};
|
|
14
21
|
|
|
22
|
+
let PanelSafeAreaProvider: ComponentType<{ children: ReactNode }> | null = null;
|
|
23
|
+
try {
|
|
24
|
+
PanelSafeAreaProvider = require("react-native-safe-area-context").SafeAreaProvider;
|
|
25
|
+
} catch {
|
|
26
|
+
PanelSafeAreaProvider = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
// Mirrors web PanelShell — neutral chrome for chat screens, brand bg for home
|
|
16
30
|
function usePanelBackground(view: string): string {
|
|
17
31
|
const { widgetConfig } = useLiveChatContext();
|
|
@@ -104,13 +118,22 @@ function PanelContent({ onClose }: LiveChatPanelProps) {
|
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
export function LiveChatPanel({ onClose }: LiveChatPanelProps) {
|
|
107
|
-
|
|
121
|
+
const panel = (
|
|
108
122
|
<PanelRouterProvider>
|
|
109
123
|
<View style={styles.flex}>
|
|
110
124
|
<PanelContent onClose={onClose} />
|
|
111
125
|
</View>
|
|
112
126
|
</PanelRouterProvider>
|
|
113
127
|
);
|
|
128
|
+
|
|
129
|
+
const withKeyboard = KeyboardProvider ? (
|
|
130
|
+
<KeyboardProvider>{panel}</KeyboardProvider>
|
|
131
|
+
) : panel;
|
|
132
|
+
|
|
133
|
+
if (PanelSafeAreaProvider) {
|
|
134
|
+
return <PanelSafeAreaProvider>{withKeyboard}</PanelSafeAreaProvider>;
|
|
135
|
+
}
|
|
136
|
+
return withKeyboard;
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
const styles = StyleSheet.create({
|
|
@@ -23,6 +23,8 @@ export const LiveChatContext = createContext<LiveChatContextValue>({
|
|
|
23
23
|
isVisible: true,
|
|
24
24
|
show: () => undefined,
|
|
25
25
|
hide: () => undefined,
|
|
26
|
+
activeConversationId: null,
|
|
27
|
+
setActiveConversationId: () => undefined,
|
|
26
28
|
wsClient: null,
|
|
27
29
|
connectionState: { isConnected: false, isConnecting: false, isAwaitingRetry: false, lastError: null },
|
|
28
30
|
theme: defaultTheme,
|