vdb-ai-chat 1.0.1 → 1.0.2

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 (114) hide show
  1. package/dist/10.chat-widget.js +2 -0
  2. package/dist/10.chat-widget.js.LICENSE.txt +1 -0
  3. package/dist/104.chat-widget.js +1 -0
  4. package/dist/50.chat-widget.js +1 -0
  5. package/dist/521.chat-widget.js +1 -0
  6. package/dist/538.chat-widget.js +1 -1
  7. package/dist/572.chat-widget.js +1 -0
  8. package/dist/694.chat-widget.js +1 -0
  9. package/dist/chat-widget.js +1 -1
  10. package/lib/commonjs/api.js +4 -3
  11. package/lib/commonjs/api.js.map +1 -1
  12. package/lib/commonjs/components/BetaNotice.js +38 -0
  13. package/lib/commonjs/components/BetaNotice.js.map +1 -0
  14. package/lib/commonjs/components/ChatHeader.js +27 -20
  15. package/lib/commonjs/components/ChatHeader.js.map +1 -1
  16. package/lib/commonjs/components/ChatInput.js +21 -21
  17. package/lib/commonjs/components/ChatInput.js.map +1 -1
  18. package/lib/commonjs/components/ChatWidget.js +119 -72
  19. package/lib/commonjs/components/ChatWidget.js.map +1 -1
  20. package/lib/commonjs/components/MessageBubble.js +26 -90
  21. package/lib/commonjs/components/MessageBubble.js.map +1 -1
  22. package/lib/commonjs/components/MessageMetaRow.js +135 -0
  23. package/lib/commonjs/components/MessageMetaRow.js.map +1 -0
  24. package/lib/commonjs/components/ProductsGrid.js +139 -0
  25. package/lib/commonjs/components/ProductsGrid.js.map +1 -0
  26. package/lib/commonjs/components/ProductsList.js +22 -126
  27. package/lib/commonjs/components/ProductsList.js.map +1 -1
  28. package/lib/commonjs/components/ProductsListView.js +139 -0
  29. package/lib/commonjs/components/ProductsListView.js.map +1 -0
  30. package/lib/commonjs/components/SuggestionsRow.js +41 -23
  31. package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
  32. package/lib/commonjs/components/utils.js +4 -3
  33. package/lib/commonjs/components/utils.js.map +1 -1
  34. package/lib/commonjs/index.web.js +86 -29
  35. package/lib/commonjs/index.web.js.map +1 -1
  36. package/lib/commonjs/theme.js +4 -4
  37. package/lib/commonjs/theme.js.map +1 -1
  38. package/lib/module/api.js +4 -3
  39. package/lib/module/api.js.map +1 -1
  40. package/lib/module/components/BetaNotice.js +30 -0
  41. package/lib/module/components/BetaNotice.js.map +1 -0
  42. package/lib/module/components/ChatHeader.js +27 -20
  43. package/lib/module/components/ChatHeader.js.map +1 -1
  44. package/lib/module/components/ChatInput.js +21 -21
  45. package/lib/module/components/ChatInput.js.map +1 -1
  46. package/lib/module/components/ChatWidget.js +120 -73
  47. package/lib/module/components/ChatWidget.js.map +1 -1
  48. package/lib/module/components/MessageBubble.js +26 -92
  49. package/lib/module/components/MessageBubble.js.map +1 -1
  50. package/lib/module/components/MessageMetaRow.js +127 -0
  51. package/lib/module/components/MessageMetaRow.js.map +1 -0
  52. package/lib/module/components/ProductsGrid.js +133 -0
  53. package/lib/module/components/ProductsGrid.js.map +1 -0
  54. package/lib/module/components/ProductsList.js +21 -126
  55. package/lib/module/components/ProductsList.js.map +1 -1
  56. package/lib/module/components/ProductsListView.js +132 -0
  57. package/lib/module/components/ProductsListView.js.map +1 -0
  58. package/lib/module/components/SuggestionsRow.js +41 -23
  59. package/lib/module/components/SuggestionsRow.js.map +1 -1
  60. package/lib/module/components/utils.js +4 -3
  61. package/lib/module/components/utils.js.map +1 -1
  62. package/lib/module/index.web.js +86 -29
  63. package/lib/module/index.web.js.map +1 -1
  64. package/lib/module/theme.js +4 -4
  65. package/lib/module/theme.js.map +1 -1
  66. package/lib/typescript/api.d.ts.map +1 -1
  67. package/lib/typescript/components/BetaNotice.d.ts +5 -0
  68. package/lib/typescript/components/BetaNotice.d.ts.map +1 -0
  69. package/lib/typescript/components/ChatHeader.d.ts +5 -2
  70. package/lib/typescript/components/ChatHeader.d.ts.map +1 -1
  71. package/lib/typescript/components/ChatInput.d.ts.map +1 -1
  72. package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
  73. package/lib/typescript/components/MessageBubble.d.ts +7 -3
  74. package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
  75. package/lib/typescript/components/MessageMetaRow.d.ts +14 -0
  76. package/lib/typescript/components/MessageMetaRow.d.ts.map +1 -0
  77. package/lib/typescript/components/ProductsGrid.d.ts +10 -0
  78. package/lib/typescript/components/ProductsGrid.d.ts.map +1 -0
  79. package/lib/typescript/components/ProductsList.d.ts +4 -2
  80. package/lib/typescript/components/ProductsList.d.ts.map +1 -1
  81. package/lib/typescript/components/ProductsListView.d.ts +10 -0
  82. package/lib/typescript/components/ProductsListView.d.ts.map +1 -0
  83. package/lib/typescript/components/SuggestionsRow.d.ts +2 -0
  84. package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
  85. package/lib/typescript/components/utils.d.ts +1 -0
  86. package/lib/typescript/components/utils.d.ts.map +1 -1
  87. package/lib/typescript/index.web.d.ts +1 -1
  88. package/lib/typescript/index.web.d.ts.map +1 -1
  89. package/lib/typescript/types.d.ts +3 -1
  90. package/lib/typescript/types.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/api.ts +4 -3
  93. package/src/components/BetaNotice.tsx +32 -0
  94. package/src/components/ChatHeader.tsx +32 -18
  95. package/src/components/ChatInput.tsx +20 -21
  96. package/src/components/ChatWidget.tsx +258 -220
  97. package/src/components/MessageBubble.tsx +44 -149
  98. package/src/components/MessageMetaRow.tsx +199 -0
  99. package/src/components/ProductsGrid.tsx +163 -0
  100. package/src/components/ProductsList.tsx +20 -146
  101. package/src/components/ProductsListView.tsx +149 -0
  102. package/src/components/SuggestionsRow.tsx +45 -21
  103. package/src/components/utils.ts +6 -4
  104. package/src/index.web.tsx +87 -32
  105. package/src/theme.ts +4 -4
  106. package/src/types.ts +3 -2
  107. package/dist/751.chat-widget.js +0 -1
  108. package/lib/commonjs/contexts/SegmentClientContext.js +0 -19
  109. package/lib/commonjs/contexts/SegmentClientContext.js.map +0 -1
  110. package/lib/module/contexts/SegmentClientContext.js +0 -10
  111. package/lib/module/contexts/SegmentClientContext.js.map +0 -1
  112. package/lib/typescript/contexts/SegmentClientContext.d.ts +0 -9
  113. package/lib/typescript/contexts/SegmentClientContext.d.ts.map +0 -1
  114. package/src/contexts/SegmentClientContext.tsx +0 -20
@@ -1,61 +1,39 @@
1
1
  import React, { memo } from "react";
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- Image,
7
- TouchableOpacity,
8
- Platform,
9
- ActivityIndicator,
10
- } from "react-native";
2
+ import { View, Text, StyleSheet } from "react-native";
3
+ import SuggestionsRow from "./SuggestionsRow";
11
4
  import type { ChatMessage, ChatTheme } from "../types";
12
- import { FeedbackAction, formatToTime } from "./utils";
13
-
14
- // Use expo-image on native if available, fallback to RN Image
15
- let ImageComponent: typeof Image = Image;
16
- if (Platform.OS !== "web") {
17
- try {
18
- const ExpoImage = require("expo-image").Image;
19
- if (ExpoImage) ImageComponent = ExpoImage;
20
- } catch {
21
- // expo-image not installed, use React Native Image
22
- }
23
- }
24
5
 
25
6
  interface Props {
26
7
  message: ChatMessage;
27
8
  theme: ChatTheme;
28
- conversationId: string | null;
29
- handleFeedbackAction: (
30
- action: FeedbackAction,
31
- conversation_id: string,
32
- message_id: string
33
- ) => void;
9
+ priceMode?: string;
10
+ handleFeedbackAction?: (...args: any[]) => void;
34
11
  onReloadResults?: (message: ChatMessage) => void;
35
12
  reloading?: boolean;
36
13
  hasResults?: boolean;
14
+ totalResults?: number;
15
+ shownResults?: number;
16
+ onSuggestionSelect?: (value: string) => void;
17
+ isLatest?: boolean;
18
+ isTyping?: boolean;
37
19
  }
38
20
 
39
21
  const MessageBubbleComponent: React.FC<Props> = ({
40
22
  message,
41
23
  theme,
42
- conversationId,
24
+ priceMode,
43
25
  handleFeedbackAction,
44
26
  onReloadResults,
45
27
  reloading,
46
28
  hasResults,
29
+ totalResults,
30
+ shownResults,
31
+ onSuggestionSelect,
32
+ isLatest,
33
+ isTyping,
47
34
  }) => {
48
35
  const isUser = message.role === "user";
49
- const isValidMessageId =
50
- typeof message.id === "string" &&
51
- message.id.length > 0 &&
52
- !message.id.startsWith("bot-loading-");
53
- const canFeedback =
54
- message.role === "assistant" &&
55
- !message.isLoading &&
56
- isValidMessageId &&
57
- !!conversationId;
58
-
36
+
59
37
  return (
60
38
  <View
61
39
  style={[styles.container, isUser ? styles.alignRight : styles.alignLeft]}
@@ -67,9 +45,9 @@ const MessageBubbleComponent: React.FC<Props> = ({
67
45
  backgroundColor: isUser
68
46
  ? theme.userBubbleColor
69
47
  : theme.botBubbleColor,
70
- borderRadius: theme.borderRadius || 4,
71
- borderTopRightRadius: isUser ? 4 : theme.borderRadius,
72
- borderTopLeftRadius: isUser ? theme.borderRadius : 4,
48
+ borderRadius: theme.borderRadius || 8,
49
+ borderBottomRightRadius: isUser ? 0 : theme.borderRadius,
50
+ borderBottomLeftRadius: isUser ? theme.borderRadius : 0,
73
51
  },
74
52
  ]}
75
53
  >
@@ -84,100 +62,26 @@ const MessageBubbleComponent: React.FC<Props> = ({
84
62
  ]}
85
63
  >
86
64
  {message.text || ""}
65
+ {message.role === "assistant" && hasResults && typeof totalResults === "number" ? (
66
+ ` Found ${Number(totalResults).toLocaleString()} diamonds (showing first ${shownResults})`
67
+ ) : null}
87
68
  </Text>
88
- </View>
89
- {(canFeedback ||
90
- (message.search_payload &&
91
- typeof message.search_payload === "object" &&
92
- Object.keys(message.search_payload).length > 0)) && (
93
- <View style={styles.rowContainer}>
94
- <View style={styles.time}>
95
- <Text>{formatToTime(message.createdAt)}</Text>
96
- </View>
97
- {message.search_payload &&
98
- typeof message.search_payload === "object" &&
99
- Object.keys(message.search_payload).length > 0 &&
100
- !hasResults &&
101
- (reloading ? (
102
- <View
103
- style={{ flexDirection: "row", alignItems: "center", gap: 6 }}
104
- >
105
- <ActivityIndicator size="small" color="#1a73e8" />
106
- <Text
107
- style={{
108
- fontSize: 12,
109
- color: "#1a73e8",
110
- textDecorationLine: "underline",
111
- }}
112
- >
113
- Loading…
114
- </Text>
115
- </View>
116
- ) : (
117
- <TouchableOpacity
118
- onPress={() => onReloadResults?.(message)}
119
- disabled={reloading}
120
- >
121
- <Text
122
- style={{
123
- fontSize: 12,
124
- color: "#1a73e8",
125
- textDecorationLine: "underline",
126
- }}
127
- >
128
- Reload Results
129
- </Text>
130
- </TouchableOpacity>
131
- ))}
132
- <View style={styles.likeDislikeContainer}>
133
- <TouchableOpacity
134
- onPress={() =>
135
- handleFeedbackAction(
136
- FeedbackAction.LIKE,
137
- String(conversationId),
138
- message.id
139
- )
140
- }
141
- disabled={!canFeedback}
142
- >
143
- <ImageComponent
144
- source={{
145
- uri:
146
- message.reaction === FeedbackAction.LIKE
147
- ? "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like-filled.svg"
148
- : "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like.svg",
149
- }}
150
- resizeMode="contain"
151
- style={{ width: 16, height: 16 }}
69
+ {message.role === "assistant" &&
70
+ Array.isArray(message.suggestions) &&
71
+ message.suggestions.length > 0 &&
72
+ isLatest &&
73
+ !isTyping && (
74
+ <View style={styles.suggestionsWrapper}>
75
+ <SuggestionsRow
76
+ suggestions={message.suggestions}
77
+ onSelect={(v) => onSuggestionSelect?.(v)}
78
+ variant="inline"
152
79
  />
153
- </TouchableOpacity>
154
- <TouchableOpacity
155
- onPress={() =>
156
- handleFeedbackAction(
157
- FeedbackAction.DISLIKE,
158
- String(conversationId),
159
- message.id
160
- )
161
- }
162
- disabled={!canFeedback}
163
- >
164
- <ImageComponent
165
- source={{
166
- uri:
167
- message.reaction === FeedbackAction.DISLIKE
168
- ? "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike-filled.svg"
169
- : "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike.svg",
170
- }}
171
- resizeMode="contain"
172
- style={{ width: 16, height: 16 }}
173
- />
174
- </TouchableOpacity>
175
- </View>
176
- </View>
177
- )}
178
- </View>
179
- );
180
- };
80
+ </View>
81
+ )}
82
+ </View>
83
+ </View>
84
+ )};
181
85
 
182
86
  // Memoize to prevent re-renders when parent re-renders
183
87
  export const MessageBubble = memo(MessageBubbleComponent);
@@ -185,21 +89,10 @@ export const MessageBubble = memo(MessageBubbleComponent);
185
89
  const styles = StyleSheet.create({
186
90
  container: {
187
91
  paddingHorizontal: 8,
188
- marginVertical: 4,
92
+ marginVertical: 0,
189
93
  width: "100%",
190
94
  },
191
- rowContainer: {
192
- flexDirection: "row",
193
- justifyContent: "space-between",
194
- alignItems: "center",
195
- gap: 12,
196
- marginTop: 8,
197
- marginBottom: 8,
198
- },
199
- likeDislikeContainer: {
200
- flexDirection: "row",
201
- gap: 12,
202
- },
95
+
203
96
  alignRight: {
204
97
  alignItems: "flex-end",
205
98
  },
@@ -214,10 +107,12 @@ const styles = StyleSheet.create({
214
107
  text: {
215
108
  lineHeight: 20,
216
109
  },
217
- time: {
110
+ resultsInfo: {
111
+ marginTop: 4,
218
112
  fontSize: 12,
219
113
  color: "#666",
220
- marginTop: 4,
221
- marginLeft: 8,
114
+ },
115
+ suggestionsWrapper: {
116
+ marginTop: 8,
222
117
  },
223
118
  });
@@ -0,0 +1,199 @@
1
+ import React from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Image,
7
+ Platform,
8
+ ActivityIndicator,
9
+ Pressable,
10
+ } from "react-native";
11
+ import type { ChatMessage } from "../types";
12
+ import { FeedbackAction, fetchConversationId, formatToTime } from "./utils";
13
+
14
+ let ImageComponent: typeof Image = Image;
15
+ if (Platform.OS !== "web") {
16
+ try {
17
+ const ExpoImage = require("expo-image").Image;
18
+ if (ExpoImage) ImageComponent = ExpoImage;
19
+ } catch {}
20
+ }
21
+
22
+ interface Props {
23
+ message: ChatMessage;
24
+ priceMode: string;
25
+ handleFeedbackAction: (
26
+ action: FeedbackAction,
27
+ conversationId: string,
28
+ message_id: string
29
+ ) => void;
30
+ onReloadResults?: (message: ChatMessage) => void;
31
+ reloading?: boolean;
32
+ hasResults?: boolean;
33
+ }
34
+
35
+ export const MessageMetaRow: React.FC<Props> = ({
36
+ message,
37
+ priceMode,
38
+ handleFeedbackAction,
39
+ onReloadResults,
40
+ reloading,
41
+ hasResults,
42
+ }) => {
43
+ const isUser = message.role === "user";
44
+ const [conversationId, setConversationId] = React.useState<string | null>(
45
+ null
46
+ );
47
+
48
+ React.useEffect(() => {
49
+ const fetchId = async () => {
50
+ const id = await fetchConversationId(priceMode);
51
+ setConversationId(id);
52
+ };
53
+ fetchId();
54
+ }, [priceMode]);
55
+
56
+ const isValidMessageId =
57
+ typeof message.id === "string" &&
58
+ message.id.length > 0 &&
59
+ !message.id.startsWith("bot-loading-");
60
+ const canFeedback =
61
+ message.role === "assistant" &&
62
+ !message.isLoading &&
63
+ isValidMessageId &&
64
+ !!conversationId;
65
+
66
+ return (
67
+ <View
68
+ style={[
69
+ styles.rowContainer,
70
+ isUser ? styles.alignRight : styles.alignLeft,
71
+ ]}
72
+ >
73
+ <View style={styles.timeContainer}>
74
+ <Text style={styles.timeText}>{formatToTime(message.createdAt)}</Text>
75
+ </View>
76
+ {canFeedback ? (
77
+ <View style={styles.likeDislikeContainer}>
78
+ <Pressable
79
+ style={(state: any) => [
80
+ styles.borderButton,
81
+ state?.hovered && styles.borderButtonHover,
82
+ ]}
83
+ onPress={() =>
84
+ handleFeedbackAction(
85
+ FeedbackAction.LIKE,
86
+ String(conversationId),
87
+ message.id
88
+ )
89
+ }
90
+ disabled={!canFeedback}
91
+ >
92
+ <ImageComponent
93
+ source={{
94
+ uri:
95
+ message.reaction === FeedbackAction.LIKE
96
+ ? "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like-filled.svg"
97
+ : "https://cdn.vdbapp.com/ai/chat-widget/assets/img/like.svg",
98
+ }}
99
+ resizeMode="contain"
100
+ style={{ width: 16, height: 16 }}
101
+ />
102
+ </Pressable>
103
+ <Pressable
104
+ style={(state: any) => [
105
+ styles.borderButton,
106
+ state?.hovered && styles.borderButtonHover,
107
+ ]}
108
+ onPress={() =>
109
+ handleFeedbackAction(
110
+ FeedbackAction.DISLIKE,
111
+ String(conversationId),
112
+ message.id
113
+ )
114
+ }
115
+ disabled={!canFeedback}
116
+ >
117
+ <ImageComponent
118
+ source={{
119
+ uri:
120
+ message.reaction === FeedbackAction.DISLIKE
121
+ ? "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike-filled.svg"
122
+ : "https://cdn.vdbapp.com/ai/chat-widget/assets/img/dislike.svg",
123
+ }}
124
+ resizeMode="contain"
125
+ style={{ width: 16, height: 16 }}
126
+ />
127
+ </Pressable>
128
+ </View>
129
+ ) : null}
130
+ {message.search_payload &&
131
+ typeof message.search_payload === "object" &&
132
+ Object.keys(message.search_payload).length > 0 &&
133
+ !hasResults &&
134
+ (reloading ? (
135
+ <View style={styles.borderButton}>
136
+ <ActivityIndicator size="small" color="#020001" style={{ width: 16, height: 16 }} />
137
+ </View>
138
+ ) : (
139
+ <Pressable
140
+ style={(state: any) => [
141
+ styles.borderButton,
142
+ state?.hovered && styles.borderButtonHover,
143
+ ]}
144
+ onPress={() => onReloadResults?.(message)}
145
+ disabled={reloading}
146
+ >
147
+ <ImageComponent
148
+ source={{
149
+ uri: "https://cdn.vdbapp.com/ai/chat-widget/assets/img/arrow-reset.svg",
150
+ }}
151
+ resizeMode="contain"
152
+ style={{ width: 16, height: 16 }}
153
+ />
154
+ </Pressable>
155
+ ))}
156
+ </View>
157
+ );
158
+ };
159
+
160
+ const styles = StyleSheet.create({
161
+ rowContainer: {
162
+ flexDirection: "row",
163
+ justifyContent: "space-between",
164
+ alignItems: "center",
165
+ gap: 6,
166
+ marginHorizontal: 8,
167
+ marginTop: 0,
168
+ marginBottom: 12,
169
+ },
170
+ alignRight: {
171
+ alignSelf: "flex-end",
172
+ },
173
+ alignLeft: {
174
+ alignSelf: "flex-start",
175
+ },
176
+ likeDislikeContainer: {
177
+ flexDirection: "row",
178
+ gap: 6,
179
+ },
180
+ timeContainer: {
181
+ margin: 0,
182
+ },
183
+ borderButton: {
184
+ borderWidth: 1,
185
+ borderColor: "#D5D5DC",
186
+ borderRadius: 8,
187
+ paddingHorizontal: 4,
188
+ paddingVertical: 4,
189
+ },
190
+ borderButtonHover: {
191
+ backgroundColor: "#EDEDF2",
192
+ },
193
+ timeText: {
194
+ fontSize: 12,
195
+ color: "#666",
196
+ },
197
+ });
198
+
199
+ export default MessageMetaRow;
@@ -0,0 +1,163 @@
1
+ import {
2
+ View,
3
+ Image,
4
+ StyleSheet,
5
+ Text,
6
+ ScrollView,
7
+ TouchableOpacity,
8
+ Platform,
9
+ } from "react-native";
10
+ import React, { memo } from "react";
11
+
12
+ // Use expo-image on native if available, fallback to RN Image
13
+ let ImageComponent: typeof Image = Image;
14
+ if (Platform.OS !== "web") {
15
+ try {
16
+ const ExpoImage = require("expo-image").Image;
17
+ if (ExpoImage) ImageComponent = ExpoImage;
18
+ } catch {
19
+ // expo-image not installed, use React Native Image
20
+ }
21
+ }
22
+
23
+ interface ProductsGridProps {
24
+ data: any[];
25
+ onViewAll?: () => void;
26
+ onItemPress?: (item: any) => void;
27
+ totalResults?: number;
28
+ }
29
+
30
+ const ProductsGridComponent: React.FC<ProductsGridProps> = ({
31
+ data,
32
+ onViewAll,
33
+ onItemPress,
34
+ }) => {
35
+ if (!data || !data.length) return null;
36
+
37
+ return (
38
+ <View style={styles.wrapper}>
39
+ <ScrollView
40
+ horizontal
41
+ showsHorizontalScrollIndicator={false}
42
+ contentContainerStyle={styles.listContent}
43
+ >
44
+ {data.map((item: any) => (
45
+ <TouchableOpacity
46
+ key={item.id}
47
+ onPress={() => {
48
+ onItemPress?.(item);
49
+ }}
50
+ >
51
+ <View key={item.id} style={styles.card}>
52
+ {item.image_thumb_url ? (
53
+ <ImageComponent
54
+ style={styles.image}
55
+ source={{ uri: item.image_thumb_url }}
56
+ />
57
+ ) : null}
58
+
59
+ <View style={styles.content}>
60
+ <Text numberOfLines={2} style={styles.title}>
61
+ {item.short_title}
62
+ </Text>
63
+ <Text style={styles.price}>${item.total_sales_price}</Text>
64
+ </View>
65
+ </View>
66
+ </TouchableOpacity>
67
+ ))}
68
+ </ScrollView>
69
+
70
+ {/* View All Button */}
71
+ <TouchableOpacity
72
+ style={styles.button}
73
+ activeOpacity={0.8}
74
+ onPress={onViewAll}
75
+ >
76
+ <Text style={styles.buttonText}>View All {">>"}</Text>
77
+ </TouchableOpacity>
78
+ </View>
79
+ );
80
+ };
81
+
82
+ const styles = StyleSheet.create({
83
+ wrapper: {
84
+ paddingHorizontal: 12,
85
+ marginHorizontal: 12,
86
+ marginBottom: 12,
87
+ paddingTop: 6,
88
+ paddingBottom: 14,
89
+ backgroundColor: "#fff",
90
+ borderRadius: 8,
91
+ borderWidth: 1,
92
+ borderColor: "#e8e8e8",
93
+ elevation: 3,
94
+ shadowColor: "#000",
95
+ shadowOpacity: 0.08,
96
+ shadowOffset: { width: 0, height: 2 },
97
+ shadowRadius: 6,
98
+ },
99
+
100
+ listContent: {
101
+ gap: 12,
102
+ paddingVertical: 6,
103
+ },
104
+
105
+ card: {
106
+ width: 150,
107
+ backgroundColor: "#fff",
108
+ borderRadius: 14,
109
+ overflow: "hidden",
110
+ borderColor: "#e8e8e8",
111
+ borderWidth: 1,
112
+ elevation: 3,
113
+ shadowColor: "#000",
114
+ shadowOpacity: 0.08,
115
+ shadowOffset: { width: 0, height: 2 },
116
+ shadowRadius: 6,
117
+ },
118
+
119
+ image: {
120
+ width: "100%",
121
+ height: 120,
122
+ borderBottomWidth: 1,
123
+ borderBottomColor: "#e8e8e8",
124
+ },
125
+
126
+ content: {
127
+ padding: 10,
128
+ gap: 4,
129
+ },
130
+
131
+ title: {
132
+ fontSize: 13,
133
+ color: "#222",
134
+ fontWeight: "500",
135
+ },
136
+
137
+ price: {
138
+ marginTop: 4,
139
+ fontSize: 14,
140
+ fontWeight: "700",
141
+ color: "#000",
142
+ },
143
+
144
+ button: {
145
+ marginTop: 12,
146
+ alignSelf: "center",
147
+ paddingHorizontal: 20,
148
+ paddingVertical: 8,
149
+ backgroundColor: "#804195",
150
+ borderRadius: 20,
151
+ width: 300,
152
+ alignItems: "center",
153
+ },
154
+
155
+ buttonText: {
156
+ color: "#fff",
157
+ fontSize: 14,
158
+ fontWeight: "600",
159
+ },
160
+ });
161
+
162
+ const ProductsGrid = memo(ProductsGridComponent);
163
+ export default ProductsGrid;