react-native-srschat 0.1.13 → 0.1.15

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.
Files changed (140) hide show
  1. package/lib/commonjs/components/email.js +243 -0
  2. package/lib/commonjs/components/email.js.map +1 -0
  3. package/lib/commonjs/components/feedback.js +115 -0
  4. package/lib/commonjs/components/feedback.js.map +1 -0
  5. package/lib/commonjs/components/header.js +35 -17
  6. package/lib/commonjs/components/header.js.map +1 -1
  7. package/lib/commonjs/components/input.js +106 -0
  8. package/lib/commonjs/components/input.js.map +1 -0
  9. package/lib/commonjs/components/productCard.js +234 -0
  10. package/lib/commonjs/components/productCard.js.map +1 -0
  11. package/lib/commonjs/components/testing.js +19 -6
  12. package/lib/commonjs/components/testing.js.map +1 -1
  13. package/lib/commonjs/{layout/chatIcon.js → components/welcomeButton.js} +40 -37
  14. package/lib/commonjs/components/welcomeButton.js.map +1 -0
  15. package/lib/commonjs/components/welcomeInput.js +87 -0
  16. package/lib/commonjs/components/welcomeInput.js.map +1 -0
  17. package/lib/commonjs/contexts/AppContext.js +272 -56
  18. package/lib/commonjs/contexts/AppContext.js.map +1 -1
  19. package/lib/commonjs/hooks/{Stream.js → stream.js} +107 -39
  20. package/lib/commonjs/hooks/stream.js.map +1 -0
  21. package/lib/commonjs/hooks/useAsyncStorage.js +36 -0
  22. package/lib/commonjs/hooks/useAsyncStorage.js.map +1 -0
  23. package/lib/commonjs/index.js +13 -6
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/layout/disclaimer.js +200 -0
  26. package/lib/commonjs/layout/disclaimer.js.map +1 -0
  27. package/lib/commonjs/layout/ex.js +272 -0
  28. package/lib/commonjs/layout/ex.js.map +1 -0
  29. package/lib/commonjs/layout/icon.js +114 -0
  30. package/lib/commonjs/layout/icon.js.map +1 -0
  31. package/lib/commonjs/layout/layout.js +148 -14
  32. package/lib/commonjs/layout/layout.js.map +1 -1
  33. package/lib/commonjs/layout/welcome.js +135 -0
  34. package/lib/commonjs/layout/welcome.js.map +1 -0
  35. package/lib/commonjs/layout/window.js +205 -0
  36. package/lib/commonjs/layout/window.js.map +1 -0
  37. package/lib/module/components/email.js +233 -0
  38. package/lib/module/components/email.js.map +1 -0
  39. package/lib/module/components/feedback.js +105 -0
  40. package/lib/module/components/feedback.js.map +1 -0
  41. package/lib/module/components/header.js +35 -17
  42. package/lib/module/components/header.js.map +1 -1
  43. package/lib/module/components/input.js +96 -0
  44. package/lib/module/components/input.js.map +1 -0
  45. package/lib/module/components/productCard.js +225 -0
  46. package/lib/module/components/productCard.js.map +1 -0
  47. package/lib/module/components/testing.js +20 -7
  48. package/lib/module/components/testing.js.map +1 -1
  49. package/lib/module/components/welcomeButton.js +48 -0
  50. package/lib/module/components/welcomeButton.js.map +1 -0
  51. package/lib/module/components/welcomeInput.js +77 -0
  52. package/lib/module/components/welcomeInput.js.map +1 -0
  53. package/lib/module/contexts/AppContext.js +272 -57
  54. package/lib/module/contexts/AppContext.js.map +1 -1
  55. package/lib/module/hooks/{Stream.js → stream.js} +107 -39
  56. package/lib/module/hooks/stream.js.map +1 -0
  57. package/lib/module/hooks/useAsyncStorage.js +29 -0
  58. package/lib/module/hooks/useAsyncStorage.js.map +1 -0
  59. package/lib/module/index.js +13 -6
  60. package/lib/module/index.js.map +1 -1
  61. package/lib/module/layout/disclaimer.js +190 -0
  62. package/lib/module/layout/disclaimer.js.map +1 -0
  63. package/lib/module/layout/ex.js +262 -0
  64. package/lib/module/layout/ex.js.map +1 -0
  65. package/lib/module/layout/icon.js +104 -0
  66. package/lib/module/layout/icon.js.map +1 -0
  67. package/lib/module/layout/layout.js +150 -16
  68. package/lib/module/layout/layout.js.map +1 -1
  69. package/lib/module/layout/welcome.js +126 -0
  70. package/lib/module/layout/welcome.js.map +1 -0
  71. package/lib/module/layout/window.js +195 -0
  72. package/lib/module/layout/window.js.map +1 -0
  73. package/lib/typescript/components/email.d.ts +5 -0
  74. package/lib/typescript/components/email.d.ts.map +1 -0
  75. package/lib/typescript/components/feedback.d.ts +6 -0
  76. package/lib/typescript/components/feedback.d.ts.map +1 -0
  77. package/lib/typescript/components/header.d.ts.map +1 -1
  78. package/lib/typescript/components/input.d.ts +6 -0
  79. package/lib/typescript/components/input.d.ts.map +1 -0
  80. package/lib/typescript/components/productCard.d.ts +5 -0
  81. package/lib/typescript/components/productCard.d.ts.map +1 -0
  82. package/lib/typescript/components/testing.d.ts.map +1 -1
  83. package/lib/typescript/components/welcomeButton.d.ts +4 -0
  84. package/lib/typescript/components/welcomeButton.d.ts.map +1 -0
  85. package/lib/typescript/components/welcomeInput.d.ts +6 -0
  86. package/lib/typescript/components/welcomeInput.d.ts.map +1 -0
  87. package/lib/typescript/contexts/AppContext.d.ts +5 -1
  88. package/lib/typescript/contexts/AppContext.d.ts.map +1 -1
  89. package/lib/typescript/hooks/{Stream.d.ts → stream.d.ts} +1 -1
  90. package/lib/typescript/hooks/stream.d.ts.map +1 -0
  91. package/lib/typescript/hooks/useAsyncStorage.d.ts +2 -0
  92. package/lib/typescript/hooks/useAsyncStorage.d.ts.map +1 -0
  93. package/lib/typescript/index.d.ts +2 -1
  94. package/lib/typescript/index.d.ts.map +1 -1
  95. package/lib/typescript/layout/disclaimer.d.ts +5 -0
  96. package/lib/typescript/layout/disclaimer.d.ts.map +1 -0
  97. package/lib/typescript/layout/ex.d.ts +3 -0
  98. package/lib/typescript/layout/ex.d.ts.map +1 -0
  99. package/lib/typescript/layout/{chatIcon.d.ts → icon.d.ts} +1 -1
  100. package/lib/typescript/layout/icon.d.ts.map +1 -0
  101. package/lib/typescript/layout/layout.d.ts +1 -4
  102. package/lib/typescript/layout/layout.d.ts.map +1 -1
  103. package/lib/typescript/layout/welcome.d.ts +5 -0
  104. package/lib/typescript/layout/welcome.d.ts.map +1 -0
  105. package/lib/typescript/layout/window.d.ts +5 -0
  106. package/lib/typescript/layout/window.d.ts.map +1 -0
  107. package/package.json +7 -1
  108. package/src/components/email.js +210 -0
  109. package/src/components/feedback.js +114 -0
  110. package/src/components/header.js +32 -17
  111. package/src/components/input.js +95 -0
  112. package/src/components/productCard.js +240 -0
  113. package/src/components/testing.js +17 -4
  114. package/src/components/welcomeButton.js +51 -0
  115. package/src/components/welcomeInput.js +81 -0
  116. package/src/contexts/AppContext.js +237 -52
  117. package/src/hooks/{Stream.js → stream.js} +123 -41
  118. package/src/hooks/useAsyncStorage.js +33 -0
  119. package/src/index.js +7 -3
  120. package/src/layout/disclaimer.js +187 -0
  121. package/src/layout/ex.js +251 -0
  122. package/src/layout/icon.js +96 -0
  123. package/src/layout/layout.js +137 -10
  124. package/src/layout/welcome.js +124 -0
  125. package/src/layout/window.js +194 -0
  126. package/lib/commonjs/hooks/Stream.js.map +0 -1
  127. package/lib/commonjs/layout/chatIcon.js.map +0 -1
  128. package/lib/commonjs/layout/chatWindow.js +0 -214
  129. package/lib/commonjs/layout/chatWindow.js.map +0 -1
  130. package/lib/module/hooks/Stream.js.map +0 -1
  131. package/lib/module/layout/chatIcon.js +0 -44
  132. package/lib/module/layout/chatIcon.js.map +0 -1
  133. package/lib/module/layout/chatWindow.js +0 -204
  134. package/lib/module/layout/chatWindow.js.map +0 -1
  135. package/lib/typescript/hooks/Stream.d.ts.map +0 -1
  136. package/lib/typescript/layout/chatIcon.d.ts.map +0 -1
  137. package/lib/typescript/layout/chatWindow.d.ts +0 -6
  138. package/lib/typescript/layout/chatWindow.d.ts.map +0 -1
  139. package/src/layout/chatIcon.js +0 -38
  140. package/src/layout/chatWindow.js +0 -207
@@ -0,0 +1,187 @@
1
+ import React, { useContext } from "react";
2
+ import { View, Text, ScrollView, StyleSheet, TouchableOpacity } from "react-native";
3
+ import { AppContext } from "../contexts/AppContext";
4
+ import Feather from "react-native-vector-icons/Feather";
5
+ import { Header } from '../components/header';
6
+ import { Testing } from "../components/testing";
7
+
8
+ export const Disclaimer = ({ panHandlers }) => {
9
+ const { setShowModal, confirmDisclaimer, uiConfig, onAddToCartClick, onProductCardClick } = useContext(AppContext);
10
+
11
+ const sections = [
12
+ {
13
+ title: "General Information",
14
+ details: "The AI agent is an automated system powered by Instalily. It is designed to provide information and assistance based on HeritagePool+ Suppliers and documentation in product brand manuals. While we strive to provide accurate information through the AI agent, we cannot guarantee the accuracy, completeness, or up-to-date nature of the information provided. We recommend that you use responses as a starting point and verify the information in the linked public documentation sources.",
15
+ icon: "file-text"
16
+ },
17
+ {
18
+ title: "User Responsibility",
19
+ details: "Users of the AI agent bear sole responsibility for their interactions and reliance on the information provided. It is important to exercise caution and use your discretion while interpreting and acting upon the agent's responses. We cannot be held liable for any actions, losses, or damages resulting from the use of the agent.",
20
+ icon: "thumbs-up"
21
+ },
22
+ {
23
+ title: "Data Privacy and Security",
24
+ details: "We prioritize the privacy and security of our users' information. Be aware that agent conversations are recorded and stored for later review and quality monitoring by SRS Distribution and its contractors, including by SRS’ content team to improve results. While SRS and the vendor retain logs of these conversations, this data is not used to train their AI models.",
25
+ icon: "lock"
26
+ }
27
+ ];
28
+
29
+ return (
30
+ <View style={styles.container}>
31
+ <View {...panHandlers}>
32
+ <Header />
33
+ </View>
34
+ <ScrollView contentContainerStyle={styles.scrollContainer}>
35
+ <View style={styles.header}>
36
+ <Text style={styles.welcomeTitle}>Welcome to your AI Agent</Text>
37
+ <View style={styles.betaTag}>
38
+ <Text style={styles.betaText}>BETA</Text>
39
+ </View>
40
+ </View>
41
+
42
+ <Text style={styles.disclaimerText}>
43
+ The following disclaimer is intended to inform users of the limitations and usage guidelines of the AI agent.
44
+ </Text>
45
+ <Text style={styles.disclaimerText}>
46
+ Please read this disclaimer carefully before engaging with the agent.
47
+ </Text>
48
+
49
+ {sections.map((section, index) => (
50
+ <Paragraph key={index} title={section.title} details={section.details} icon={section.icon} />
51
+ ))}
52
+
53
+ <Text style={styles.disclaimerText}>
54
+ Do not share any sensitive or personal information in your conversations.
55
+ </Text>
56
+ <Text style={styles.disclaimerText}>
57
+ By using our AI agent, you indicate your acceptance and understanding of the above disclaimer. If you do not agree with any part of this disclaimer, we recommend refraining from using the agent. For further assistance or inquiries, please contact <Text style={styles.boldText}>srs@instalily.ai</Text>.
58
+ </Text>
59
+ </ScrollView>
60
+ {uiConfig.testButtons &&
61
+ <Testing onProductCardClick={onProductCardClick} onAddToCartClick={onAddToCartClick} />
62
+ }
63
+ <View style={styles.confirmArea}>
64
+ <TouchableOpacity style={styles.button} onPress={confirmDisclaimer}>
65
+ <Text style={styles.buttonText}>Confirm</Text>
66
+ </TouchableOpacity>
67
+ <Text style={styles.confirmText}>
68
+ By clicking the "Confirm" button above, you understand and confirm that your use of Heritage Pool+ AI Agent is subject to our current Terms of Service and Privacy Policy, and you confirm your understanding of the above.
69
+ </Text>
70
+ </View>
71
+ </View>
72
+ );
73
+ };
74
+
75
+ const Paragraph = ({ title, details, icon }) => {
76
+ return (
77
+ <View style={styles.sectionContainer}>
78
+ <View style={styles.iconContainer}>
79
+ <Feather name={icon} size={18} color="#1976d2" />
80
+ </View>
81
+ <View style={styles.textContainer}>
82
+ <Text style={styles.sectionTitle}>{title}</Text>
83
+ <Text style={styles.sectionDetails}>{details}</Text>
84
+ </View>
85
+ </View>
86
+ );
87
+ };
88
+
89
+ const styles = StyleSheet.create({
90
+ container: {
91
+ flex: 1,
92
+ },
93
+ scrollContainer: {
94
+ paddingBottom: 20,
95
+ padding: 20,
96
+ },
97
+ header: {
98
+ flexDirection: "row",
99
+ alignItems: "center",
100
+ marginBottom: 10,
101
+ },
102
+ welcomeTitle: {
103
+ fontSize: 18,
104
+ fontWeight: "500",
105
+ color: "#161616",
106
+ },
107
+ betaTag: {
108
+ backgroundColor: "#f6921e",
109
+ borderRadius: 8,
110
+ paddingHorizontal: 10,
111
+ paddingVertical: 4,
112
+ marginLeft: 10,
113
+ },
114
+ betaText: {
115
+ color: "#ffffff",
116
+ fontSize: 11,
117
+ fontWeight: "500",
118
+ },
119
+ disclaimerText: {
120
+ fontSize: 13,
121
+ color: "#505050",
122
+ lineHeight: 18,
123
+ marginBottom: 8,
124
+ },
125
+ boldText: {
126
+ fontWeight: "bold",
127
+ },
128
+ sectionContainer: {
129
+ flexDirection: "row",
130
+ alignItems: "flex-start",
131
+ backgroundColor: "#fff",
132
+ padding: 12,
133
+ marginBottom: 10,
134
+ borderRadius: 10,
135
+ elevation: 1,
136
+ },
137
+ iconContainer: {
138
+ backgroundColor: "#bbdefb",
139
+ borderRadius: 10,
140
+ padding: 10,
141
+ alignItems: "center",
142
+ justifyContent: "center",
143
+ marginRight: 10,
144
+ },
145
+ textContainer: {
146
+ flex: 1,
147
+ },
148
+ sectionTitle: {
149
+ fontSize: 14,
150
+ fontWeight: "500",
151
+ color: "#161616",
152
+ marginBottom: 3,
153
+ },
154
+ sectionDetails: {
155
+ fontSize: 12,
156
+ color: "#505050",
157
+ lineHeight: 18,
158
+ },
159
+ confirmArea: {
160
+ alignItems: "center",
161
+ padding: 20,
162
+ backgroundColor: '#f6f6f6',
163
+ borderTopWidth: 1,
164
+ borderTopColor: 'rgba(0, 0, 0, 0.1)',
165
+ paddingBottom: 25,
166
+ },
167
+ button: {
168
+ backgroundColor: "#367CB6",
169
+ width: "100%",
170
+ paddingVertical: 12,
171
+ borderRadius: 5,
172
+ alignItems: "center",
173
+ marginBottom: 10,
174
+ },
175
+ buttonText: {
176
+ color: "#FFFFFF",
177
+ fontSize: 16,
178
+ fontWeight: "500",
179
+ },
180
+ confirmText: {
181
+ fontSize: 11,
182
+ color: "gray",
183
+ lineHeight: 16,
184
+ paddingHorizontal: 10,
185
+ },
186
+ });
187
+
@@ -0,0 +1,251 @@
1
+ import React, { useState, useEffect, useContext, useRef } from 'react';
2
+ import { Text, StyleSheet, View, TextInput, TouchableOpacity, Platform, KeyboardAvoidingView,
3
+ Keyboard, Animated, PanResponder, Pressable } from 'react-native';
4
+ import { Header } from '../components/header';
5
+ import { AppContext } from '../contexts/AppContext';
6
+ import Ionicons from 'react-native-vector-icons/Ionicons';
7
+ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
8
+ import { Testing } from '../components/testing';
9
+ import { ChatInput } from '../components/input';
10
+ import { useWebSocketMessage } from '../hooks/stream';
11
+ import { ProductCard } from '../components/productCard'
12
+ import Markdown from 'react-native-markdown-display';
13
+
14
+ export const ChatWindow = () => {
15
+ const { handleSend, messages, input, setInput, ghostMessage, handleButtonClick, setShowModal } = useContext(AppContext);
16
+
17
+ const scrollViewRef = useRef(null);
18
+ const fadeAnim = useRef(new Animated.Value(0.6)).current;
19
+ const panY = useRef(new Animated.Value(0)).current;
20
+ const isDragging = useRef(false);
21
+
22
+ useEffect(() => {
23
+ if (scrollViewRef.current) {
24
+ setTimeout(() => {
25
+ scrollViewRef.current.scrollToEnd({ animated: false });
26
+ }, 100);
27
+ }
28
+ }, []);
29
+
30
+ useEffect(() => {
31
+ if (ghostMessage) {
32
+ Animated.loop(
33
+ Animated.sequence([
34
+ Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: false }),
35
+ Animated.timing(fadeAnim, { toValue: 0.6, duration: 500, useNativeDriver: false }),
36
+ ])
37
+ ).start();
38
+ }
39
+ }, [ghostMessage]);
40
+
41
+ useWebSocketMessage()
42
+
43
+ const headerPanResponder = PanResponder.create({
44
+ onStartShouldSetPanResponder: () => true,
45
+ onMoveShouldSetPanResponder: () => true,
46
+ onPanResponderGrant: () => {
47
+ isDragging.current = true;
48
+ },
49
+ onPanResponderMove: (evt, gestureState) => {
50
+ if (isDragging.current && gestureState.dy > 0) { // Downward swipe
51
+ panY.setValue(gestureState.dy);
52
+ }
53
+ },
54
+ onPanResponderRelease: (evt, gestureState) => {
55
+ if (gestureState.dy > 100) { // Swiped down enough
56
+ handleClick()
57
+ } else {
58
+ Animated.spring(panY, { toValue: 0, useNativeDriver: false }).start();
59
+ }
60
+ isDragging.current = false;
61
+ },
62
+ });
63
+
64
+ const handleClick = () => {
65
+ if ((uiConfig.showIcon ?? true) !== true) {
66
+ setShowModal("Off");
67
+ } else {
68
+ setShowModal("Icon");
69
+ }
70
+ };
71
+
72
+ return (
73
+ <View style={styles.overlay}>
74
+ {/* Click outside chat to close */}
75
+ <Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
76
+
77
+ <Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
78
+ {/* Header - Only this section triggers drag-to-close */}
79
+ <View style={styles.headerContainer} {...headerPanResponder.panHandlers}>
80
+ <Header />
81
+ </View>
82
+
83
+ {/* Messages area */}
84
+ <KeyboardAwareScrollView
85
+ ref={scrollViewRef}
86
+ contentContainerStyle={styles.messagesContent}
87
+ enableOnAndroid
88
+ contentInsetAdjustmentBehavior="never"
89
+ automaticallyAdjustContentInsets={false}
90
+ contentInset={{ bottom: 0 }}
91
+ keyboardShouldPersistTaps="always"
92
+ showsVerticalScrollIndicator={false}
93
+ extraScrollHeight={0}
94
+ onContentSizeChange={() => {
95
+ scrollViewRef.current?.scrollToEnd({ animated: true });
96
+ }}
97
+ >
98
+ {messages.map((msg, i) => (
99
+
100
+ <View key={i} style={styles.messageWrapper}>
101
+ {msg.type !== "middle" && (
102
+ <View style={[ styles.messageBubble, msg.type === "user" ? styles.userMessage : styles.aiMessage,]}>
103
+ <Markdown style={{ body: { color: msg.type === "user" ? "#ffffff" : "#161616",fontSize: 16, lineHeight: 22 }}}>
104
+ {msg.text}
105
+ </Markdown>
106
+ </View>
107
+ )}
108
+
109
+ {msg.products && msg.products.length > 0 &&
110
+ msg.products.map((prod, index) => (
111
+ <View key={index} style={styles.productCardWrapper}>
112
+ <ProductCard prod={prod} />
113
+ </View>
114
+ ))}
115
+
116
+ {msg.suggested_questions && Array.isArray(msg.questions) && msg.questions.map((question, index) => (
117
+ <TouchableOpacity key={index} style={styles.suggestedQuestionButton}
118
+ onPress={() => handleButtonClick(question)}>
119
+ <Text style={styles.suggestedQuestionText}>{question}</Text>
120
+ </TouchableOpacity>
121
+ ))}
122
+
123
+ {ghostMessage && i === messages.length - 1 && (
124
+ <View style={styles.ghostMessageContainer}>
125
+ <Animated.View style={[styles.ghostBar, styles.ghostBarShort, { opacity: fadeAnim }]} />
126
+ <Animated.View style={[styles.ghostBar, styles.ghostBarLong, { opacity: fadeAnim }]} />
127
+ <Animated.View style={[styles.ghostBar, styles.ghostBarMedium, { opacity: fadeAnim }]} />
128
+ </View>
129
+ )}
130
+ </View>
131
+ ))}
132
+ </KeyboardAwareScrollView>
133
+
134
+ <KeyboardAvoidingView
135
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
136
+ keyboardVerticalOffset={Platform.OS === 'ios' ? 120 : 60}
137
+ >
138
+ <ChatInput/>
139
+ </KeyboardAvoidingView>
140
+ </Animated.View>
141
+ </View>
142
+ );
143
+ };
144
+
145
+ const styles = StyleSheet.create({
146
+ overlay: {
147
+ flex: 1,
148
+ position: 'absolute',
149
+ left: 0,
150
+ right: 0,
151
+ top: 0,
152
+ bottom: 0,
153
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
154
+ justifyContent: 'flex-end',
155
+ },
156
+ outsideTouchable: {
157
+ flex: 1, // Takes up the space above the chat, allows clicking to close
158
+ },
159
+ container: {
160
+ flex: 1,
161
+ backgroundColor: '#f6f6f6',
162
+ position: 'absolute',
163
+ zIndex: 1000,
164
+ left: 0,
165
+ right: 0,
166
+ bottom: 0,
167
+ top: 140,
168
+ pointerEvents: 'box-none',
169
+ borderTopWidth: 1,
170
+ borderTopColor: '#DDD',
171
+ borderTopLeftRadius: 16,
172
+ borderTopRightRadius: 16,
173
+ overflow: 'hidden',
174
+ },
175
+ headerContainer: {
176
+ alignItems: "center",
177
+ backgroundColor: "#f6f6f6",
178
+ borderTopLeftRadius: 16,
179
+ borderTopRightRadius: 16,
180
+ },
181
+ messagesContent: {
182
+ padding: 16,
183
+ justifyContent: 'flex-end',
184
+ },
185
+ messageWrapper: {
186
+ marginBottom: 0,
187
+ },
188
+ messageBubble: {
189
+ padding: 6,
190
+ paddingHorizontal: 16,
191
+ borderRadius: 12,
192
+ marginBottom: 5
193
+ },
194
+ userMessage: {
195
+ alignSelf: 'flex-end',
196
+ backgroundColor: "#003764",
197
+ color: "#ffffff"
198
+ },
199
+ aiMessage: {
200
+ alignSelf: 'flex-start',
201
+ backgroundColor: '#FFFFFF',
202
+ width: '100%',
203
+ },
204
+ productCardWrapper: {
205
+ marginTop: 5,
206
+ },
207
+ suggestedQuestionButton: {
208
+ backgroundColor: "white",
209
+ borderWidth: 1,
210
+ borderColor: "#004687",
211
+ borderRadius: 18,
212
+ paddingVertical: 10,
213
+ paddingHorizontal: 16,
214
+ marginBottom: 7,
215
+ alignSelf: "flex-start",
216
+ },
217
+ suggestedQuestionText: {
218
+ color: "#004687",
219
+ fontSize: 13,
220
+ textAlign: "left",
221
+ },
222
+ ghostMessageContainer: {
223
+ alignSelf: 'flex-start',
224
+ width: '100%',
225
+ backgroundColor: "#FFFFFF",
226
+ borderRadius: 10,
227
+ borderTopLeftRadius: 0,
228
+ padding: 14,
229
+ marginVertical: 5,
230
+ },
231
+ ghostBar: {
232
+ height: 20,
233
+ borderRadius: 10,
234
+ backgroundColor: "#ebebeb",
235
+ marginVertical: 3,
236
+ },
237
+ ghostBarShort: {
238
+ width: "50%",
239
+ },
240
+ ghostBarMedium: {
241
+ width: "75%",
242
+ },
243
+ ghostBarLong: {
244
+ width: "100%",
245
+ },
246
+ });
247
+
248
+ {/* <Testing
249
+ onProductCardClick={onProductCardClick}
250
+ onAddToCartClick={onAddToCartClick}
251
+ /> */}
@@ -0,0 +1,96 @@
1
+ import React, { useContext } from 'react';
2
+ import { TouchableOpacity, View, StyleSheet, Text } from 'react-native';
3
+ import { AppContext } from '../contexts/AppContext';
4
+ import Ionicons from 'react-native-vector-icons/Ionicons';
5
+
6
+ export const ChatIcon = () => {
7
+ const { setShowModal, messages, maintenance, disclaimer, uiConfig } = useContext(AppContext);
8
+
9
+ // Determine position from uiConfig or default
10
+ const iconPosition = {
11
+ bottom: uiConfig?.iconPosition?.bottom ?? 80,
12
+ right: uiConfig?.iconPosition?.right ?? 20,
13
+ };
14
+
15
+ // Determine icon type (bubble or tab)
16
+ const iconType = uiConfig?.iconType ?? 'bubble';
17
+
18
+ const handleClick = () => {
19
+ if (!disclaimer) {
20
+ setShowModal("Form");
21
+ } else {
22
+ if (messages.length > 1 || maintenance) {
23
+ setShowModal("ChatWindow");
24
+ } else {
25
+ setShowModal("Welcome");
26
+ }
27
+ }
28
+ };
29
+
30
+ // Different styles for bubble and tab
31
+ const containerStyle =
32
+ iconType === 'tab'
33
+ ? [styles.tabContainer, iconPosition]
34
+ : [styles.bubbleContainer, iconPosition];
35
+
36
+ return (
37
+ <TouchableOpacity style={containerStyle} onPress={handleClick} activeOpacity={0.7}>
38
+ <View style={styles.iconContent}>
39
+ <Ionicons name="chatbubble-ellipses" size={28} color="white" />
40
+ {iconType === 'tab' && (
41
+ <Text style={styles.tabText}>Chat with Heritage</Text>
42
+ )}
43
+ </View>
44
+ </TouchableOpacity>
45
+ );
46
+ };
47
+
48
+ const styles = StyleSheet.create({
49
+ // Bubble Style
50
+ bubbleContainer: {
51
+ position: 'absolute',
52
+ width: 60,
53
+ height: 60,
54
+ borderRadius: 30,
55
+ backgroundColor: '#FFA500',
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ zIndex: 10,
59
+ shadowColor: '#000',
60
+ shadowOffset: { width: 0, height: 2 },
61
+ shadowOpacity: 0.2,
62
+ shadowRadius: 3,
63
+ elevation: 5,
64
+ },
65
+
66
+ // Tab Style
67
+ tabContainer: {
68
+ position: 'absolute',
69
+ width: 130,
70
+ height: 50,
71
+ borderTopLeftRadius: 25,
72
+ borderBottomLeftRadius: 25,
73
+ backgroundColor: '#367CB6',
74
+ flexDirection: 'row',
75
+ alignItems: 'center',
76
+ paddingHorizontal: 15,
77
+ justifyContent: 'flex-start',
78
+ zIndex: 10,
79
+ shadowColor: '#000',
80
+ shadowOffset: { width: 0, height: 2 },
81
+ shadowOpacity: 0.2,
82
+ shadowRadius: 3,
83
+ elevation: 5,
84
+ },
85
+ iconContent: {
86
+ flexDirection: 'row',
87
+ alignItems: 'center',
88
+ },
89
+
90
+ tabText: {
91
+ color: 'white',
92
+ fontSize: 14,
93
+ fontWeight: '500',
94
+ marginLeft: 8,
95
+ },
96
+ });
@@ -1,27 +1,154 @@
1
- import React, { useState, useCallback, useContext } from 'react';
2
- import { SafeAreaView, Text, StyleSheet, View, TextInput, ScrollView, KeyboardAvoidingView,
3
- Platform, TouchableOpacity, RefreshControl, } from 'react-native';
4
- import { ChatWindow } from './chatWindow';
1
+ import React, { useRef, useState, useContext, useEffect } from 'react';
2
+ import { View, Animated, PanResponder, Pressable, StyleSheet } from 'react-native';
5
3
  import { AppContext } from '../contexts/AppContext';
6
- import { ChatIcon } from './chatIcon';
7
4
 
8
- export const Layout = ({ onProductCardClick, onAddToCartClick }) => {
9
- const { showModal } = useContext(AppContext);
5
+ // Pages
6
+ import { ChatWindow } from './window';
7
+ import { ChatIcon } from './icon';
8
+ import { Welcome } from './welcome'
9
+ import { Disclaimer } from './disclaimer';
10
+ import { EmailForm } from '../components/email';
11
+
12
+ export const Layout = () => {
13
+ const { showModal, setShowModal, uiConfig, disclaimer, maintenance, messages } = useContext(AppContext);
14
+
15
+ const panY = useRef(new Animated.Value(0)).current;
16
+ const isDragging = useRef(false);
17
+
18
+ const handleClick = () => {
19
+ if ((uiConfig.showIcon ?? true) !== true) {
20
+ setShowModal("Off");
21
+ } else {
22
+ setShowModal("Icon");
23
+ }
24
+ };
10
25
 
26
+ const panResponder = PanResponder.create({
27
+ onStartShouldSetPanResponder: () => true,
28
+ onMoveShouldSetPanResponder: () => true,
29
+ onPanResponderGrant: () => {
30
+ isDragging.current = true;
31
+ },
32
+ onPanResponderMove: (evt, gestureState) => {
33
+ if (isDragging.current && gestureState.dy >= 0) {
34
+ panY.setValue(gestureState.dy);
35
+ }
36
+ },
37
+ onPanResponderRelease: (evt, gestureState) => {
38
+ if (gestureState.dy > 120) {
39
+ // Close if dragged far enough
40
+ Animated.timing(panY, {
41
+ toValue: 600, // Off-screen
42
+ duration: 200,
43
+ useNativeDriver: false,
44
+ }).start(() => {
45
+ handleClick();
46
+ panY.setValue(0); // Reset for next time
47
+ });
48
+ } else {
49
+ // Snap back to full height
50
+ Animated.spring(panY, {
51
+ toValue: 0,
52
+ friction: 6,
53
+ tension: 80,
54
+ useNativeDriver: false,
55
+ }).start();
56
+ }
57
+ isDragging.current = false;
58
+ },
59
+ });
60
+
61
+ // uiConfig.showIcon, uiConfig.toggleChat
62
+
63
+ useEffect(() => {
64
+ if (!disclaimer) {
65
+ setShowModal("Form");
66
+ } else {
67
+ if (messages.length > 1 || maintenance) {
68
+ setShowModal("ChatWindow");
69
+ }
70
+ else {
71
+ setShowModal("Welcome")
72
+ }
73
+ }
74
+ }, [uiConfig.toggleChat]);
75
+
11
76
  return (
12
- <View style={styles.container} pointerEvents="box-none">
13
- {showModal === "Icon" ? <ChatIcon /> : <ChatWindow onProductCardClick={onProductCardClick} onAddToCartClick={onAddToCartClick}/>}
77
+ <View style={styles.layout} pointerEvents="box-none">
78
+ {showModal === "Icon" && (uiConfig.showIcon ?? true) && (
79
+ <ChatIcon />
80
+ )}
81
+ {showModal === "ChatWindow" && (
82
+ <>
83
+ <Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
84
+ <Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
85
+ <ChatWindow panHandlers={panResponder.panHandlers} />
86
+ </Animated.View>
87
+ </>
88
+ )}
89
+ {showModal === "Welcome" && (
90
+ <>
91
+ <Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
92
+ <Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
93
+ <Welcome panHandlers={panResponder.panHandlers} />
94
+ </Animated.View>
95
+ </>
96
+ )}
97
+ {showModal === "Form" &&
98
+ <>
99
+ <Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
100
+ <Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
101
+ <Disclaimer panHandlers={panResponder.panHandlers} />
102
+ </Animated.View>
103
+ </>
104
+ }
105
+ {showModal === "Email" &&
106
+ <>
107
+ <Pressable style={styles.outsideTouchable} onPress={() => handleClick()} />
108
+ <Animated.View style={[styles.container, { transform: [{ translateY: panY }] }]}>
109
+ <EmailForm panHandlers={panResponder.panHandlers} />
110
+ </Animated.View>
111
+ </>
112
+ }
14
113
  </View>
15
114
  );
16
115
  };
17
116
 
18
117
  const styles = StyleSheet.create({
19
- container: {
118
+ layout: {
20
119
  flex: 1,
21
120
  position: 'absolute',
22
121
  top: 0,
23
122
  left: 0,
24
123
  right: 0,
25
124
  bottom: 0,
125
+ backgroundColor: 'rgba(0, 0, 0, 0)',
126
+ justifyContent: 'flex-end',
127
+ },
128
+ container: {
129
+ flex: 1,
130
+ backgroundColor: '#f6f6f6',
131
+ position: 'absolute',
132
+ zIndex: 1000,
133
+ left: 0,
134
+ right: 0,
135
+ bottom: 0,
136
+ top: 140,
137
+ pointerEvents: 'box-none',
138
+ borderTopWidth: 1,
139
+ borderTopColor: '#DDD',
140
+ borderTopLeftRadius: 16,
141
+ borderTopRightRadius: 16,
142
+ overflow: 'hidden',
143
+ },
144
+ outsideTouchable: {
145
+ flex: 1,
26
146
  },
147
+ dragHandle: {
148
+ height: 20,
149
+ width: '100%',
150
+ backgroundColor: '#DDD',
151
+ borderTopLeftRadius: 16,
152
+ borderTopRightRadius: 16,
153
+ }
27
154
  });