srs-heritage-chatbot 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.
Files changed (187) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +194 -0
  3. package/lib/commonjs/assets/chat-icon-mobile.svg +1 -0
  4. package/lib/commonjs/assets/heritage.png +0 -0
  5. package/lib/commonjs/assets/posiden.svg +51 -0
  6. package/lib/commonjs/components/LoadingTips.js +104 -0
  7. package/lib/commonjs/components/LoadingTips.js.map +1 -0
  8. package/lib/commonjs/components/email.js +461 -0
  9. package/lib/commonjs/components/email.js.map +1 -0
  10. package/lib/commonjs/components/feedback.js +114 -0
  11. package/lib/commonjs/components/feedback.js.map +1 -0
  12. package/lib/commonjs/components/header.js +126 -0
  13. package/lib/commonjs/components/header.js.map +1 -0
  14. package/lib/commonjs/components/input.js +144 -0
  15. package/lib/commonjs/components/input.js.map +1 -0
  16. package/lib/commonjs/components/productCard.js +688 -0
  17. package/lib/commonjs/components/productCard.js.map +1 -0
  18. package/lib/commonjs/components/progressCircle.js +99 -0
  19. package/lib/commonjs/components/progressCircle.js.map +1 -0
  20. package/lib/commonjs/components/testing.js +74 -0
  21. package/lib/commonjs/components/testing.js.map +1 -0
  22. package/lib/commonjs/components/voice.js +184 -0
  23. package/lib/commonjs/components/voice.js.map +1 -0
  24. package/lib/commonjs/components/welcomeButton.js +149 -0
  25. package/lib/commonjs/components/welcomeButton.js.map +1 -0
  26. package/lib/commonjs/components/welcomeInput.js +137 -0
  27. package/lib/commonjs/components/welcomeInput.js.map +1 -0
  28. package/lib/commonjs/contexts/AppContext.js +552 -0
  29. package/lib/commonjs/contexts/AppContext.js.map +1 -0
  30. package/lib/commonjs/hooks/Stream.js +599 -0
  31. package/lib/commonjs/hooks/Stream.js.map +1 -0
  32. package/lib/commonjs/hooks/useAsyncStorage.js +36 -0
  33. package/lib/commonjs/hooks/useAsyncStorage.js.map +1 -0
  34. package/lib/commonjs/index.js +44 -0
  35. package/lib/commonjs/index.js.map +1 -0
  36. package/lib/commonjs/layout/disclaimer.js +208 -0
  37. package/lib/commonjs/layout/disclaimer.js.map +1 -0
  38. package/lib/commonjs/layout/ex.js +254 -0
  39. package/lib/commonjs/layout/ex.js.map +1 -0
  40. package/lib/commonjs/layout/icon.js +118 -0
  41. package/lib/commonjs/layout/icon.js.map +1 -0
  42. package/lib/commonjs/layout/layout.js +168 -0
  43. package/lib/commonjs/layout/layout.js.map +1 -0
  44. package/lib/commonjs/layout/welcome.js +160 -0
  45. package/lib/commonjs/layout/welcome.js.map +1 -0
  46. package/lib/commonjs/layout/window.js +396 -0
  47. package/lib/commonjs/layout/window.js.map +1 -0
  48. package/lib/commonjs/utils/audioRecorder.js +412 -0
  49. package/lib/commonjs/utils/audioRecorder.js.map +1 -0
  50. package/lib/commonjs/utils/cloudinary.js +69 -0
  51. package/lib/commonjs/utils/cloudinary.js.map +1 -0
  52. package/lib/commonjs/utils/storage.js +76 -0
  53. package/lib/commonjs/utils/storage.js.map +1 -0
  54. package/lib/commonjs/utils/textToSpeech.js +53 -0
  55. package/lib/commonjs/utils/textToSpeech.js.map +1 -0
  56. package/lib/module/assets/chat-icon-mobile.svg +1 -0
  57. package/lib/module/assets/heritage.png +0 -0
  58. package/lib/module/assets/posiden.svg +51 -0
  59. package/lib/module/components/LoadingTips.js +95 -0
  60. package/lib/module/components/LoadingTips.js.map +1 -0
  61. package/lib/module/components/email.js +452 -0
  62. package/lib/module/components/email.js.map +1 -0
  63. package/lib/module/components/feedback.js +105 -0
  64. package/lib/module/components/feedback.js.map +1 -0
  65. package/lib/module/components/header.js +117 -0
  66. package/lib/module/components/header.js.map +1 -0
  67. package/lib/module/components/input.js +135 -0
  68. package/lib/module/components/input.js.map +1 -0
  69. package/lib/module/components/productCard.js +679 -0
  70. package/lib/module/components/productCard.js.map +1 -0
  71. package/lib/module/components/progressCircle.js +91 -0
  72. package/lib/module/components/progressCircle.js.map +1 -0
  73. package/lib/module/components/testing.js +66 -0
  74. package/lib/module/components/testing.js.map +1 -0
  75. package/lib/module/components/voice.js +175 -0
  76. package/lib/module/components/voice.js.map +1 -0
  77. package/lib/module/components/welcomeButton.js +140 -0
  78. package/lib/module/components/welcomeButton.js.map +1 -0
  79. package/lib/module/components/welcomeInput.js +128 -0
  80. package/lib/module/components/welcomeInput.js.map +1 -0
  81. package/lib/module/contexts/AppContext.js +542 -0
  82. package/lib/module/contexts/AppContext.js.map +1 -0
  83. package/lib/module/hooks/Stream.js +592 -0
  84. package/lib/module/hooks/Stream.js.map +1 -0
  85. package/lib/module/hooks/useAsyncStorage.js +29 -0
  86. package/lib/module/hooks/useAsyncStorage.js.map +1 -0
  87. package/lib/module/index.js +36 -0
  88. package/lib/module/index.js.map +1 -0
  89. package/lib/module/layout/disclaimer.js +199 -0
  90. package/lib/module/layout/disclaimer.js.map +1 -0
  91. package/lib/module/layout/ex.js +253 -0
  92. package/lib/module/layout/ex.js.map +1 -0
  93. package/lib/module/layout/icon.js +108 -0
  94. package/lib/module/layout/icon.js.map +1 -0
  95. package/lib/module/layout/layout.js +160 -0
  96. package/lib/module/layout/layout.js.map +1 -0
  97. package/lib/module/layout/welcome.js +150 -0
  98. package/lib/module/layout/welcome.js.map +1 -0
  99. package/lib/module/layout/window.js +387 -0
  100. package/lib/module/layout/window.js.map +1 -0
  101. package/lib/module/utils/audioRecorder.js +398 -0
  102. package/lib/module/utils/audioRecorder.js.map +1 -0
  103. package/lib/module/utils/cloudinary.js +61 -0
  104. package/lib/module/utils/cloudinary.js.map +1 -0
  105. package/lib/module/utils/storage.js +67 -0
  106. package/lib/module/utils/storage.js.map +1 -0
  107. package/lib/module/utils/textToSpeech.js +43 -0
  108. package/lib/module/utils/textToSpeech.js.map +1 -0
  109. package/lib/typescript/components/LoadingTips.d.ts +3 -0
  110. package/lib/typescript/components/LoadingTips.d.ts.map +1 -0
  111. package/lib/typescript/components/email.d.ts +6 -0
  112. package/lib/typescript/components/email.d.ts.map +1 -0
  113. package/lib/typescript/components/feedback.d.ts +6 -0
  114. package/lib/typescript/components/feedback.d.ts.map +1 -0
  115. package/lib/typescript/components/header.d.ts +3 -0
  116. package/lib/typescript/components/header.d.ts.map +1 -0
  117. package/lib/typescript/components/input.d.ts +6 -0
  118. package/lib/typescript/components/input.d.ts.map +1 -0
  119. package/lib/typescript/components/productCard.d.ts +7 -0
  120. package/lib/typescript/components/productCard.d.ts.map +1 -0
  121. package/lib/typescript/components/progressCircle.d.ts +3 -0
  122. package/lib/typescript/components/progressCircle.d.ts.map +1 -0
  123. package/lib/typescript/components/testing.d.ts +6 -0
  124. package/lib/typescript/components/testing.d.ts.map +1 -0
  125. package/lib/typescript/components/voice.d.ts +5 -0
  126. package/lib/typescript/components/voice.d.ts.map +1 -0
  127. package/lib/typescript/components/welcomeButton.d.ts +4 -0
  128. package/lib/typescript/components/welcomeButton.d.ts.map +1 -0
  129. package/lib/typescript/components/welcomeInput.d.ts +6 -0
  130. package/lib/typescript/components/welcomeInput.d.ts.map +1 -0
  131. package/lib/typescript/contexts/AppContext.d.ts +10 -0
  132. package/lib/typescript/contexts/AppContext.d.ts.map +1 -0
  133. package/lib/typescript/hooks/Stream.d.ts +2 -0
  134. package/lib/typescript/hooks/Stream.d.ts.map +1 -0
  135. package/lib/typescript/hooks/useAsyncStorage.d.ts +2 -0
  136. package/lib/typescript/hooks/useAsyncStorage.d.ts.map +1 -0
  137. package/lib/typescript/index.d.ts +8 -0
  138. package/lib/typescript/index.d.ts.map +1 -0
  139. package/lib/typescript/layout/disclaimer.d.ts +5 -0
  140. package/lib/typescript/layout/disclaimer.d.ts.map +1 -0
  141. package/lib/typescript/layout/ex.d.ts +1 -0
  142. package/lib/typescript/layout/ex.d.ts.map +1 -0
  143. package/lib/typescript/layout/icon.d.ts +3 -0
  144. package/lib/typescript/layout/icon.d.ts.map +1 -0
  145. package/lib/typescript/layout/layout.d.ts +3 -0
  146. package/lib/typescript/layout/layout.d.ts.map +1 -0
  147. package/lib/typescript/layout/welcome.d.ts +6 -0
  148. package/lib/typescript/layout/welcome.d.ts.map +1 -0
  149. package/lib/typescript/layout/window.d.ts +5 -0
  150. package/lib/typescript/layout/window.d.ts.map +1 -0
  151. package/lib/typescript/utils/audioRecorder.d.ts +9 -0
  152. package/lib/typescript/utils/audioRecorder.d.ts.map +1 -0
  153. package/lib/typescript/utils/cloudinary.d.ts +17 -0
  154. package/lib/typescript/utils/cloudinary.d.ts.map +1 -0
  155. package/lib/typescript/utils/storage.d.ts +29 -0
  156. package/lib/typescript/utils/storage.d.ts.map +1 -0
  157. package/lib/typescript/utils/textToSpeech.d.ts +2 -0
  158. package/lib/typescript/utils/textToSpeech.d.ts.map +1 -0
  159. package/package.json +109 -0
  160. package/src/assets/chat-icon-mobile.svg +1 -0
  161. package/src/assets/heritage.png +0 -0
  162. package/src/assets/posiden.svg +51 -0
  163. package/src/components/LoadingTips.js +99 -0
  164. package/src/components/email.js +467 -0
  165. package/src/components/feedback.js +114 -0
  166. package/src/components/header.js +119 -0
  167. package/src/components/input.js +133 -0
  168. package/src/components/productCard.js +815 -0
  169. package/src/components/progressCircle.js +88 -0
  170. package/src/components/testing.js +60 -0
  171. package/src/components/voice.js +228 -0
  172. package/src/components/welcomeButton.js +161 -0
  173. package/src/components/welcomeInput.js +133 -0
  174. package/src/contexts/AppContext.js +678 -0
  175. package/src/hooks/Stream.js +655 -0
  176. package/src/hooks/useAsyncStorage.js +33 -0
  177. package/src/index.js +30 -0
  178. package/src/layout/disclaimer.js +231 -0
  179. package/src/layout/ex.js +252 -0
  180. package/src/layout/icon.js +105 -0
  181. package/src/layout/layout.js +160 -0
  182. package/src/layout/welcome.js +172 -0
  183. package/src/layout/window.js +476 -0
  184. package/src/utils/audioRecorder.js +445 -0
  185. package/src/utils/cloudinary.js +61 -0
  186. package/src/utils/storage.ts +89 -0
  187. package/src/utils/textToSpeech.js +49 -0
@@ -0,0 +1,467 @@
1
+ import React, { useState, useContext, useRef, useEffect } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ TouchableOpacity,
7
+ ActivityIndicator,
8
+ StyleSheet,
9
+ Platform,
10
+ findNodeHandle
11
+ } from "react-native";
12
+ import Ionicons from "react-native-vector-icons/Ionicons";
13
+ import { AppContext } from "../contexts/AppContext";
14
+ import { Header } from "./header";
15
+ import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
16
+
17
+ export const EmailForm = ({ panHandlers }) => {
18
+ const { data, BASE_URL, setShowModal, formatChatHistory, conversationStartTime, sessionId, theme } = useContext(AppContext);
19
+ const API_PREFIX = "https://";
20
+ const [subject, setSubject] = useState("");
21
+ const [message, setMessage] = useState("");
22
+ const [userEmail, setUserEmail] = useState(data?.user_email || ""); // Sender's email
23
+ const [branchEmail, setBranchEmail] = useState((data?.branch_email) || ""); // Recipient's email
24
+ const [isLoading, setIsLoading] = useState(false);
25
+ const [error, setError] = useState("");
26
+ const [success, setSuccess] = useState(false);
27
+ const [isComposing, setIsComposing] = useState(false);
28
+
29
+ // Refs for the scroll view and inputs
30
+ const scrollViewRef = useRef(null);
31
+ const subjectInputRef = useRef(null);
32
+ const messageInputRef = useRef(null);
33
+ const emailInputRef = useRef(null);
34
+
35
+ // Industry-standard email validation following RFC 5322 guidelines
36
+ const validateEmailFormat = (email) => {
37
+ // More comprehensive regex that handles most valid email formats
38
+ // This regex covers:
39
+ // - Alphanumeric characters, dots, hyphens, underscores, plus signs
40
+ // - Quoted strings in local part
41
+ // - IP addresses in domain part
42
+ // - International domain names
43
+ const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
44
+
45
+ // Length validation (RFC 5321 limits)
46
+ const MAX_EMAIL_LENGTH = 254;
47
+ const MAX_LOCAL_LENGTH = 64;
48
+ const MAX_DOMAIN_LENGTH = 253;
49
+
50
+ if (email.length > MAX_EMAIL_LENGTH) {
51
+ return false;
52
+ }
53
+
54
+ const [localPart, domainPart] = email.split('@');
55
+
56
+ if (!localPart || !domainPart) {
57
+ return false;
58
+ }
59
+
60
+ if (localPart.length > MAX_LOCAL_LENGTH || domainPart.length > MAX_DOMAIN_LENGTH) {
61
+ return false;
62
+ }
63
+
64
+ return emailRegex.test(email);
65
+ };
66
+
67
+ const validateEmailDomain = (email) => {
68
+ const domainPart = email.split('@')[1];
69
+ if (!domainPart) return false;
70
+
71
+ // Common domain validation checks
72
+ const commonIssues = [
73
+ // Missing or invalid TLD
74
+ /\.(com|org|net|edu|gov|mil|int|co|io|ly|me|tv|info|biz|name|mobi|travel|museum|aero|coop|pro|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)$/i,
75
+ ];
76
+
77
+ // Check for valid TLD
78
+ const hasValidTLD = commonIssues[0].test(domainPart);
79
+
80
+ // Check for common typos in popular domains
81
+ const commonDomainTypos = {
82
+ 'gmial.com': 'gmail.com',
83
+ 'gmail.co': 'gmail.com',
84
+ 'gmai.com': 'gmail.com',
85
+ 'yahooo.com': 'yahoo.com',
86
+ 'yaho.com': 'yahoo.com',
87
+ 'hotmial.com': 'hotmail.com',
88
+ 'hotmai.com': 'hotmail.com',
89
+ 'outlok.com': 'outlook.com',
90
+ 'outloo.com': 'outlook.com'
91
+ };
92
+
93
+ const suggestion = commonDomainTypos[domainPart.toLowerCase()];
94
+
95
+ return {
96
+ isValid: hasValidTLD && !suggestion,
97
+ suggestion: suggestion
98
+ };
99
+ };
100
+
101
+ const validateEmail = (email) => {
102
+ if (!email) {
103
+ return "Email address is required";
104
+ }
105
+
106
+ // Trim whitespace
107
+ email = email.trim();
108
+
109
+ // Basic format validation
110
+ if (!validateEmailFormat(email)) {
111
+ return "Please enter a valid email address";
112
+ }
113
+
114
+ // Domain validation with suggestions
115
+ const domainValidation = validateEmailDomain(email);
116
+ if (!domainValidation.isValid) {
117
+ if (domainValidation.suggestion) {
118
+ return `Did you mean ${email.split('@')[0]}@${domainValidation.suggestion}?`;
119
+ }
120
+ return "Please enter a valid email address";
121
+ }
122
+
123
+ // Additional checks for common formatting errors
124
+ const commonFormatIssues = [
125
+ { pattern: /\.\./, message: "Email address cannot contain consecutive dots" },
126
+ { pattern: /^\./, message: "Email address cannot start with a dot" },
127
+ { pattern: /\.$/, message: "Email address cannot end with a dot" },
128
+ { pattern: /@\./, message: "Invalid format after @ symbol" },
129
+ { pattern: /\.@/, message: "Invalid format before @ symbol" },
130
+ { pattern: /\s/, message: "Email address cannot contain spaces" },
131
+ { pattern: /@.*@/, message: "Email address can only contain one @ symbol" }
132
+ ];
133
+
134
+ for (const issue of commonFormatIssues) {
135
+ if (issue.pattern.test(email)) {
136
+ return issue.message;
137
+ }
138
+ }
139
+
140
+ return "";
141
+ };
142
+
143
+ const buildEmailRequestPayload = (chatHistory) => ({
144
+ chat_history: chatHistory,
145
+ conversation_start_time: conversationStartTime ?? null,
146
+ customer_name: data?.customer_name ?? null,
147
+ user_id: data?.user_id ?? null,
148
+ session_id: data?.session_id ?? data?.session ?? sessionId ?? null,
149
+ branch_email: branchEmail ?? "",
150
+ });
151
+
152
+ const handleComposeEmail = async () => {
153
+ setIsComposing(true);
154
+ setError("");
155
+
156
+ try {
157
+ const chatHistory = formatChatHistory().map(({ variant_type, ...rest }) => rest);
158
+
159
+ const payload = {
160
+ chat_history: chatHistory,
161
+ conversation_start_time: conversationStartTime || null,
162
+ customer_name: data?.customer_name || null,
163
+ user_id: data?.user_id || null,
164
+ session_id: data?.session_id || data?.session || sessionId || null,
165
+ user_email: data?.user_email || "",
166
+ branch_email: data?.branch_email || ""
167
+ };
168
+
169
+ console.log("Email composition request payload:", payload);
170
+
171
+ const response = await fetch(`${API_PREFIX}${BASE_URL}/compose-email`, {
172
+ method: 'POST',
173
+ headers: {
174
+ 'Content-Type': 'application/json',
175
+ },
176
+ body: JSON.stringify(payload),
177
+ });
178
+
179
+ if (!response.ok) {
180
+ throw new Error(`HTTP error! status: ${response.status}`);
181
+ }
182
+
183
+ const responseData = await response.json();
184
+
185
+ const textToType = responseData.message;
186
+ let currentText = "";
187
+ const typingSpeed = 10;
188
+
189
+ for (let i = 0; i < textToType.length; i++) {
190
+ await new Promise(resolve => setTimeout(resolve, typingSpeed));
191
+ currentText += textToType[i];
192
+ setMessage(currentText);
193
+ }
194
+
195
+ setSubject(responseData.subject || "");
196
+ setUserEmail(responseData.user_email || userEmail || "");
197
+ // Don't overwrite branch email if response doesn't have it
198
+ if (responseData.branch_email) {
199
+ setBranchEmail(responseData.branch_email);
200
+ }
201
+
202
+ } catch (error) {
203
+ setError(
204
+ error.message ||
205
+ "Failed to compose email"
206
+ );
207
+ } finally {
208
+ setIsComposing(false);
209
+ }
210
+ };
211
+
212
+ const handleSubmit = async () => {
213
+ if (!userEmail || !subject || !message) {
214
+ setError("Please fill in all fields");
215
+ return;
216
+ }
217
+
218
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(userEmail)) {
219
+ setError("Please enter a valid email address");
220
+ return;
221
+ }
222
+
223
+ setIsLoading(true);
224
+
225
+ try {
226
+ const chatHistory = formatChatHistory().map(({ variant_type, ...rest }) => rest);
227
+ console.log("Sending email with chat history:", chatHistory);
228
+
229
+ const response = await fetch(`https://${BASE_URL}/send-email`, {
230
+ method: 'POST',
231
+ headers: {
232
+ 'Content-Type': 'application/json',
233
+ },
234
+ body: JSON.stringify({
235
+ user_email: userEmail,
236
+ subject,
237
+ message,
238
+ chat_history: chatHistory,
239
+ conversation_start_time: conversationStartTime,
240
+ customer_name: data?.customer_name,
241
+ branch_email: branchEmail || data?.branch_email || ""
242
+ }),
243
+ });
244
+
245
+ if (!response.ok) {
246
+ throw new Error(`HTTP error! status: ${response.status}`);
247
+ }
248
+
249
+ setSuccess(true);
250
+ setTimeout(() => {
251
+ setSuccess(false);
252
+ setShowModal("ChatWindow");
253
+ }, 2000);
254
+ } catch (error) {
255
+ setError(
256
+ error.message ||
257
+ "Failed to send email",
258
+ );
259
+ } finally {
260
+ setIsLoading(false);
261
+ }
262
+ };
263
+
264
+ return (
265
+ <View style={styles.container}>
266
+ <Header />
267
+ <KeyboardAwareScrollView
268
+ ref={scrollViewRef}
269
+ style={styles.content}
270
+ contentContainerStyle={{ flexGrow: 1, paddingBottom: 50 }}
271
+ keyboardShouldPersistTaps="handled"
272
+ showsVerticalScrollIndicator={true}
273
+ enableOnAndroid={true}
274
+ extraScrollHeight={40}
275
+ keyboardDismissMode="interactive"
276
+ bounces={false}
277
+ >
278
+ <View style={styles.topBar}>
279
+ <TouchableOpacity onPress={() => setShowModal("ChatWindow")} style={styles.backButton}>
280
+ <Ionicons name="arrow-back" size={20} color="#000" />
281
+ </TouchableOpacity>
282
+ <Text style={styles.topBarTitle}>Contact Us</Text>
283
+ </View>
284
+
285
+ <Text style={styles.infoText}>
286
+ Press "Draft My Email" to generate an email based on your chat conversation. You can review and modify before sending.
287
+ </Text>
288
+
289
+ <TouchableOpacity style={[styles.composeButton, { backgroundColor: theme.primaryColor }]} onPress={handleComposeEmail} disabled={isComposing}>
290
+ {isComposing ? (
291
+ <ActivityIndicator size="small" color="#FFFFFF" />
292
+ ) : (
293
+ <Text style={styles.buttonText}>Draft My Email</Text>
294
+ )}
295
+ </TouchableOpacity>
296
+
297
+ <TextInput
298
+ style={styles.input}
299
+ value={branchEmail}
300
+ editable={false}
301
+ placeholder="Branch Email"
302
+ placeholderTextColor="#999"
303
+ />
304
+ <TextInput
305
+ ref={emailInputRef}
306
+ style={styles.input}
307
+ value={userEmail}
308
+ onChangeText={(text) => {
309
+ setUserEmail(text);
310
+ const emailValidationError = validateEmail(text);
311
+ if (emailValidationError) {
312
+ setError(emailValidationError);
313
+ setSuccess(false);
314
+ } else if (error === "Please enter a valid email address" || error === "Email address is required") {
315
+ setError("");
316
+ }
317
+ }}
318
+ placeholder="Your Email"
319
+ placeholderTextColor="#999"
320
+ onFocus={() =>
321
+ scrollViewRef.current?.scrollToFocusedInput(
322
+ findNodeHandle(emailInputRef.current)
323
+ )
324
+ }
325
+ keyboardType="email-address"
326
+ autoCapitalize="none"
327
+ />
328
+ <TextInput
329
+ ref={subjectInputRef}
330
+ style={styles.input}
331
+ value={subject}
332
+ onChangeText={(text) => {
333
+ setSubject(text);
334
+ if (error && !validateEmail(userEmail) && !message) {
335
+ setError("");
336
+ } else if (error === "Please fill in all fields" && text && message) {
337
+ setError("");
338
+ }
339
+ }}
340
+ placeholder="Subject"
341
+ placeholderTextColor="#999"
342
+ onFocus={() =>
343
+ scrollViewRef.current?.scrollToFocusedInput(
344
+ findNodeHandle(subjectInputRef.current)
345
+ )
346
+ }
347
+ />
348
+ <TextInput
349
+ ref={messageInputRef}
350
+ style={styles.textArea}
351
+ value={message}
352
+ onChangeText={(text) => {
353
+ setMessage(text);
354
+ if (error && !validateEmail(userEmail) && !subject) {
355
+ setError("");
356
+ } else if (error === "Please fill in all fields" && text && subject) {
357
+ setError("");
358
+ }
359
+ }}
360
+ placeholder="Message"
361
+ placeholderTextColor="#999"
362
+ multiline
363
+ onFocus={() =>
364
+ scrollViewRef.current?.scrollToFocusedInput(
365
+ findNodeHandle(messageInputRef.current)
366
+ )
367
+ }
368
+ />
369
+
370
+ {error ? <Text style={styles.errorText}>{error}</Text> : null}
371
+ {success ? <Text style={styles.successText}>Email sent successfully!</Text> : null}
372
+
373
+ <TouchableOpacity style={[styles.sendButton, { backgroundColor: theme.primaryColor }]} onPress={handleSubmit} disabled={isLoading}>
374
+ {isLoading ? <ActivityIndicator size="small" color="#FFFFFF" /> : <Text style={styles.buttonText}>Send Email</Text>}
375
+ </TouchableOpacity>
376
+ </KeyboardAwareScrollView>
377
+ </View>
378
+ );
379
+ };
380
+
381
+ const styles = StyleSheet.create({
382
+ container: {
383
+ flex: 1,
384
+ },
385
+ content: {
386
+ padding: 20,
387
+ paddingTop: 5,
388
+ flex: 1,
389
+ },
390
+ topBar: {
391
+ flexDirection: "row",
392
+ alignItems: "center",
393
+ paddingVertical: 10,
394
+ marginTop: 10,
395
+ },
396
+ backButton: {
397
+ padding: 8,
398
+ borderRadius: 20,
399
+ backgroundColor: "#FFF",
400
+ },
401
+ topBarTitle: {
402
+ fontSize: 18,
403
+ fontWeight: "500",
404
+ marginLeft: 12,
405
+ color: "#000",
406
+ },
407
+ infoText: {
408
+ textAlign: "left",
409
+ color: "#666",
410
+ fontSize: 15,
411
+ lineHeight: 22,
412
+ marginBottom: 20,
413
+ },
414
+ input: {
415
+ backgroundColor: "white",
416
+ borderRadius: 8,
417
+ padding: 12,
418
+ fontSize: 16,
419
+ borderWidth: 1,
420
+ borderColor: "#e0e0e0",
421
+ marginBottom: 10,
422
+ color: "#000",
423
+ },
424
+ textArea: {
425
+ backgroundColor: "white",
426
+ borderRadius: 8,
427
+ padding: 12,
428
+ fontSize: 16,
429
+ borderWidth: 1,
430
+ borderColor: "#e0e0e0",
431
+ marginBottom: 10,
432
+ height: 180,
433
+ textAlignVertical: "top",
434
+ color: "#000",
435
+ },
436
+ composeButton: {
437
+ borderRadius: 5,
438
+ padding: 12,
439
+ alignItems: "center",
440
+ marginBottom: 20,
441
+ },
442
+ sendButton: {
443
+ borderRadius: 5,
444
+ padding: 12,
445
+ alignItems: "center",
446
+ marginTop: 20,
447
+ },
448
+ buttonText: {
449
+ color: "#FFF",
450
+ fontSize: 16,
451
+ fontWeight: "500",
452
+ },
453
+ errorText: {
454
+ color: "red",
455
+ fontSize: 14,
456
+ textAlign: "center",
457
+ marginTop: 10,
458
+ },
459
+ successText: {
460
+ color: "green",
461
+ fontSize: 14,
462
+ textAlign: "center",
463
+ marginTop: 10,
464
+ },
465
+ });
466
+
467
+ export default EmailForm;
@@ -0,0 +1,114 @@
1
+ import React, { useContext } from "react";
2
+ import { View, Text, TouchableOpacity, TextInput, StyleSheet } from "react-native";
3
+ import Ionicons from "react-native-vector-icons/Ionicons";
4
+ import { AppContext } from "../contexts/AppContext";
5
+ import Feather from "react-native-vector-icons/Feather";
6
+
7
+ export const Feedback = ({ message, messageId }) => {
8
+ const { handleFeedback, feedback, handleWrittenFeedback, feedbackOpen, setFeedbackOpen, writeFeedback,
9
+ setWriteFeedback, switchFeedbackOpen } = useContext(AppContext);
10
+
11
+ return (
12
+ <View style={styles.container}>
13
+
14
+ {/* Feedback Icons */}
15
+ <View style={styles.iconContainer}>
16
+ {/* Thumbs Up Button */}
17
+ <TouchableOpacity onPress={() => handleFeedback(1, messageId)}>
18
+ <Feather
19
+ name="thumbs-up"
20
+ size={18}
21
+ color={feedback[messageId] === 1 ? "#367CB6" : "#808080"}
22
+ style={styles.icon}
23
+ />
24
+ </TouchableOpacity>
25
+
26
+ {/* Thumbs Down Button */}
27
+ <TouchableOpacity onPress={() => handleFeedback(-1, messageId)}>
28
+ <Feather
29
+ name="thumbs-down"
30
+ size={18}
31
+ color={feedback[messageId] === -1 ? "#367CB6" : "#808080"}
32
+ style={styles.icon}
33
+ />
34
+ </TouchableOpacity>
35
+ </View>
36
+
37
+ {/* Feedback Input */}
38
+ {feedbackOpen[messageId] == true && (
39
+ <View style={styles.feedbackSection}>
40
+ <TextInput
41
+ multiline
42
+ numberOfLines={3}
43
+ placeholder="Enter your feedback..."
44
+ style={styles.textInput}
45
+ value={writeFeedback}
46
+ onChangeText={setWriteFeedback}
47
+ />
48
+
49
+ {/* Buttons */}
50
+ <View style={styles.buttonContainer}>
51
+ <TouchableOpacity
52
+ style={styles.button}
53
+ onPress={() => switchFeedbackOpen(false, messageId, true)}
54
+ >
55
+ <Text style={styles.buttonText}>Close</Text>
56
+ </TouchableOpacity>
57
+
58
+ <TouchableOpacity
59
+ style={styles.button}
60
+ onPress={() => handleWrittenFeedback(messageId)}
61
+ >
62
+ <Text style={styles.buttonText}>Send</Text>
63
+ </TouchableOpacity>
64
+ </View>
65
+ </View>
66
+ )}
67
+ </View>
68
+ );
69
+ };
70
+
71
+ const styles = StyleSheet.create({
72
+ container: {
73
+ marginTop: 0,
74
+ marginBottom: 5
75
+ },
76
+ iconContainer: {
77
+ flexDirection: "row",
78
+ alignItems: "center",
79
+ justifyContent: 'flex-end'
80
+ },
81
+ icon: {
82
+ marginRight: 10,
83
+ },
84
+ feedbackSection: {
85
+ marginTop: 10,
86
+ },
87
+ textInput: {
88
+ width: "100%",
89
+ borderWidth: 1,
90
+ borderColor: "#808080",
91
+ borderRadius: 5,
92
+ padding: 10,
93
+ fontSize: 14,
94
+ color: "#161616",
95
+ backgroundColor: "#fff",
96
+ },
97
+ buttonContainer: {
98
+ flexDirection: "row",
99
+ justifyContent: "flex-end",
100
+ marginTop: 5,
101
+ },
102
+ button: {
103
+ backgroundColor: "#367CB6",
104
+ paddingVertical: 8,
105
+ paddingHorizontal: 15,
106
+ borderRadius: 5,
107
+ marginLeft: 10,
108
+ },
109
+ buttonText: {
110
+ color: "white",
111
+ fontSize: 14,
112
+ textTransform: "none",
113
+ },
114
+ });
@@ -0,0 +1,119 @@
1
+ import React, { useContext } from 'react';
2
+ import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native';
3
+ import { AppContext } from '../contexts/AppContext';
4
+ import Ionicons from 'react-native-vector-icons/Ionicons';
5
+ import CloudinaryImage from '../utils/cloudinary';
6
+ // import { PoseidonLogo } from './PoseidonLogo';
7
+
8
+ export const Header = () => {
9
+ const { showModal, setShowModal, setMessages, defaultMessage, handleClearState, uiConfig, theme, brandLogo } = useContext(AppContext);
10
+
11
+ const handleClick = () => {
12
+ if ((uiConfig.showIcon ?? true) !== true) {
13
+ setShowModal("Off");
14
+ } else {
15
+ setShowModal("Icon");
16
+ }
17
+
18
+ if (typeof uiConfig.setToggleChat === 'function') {
19
+ uiConfig.setToggleChat(false);
20
+ }
21
+ };
22
+
23
+ return (
24
+ <View style={[styles.header, { backgroundColor: theme.primaryColor }]}>
25
+ {/* Logo on the left */}
26
+ <View style={styles.section}>
27
+ {/* <PoseidonLogo width={150} height={35} color="white" /> */}
28
+ {/* <Image source={require('../assets/heritage.png')} style={[styles.logo, { tintColor: "white" }]} /> */}
29
+ {/* <SvgCssUri
30
+ uri="https://media.heritageplus.com/image/upload/v1743632330/MobileApp/posiden.svg"
31
+ width={150}
32
+ height={35}
33
+ /> */}
34
+ <CloudinaryImage cldImg={brandLogo} imageStyle={{ width: 150, height: 35 }} />
35
+ </View>
36
+
37
+ {/* Title in the center */}
38
+ {/* <View style={[styles.section, styles.titleContainer]}>
39
+ <View style={styles.titleWrapper}>
40
+ <Text style={styles.title}>
41
+ Poseidon
42
+ </Text>
43
+ <Text style={styles.betaText}><Ionicons name="sparkles-outline" size={10} color="#007AFF" />beta</Text>
44
+ </View>
45
+ </View> */}
46
+
47
+ {/* Close button on the right */}
48
+ <View style={styles.iconsSection}>
49
+ {showModal !== "Form" && showModal !== "Email" &&
50
+ <>
51
+ <TouchableOpacity onPress={() => setShowModal("Email")}>
52
+ <Ionicons name="mail" size={24} color={theme.textColorSecondary} />
53
+ </TouchableOpacity>
54
+ <TouchableOpacity onPress={() => handleClearState()}>
55
+ <Ionicons name="trash" size={22} color={theme.textColorSecondary} />
56
+ </TouchableOpacity>
57
+ </>
58
+ }
59
+ <TouchableOpacity onPress={() => handleClick()}>
60
+ <Ionicons name="close" size={26} color={theme.textColorSecondary} />
61
+ </TouchableOpacity>
62
+ </View>
63
+ </View>
64
+ );
65
+ };
66
+
67
+ const styles = StyleSheet.create({
68
+ header: {
69
+ flexDirection: 'row',
70
+ alignItems: 'center',
71
+ backgroundColor: '#437D3D',
72
+ padding: 16,
73
+ paddingTop: 60,
74
+ borderBottomWidth: 1,
75
+ borderBottomColor: '#ddd',
76
+ justifyContent: 'space-between'
77
+ },
78
+ section: {
79
+ flex: 1,
80
+ alignItems: 'flex-start'
81
+ },
82
+ iconsSection: {
83
+ flex: 1,
84
+ display: 'flex',
85
+ flexDirection: 'row',
86
+ alignItems: 'center',
87
+ justifyContent: 'flex-end',
88
+ marginRight: 10,
89
+ gap: 8,
90
+ },
91
+ logo: {
92
+ width: 110,
93
+ height: 40,
94
+ marginLeft: 10,
95
+ resizeMode: 'contain'
96
+ },
97
+ titleContainer: {
98
+ flex: 2, // Slightly larger space for the title
99
+ marginTop: 13,
100
+ },
101
+ titleWrapper: {
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ },
105
+ title: {
106
+ fontSize: 22,
107
+ fontWeight: 300,
108
+ color: '#404040',
109
+ textAlign: 'center',
110
+ flexDirection: 'row',
111
+ },
112
+ betaText: {
113
+ fontSize: 12,
114
+ color: '#404040',
115
+ position: 'absolute',
116
+ top: -10,
117
+ right: -35,
118
+ },
119
+ });