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,88 @@
1
+ import React, { useEffect, useRef, useContext } from "react";
2
+ import { View, Animated, StyleSheet, Pressable, Text } from "react-native";
3
+ import { AppContext } from "../contexts/AppContext";
4
+ import { LoadingTips } from "./LoadingTips";
5
+
6
+ export const ProgressCircle = () => {
7
+ const { stopGenerating } = useContext(AppContext);
8
+ const spinValue = useRef(new Animated.Value(0)).current;
9
+
10
+ useEffect(() => {
11
+ const spinAnimation = Animated.loop(
12
+ Animated.timing(spinValue, {
13
+ toValue: 1,
14
+ duration: 1400,
15
+ useNativeDriver: true,
16
+ })
17
+ );
18
+ spinAnimation.start();
19
+
20
+ return () => spinAnimation.stop(); // Cleanup on unmount
21
+ }, [spinValue]);
22
+
23
+ const spin = spinValue.interpolate({
24
+ inputRange: [0, 1],
25
+ outputRange: ["0deg", "360deg"],
26
+ });
27
+
28
+ return (
29
+ <>
30
+ <Text style={styles.textBeta}>Beta version. AI Assistant is still learning!</Text>
31
+ <View style={styles.container}>
32
+ <LoadingTips />
33
+ <Pressable style={styles.circleContainer} onPress={stopGenerating}>
34
+ <Animated.View style={[styles.circle, { transform: [{ rotate: spin }] }]} />
35
+ <View style={styles.stopSquare} />
36
+ </Pressable>
37
+ </View>
38
+ </>
39
+ );
40
+ };
41
+
42
+ const styles = StyleSheet.create({
43
+ container: {
44
+ flexDirection: "row",
45
+ justifyContent: "flex-end",
46
+ alignItems: "center",
47
+ paddingHorizontal: 6,
48
+ paddingVertical: 6,
49
+ backgroundColor: "#f6f6f6",
50
+ borderTopWidth: 1,
51
+ borderTopColor: "rgba(0, 0, 0, 0.1)",
52
+ paddingBottom: 35,
53
+ paddingTop: 15
54
+ },
55
+ circleContainer: {
56
+ justifyContent: "center",
57
+ alignItems: "center",
58
+ width: 50,
59
+ height: 50,
60
+ marginRight: 20,
61
+ position: "relative",
62
+ },
63
+ circle: {
64
+ borderWidth: 4,
65
+ borderColor: "#dddbd9",
66
+ borderTopColor: "#666666",
67
+ borderRadius: 50,
68
+ width: 40,
69
+ height: 40,
70
+ },
71
+ stopSquare: {
72
+ width: 13,
73
+ height: 13,
74
+ backgroundColor: "#666666",
75
+ position: "absolute",
76
+ top: "50%",
77
+ left: "50%",
78
+ transform: [{ translateX: -6.5 }, { translateY: -6.5 }],
79
+ },
80
+ textBeta: {
81
+ textAlign: 'center',
82
+ fontSize: 11,
83
+ color: '#808080',
84
+ fontWeight: '400',
85
+ marginTop: 5,
86
+ marginBottom: 2
87
+ },
88
+ });
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { View, Button, StyleSheet, TouchableOpacity, Text } from 'react-native';
3
+
4
+ export const Testing = ({ onProductCardClick, onAddToCartClick }) => {
5
+
6
+ const product = {
7
+ "part_number": "AEQO177",
8
+ "inventory_info": {
9
+ "default_uom": "EA",
10
+ "is_valid": true,
11
+ "info_by_uom": {
12
+ "EA": {
13
+ "gross_price": 7.83,
14
+ "net_price": 7.83,
15
+ "is_on_sale": false,
16
+ "quantity_available": 0,
17
+ "discounts": null
18
+ }
19
+ }
20
+ },
21
+ "product_details": {
22
+ "product_name": "Aladdin Hayward SPX1600S SuperPump Lid Gasket | O-177-2",
23
+ "part_number": "AEQO177",
24
+ "manufacturer_id": "O-177-2",
25
+ "heritage_link": "https://www.heritagepoolplus.com/aeqo177-aladdin-o-ring-o-177-o-177-2-o-177-2",
26
+ "image_url": "https://media.heritageplus.com/image/upload/v1668157956/image/AEQO177_0.jpg"
27
+ }
28
+ }
29
+
30
+ return (
31
+ <View style={styles.container}>
32
+ <TouchableOpacity style={styles.button} onPress={() => onProductCardClick(product)}>
33
+ <Text style={styles.buttonText}>Product Card Click</Text>
34
+ </TouchableOpacity>
35
+ <TouchableOpacity style={styles.button} onPress={() => onAddToCartClick({"quantity":1,"product":product})}>
36
+ <Text style={styles.buttonText}>Add to Cart</Text>
37
+ </TouchableOpacity>
38
+ </View>
39
+ );
40
+ };
41
+
42
+ const styles = StyleSheet.create({
43
+ container: {
44
+ flexDirection: 'row',
45
+ alignItems: 'center',
46
+ justifyContent: 'space-evenly',
47
+ paddingHorizontal: 16,
48
+ borderTopWidth: 1,
49
+ borderTopColor: '#DDD',
50
+ },
51
+ button: {
52
+ backgroundColor: "#d4d4d4",
53
+ paddingVertical: 12,
54
+ paddingHorizontal: 12,
55
+ borderRadius: 5,
56
+ alignItems: "center",
57
+ marginBottom: 10,
58
+ marginTop: 10,
59
+ },
60
+ });
@@ -0,0 +1,228 @@
1
+ // VoiceButton.js
2
+
3
+ import React, { useState, useContext, useEffect } from 'react';
4
+ import {
5
+ TouchableOpacity,
6
+ ActivityIndicator,
7
+ StyleSheet,
8
+ Alert,
9
+ Linking,
10
+ Platform,
11
+ } from 'react-native';
12
+ import Ionicons from 'react-native-vector-icons/Ionicons';
13
+ import useAsyncStorage from '../hooks/useAsyncStorage';
14
+
15
+ import {
16
+ startRecording,
17
+ stopRecording,
18
+ cancelRecording,
19
+ requestAudioPermission,
20
+ cleanup,
21
+ initVoice,
22
+ resetStoredPermission,
23
+ setPermissionStatusHandlers,
24
+ } from '../utils/audioRecorder';
25
+ import { AppContext } from '../contexts/AppContext';
26
+
27
+ const PERMISSION_STORAGE_KEY = '@voice_permission_status';
28
+
29
+ export const VoiceButton = ({ setInput }) => {
30
+ const { handleVoiceSend, isListening, setIsListening } =
31
+ useContext(AppContext);
32
+ const [loading, setLoading] = useState(false);
33
+ const [permissionChecked, setPermissionChecked] = useState(false);
34
+ const [hasPermission, setHasPermission] = useState(null);
35
+
36
+ // Use your custom AsyncStorage hook
37
+ const [permissionStatus, setPermissionStatus] = useAsyncStorage(
38
+ PERMISSION_STORAGE_KEY,
39
+ null,
40
+ );
41
+
42
+ useEffect(() => {
43
+ // Register our permission handlers
44
+ setPermissionStatusHandlers(() => permissionStatus, setPermissionStatus);
45
+
46
+ const setupVoice = async () => {
47
+ try {
48
+ // Check stored permission first
49
+ if (permissionStatus === 'denied') {
50
+ // We already know permission was denied, don't show alert again
51
+ setHasPermission(false);
52
+ setPermissionChecked(true);
53
+ return;
54
+ }
55
+
56
+ // Only request permission if not already denied
57
+ const permissionResult = await requestAudioPermission();
58
+ setHasPermission(permissionResult);
59
+
60
+ if (permissionResult) {
61
+ const initialized = await initVoice(
62
+ // Final result callback
63
+ (result, error) => {
64
+ console.log('Voice final result:', result, 'Error:', error);
65
+
66
+ // Always reset states when the recognition ends
67
+ setIsListening(false);
68
+ setLoading(false);
69
+
70
+ if (error) {
71
+ // Don't show alert for permission errors since we handle that elsewhere
72
+ if (!error.includes('permission')) {
73
+ Alert.alert('Error', error);
74
+ }
75
+ return;
76
+ }
77
+
78
+ if (result) {
79
+ if (setInput) {
80
+ // For live transcription mode: just update input, don't auto-send
81
+ setInput(result);
82
+ } else {
83
+ // For original mode: send the message automatically
84
+ handleVoiceSend(null, result);
85
+ }
86
+ }
87
+ },
88
+ // Partial result callback for live transcription
89
+ setInput
90
+ ? partialResult => {
91
+ if (partialResult) {
92
+ setInput(partialResult);
93
+ }
94
+ }
95
+ : null,
96
+ );
97
+
98
+ if (!initialized) {
99
+ // Only show this alert once per session
100
+ if (!permissionChecked) {
101
+ Alert.alert(
102
+ 'Error',
103
+ 'Speech recognition is not available on this device',
104
+ );
105
+ }
106
+ }
107
+ }
108
+ } catch (error) {
109
+ console.error('Error in setupVoice:', error);
110
+ } finally {
111
+ setPermissionChecked(true);
112
+ }
113
+ };
114
+
115
+ if (!permissionChecked) {
116
+ setupVoice();
117
+ }
118
+
119
+ return () => {
120
+ // Optional: only if the entire feature is being removed from the app
121
+ // cleanup();
122
+ };
123
+ }, [permissionStatus, permissionChecked]);
124
+
125
+ const toggleRecording = async () => {
126
+ try {
127
+ if (!isListening) {
128
+ // If we already know we don't have permission, show a better message
129
+ if (hasPermission === false) {
130
+ Alert.alert(
131
+ 'Permission Required',
132
+ 'Voice recognition requires microphone permission. Would you like to update your settings?',
133
+ [
134
+ {
135
+ text: 'Cancel',
136
+ style: 'cancel',
137
+ },
138
+ {
139
+ text: 'Settings',
140
+ onPress: () => {
141
+ // Reset stored permission so we can check again
142
+ setPermissionStatus(null);
143
+ // Open device settings
144
+ openAppSettings();
145
+ // Reset our permission check
146
+ setPermissionChecked(false);
147
+ },
148
+ },
149
+ ],
150
+ );
151
+ return;
152
+ }
153
+
154
+ setLoading(true);
155
+ const checkPermission = await requestAudioPermission();
156
+ if (!checkPermission) {
157
+ setHasPermission(false);
158
+ setLoading(false);
159
+ return;
160
+ }
161
+
162
+ const started = await startRecording();
163
+ if (started) {
164
+ setIsListening(true);
165
+ } else {
166
+ // Only show error if not permission related
167
+ Alert.alert('Error', 'Failed to start voice recognition');
168
+ }
169
+ } else {
170
+ setLoading(true);
171
+ setIsListening(false);
172
+ await stopRecording();
173
+ }
174
+ } catch (error) {
175
+ console.error('Error in toggleRecording:', error);
176
+ Alert.alert(
177
+ 'Error',
178
+ 'An error occurred while managing voice recognition',
179
+ );
180
+ setIsListening(false);
181
+ // Don't call cleanup here - let the natural session cleanup handle it
182
+ } finally {
183
+ setLoading(false);
184
+ }
185
+ };
186
+
187
+ const openAppSettings = async () => {
188
+ try {
189
+ if (Platform.OS === 'ios') {
190
+ // For iOS
191
+ await Linking.openURL('app-settings://');
192
+ } else {
193
+ // For Android
194
+ await Linking.openSettings();
195
+ }
196
+ } catch (error) {
197
+ console.error('Cannot open settings', error);
198
+ Alert.alert(
199
+ 'Error',
200
+ 'Unable to open settings. Please open settings manually.',
201
+ );
202
+ }
203
+ };
204
+
205
+ return (
206
+ <TouchableOpacity
207
+ style={styles.button}
208
+ onPress={toggleRecording}
209
+ disabled={loading}>
210
+ {loading ? (
211
+ <ActivityIndicator size="small" color="#8E8E93" />
212
+ ) : (
213
+ <Ionicons
214
+ name={isListening ? 'stop-circle' : 'mic-outline'}
215
+ size={24}
216
+ color="#161616"
217
+ />
218
+ )}
219
+ </TouchableOpacity>
220
+ );
221
+ };
222
+
223
+ const styles = StyleSheet.create({
224
+ button: {
225
+ justifyContent: 'center',
226
+ alignItems: 'center',
227
+ },
228
+ });
@@ -0,0 +1,161 @@
1
+ // WelcomeButton.js
2
+ import React, { useState, useContext } from 'react';
3
+ import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
4
+ import { AppContext } from '../contexts/AppContext';
5
+ import Ionicons from 'react-native-vector-icons/Ionicons';
6
+
7
+ const landscapeQuestions = [
8
+ { text: 'What are the hours of my current branch?' },
9
+ { text: 'Do you have part # RBLESPLXME2 in stock?' },
10
+ { text: 'How do I install the seal on B82456?' },
11
+ { text: 'Where can I find my POs?' },
12
+ { text: '¿Puedes ayudarme en español?' },
13
+ ];
14
+
15
+ const poolQuestions = [
16
+ { text: 'What are the hours of my current branch?' },
17
+ { text: 'Do you have part # JNDDEV48 in stock?' },
18
+ { text: 'Do you carry Hayward super pumps?' },
19
+ { text: 'Which grid assembly goes with the Hayward DE4820 filter?' },
20
+ { text: '¿Puedes ayudarme en español?' },
21
+ ];
22
+
23
+ const ButtonComponent = () => {
24
+ const {
25
+ toggleLanguage,
26
+ getCurrentTranslations,
27
+ handleButtonClick,
28
+ theme,
29
+ data,
30
+ } = useContext(AppContext);
31
+ const isPool = data?.brand_version === 'pool';
32
+ const suggestedQuestions = isPool ? poolQuestions : landscapeQuestions;
33
+
34
+ const [suggestedQuestionsFromCategory, setSuggestedQuestionsFromCategory] =
35
+ useState([]);
36
+
37
+ const translations = getCurrentTranslations();
38
+
39
+ const starterCategories = [
40
+ {
41
+ key: 'products',
42
+ text: 'Products',
43
+ relatedQuestions: translations.followUpQuestions.products,
44
+ },
45
+ {
46
+ key: 'orders',
47
+ text: 'Orders',
48
+ relatedQuestions: translations.followUpQuestions.orders,
49
+ },
50
+ {
51
+ key: 'invoices',
52
+ text: 'Invoices',
53
+ relatedQuestions: translations.followUpQuestions.invoices,
54
+ },
55
+ {
56
+ key: 'branchInfo',
57
+ text: 'Branch Information',
58
+ relatedQuestions: translations.followUpQuestions.branchInfo,
59
+ },
60
+ {
61
+ key: 'spanish',
62
+ text: 'Ayuda es Español',
63
+ relatedQuestions: [],
64
+ },
65
+ {
66
+ key: 'reset',
67
+ text: '',
68
+ relatedQuestions: [],
69
+ },
70
+ ];
71
+
72
+ const handleCategoryPick = item => {
73
+ console.log(item);
74
+
75
+ if (item.key === 'spanish') {
76
+ console.log('switching to spanish');
77
+ toggleLanguage();
78
+ } else if (item.key === 'reset') {
79
+ setSuggestedQuestionsFromCategory(item.relatedQuestions);
80
+ } else {
81
+ setSuggestedQuestionsFromCategory(item.relatedQuestions);
82
+ }
83
+ };
84
+
85
+ return (
86
+ <View style={styles.buttonContainer}>
87
+ {suggestedQuestionsFromCategory.length !== 0 ? (
88
+ <View>
89
+ <TouchableOpacity
90
+ onPress={() =>
91
+ setSuggestedQuestionsFromCategory(
92
+ starterCategories[starterCategories.length - 1]
93
+ .relatedQuestions,
94
+ )
95
+ }>
96
+ <Ionicons
97
+ name="arrow-back-outline"
98
+ size={16}
99
+ color="#367CB6"
100
+ style={{ marginBottom: 10 }}
101
+ />
102
+ </TouchableOpacity>
103
+ {suggestedQuestionsFromCategory.map((item, index) => (
104
+ <TouchableOpacity
105
+ key={index}
106
+ style={[styles.button, { borderColor: '#004687' }]}
107
+ onPress={() => handleButtonClick(item)}>
108
+ <Text style={[styles.buttonTextQuestion, { color: '#004687' }]}>
109
+ {item}
110
+ </Text>
111
+ </TouchableOpacity>
112
+ ))}
113
+ </View>
114
+ ) : (
115
+ starterCategories.map((item, index) => {
116
+ if (item.key === 'reset') {
117
+ return null;
118
+ } else {
119
+ return (
120
+ <TouchableOpacity
121
+ key={index}
122
+ style={[styles.button, { borderColor: '#004687' }]}
123
+ onPress={() => handleCategoryPick(item)}>
124
+ <Text style={[styles.buttonText, { color: '#004687' }]}>
125
+ {item.text}
126
+ </Text>
127
+ </TouchableOpacity>
128
+ );
129
+ }
130
+ })
131
+ )}
132
+ </View>
133
+ );
134
+ };
135
+
136
+ const styles = StyleSheet.create({
137
+ buttonContainer: {},
138
+ button: {
139
+ backgroundColor: 'white',
140
+ borderColor: '#004687',
141
+ borderWidth: 1,
142
+ borderRadius: 18,
143
+ paddingVertical: 14,
144
+ paddingHorizontal: 16,
145
+ marginBottom: 10,
146
+ },
147
+ buttonText: {
148
+ color: '#004687',
149
+ fontSize: 13,
150
+ fontWeight: '400',
151
+ textAlign: 'center',
152
+ },
153
+ buttonTextQuestion: {
154
+ color: '#004687',
155
+ fontSize: 13,
156
+ fontWeight: '400',
157
+ textAlign: 'left',
158
+ },
159
+ });
160
+
161
+ export default ButtonComponent;
@@ -0,0 +1,133 @@
1
+ import React, { useState, useEffect, useContext, useRef } from 'react';
2
+ import {
3
+ Text,
4
+ StyleSheet,
5
+ View,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ Platform,
9
+ KeyboardAvoidingView,
10
+ Keyboard,
11
+ } from 'react-native';
12
+ import { Header } from '../components/header';
13
+ import { AppContext } from '../contexts/AppContext';
14
+ import Ionicons from 'react-native-vector-icons/Ionicons';
15
+ import { VoiceButton } from './voice';
16
+
17
+ export const WelcomeInput = ({ onProductCardClick, onAddToCartClick }) => {
18
+ const { data, handleSend, input, setInput, showModal, theme, isListening } =
19
+ useContext(AppContext);
20
+ const inputRef = useRef(null);
21
+
22
+ const handleKeyPress = ({ nativeEvent }) => {
23
+ if (nativeEvent.key === 'return' && !nativeEvent.shiftKey) {
24
+ nativeEvent.preventDefault && nativeEvent.preventDefault();
25
+ handleSend(input);
26
+ return;
27
+ }
28
+ };
29
+
30
+ const onSubmitEditing = () => {
31
+ if (input.trim()) {
32
+ handleSend(input);
33
+ }
34
+ };
35
+
36
+ return (
37
+ <View style={styles.ringContainer}>
38
+ <View style={styles.inputContainer}>
39
+ <TextInput
40
+ ref={inputRef}
41
+ style={styles.input}
42
+ value={input}
43
+ onChangeText={setInput}
44
+ placeholder={isListening && !input ? 'Listening...' : 'Ask away...'}
45
+ placeholderTextColor="#999"
46
+ editable={!isListening}
47
+ caretHidden={isListening}
48
+ showSoftInputOnFocus={!isListening}
49
+ multiline={false}
50
+ returnKeyType="send"
51
+ enablesReturnKeyAutomatically={true}
52
+ onKeyPress={handleKeyPress}
53
+ onSubmitEditing={onSubmitEditing}
54
+ selection={undefined}
55
+ // blurOnSubmit={false}
56
+ />
57
+ <VoiceButton
58
+ setInput={text => {
59
+ setInput(text);
60
+ if (!isListening && inputRef.current) {
61
+ if (Platform.OS === 'android') {
62
+ setTimeout(() => {
63
+ if (inputRef.current) {
64
+ inputRef.current.setSelection(text.length, text.length);
65
+ }
66
+ }, 10);
67
+ } else {
68
+ inputRef.current.setSelection(text.length, text.length);
69
+ }
70
+ }
71
+ }}
72
+ />
73
+ <TouchableOpacity
74
+ style={styles.sendButton}
75
+ onPress={() => handleSend(input)}
76
+ disabled={!input.trim()}>
77
+ <Ionicons
78
+ name="paper-plane-outline"
79
+ size={24}
80
+ color={input.trim() ? theme.primaryColor : '#161616'}
81
+ />
82
+ </TouchableOpacity>
83
+ </View>
84
+ </View>
85
+ );
86
+ };
87
+
88
+ const styles = StyleSheet.create({
89
+ ringContainer: {
90
+ paddingHorizontal: 0.5,
91
+ paddingVertical: 0.5,
92
+ borderRadius: 18,
93
+ borderWidth: 3,
94
+ borderColor: '#FFFFFF', // Or your theme color
95
+ backgroundColor: '#FFFFFF',
96
+ },
97
+ inputContainer: {
98
+ flexDirection: 'row',
99
+ alignItems: 'center',
100
+ paddingHorizontal: 8,
101
+ paddingVertical: 8,
102
+ backgroundColor: '#FFFFFF',
103
+ borderRadius: 16,
104
+ shadowColor: '#000',
105
+ shadowOffset: {
106
+ width: 0,
107
+ height: 2,
108
+ },
109
+ shadowOpacity: 0.1,
110
+ shadowRadius: 4,
111
+ elevation: 3,
112
+ borderColor: '#dadada',
113
+ borderWidth: 1,
114
+ borderRadius: 14,
115
+ },
116
+ input: {
117
+ flex: 1,
118
+ fontSize: 16,
119
+ paddingVertical: 8,
120
+ paddingHorizontal: 12,
121
+ color: '#000000',
122
+ },
123
+ inputButton: {
124
+ padding: 6,
125
+ },
126
+ sendButton: {
127
+ padding: 6,
128
+ marginLeft: 'auto',
129
+ },
130
+ disabledButton: {
131
+ opacity: 0.7,
132
+ },
133
+ });