react-native-srschat 0.1.68 → 0.1.70

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 (62) hide show
  1. package/README.md +1 -0
  2. package/lib/commonjs/components/email.js +57 -53
  3. package/lib/commonjs/components/email.js.map +1 -1
  4. package/lib/commonjs/components/header.js +10 -6
  5. package/lib/commonjs/components/header.js.map +1 -1
  6. package/lib/commonjs/components/productCard.js +46 -3
  7. package/lib/commonjs/components/productCard.js.map +1 -1
  8. package/lib/commonjs/components/welcomeButton.js +23 -4
  9. package/lib/commonjs/components/welcomeButton.js.map +1 -1
  10. package/lib/commonjs/contexts/AppContext.js +37 -11
  11. package/lib/commonjs/contexts/AppContext.js.map +1 -1
  12. package/lib/commonjs/layout/disclaimer.js +13 -8
  13. package/lib/commonjs/layout/disclaimer.js.map +1 -1
  14. package/lib/commonjs/layout/welcome.js +13 -5
  15. package/lib/commonjs/layout/welcome.js.map +1 -1
  16. package/lib/commonjs/layout/window.js +11 -4
  17. package/lib/commonjs/layout/window.js.map +1 -1
  18. package/lib/commonjs/utils/audioRecorder.js +11 -5
  19. package/lib/commonjs/utils/audioRecorder.js.map +1 -1
  20. package/lib/commonjs/utils/cloudinary.js +12 -4
  21. package/lib/commonjs/utils/cloudinary.js.map +1 -1
  22. package/lib/module/components/email.js +57 -53
  23. package/lib/module/components/email.js.map +1 -1
  24. package/lib/module/components/header.js +10 -6
  25. package/lib/module/components/header.js.map +1 -1
  26. package/lib/module/components/productCard.js +46 -3
  27. package/lib/module/components/productCard.js.map +1 -1
  28. package/lib/module/components/welcomeButton.js +23 -4
  29. package/lib/module/components/welcomeButton.js.map +1 -1
  30. package/lib/module/contexts/AppContext.js +37 -11
  31. package/lib/module/contexts/AppContext.js.map +1 -1
  32. package/lib/module/layout/disclaimer.js +13 -8
  33. package/lib/module/layout/disclaimer.js.map +1 -1
  34. package/lib/module/layout/welcome.js +13 -5
  35. package/lib/module/layout/welcome.js.map +1 -1
  36. package/lib/module/layout/window.js +11 -4
  37. package/lib/module/layout/window.js.map +1 -1
  38. package/lib/module/utils/audioRecorder.js +11 -5
  39. package/lib/module/utils/audioRecorder.js.map +1 -1
  40. package/lib/module/utils/cloudinary.js +10 -3
  41. package/lib/module/utils/cloudinary.js.map +1 -1
  42. package/lib/typescript/components/email.d.ts.map +1 -1
  43. package/lib/typescript/components/header.d.ts.map +1 -1
  44. package/lib/typescript/components/productCard.d.ts +1 -1
  45. package/lib/typescript/components/productCard.d.ts.map +1 -1
  46. package/lib/typescript/components/welcomeButton.d.ts.map +1 -1
  47. package/lib/typescript/contexts/AppContext.d.ts.map +1 -1
  48. package/lib/typescript/layout/welcome.d.ts.map +1 -1
  49. package/lib/typescript/layout/window.d.ts.map +1 -1
  50. package/lib/typescript/utils/audioRecorder.d.ts.map +1 -1
  51. package/lib/typescript/utils/cloudinary.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/components/email.js +61 -54
  54. package/src/components/header.js +6 -9
  55. package/src/components/productCard.js +45 -3
  56. package/src/components/welcomeButton.js +14 -4
  57. package/src/contexts/AppContext.js +42 -13
  58. package/src/layout/disclaimer.js +6 -8
  59. package/src/layout/welcome.js +5 -8
  60. package/src/layout/window.js +8 -5
  61. package/src/utils/audioRecorder.js +18 -13
  62. package/src/utils/cloudinary.js +6 -3
@@ -1,17 +1,21 @@
1
- import React from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import { Cloudinary } from '@cloudinary/url-gen';
3
3
  import { Platform, View } from 'react-native';
4
4
  import { AdvancedImage } from 'cloudinary-react-native';
5
5
  import { scale } from '@cloudinary/url-gen/actions/resize';
6
+ import { AppContext } from '../contexts/AppContext';
6
7
  const CloudinaryImage = ({
7
8
  cldImg,
8
9
  imageStyle,
9
10
  accessibilityLabel,
10
11
  testID
11
12
  }) => {
13
+ const {
14
+ brandCloudName
15
+ } = useContext(AppContext);
12
16
  const cld = new Cloudinary({
13
17
  cloud: {
14
- cloudName: 'mktg'
18
+ cloudName: brandCloudName || 'mktg'
15
19
  }
16
20
  });
17
21
  const myImage = Platform.OS === 'ios' ? cld.image(`${cldImg}`) : cld.image(`${cldImg}`).format('png');
@@ -32,9 +36,12 @@ export const CloudinaryBannerImage = ({
32
36
  width = 345,
33
37
  height = 100
34
38
  }) => {
39
+ const {
40
+ brandCloudName
41
+ } = useContext(AppContext);
35
42
  const cld = new Cloudinary({
36
43
  cloud: {
37
- cloudName: 'mktg'
44
+ cloudName: brandCloudName || 'mktg'
38
45
  }
39
46
  });
40
47
  const myImage = cld.image(`${cldImg}`);
@@ -1 +1 @@
1
- {"version":3,"names":["React","Cloudinary","Platform","View","AdvancedImage","scale","CloudinaryImage","cldImg","imageStyle","accessibilityLabel","testID","cld","cloud","cloudName","myImage","OS","image","format","createElement","style","resizeMode","CloudinaryBannerImage","width","height","resize","parseInt"],"sourceRoot":"../../../src","sources":["utils/cloudinary.js"],"mappings":"AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,UAAU,QAAQ,qBAAqB;AAChD,SAASC,QAAQ,EAAEC,IAAI,QAAQ,cAAc;AAC7C,SAASC,aAAa,QAAQ,yBAAyB;AACvD,SAASC,KAAK,QAAQ,oCAAoC;AAE1D,MAAMC,eAAe,GAAGA,CAAC;EAAEC,MAAM;EAAEC,UAAU;EAAEC,kBAAkB;EAAEC;AAAO,CAAC,KAAK;EAC9E,MAAMC,GAAG,GAAG,IAAIV,UAAU,CAAC;IACzBW,KAAK,EAAE;MACLC,SAAS,EAAE;IACb;EACF,CAAC,CAAC;EAEF,MAAMC,OAAO,GAAGZ,QAAQ,CAACa,EAAE,KAAK,KAAK,GAAGJ,GAAG,CAACK,KAAK,CAAC,GAAGT,MAAM,EAAE,CAAC,GAAGI,GAAG,CAACK,KAAK,CAAC,GAAGT,MAAM,EAAE,CAAC,CAACU,MAAM,CAAC,KAAK,CAAC;EACrG,oBACEjB,KAAA,CAAAkB,aAAA,CAACf,IAAI,qBACHH,KAAA,CAAAkB,aAAA,CAACd,aAAa;IACZG,MAAM,EAAEO,OAAQ;IAChBL,kBAAkB,EAAEA,kBAAmB;IACvCC,MAAM,EAAEA,MAAO;IACfS,KAAK,EAAE,CAAC;MAAEC,UAAU,EAAE;IAAU,CAAC,EAAEZ,UAAU;EAAE,CAChD,CACG,CAAC;AAEX,CAAC;AAED,OAAO,MAAMa,qBAAqB,GAAGA,CAAC;EACpCd,MAAM;EACNC,UAAU;EACVC,kBAAkB;EAClBC,MAAM;EACNY,KAAK,GAAG,GAAG;EACXC,MAAM,GAAG;AACX,CAAC,KAAK;EACJ,MAAMZ,GAAG,GAAG,IAAIV,UAAU,CAAC;IACzBW,KAAK,EAAE;MACLC,SAAS,EAAE;IACb;EACF,CAAC,CAAC;EACF,MAAMC,OAAO,GAAGH,GAAG,CAACK,KAAK,CAAC,GAAGT,MAAM,EAAE,CAAC;EACtCO,OAAO,CAACU,MAAM,CACZnB,KAAK,CAAC,CAAC,CACJiB,KAAK,CAACG,QAAQ,CAACH,KAAK,GAAG,CAAC,CAAC,CAAC,CAC1BC,MAAM,CAACE,QAAQ,CAACF,MAAM,GAAG,CAAC,CAAC,CAChC,CAAC;EACD,oBACEvB,KAAA,CAAAkB,aAAA,CAACf,IAAI,qBACHH,KAAA,CAAAkB,aAAA,CAACd,aAAa;IACZG,MAAM,EAAEO,OAAQ;IAChBL,kBAAkB,EAAEA,kBAAmB;IACvCC,MAAM,EAAEA,MAAO;IACfS,KAAK,EAAE,CAAC;MAAEC,UAAU,EAAE,SAAS;MAAEE,KAAK,EAAEA,KAAK;MAAEC,MAAM,EAAEA;IAAO,CAAC,EAAEf,UAAU;EAAE,CAC9E,CACG,CAAC;AAEX,CAAC;AAED,eAAeF,eAAe","ignoreList":[]}
1
+ {"version":3,"names":["React","useContext","Cloudinary","Platform","View","AdvancedImage","scale","AppContext","CloudinaryImage","cldImg","imageStyle","accessibilityLabel","testID","brandCloudName","cld","cloud","cloudName","myImage","OS","image","format","createElement","style","resizeMode","CloudinaryBannerImage","width","height","resize","parseInt"],"sourceRoot":"../../../src","sources":["utils/cloudinary.js"],"mappings":"AAAA,OAAOA,KAAK,IAAIC,UAAU,QAAQ,OAAO;AACzC,SAASC,UAAU,QAAQ,qBAAqB;AAChD,SAASC,QAAQ,EAAEC,IAAI,QAAQ,cAAc;AAC7C,SAASC,aAAa,QAAQ,yBAAyB;AACvD,SAASC,KAAK,QAAQ,oCAAoC;AAC1D,SAASC,UAAU,QAAQ,wBAAwB;AAEnD,MAAMC,eAAe,GAAGA,CAAC;EAAEC,MAAM;EAAEC,UAAU;EAAEC,kBAAkB;EAAEC;AAAO,CAAC,KAAK;EAC9E,MAAM;IAAEC;EAAe,CAAC,GAAGZ,UAAU,CAACM,UAAU,CAAC;EACjD,MAAMO,GAAG,GAAG,IAAIZ,UAAU,CAAC;IACzBa,KAAK,EAAE;MACLC,SAAS,EAAEH,cAAc,IAAI;IAC/B;EACF,CAAC,CAAC;EAEF,MAAMI,OAAO,GAAGd,QAAQ,CAACe,EAAE,KAAK,KAAK,GAAGJ,GAAG,CAACK,KAAK,CAAC,GAAGV,MAAM,EAAE,CAAC,GAAGK,GAAG,CAACK,KAAK,CAAC,GAAGV,MAAM,EAAE,CAAC,CAACW,MAAM,CAAC,KAAK,CAAC;EACrG,oBACEpB,KAAA,CAAAqB,aAAA,CAACjB,IAAI,qBACHJ,KAAA,CAAAqB,aAAA,CAAChB,aAAa;IACZI,MAAM,EAAEQ,OAAQ;IAChBN,kBAAkB,EAAEA,kBAAmB;IACvCC,MAAM,EAAEA,MAAO;IACfU,KAAK,EAAE,CAAC;MAAEC,UAAU,EAAE;IAAU,CAAC,EAAEb,UAAU;EAAE,CAChD,CACG,CAAC;AAEX,CAAC;AAED,OAAO,MAAMc,qBAAqB,GAAGA,CAAC;EACpCf,MAAM;EACNC,UAAU;EACVC,kBAAkB;EAClBC,MAAM;EACNa,KAAK,GAAG,GAAG;EACXC,MAAM,GAAG;AACX,CAAC,KAAK;EACJ,MAAM;IAAEb;EAAe,CAAC,GAAGZ,UAAU,CAACM,UAAU,CAAC;EACjD,MAAMO,GAAG,GAAG,IAAIZ,UAAU,CAAC;IACzBa,KAAK,EAAE;MACLC,SAAS,EAAEH,cAAc,IAAI;IAC/B;EACF,CAAC,CAAC;EACF,MAAMI,OAAO,GAAGH,GAAG,CAACK,KAAK,CAAC,GAAGV,MAAM,EAAE,CAAC;EACtCQ,OAAO,CAACU,MAAM,CACZrB,KAAK,CAAC,CAAC,CACJmB,KAAK,CAACG,QAAQ,CAACH,KAAK,GAAG,CAAC,CAAC,CAAC,CAC1BC,MAAM,CAACE,QAAQ,CAACF,MAAM,GAAG,CAAC,CAAC,CAChC,CAAC;EACD,oBACE1B,KAAA,CAAAqB,aAAA,CAACjB,IAAI,qBACHJ,KAAA,CAAAqB,aAAA,CAAChB,aAAa;IACZI,MAAM,EAAEQ,OAAQ;IAChBN,kBAAkB,EAAEA,kBAAmB;IACvCC,MAAM,EAAEA,MAAO;IACfU,KAAK,EAAE,CAAC;MAAEC,UAAU,EAAE,SAAS;MAAEE,KAAK,EAAEA,KAAK;MAAEC,MAAM,EAAEA;IAAO,CAAC,EAAEhB,UAAU;EAAE,CAC9E,CACG,CAAC;AAEX,CAAC;AAED,eAAeF,eAAe","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../src/components/email.js"],"names":[],"mappings":"AAiBO;;sBA4UN;;kBA7V8D,OAAO"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../src/components/email.js"],"names":[],"mappings":"AAiBO;;sBAqVN;;kBAtW8D,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../src/components/header.js"],"names":[],"mappings":"AAOO,4CA4DN;kBAnEiC,OAAO"}
1
+ {"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../src/components/header.js"],"names":[],"mappings":"AAOO,4CAyDN;kBAhEiC,OAAO"}
@@ -2,6 +2,6 @@ export function ProductCard({ prod, onFocusQuantityInput, messageId }: {
2
2
  prod: any;
3
3
  onFocusQuantityInput: any;
4
4
  messageId: any;
5
- }): React.JSX.Element;
5
+ }): React.JSX.Element | null;
6
6
  import React from "react";
7
7
  //# sourceMappingURL=productCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"productCard.d.ts","sourceRoot":"","sources":["../../../src/components/productCard.js"],"names":[],"mappings":"AAOO;;;;sBA8dN;kBAresD,OAAO"}
1
+ {"version":3,"file":"productCard.d.ts","sourceRoot":"","sources":["../../../src/components/productCard.js"],"names":[],"mappings":"AAOO;;;;6BAwgBN;kBA/gBsD,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"welcomeButton.d.ts","sourceRoot":"","sources":["../../../src/components/welcomeButton.js"],"names":[],"mappings":";AAaA,sDAgBC;kBA5BiC,OAAO"}
1
+ {"version":3,"file":"welcomeButton.d.ts","sourceRoot":"","sources":["../../../src/components/welcomeButton.js"],"names":[],"mappings":";AAqBA,sDAkBC;kBAtCiC,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"AppContext.d.ts","sourceRoot":"","sources":["../../../src/contexts/AppContext.js"],"names":[],"mappings":"AAMA,4CAA0C;AAEnC;;;;;;sBA8XN;kBAtY6E,OAAO"}
1
+ {"version":3,"file":"AppContext.d.ts","sourceRoot":"","sources":["../../../src/contexts/AppContext.js"],"names":[],"mappings":"AAMA,4CAA0C;AAEnC;;;;;;sBA2ZN;kBAna6E,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../../../src/layout/welcome.js"],"names":[],"mappings":"AASO;;sBAmDN;;kBA3DiC,OAAO"}
1
+ {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../../../src/layout/welcome.js"],"names":[],"mappings":"AASO;;sBAgDN;;kBAxDiC,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/layout/window.js"],"names":[],"mappings":"AAeO;;sBAuLN;kBAtM8D,OAAO"}
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/layout/window.js"],"names":[],"mappings":"AAeO;;sBA0LN;kBAzM8D,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"audioRecorder.d.ts","sourceRoot":"","sources":["../../../src/utils/audioRecorder.js"],"names":[],"mappings":"AAqBA,4EAGC;AAGD,mFAqHC;AA+ED,mDAmCC;AAED,+CAkDC;AAED,iDASC;AAED,2DA2BC;AAqDD,iDAMC;AAED,gCAgBC"}
1
+ {"version":3,"file":"audioRecorder.d.ts","sourceRoot":"","sources":["../../../src/utils/audioRecorder.js"],"names":[],"mappings":"AAqBA,4EAGC;AAGD,mFAmHC;AAiFD,mDAwCC;AAED,+CAkDC;AAED,iDASC;AAED,2DA2BC;AAqDD,iDAMC;AAED,gCAgBC"}
@@ -1 +1 @@
1
- {"version":3,"file":"cloudinary.d.ts","sourceRoot":"","sources":["../../../src/utils/cloudinary.js"],"names":[],"mappings":"AA0BO;;;;;;;sBA6BN;;kBAvDiB,OAAO;AAMzB;;;;;sBAkBC"}
1
+ {"version":3,"file":"cloudinary.d.ts","sourceRoot":"","sources":["../../../src/utils/cloudinary.js"],"names":[],"mappings":"AA4BO;;;;;;;sBA8BN;;kBA1DiC,OAAO;AAOzC;;;;;sBAmBC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-srschat",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
4
4
  "private": false,
5
5
  "description": "A modern, sophisticated chat interface for React Native",
6
6
  "main": "lib/commonjs/index",
@@ -16,11 +16,12 @@ import { Header } from "./header";
16
16
  import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
17
17
 
18
18
  export const EmailForm = ({ panHandlers }) => {
19
- const { data, BASE_URL, setShowModal, formatChatHistory, conversationStartTime, sessionId } = useContext(AppContext);
19
+ const { data, BASE_URL, setShowModal, formatChatHistory, conversationStartTime, sessionId, theme } = useContext(AppContext);
20
+ const API_PREFIX = "https://";
20
21
  const [subject, setSubject] = useState("");
21
22
  const [message, setMessage] = useState("");
22
- const [userEmail, setUserEmail] = useState(data?.user_email || "");
23
- const [branchEmail, setBranchEmail] = useState((data?.branch_email) || "");
23
+ const [userEmail, setUserEmail] = useState(data?.user_email || ""); // Sender's email
24
+ const [branchEmail, setBranchEmail] = useState((data?.branch_email) || ""); // Recipient's email
24
25
  const [isLoading, setIsLoading] = useState(false);
25
26
  const [error, setError] = useState("");
26
27
  const [success, setSuccess] = useState(false);
@@ -152,81 +153,89 @@ export const EmailForm = ({ panHandlers }) => {
152
153
  const handleComposeEmail = async () => {
153
154
  setIsComposing(true);
154
155
  setError("");
155
- setSuccess(false);
156
156
 
157
157
  try {
158
- const chatHistory = formatChatHistory();
159
- const payload = buildEmailRequestPayload(chatHistory);
158
+ const chatHistory = formatChatHistory().map(({ variant_type, ...rest }) => rest);
159
+
160
+ const payload = {
161
+ chat_history: chatHistory,
162
+ conversation_start_time: conversationStartTime || null,
163
+ customer_name: data?.customer_name || null,
164
+ user_id: data?.user_id || null,
165
+ session_id: data?.session_id || data?.session || sessionId || null,
166
+ user_email: data?.user_email || "",
167
+ branch_email: data?.branch_email || ""
168
+ };
169
+
170
+ console.log("Email composition request payload:", payload);
171
+
172
+ const response = await axios.post(`${API_PREFIX}${BASE_URL}/compose-email`, payload);
173
+
174
+ const textToType = response.data.message;
175
+ let currentText = "";
176
+ const typingSpeed = 10;
177
+
178
+ for (let i = 0; i < textToType.length; i++) {
179
+ await new Promise(resolve => setTimeout(resolve, typingSpeed));
180
+ currentText += textToType[i];
181
+ setMessage(currentText);
182
+ }
160
183
 
161
- const response = await axios.post(`https://${BASE_URL}/compose-email`, payload);
162
- // Use customer name from data if available, otherwise leave blank
163
-
164
- setMessage(response.data.message);
165
184
  setSubject(response.data.subject || "");
185
+ setUserEmail(response.data.user_email || userEmail || "");
186
+ // Don't overwrite branch email if response doesn't have it
187
+ if (response.data.branch_email) {
188
+ setBranchEmail(response.data.branch_email);
189
+ }
190
+
166
191
  } catch (error) {
167
- setError(error.response?.data?.message || "Failed to compose email");
168
- setSuccess(false);
192
+ setError(
193
+ error.response?.data?.message ||
194
+ error.message ||
195
+ "Failed to compose email"
196
+ );
169
197
  } finally {
170
198
  setIsComposing(false);
171
199
  }
172
200
  };
173
201
 
174
202
  const handleSubmit = async () => {
175
- const emailError = validateEmail(userEmail);
176
- if (emailError) {
177
- setError(emailError);
178
- setSuccess(false);
203
+ if (!userEmail || !subject || !message) {
204
+ setError("Please fill in all fields");
179
205
  return;
180
206
  }
181
- if (!subject || !message) {
182
- setError("Please fill in all fields");
183
- setSuccess(false);
207
+
208
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(userEmail)) {
209
+ setError("Please enter a valid email address");
184
210
  return;
185
211
  }
186
212
 
187
213
  setIsLoading(true);
188
- setError("");
189
- setSuccess(false);
190
214
 
191
215
  try {
192
- const chatHistory = formatChatHistory();
193
- const payload = {
194
- ...buildEmailRequestPayload(chatHistory),
216
+ const chatHistory = formatChatHistory().map(({ variant_type, ...rest }) => rest);
217
+ console.log("Sending email with chat history:", chatHistory);
218
+
219
+ await axios.post(`https://${BASE_URL}/send-email`, {
195
220
  user_email: userEmail,
196
221
  subject,
197
222
  message,
198
- branch_email: branchEmail,
199
- customer_name: data?.customer_name ?? null,
200
- conversation_start_time: conversationStartTime ?? null,
201
- };
202
-
203
- await axios.post(`https://${BASE_URL}/send-email`, payload);
204
-
223
+ chat_history: chatHistory,
224
+ conversation_start_time: conversationStartTime,
225
+ customer_name: data?.customer_name,
226
+ branch_email: branchEmail || data?.branch_email || ""
227
+ });
205
228
  setSuccess(true);
206
- setError("");
207
229
  setTimeout(() => {
208
230
  setSuccess(false);
209
231
  setShowModal("ChatWindow");
210
232
  }, 2000);
211
233
  } catch (error) {
212
- console.error("Email send error:", {
213
- status: error.response?.status,
214
- statusText: error.response?.statusText,
215
- errorMessage: error.response?.data?.message,
216
- errorDetails: error.response?.data,
217
- requestPayload: {
218
- user_email: userEmail,
219
- subject,
220
- message_length: message?.length,
221
- branch_email: branchEmail,
222
- customer_name: data?.customer_name,
223
- conversation_start_time: conversationStartTime,
224
- session_id: data?.session_id || data?.session || sessionId,
225
- chat_history_length: chatHistory?.length
226
- }
227
- });
228
- setError(error.response?.data?.message || "Failed to send email");
229
- setSuccess(false);
234
+ setError(
235
+ error.response?.data?.message ||
236
+ error.message ||
237
+ "Failed to send email",
238
+ );
230
239
  } finally {
231
240
  setIsLoading(false);
232
241
  }
@@ -257,7 +266,7 @@ export const EmailForm = ({ panHandlers }) => {
257
266
  Press "Draft My Email" to generate an email based on your chat conversation. You can review and modify before sending.
258
267
  </Text>
259
268
 
260
- <TouchableOpacity style={styles.composeButton} onPress={handleComposeEmail} disabled={isComposing}>
269
+ <TouchableOpacity style={[styles.composeButton, { backgroundColor: theme.primaryColor }]} onPress={handleComposeEmail} disabled={isComposing}>
261
270
  {isComposing ? (
262
271
  <ActivityIndicator size="small" color="#FFFFFF" />
263
272
  ) : (
@@ -341,7 +350,7 @@ export const EmailForm = ({ panHandlers }) => {
341
350
  {error ? <Text style={styles.errorText}>{error}</Text> : null}
342
351
  {success ? <Text style={styles.successText}>Email sent successfully!</Text> : null}
343
352
 
344
- <TouchableOpacity style={styles.sendButton} onPress={handleSubmit} disabled={isLoading}>
353
+ <TouchableOpacity style={[styles.sendButton, { backgroundColor: theme.primaryColor }]} onPress={handleSubmit} disabled={isLoading}>
345
354
  {isLoading ? <ActivityIndicator size="small" color="#FFFFFF" /> : <Text style={styles.buttonText}>Send Email</Text>}
346
355
  </TouchableOpacity>
347
356
  </KeyboardAwareScrollView>
@@ -405,14 +414,12 @@ const styles = StyleSheet.create({
405
414
  color: "#000",
406
415
  },
407
416
  composeButton: {
408
- backgroundColor: "#367CB6",
409
417
  borderRadius: 5,
410
418
  padding: 12,
411
419
  alignItems: "center",
412
420
  marginBottom: 20,
413
421
  },
414
422
  sendButton: {
415
- backgroundColor: "#367CB6",
416
423
  borderRadius: 5,
417
424
  padding: 12,
418
425
  alignItems: "center",
@@ -6,7 +6,7 @@ import CloudinaryImage from '../utils/cloudinary';
6
6
  // import { PoseidonLogo } from './PoseidonLogo';
7
7
 
8
8
  export const Header = () => {
9
- const { showModal, setShowModal, setMessages, defaultMessage, handleClearState, uiConfig } = useContext(AppContext);
9
+ const { showModal, setShowModal, setMessages, defaultMessage, handleClearState, uiConfig, theme, brandLogo } = useContext(AppContext);
10
10
 
11
11
  const handleClick = () => {
12
12
  if ((uiConfig.showIcon ?? true) !== true) {
@@ -21,7 +21,7 @@ export const Header = () => {
21
21
  };
22
22
 
23
23
  return (
24
- <View style={styles.header}>
24
+ <View style={[styles.header, { backgroundColor: theme.primaryColor }]}>
25
25
  {/* Logo on the left */}
26
26
  <View style={styles.section}>
27
27
  {/* <PoseidonLogo width={150} height={35} color="white" /> */}
@@ -31,10 +31,7 @@ export const Header = () => {
31
31
  width={150}
32
32
  height={35}
33
33
  /> */}
34
- <CloudinaryImage
35
- cldImg="logos/HLSG_Logo_Lockup_Color"
36
- imageStyle={{ width: 150, height: 35 }}
37
- />
34
+ <CloudinaryImage cldImg={brandLogo} imageStyle={{ width: 150, height: 35 }} />
38
35
  </View>
39
36
 
40
37
  {/* Title in the center */}
@@ -52,15 +49,15 @@ export const Header = () => {
52
49
  {showModal !== "Form" && showModal !== "Email" &&
53
50
  <>
54
51
  <TouchableOpacity onPress={() => setShowModal("Email")}>
55
- <Ionicons name="mail" size={24} color="white" />
52
+ <Ionicons name="mail" size={24} color={theme.textColorSecondary} />
56
53
  </TouchableOpacity>
57
54
  <TouchableOpacity onPress={() => handleClearState()}>
58
- <Ionicons name="trash" size={22} color="white" />
55
+ <Ionicons name="trash" size={22} color={theme.textColorSecondary} />
59
56
  </TouchableOpacity>
60
57
  </>
61
58
  }
62
59
  <TouchableOpacity onPress={() => handleClick()}>
63
- <Ionicons name="close" size={26} color="white" />
60
+ <Ionicons name="close" size={26} color={theme.textColorSecondary} />
64
61
  </TouchableOpacity>
65
62
  </View>
66
63
  </View>
@@ -9,6 +9,48 @@ export const ProductCard = ({ prod, onFocusQuantityInput, messageId }) => {
9
9
  const { onProductCardClick, onAddToCartClick, sessionId, data, ADD_TO_CART_URL } = useContext(AppContext);
10
10
  const [keyboardVisible, setKeyboardVisible] = useState(false);
11
11
 
12
+ // Validation: Check for incomplete product information
13
+ const hasValidProductDetails = () => {
14
+ if (!prod || !prod.product_details) {
15
+ return false;
16
+ }
17
+
18
+ const details = prod.product_details;
19
+ if (!details.product_name || !details.part_number || !details.image_url) {
20
+ return false;
21
+ }
22
+
23
+ return true;
24
+ };
25
+
26
+ const hasValidInventoryInfo = () => {
27
+ if (!prod.inventory_info || !prod.inventory_info.info_by_uom) {
28
+ return false;
29
+ }
30
+
31
+ const uomData = prod.inventory_info.info_by_uom;
32
+ const uomKeys = Object.keys(uomData);
33
+
34
+ if (uomKeys.length === 0) {
35
+ return false;
36
+ }
37
+
38
+ // Check if at least one UOM has valid pricing
39
+ const hasValidPricing = uomKeys.some(uom => {
40
+ const uomInfo = uomData[uom];
41
+ return uomInfo &&
42
+ typeof uomInfo.gross_price === 'number' &&
43
+ uomInfo.gross_price > 0;
44
+ });
45
+
46
+ return hasValidPricing;
47
+ };
48
+
49
+ // Return null if product lacks essential information
50
+ if (!hasValidProductDetails() || !hasValidInventoryInfo()) {
51
+ return null;
52
+ }
53
+
12
54
  const [selectedUom, setSelectedUom] = useState(() => {
13
55
  if (prod.inventory_info?.info_by_uom) {
14
56
  const availableUoms = Object.keys(prod.inventory_info.info_by_uom);
@@ -360,13 +402,13 @@ export const ProductCard = ({ prod, onFocusQuantityInput, messageId }) => {
360
402
  <View style={styles.priceContainer}>
361
403
  {isOnSale ? (
362
404
  <>
363
- <Text style={styles.originalPrice} allowFontScaling={false}>${Number(grossPrice).toFixed(2)}</Text>
364
- <Text style={styles.salePrice} allowFontScaling={false}>${Number(netPrice).toFixed(2)}</Text>
405
+ <Text style={styles.originalPrice} allowFontScaling={false}>${Number(grossPrice).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</Text>
406
+ <Text style={styles.salePrice} allowFontScaling={false}>${Number(netPrice).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</Text>
365
407
  <Text style={styles.perUnit}>/ {selectedUom}</Text>
366
408
  </>
367
409
  ) : (
368
410
  <>
369
- <Text style={styles.price} allowFontScaling={false}>${Number(grossPrice).toFixed(2)}</Text>
411
+ <Text style={styles.price} allowFontScaling={false}>${Number(grossPrice).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</Text>
370
412
  <Text style={styles.perUnit}>/ {selectedUom}</Text>
371
413
  </>
372
414
  )}
@@ -3,7 +3,7 @@ import React, { useContext } from 'react';
3
3
  import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
4
4
  import { AppContext } from '../contexts/AppContext';
5
5
 
6
- const suggestedQuestions = [
6
+ const landscapeQuestions = [
7
7
  { text: 'What are the hours of my current branch?' },
8
8
  { text: 'Do you have part # RBLESPLXME2 in stock?' },
9
9
  { text: 'How do I install the seal on B82456?' },
@@ -11,18 +11,28 @@ const suggestedQuestions = [
11
11
  { text: 'ĀæPuedes ayudarme en espaƱol?' }
12
12
  ];
13
13
 
14
+ const poolQuestions = [
15
+ { text: 'What are the hours of my current branch?' },
16
+ { text: 'Do you have part # JNDDEV48 in stock?' },
17
+ { text: 'Do you carry Hayward super pumps?' },
18
+ { text: 'Which grid assembly goes with the Hayward DE4820 filter?' },
19
+ { text: '¿Puedes ayudarme en español?' }
20
+ ];
21
+
14
22
  const ButtonComponent = () => {
15
- const { handleButtonClick } = useContext(AppContext);
23
+ const { handleButtonClick, theme, data } = useContext(AppContext);
24
+ const isPool = data?.brand_version === 'pool';
25
+ const suggestedQuestions = isPool ? poolQuestions : landscapeQuestions;
16
26
 
17
27
  return (
18
28
  <View style={styles.buttonContainer}>
19
29
  {suggestedQuestions.map((item, index) => (
20
30
  <TouchableOpacity
21
31
  key={index}
22
- style={styles.button}
32
+ style={[styles.button, { borderColor: theme.primaryColor }]}
23
33
  onPress={() => handleButtonClick(item.text)}
24
34
  >
25
- <Text style={styles.buttonText}>{item.text}</Text>
35
+ <Text style={[styles.buttonText, { color: theme.primaryColor }]}>{item.text}</Text>
26
36
  </TouchableOpacity>
27
37
  ))}
28
38
  </View>
@@ -7,25 +7,54 @@ import { loadChat, updateChat, defaultState } from '../utils/storage';
7
7
  export const AppContext = createContext();
8
8
 
9
9
  export const AppProvider = ({ data, onProductCardClick, onAddToCartClick, uiConfig = {}, children }) => {
10
+ // Brand-driven configuration
11
+ const version = (data?.brand_version === 'pool' || data?.brand_version === 'landscape') ? data.brand_version : 'landscape';
12
+ const API_PREFIX = "https://";
13
+
14
+ const BRAND_CONFIG = (() => {
15
+ const isStage = data?.env === 'stage';
16
+ return {
17
+ pool: {
18
+ primaryColor: '#004687',
19
+ userMessageColor: '#003764',
20
+ cloudName: 'heritageplus',
21
+ // Pool assets live under mobileapp/ in heritageplus cloud
22
+ logoId: 'mobileapp/logos/HPSG_HPlus_Logo_White',
23
+ baseHost: isStage
24
+ ? 'pool-stage-external-agent-adk-586731320826.us-east1.run.app'
25
+ : 'pool-prod-external-agent-adk-586731320826.us-east1.run.app',
26
+ loggingHost: 'srs-external-agent-logging-586731320826.us-central1.run.app',
27
+ },
28
+ landscape: {
29
+ primaryColor: '#437D3D',
30
+ userMessageColor: '#437D3D',
31
+ cloudName: 'mktg',
32
+ logoId: 'logos/HLSG_Logo_Lockup_Color',
33
+ baseHost: isStage
34
+ ? 'landscape-stage-external-agent-adk-586731320826.us-east1.run.app'
35
+ : 'landscape-prod-external-agent-adk-586731320826.us-east1.run.app',
36
+ loggingHost: 'srs-external-agent-landscape-logging-586731320826.us-central1.run.app',
37
+ },
38
+ }[version];
39
+ })();
40
+
41
+ const BASE_URL = BRAND_CONFIG.baseHost;
42
+ const LOGGING_URL = BRAND_CONFIG.loggingHost;
43
+ const TRACK_CLICK_URL = `${API_PREFIX}${LOGGING_URL}/track-click`;
44
+ const ADD_TO_CART_URL = `${API_PREFIX}${LOGGING_URL}/add-to-cart`;
45
+
10
46
  const theme = {
11
- userMessage: '#003764',
12
- botMessage: '#003764',
47
+ userMessage: BRAND_CONFIG.userMessageColor,
48
+ botMessage: BRAND_CONFIG.userMessageColor,
13
49
  backgroundColor: '#f6f6f6',
14
50
  textColor: '#161616',
15
51
  textColorSecondary: '#FFFFFF',
16
52
  inlineButtonColor: '#dbd4c8',
17
- primaryColor: '#161616'
53
+ primaryColor: BRAND_CONFIG.primaryColor,
18
54
  };
19
55
 
20
- const TRACK_CLICK_URL = "https://srs-external-agent-landscape-logging-586731320826.us-central1.run.app/track-click"
21
- const ADD_TO_CART_URL = "https://srs-external-agent-landscape-logging-586731320826.us-central1.run.app/add-to-cart"
22
-
23
- // Backend URLs
24
- const BASE_URL = data.env === "stage"
25
- ? "landscape-stage-external-agent-adk-586731320826.us-east1.run.app"
26
- : "landscape-prod-external-agent-adk-586731320826.us-east1.run.app";
27
- const LOGGING_URL = "srs-external-agent-landscape-logging-586731320826.us-central1.run.app";
28
- const API_PREFIX = "https://";
56
+ const brandLogo = BRAND_CONFIG.logoId;
57
+ const brandCloudName = BRAND_CONFIG.cloudName;
29
58
 
30
59
  // Default Messages
31
60
  const defaultMessage = [
@@ -382,7 +411,7 @@ export const AppProvider = ({ data, onProductCardClick, onAddToCartClick, uiConf
382
411
  startStreaming, setStartStreaming, maintenance, setMaintenance, feedback, setFeedback, handleFeedback, feedbackOpen, setFeedbackOpen,
383
412
  writeFeedback, setWriteFeedback, writeAnswer, setWriteAnswer, BASE_URL, lastMessageId, setLastMessageId,
384
413
  onProductCardClick, onAddToCartClick: handleAddToCartWithMessage, data, sessionId, setSessionId, handleWrittenFeedback, switchFeedbackOpen, confirmDisclaimer,
385
- formatChatHistory, uiConfig, handleVoiceSend, TRACK_CLICK_URL, ADD_TO_CART_URL, isListening, setIsListening
414
+ formatChatHistory, uiConfig, handleVoiceSend, TRACK_CLICK_URL, ADD_TO_CART_URL, isListening, setIsListening, brandLogo, brandCloudName
386
415
  }}
387
416
  >
388
417
  {children}
@@ -6,7 +6,7 @@ import { Header } from '../components/header';
6
6
  import { Testing } from "../components/testing";
7
7
 
8
8
  export const Disclaimer = ({ panHandlers }) => {
9
- const { setShowModal, confirmDisclaimer, uiConfig, onAddToCartClick, onProductCardClick } = useContext(AppContext);
9
+ const { setShowModal, confirmDisclaimer, uiConfig, onAddToCartClick, onProductCardClick, theme } = useContext(AppContext);
10
10
 
11
11
  const sections = [
12
12
  {
@@ -47,7 +47,7 @@ export const Disclaimer = ({ panHandlers }) => {
47
47
  </Text>
48
48
 
49
49
  {sections.map((section, index) => (
50
- <Paragraph key={index} title={section.title} details={section.details} icon={section.icon} />
50
+ <Paragraph key={index} title={section.title} details={section.details} icon={section.icon} theme={theme} />
51
51
  ))}
52
52
 
53
53
  <Text style={styles.disclaimerText}>
@@ -61,7 +61,7 @@ export const Disclaimer = ({ panHandlers }) => {
61
61
  <Testing onProductCardClick={onProductCardClick} onAddToCartClick={onAddToCartClick} />
62
62
  }
63
63
  <View style={styles.confirmArea}>
64
- <TouchableOpacity style={styles.button} onPress={confirmDisclaimer}>
64
+ <TouchableOpacity style={[styles.button, { backgroundColor: theme.primaryColor }]} onPress={confirmDisclaimer}>
65
65
  <Text style={styles.buttonText}>Confirm</Text>
66
66
  </TouchableOpacity>
67
67
  <Text style={styles.confirmText}>
@@ -72,11 +72,11 @@ export const Disclaimer = ({ panHandlers }) => {
72
72
  );
73
73
  };
74
74
 
75
- const Paragraph = ({ title, details, icon }) => {
75
+ const Paragraph = ({ title, details, icon, theme }) => {
76
76
  return (
77
77
  <View style={styles.sectionContainer}>
78
- <View style={styles.iconContainer}>
79
- <Feather name={icon} size={18} color="#1976d2" />
78
+ <View style={[styles.iconContainer, { backgroundColor: `${theme.primaryColor}20` }]}>
79
+ <Feather name={icon} size={18} color={theme?.primaryColor || '#1976d2'} />
80
80
  </View>
81
81
  <View style={styles.textContainer}>
82
82
  <Text style={styles.sectionTitle}>{title}</Text>
@@ -135,7 +135,6 @@ const styles = StyleSheet.create({
135
135
  elevation: 1,
136
136
  },
137
137
  iconContainer: {
138
- backgroundColor: "#bbdefb",
139
138
  borderRadius: 10,
140
139
  padding: 10,
141
140
  alignItems: "center",
@@ -165,7 +164,6 @@ const styles = StyleSheet.create({
165
164
  paddingBottom: 25,
166
165
  },
167
166
  button: {
168
- backgroundColor: "#367CB6",
169
167
  width: "100%",
170
168
  paddingVertical: 12,
171
169
  borderRadius: 5,
@@ -8,7 +8,7 @@ import ButtonComponent from '../components/welcomeButton';
8
8
  import CloudinaryImage from '../utils/cloudinary';
9
9
 
10
10
  export const Welcome = ({ panHandlers }) => {
11
- const { setShowModal, uiConfig, onProductCardClick, onAddToCartClick, data } = useContext(AppContext);
11
+ const { setShowModal, uiConfig, onProductCardClick, onAddToCartClick, data, theme, brandLogo } = useContext(AppContext);
12
12
 
13
13
  const handleClick = () => {
14
14
  if ((uiConfig.showIcon ?? true) !== true) {
@@ -24,23 +24,20 @@ export const Welcome = ({ panHandlers }) => {
24
24
 
25
25
  return (
26
26
  <View style={styles.container}>
27
- <View style={styles.parentContainer}>
27
+ <View style={[styles.parentContainer, { backgroundColor: theme.primaryColor }]}>
28
28
  {/* Top section */}
29
- <View style={styles.topContainer}>
29
+ <View style={[styles.topContainer, { backgroundColor: theme.primaryColor }]}>
30
30
  <View style={styles.topHeader}>
31
31
  {/* Logo container with absolute positioning */}
32
32
  <View style={styles.logoContainer}>
33
- <CloudinaryImage
34
- cldImg="logos/HLSG_Logo_Lockup_Color"
35
- imageStyle={{ width: 150, height: 35 }}
36
- />
33
+ <CloudinaryImage cldImg={brandLogo} imageStyle={{ width: 150, height: 35 }} />
37
34
  </View>
38
35
  <TouchableOpacity onPress={handleClick} style={styles.collapseButton}>
39
36
  <Ionicons name="close" size={26} color="white" />
40
37
  </TouchableOpacity>
41
38
  </View>
42
39
 
43
- <View style={styles.blueContainer}>
40
+ <View style={[styles.blueContainer, { backgroundColor: theme.primaryColor }]}>
44
41
  <Text style={styles.welcomeHeader}>Hi {data?.customer_name || ""} šŸ‘‹</Text>
45
42
  <Text style={styles.welcomeBody2}>
46
43
  I’m your Heritage+ AI Agent. I can help you during your online visit with Product and Account information.