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.
- package/LICENSE +20 -0
- package/README.md +194 -0
- package/lib/commonjs/assets/chat-icon-mobile.svg +1 -0
- package/lib/commonjs/assets/heritage.png +0 -0
- package/lib/commonjs/assets/posiden.svg +51 -0
- package/lib/commonjs/components/LoadingTips.js +104 -0
- package/lib/commonjs/components/LoadingTips.js.map +1 -0
- package/lib/commonjs/components/email.js +461 -0
- package/lib/commonjs/components/email.js.map +1 -0
- package/lib/commonjs/components/feedback.js +114 -0
- package/lib/commonjs/components/feedback.js.map +1 -0
- package/lib/commonjs/components/header.js +126 -0
- package/lib/commonjs/components/header.js.map +1 -0
- package/lib/commonjs/components/input.js +144 -0
- package/lib/commonjs/components/input.js.map +1 -0
- package/lib/commonjs/components/productCard.js +688 -0
- package/lib/commonjs/components/productCard.js.map +1 -0
- package/lib/commonjs/components/progressCircle.js +99 -0
- package/lib/commonjs/components/progressCircle.js.map +1 -0
- package/lib/commonjs/components/testing.js +74 -0
- package/lib/commonjs/components/testing.js.map +1 -0
- package/lib/commonjs/components/voice.js +184 -0
- package/lib/commonjs/components/voice.js.map +1 -0
- package/lib/commonjs/components/welcomeButton.js +149 -0
- package/lib/commonjs/components/welcomeButton.js.map +1 -0
- package/lib/commonjs/components/welcomeInput.js +137 -0
- package/lib/commonjs/components/welcomeInput.js.map +1 -0
- package/lib/commonjs/contexts/AppContext.js +552 -0
- package/lib/commonjs/contexts/AppContext.js.map +1 -0
- package/lib/commonjs/hooks/Stream.js +599 -0
- package/lib/commonjs/hooks/Stream.js.map +1 -0
- package/lib/commonjs/hooks/useAsyncStorage.js +36 -0
- package/lib/commonjs/hooks/useAsyncStorage.js.map +1 -0
- package/lib/commonjs/index.js +44 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/layout/disclaimer.js +208 -0
- package/lib/commonjs/layout/disclaimer.js.map +1 -0
- package/lib/commonjs/layout/ex.js +254 -0
- package/lib/commonjs/layout/ex.js.map +1 -0
- package/lib/commonjs/layout/icon.js +118 -0
- package/lib/commonjs/layout/icon.js.map +1 -0
- package/lib/commonjs/layout/layout.js +168 -0
- package/lib/commonjs/layout/layout.js.map +1 -0
- package/lib/commonjs/layout/welcome.js +160 -0
- package/lib/commonjs/layout/welcome.js.map +1 -0
- package/lib/commonjs/layout/window.js +396 -0
- package/lib/commonjs/layout/window.js.map +1 -0
- package/lib/commonjs/utils/audioRecorder.js +412 -0
- package/lib/commonjs/utils/audioRecorder.js.map +1 -0
- package/lib/commonjs/utils/cloudinary.js +69 -0
- package/lib/commonjs/utils/cloudinary.js.map +1 -0
- package/lib/commonjs/utils/storage.js +76 -0
- package/lib/commonjs/utils/storage.js.map +1 -0
- package/lib/commonjs/utils/textToSpeech.js +53 -0
- package/lib/commonjs/utils/textToSpeech.js.map +1 -0
- package/lib/module/assets/chat-icon-mobile.svg +1 -0
- package/lib/module/assets/heritage.png +0 -0
- package/lib/module/assets/posiden.svg +51 -0
- package/lib/module/components/LoadingTips.js +95 -0
- package/lib/module/components/LoadingTips.js.map +1 -0
- package/lib/module/components/email.js +452 -0
- package/lib/module/components/email.js.map +1 -0
- package/lib/module/components/feedback.js +105 -0
- package/lib/module/components/feedback.js.map +1 -0
- package/lib/module/components/header.js +117 -0
- package/lib/module/components/header.js.map +1 -0
- package/lib/module/components/input.js +135 -0
- package/lib/module/components/input.js.map +1 -0
- package/lib/module/components/productCard.js +679 -0
- package/lib/module/components/productCard.js.map +1 -0
- package/lib/module/components/progressCircle.js +91 -0
- package/lib/module/components/progressCircle.js.map +1 -0
- package/lib/module/components/testing.js +66 -0
- package/lib/module/components/testing.js.map +1 -0
- package/lib/module/components/voice.js +175 -0
- package/lib/module/components/voice.js.map +1 -0
- package/lib/module/components/welcomeButton.js +140 -0
- package/lib/module/components/welcomeButton.js.map +1 -0
- package/lib/module/components/welcomeInput.js +128 -0
- package/lib/module/components/welcomeInput.js.map +1 -0
- package/lib/module/contexts/AppContext.js +542 -0
- package/lib/module/contexts/AppContext.js.map +1 -0
- package/lib/module/hooks/Stream.js +592 -0
- package/lib/module/hooks/Stream.js.map +1 -0
- package/lib/module/hooks/useAsyncStorage.js +29 -0
- package/lib/module/hooks/useAsyncStorage.js.map +1 -0
- package/lib/module/index.js +36 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/layout/disclaimer.js +199 -0
- package/lib/module/layout/disclaimer.js.map +1 -0
- package/lib/module/layout/ex.js +253 -0
- package/lib/module/layout/ex.js.map +1 -0
- package/lib/module/layout/icon.js +108 -0
- package/lib/module/layout/icon.js.map +1 -0
- package/lib/module/layout/layout.js +160 -0
- package/lib/module/layout/layout.js.map +1 -0
- package/lib/module/layout/welcome.js +150 -0
- package/lib/module/layout/welcome.js.map +1 -0
- package/lib/module/layout/window.js +387 -0
- package/lib/module/layout/window.js.map +1 -0
- package/lib/module/utils/audioRecorder.js +398 -0
- package/lib/module/utils/audioRecorder.js.map +1 -0
- package/lib/module/utils/cloudinary.js +61 -0
- package/lib/module/utils/cloudinary.js.map +1 -0
- package/lib/module/utils/storage.js +67 -0
- package/lib/module/utils/storage.js.map +1 -0
- package/lib/module/utils/textToSpeech.js +43 -0
- package/lib/module/utils/textToSpeech.js.map +1 -0
- package/lib/typescript/components/LoadingTips.d.ts +3 -0
- package/lib/typescript/components/LoadingTips.d.ts.map +1 -0
- package/lib/typescript/components/email.d.ts +6 -0
- package/lib/typescript/components/email.d.ts.map +1 -0
- package/lib/typescript/components/feedback.d.ts +6 -0
- package/lib/typescript/components/feedback.d.ts.map +1 -0
- package/lib/typescript/components/header.d.ts +3 -0
- package/lib/typescript/components/header.d.ts.map +1 -0
- package/lib/typescript/components/input.d.ts +6 -0
- package/lib/typescript/components/input.d.ts.map +1 -0
- package/lib/typescript/components/productCard.d.ts +7 -0
- package/lib/typescript/components/productCard.d.ts.map +1 -0
- package/lib/typescript/components/progressCircle.d.ts +3 -0
- package/lib/typescript/components/progressCircle.d.ts.map +1 -0
- package/lib/typescript/components/testing.d.ts +6 -0
- package/lib/typescript/components/testing.d.ts.map +1 -0
- package/lib/typescript/components/voice.d.ts +5 -0
- package/lib/typescript/components/voice.d.ts.map +1 -0
- package/lib/typescript/components/welcomeButton.d.ts +4 -0
- package/lib/typescript/components/welcomeButton.d.ts.map +1 -0
- package/lib/typescript/components/welcomeInput.d.ts +6 -0
- package/lib/typescript/components/welcomeInput.d.ts.map +1 -0
- package/lib/typescript/contexts/AppContext.d.ts +10 -0
- package/lib/typescript/contexts/AppContext.d.ts.map +1 -0
- package/lib/typescript/hooks/Stream.d.ts +2 -0
- package/lib/typescript/hooks/Stream.d.ts.map +1 -0
- package/lib/typescript/hooks/useAsyncStorage.d.ts +2 -0
- package/lib/typescript/hooks/useAsyncStorage.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/layout/disclaimer.d.ts +5 -0
- package/lib/typescript/layout/disclaimer.d.ts.map +1 -0
- package/lib/typescript/layout/ex.d.ts +1 -0
- package/lib/typescript/layout/ex.d.ts.map +1 -0
- package/lib/typescript/layout/icon.d.ts +3 -0
- package/lib/typescript/layout/icon.d.ts.map +1 -0
- package/lib/typescript/layout/layout.d.ts +3 -0
- package/lib/typescript/layout/layout.d.ts.map +1 -0
- package/lib/typescript/layout/welcome.d.ts +6 -0
- package/lib/typescript/layout/welcome.d.ts.map +1 -0
- package/lib/typescript/layout/window.d.ts +5 -0
- package/lib/typescript/layout/window.d.ts.map +1 -0
- package/lib/typescript/utils/audioRecorder.d.ts +9 -0
- package/lib/typescript/utils/audioRecorder.d.ts.map +1 -0
- package/lib/typescript/utils/cloudinary.d.ts +17 -0
- package/lib/typescript/utils/cloudinary.d.ts.map +1 -0
- package/lib/typescript/utils/storage.d.ts +29 -0
- package/lib/typescript/utils/storage.d.ts.map +1 -0
- package/lib/typescript/utils/textToSpeech.d.ts +2 -0
- package/lib/typescript/utils/textToSpeech.d.ts.map +1 -0
- package/package.json +109 -0
- package/src/assets/chat-icon-mobile.svg +1 -0
- package/src/assets/heritage.png +0 -0
- package/src/assets/posiden.svg +51 -0
- package/src/components/LoadingTips.js +99 -0
- package/src/components/email.js +467 -0
- package/src/components/feedback.js +114 -0
- package/src/components/header.js +119 -0
- package/src/components/input.js +133 -0
- package/src/components/productCard.js +815 -0
- package/src/components/progressCircle.js +88 -0
- package/src/components/testing.js +60 -0
- package/src/components/voice.js +228 -0
- package/src/components/welcomeButton.js +161 -0
- package/src/components/welcomeInput.js +133 -0
- package/src/contexts/AppContext.js +678 -0
- package/src/hooks/Stream.js +655 -0
- package/src/hooks/useAsyncStorage.js +33 -0
- package/src/index.js +30 -0
- package/src/layout/disclaimer.js +231 -0
- package/src/layout/ex.js +252 -0
- package/src/layout/icon.js +105 -0
- package/src/layout/layout.js +160 -0
- package/src/layout/welcome.js +172 -0
- package/src/layout/window.js +476 -0
- package/src/utils/audioRecorder.js +445 -0
- package/src/utils/cloudinary.js +61 -0
- package/src/utils/storage.ts +89 -0
- 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
|
+
});
|