@usechatting/react-native 0.1.2 → 0.1.4

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.
@@ -4,19 +4,20 @@ import { useChattingConversation } from "./use-chatting-conversation";
4
4
  import { styles } from "./chatting-screen-styles";
5
5
  export function ChattingConversationScreen(input) {
6
6
  const conversation = useChattingConversation(input);
7
- const title = conversation.siteConfig?.widgetTitle ?? "Chatting";
7
+ const title = conversation.siteConfig?.widgetTitle ?? "";
8
8
  const greeting = conversation.siteConfig?.greetingText?.trim() || "Start a conversation";
9
+ const brandColor = conversation.siteConfig?.brandColor?.trim() || "#2563EB";
9
10
  const teamStatus = conversation.siteConfig?.showOnlineStatus !== false && conversation.siteStatus
10
11
  ? conversation.siteStatus.online
11
12
  ? "Team is online"
12
13
  : "Team is away"
13
14
  : null;
14
- return (_jsxs(View, { style: styles.screen, children: [_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: styles.title, children: title }), teamStatus ? _jsx(Text, { style: styles.subtitle, children: teamStatus }) : null] }), _jsxs(ScrollView, { style: styles.scroll, contentContainerStyle: styles.content, children: [conversation.errorMessage ? (_jsx(View, { style: styles.banner, children: _jsx(Text, { style: [styles.bannerText, styles.error], children: conversation.errorMessage }) })) : null, conversation.isLoading ? _jsx(ActivityIndicator, { color: "#2563EB" }) : null, !conversation.isLoading && conversation.messages.length === 0 ? (_jsxs(View, { style: styles.row, children: [_jsx(Bubble, { message: greeting, isUser: false }), _jsx(View, { style: styles.spacer })] })) : null, conversation.messages.map((message) => (_jsx(View, { style: styles.row, children: message.sender === "team" ? (_jsxs(_Fragment, { children: [_jsx(Bubble, { message: message.content || "(attachment placeholder)", isUser: false }), _jsx(View, { style: styles.spacer })] })) : (_jsxs(_Fragment, { children: [_jsx(View, { style: styles.spacer }), _jsx(Bubble, { message: message.content || "(attachment placeholder)", isUser: true })] })) }, message.id))), conversation.teamTyping ? (_jsxs(View, { style: styles.row, children: [_jsx(Bubble, { message: "Team is typing...", isUser: false, subtle: true }), _jsx(View, { style: styles.spacer })] })) : null, conversation.faqSuggestions?.items.map((item) => (_jsxs(View, { style: styles.faqCard, children: [_jsx(Text, { style: styles.faqTitle, children: item.question }), _jsx(Text, { style: styles.faqText, children: item.answer })] }, item.id))), conversation.faqSuggestions?.fallbackMessage ? (_jsx(Text, { style: styles.meta, children: conversation.faqSuggestions.fallbackMessage })) : null] }), _jsx(View, { style: styles.footer, children: _jsxs(View, { style: styles.composerRow, children: [_jsx(TextInput, { value: conversation.draftMessage, onChangeText: conversation.setDraftMessage, placeholder: "Type a message", multiline: true, style: [styles.input, styles.composer] }), _jsx(TouchableOpacity, { style: [styles.button, styles.sendButton], onPress: () => void conversation.sendMessage(), children: _jsx(Text, { style: styles.buttonText, children: conversation.isSending ? "Sending..." : "Send" }) })] }) })] }));
15
+ return (_jsxs(View, { style: styles.screen, children: [_jsxs(View, { style: [styles.header, { backgroundColor: brandColor }], children: [title ? _jsx(Text, { style: styles.title, children: title }) : null, teamStatus ? _jsx(Text, { style: styles.subtitle, children: teamStatus }) : null] }), _jsxs(ScrollView, { style: styles.scroll, contentContainerStyle: styles.content, children: [conversation.errorMessage ? (_jsx(View, { style: styles.banner, children: _jsx(Text, { style: [styles.bannerText, styles.error], children: conversation.errorMessage }) })) : null, conversation.isLoading ? _jsx(ActivityIndicator, { color: brandColor }) : null, !conversation.isLoading && conversation.messages.length === 0 ? (_jsxs(View, { style: styles.row, children: [_jsx(Bubble, { message: greeting, isUser: false, brandColor: brandColor }), _jsx(View, { style: styles.spacer })] })) : null, conversation.messages.map((message) => (_jsx(View, { style: styles.row, children: message.sender === "team" ? (_jsxs(_Fragment, { children: [_jsx(Bubble, { message: message.content || "(attachment placeholder)", isUser: false, brandColor: brandColor }), _jsx(View, { style: styles.spacer })] })) : (_jsxs(_Fragment, { children: [_jsx(View, { style: styles.spacer }), _jsx(Bubble, { message: message.content || "(attachment placeholder)", isUser: true, brandColor: brandColor })] })) }, message.id))), conversation.teamTyping ? (_jsxs(View, { style: styles.row, children: [_jsx(Bubble, { message: "Team is typing...", isUser: false, subtle: true, brandColor: brandColor }), _jsx(View, { style: styles.spacer })] })) : null, conversation.faqSuggestions?.items.map((item) => (_jsxs(View, { style: styles.faqCard, children: [_jsx(Text, { style: styles.faqTitle, children: item.question }), _jsx(Text, { style: styles.faqText, children: item.answer })] }, item.id))), conversation.faqSuggestions?.fallbackMessage ? (_jsx(Text, { style: styles.meta, children: conversation.faqSuggestions.fallbackMessage })) : null] }), _jsx(View, { style: styles.footer, children: _jsxs(View, { style: styles.composerRow, children: [_jsx(TextInput, { value: conversation.draftMessage, onChangeText: conversation.setDraftMessage, placeholder: "Type a message", multiline: true, style: [styles.input, styles.composer] }), _jsx(TouchableOpacity, { style: [styles.button, styles.sendButton, { backgroundColor: brandColor }], onPress: () => void conversation.sendMessage(), children: _jsx(Text, { style: styles.buttonText, children: "Send" }) })] }) })] }));
15
16
  }
16
17
  function Bubble(input) {
17
18
  return (_jsx(View, { style: [
18
19
  styles.bubble,
19
- input.isUser ? styles.userBubble : styles.teamBubble,
20
+ input.isUser ? { backgroundColor: input.brandColor } : styles.teamBubble,
20
21
  input.subtle ? styles.typingBubble : null
21
22
  ], children: _jsx(Text, { style: input.isUser ? styles.userText : styles.teamText, children: input.message }) }));
22
23
  }
@@ -0,0 +1,4 @@
1
+ import type { ChattingMessage } from "./chatting-types";
2
+ export declare function createOptimisticMessage(content: string): ChattingMessage;
3
+ export declare function settleOptimisticMessage(messages: ChattingMessage[], optimisticId: string, savedMessage: ChattingMessage): ChattingMessage[];
4
+ export declare function removeOptimisticMessage(messages: ChattingMessage[], optimisticId: string): ChattingMessage[];
@@ -0,0 +1,19 @@
1
+ export function createOptimisticMessage(content) {
2
+ return {
3
+ id: `pending-${Date.now()}-${Math.random().toString(16).slice(2)}`,
4
+ content,
5
+ createdAt: new Date().toISOString(),
6
+ sender: "user",
7
+ attachments: [],
8
+ pending: true
9
+ };
10
+ }
11
+ export function settleOptimisticMessage(messages, optimisticId, savedMessage) {
12
+ const remaining = messages.filter((message) => message.id !== optimisticId);
13
+ return remaining.some((message) => message.id === savedMessage.id)
14
+ ? remaining
15
+ : [...remaining, savedMessage];
16
+ }
17
+ export function removeOptimisticMessage(messages, optimisticId) {
18
+ return messages.filter((message) => message.id !== optimisticId);
19
+ }
@@ -7,7 +7,6 @@ export declare const styles: {
7
7
  paddingHorizontal: number;
8
8
  paddingTop: number;
9
9
  paddingBottom: number;
10
- backgroundColor: string;
11
10
  };
12
11
  title: {
13
12
  fontSize: number;
@@ -56,9 +55,6 @@ export declare const styles: {
56
55
  paddingHorizontal: number;
57
56
  paddingVertical: number;
58
57
  };
59
- userBubble: {
60
- backgroundColor: string;
61
- };
62
58
  teamBubble: {
63
59
  backgroundColor: string;
64
60
  borderBottomLeftRadius: number;
@@ -139,7 +135,6 @@ export declare const styles: {
139
135
  paddingVertical: number;
140
136
  alignItems: string;
141
137
  justifyContent: string;
142
- backgroundColor: string;
143
138
  };
144
139
  sendButton: {
145
140
  flex: number;
@@ -1,7 +1,7 @@
1
1
  import { StyleSheet } from "react-native";
2
2
  export const styles = StyleSheet.create({
3
3
  screen: { flex: 1, backgroundColor: "#F6F8FB" },
4
- header: { paddingHorizontal: 18, paddingTop: 18, paddingBottom: 16, backgroundColor: "#2563EB" },
4
+ header: { paddingHorizontal: 18, paddingTop: 18, paddingBottom: 16 },
5
5
  title: { fontSize: 19, fontWeight: "700", color: "#FFFFFF" },
6
6
  subtitle: { marginTop: 4, fontSize: 13, color: "#DBEAFE" },
7
7
  scroll: { flex: 1 },
@@ -12,7 +12,6 @@ export const styles = StyleSheet.create({
12
12
  row: { flexDirection: "row", alignItems: "flex-end" },
13
13
  spacer: { flex: 1, minWidth: 40 },
14
14
  bubble: { maxWidth: "78%", borderRadius: 18, paddingHorizontal: 14, paddingVertical: 10 },
15
- userBubble: { backgroundColor: "#2563EB" },
16
15
  teamBubble: {
17
16
  backgroundColor: "#FFFFFF",
18
17
  borderBottomLeftRadius: 6,
@@ -33,7 +32,7 @@ export const styles = StyleSheet.create({
33
32
  composerRow: { flexDirection: "row", alignItems: "flex-end", gap: 8 },
34
33
  input: { borderWidth: 1, borderColor: "#CBD5E1", borderRadius: 20, paddingHorizontal: 14, paddingVertical: 10, backgroundColor: "#F8FAFC", color: "#0F172A" },
35
34
  composer: { flex: 1, minHeight: 44, maxHeight: 120, textAlignVertical: "top" },
36
- button: { flex: 1, borderRadius: 20, paddingVertical: 12, alignItems: "center", justifyContent: "center", backgroundColor: "#2563EB" },
35
+ button: { flex: 1, borderRadius: 20, paddingVertical: 12, alignItems: "center", justifyContent: "center" },
37
36
  sendButton: { flex: 0, minHeight: 44, paddingHorizontal: 18 },
38
37
  buttonText: { color: "#FFFFFF", fontWeight: "700" }
39
38
  });
@@ -51,6 +51,7 @@ export interface ChattingMessage {
51
51
  createdAt: string;
52
52
  sender: ChattingSender;
53
53
  attachments: ChattingMessageAttachment[];
54
+ pending?: boolean;
54
55
  }
55
56
  export interface ChattingFAQItem {
56
57
  id: string;
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { applyConversation, startConversationSync, syncTyping } from "./chatting-hook-helpers";
3
+ import { createOptimisticMessage, removeOptimisticMessage, settleOptimisticMessage } from "./chatting-optimistic-message";
3
4
  import { normalizeText, toErrorMessage } from "./chatting-utils";
4
5
  export function useChattingConversation(input) {
5
6
  const [siteConfig, setSiteConfig] = useState(null);
@@ -10,7 +11,7 @@ export function useChattingConversation(input) {
10
11
  const [emailAddress, setEmailAddress] = useState(input.draftVisitorEmail ?? "");
11
12
  const [teamTyping, setTeamTyping] = useState(false);
12
13
  const [errorMessage, setErrorMessage] = useState(null);
13
- const [isLoading, setIsLoading] = useState(false);
14
+ const [isLoading, setIsLoading] = useState(true);
14
15
  const [isSending, setIsSending] = useState(false);
15
16
  const [isSavingEmail, setIsSavingEmail] = useState(false);
16
17
  const stopSyncRef = useRef(null);
@@ -112,15 +113,21 @@ export function useChattingConversation(input) {
112
113
  if (!nextMessage) {
113
114
  return;
114
115
  }
116
+ const optimisticMessage = createOptimisticMessage(nextMessage);
115
117
  setIsSending(true);
116
118
  setErrorMessage(null);
119
+ setDraftMessageState("");
120
+ setMessages((current) => [...current, optimisticMessage]);
117
121
  try {
118
- await input.client.sendMessage(nextMessage, { context: input.context, email: emailAddress });
119
- setDraftMessageState("");
122
+ const result = await input.client.sendMessage(nextMessage, { context: input.context, email: emailAddress });
123
+ setMessages((current) => settleOptimisticMessage(current, optimisticMessage.id, result.message));
124
+ setFaqSuggestions(result.faqSuggestions);
120
125
  setTeamTyping(false);
121
126
  await refreshConversationState();
122
127
  }
123
128
  catch (error) {
129
+ setMessages((current) => removeOptimisticMessage(current, optimisticMessage.id));
130
+ setDraftMessageState((current) => current || nextMessage);
124
131
  setErrorMessage(toErrorMessage(error));
125
132
  }
126
133
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usechatting/react-native",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "React Native and Expo visitor chat SDK for Chatting.",
5
5
  "license": "Proprietary",
6
6
  "main": "dist/index.js",