@propel-nsl/propel-react-native-sdk 1.1.3 → 1.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@propel-nsl/propel-react-native-sdk",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Propel Mobile SDK - React Native Core",
5
5
  "files": [
6
6
  "src/",
@@ -54,12 +54,12 @@ const HIDDEN_SCREENS = [
54
54
 
55
55
  const getTabBarHeight = (): number => {
56
56
  if (Platform.OS === 'ios') {
57
- return screenHeight > 800 ? VSCALE(40) : VSCALE(45);
57
+ return screenHeight > 800 ? VSCALE(50) : VSCALE(55);
58
58
  }
59
59
  if (Platform.OS === 'android') {
60
- return screenHeight > 800 ? VSCALE(35) : VSCALE(55);
60
+ return screenHeight > 800 ? VSCALE(50) : VSCALE(60);
61
61
  }
62
- return VSCALE(50);
62
+ return VSCALE(55);
63
63
  };
64
64
 
65
65
  // DashboardStack - contains Dashboard and its nested screens
@@ -289,22 +289,26 @@ const styles = StyleSheet.create({
289
289
  backgroundColor: '#FFFFFF',
290
290
  borderTopWidth: 1,
291
291
  borderTopColor: '#E5E7EB',
292
- paddingTop: VSCALE(8),
292
+ paddingTop: VSCALE(6),
293
+ paddingHorizontal: SCALE(4),
293
294
  },
294
295
  tabItem: {
295
296
  flex: 1,
296
297
  alignItems: 'center',
297
298
  justifyContent: 'center',
298
- minHeight: VSCALE(45),
299
+ minHeight: VSCALE(50),
300
+ paddingVertical: VSCALE(4),
299
301
  },
300
302
  tabItemContent: {
301
303
  alignItems: 'center',
302
304
  justifyContent: 'center',
305
+ gap: VSCALE(2),
303
306
  },
304
307
  tabLabel: {
305
- fontSize: SCALE(10),
306
- marginTop: VSCALE(3),
308
+ fontSize: SCALE(9),
309
+ marginTop: VSCALE(2),
307
310
  fontWeight: '400',
311
+ textAlign: 'center',
308
312
  },
309
313
  iconContainer: {
310
314
  justifyContent: 'center',
@@ -384,7 +384,7 @@ const Dashboard: React.FC<Props> = ({ navigation }) => {
384
384
  />
385
385
  </View>
386
386
  <View style={styles.pointsCardCenter}>
387
- <Text style={styles.yourPointsLabel}>Your Points</Text>
387
+ <Text style={styles.yourPointsLabel}>Your Amount</Text>
388
388
  <Text style={styles.pointsValue} numberOfLines={1}>
389
389
  {dashboardInfoData?.current_balance || '0'}
390
390
  </Text>
@@ -202,7 +202,7 @@ const MyCart: React.FC<MyCartProps> = ({ navigation }) => {
202
202
  </Text>
203
203
  <CustomImage source={Images.greyDownArrow} imgStyle={styles.icon} />
204
204
  </TouchableOpacity>
205
- <Text style={styles.productPoints}>{item?.amount_cents / 100} points</Text>
205
+ <Text style={styles.productPoints}>₹{item?.amount_cents / 100}</Text>
206
206
  </View>
207
207
  </View>
208
208
  );
@@ -189,7 +189,7 @@ const toUrlString = (raw: any): string | null => {
189
189
  Qty : {product?.quantity}
190
190
  </Text>
191
191
  <Text style={styles.productPoints}>
192
- {pointsFromCentsFixed(product?.amount_cents, 1)} Points
192
+ {pointsFromCentsFixed(product?.amount_cents, 1)} Amount
193
193
  </Text>
194
194
  </View>
195
195
  </View>
@@ -264,7 +264,7 @@ const toUrlString = (raw: any): string | null => {
264
264
  setAmountModalVisible(true);
265
265
  }}
266
266
  >
267
- <Text style={styles.totalPoints}>{pointsFromCentsFixed(item?.grand_total, 1)} Points</Text>
267
+ <Text style={styles.totalPoints}>{pointsFromCentsFixed(item?.grand_total, 1)} Amount</Text>
268
268
  <CustomImage
269
269
  source={Images.i_blackIcon}
270
270
  imgStyle={[styles.icon, { width: 16, height: 16 }]}
@@ -139,7 +139,7 @@ const PaymentMethod: React.FC<{
139
139
  onPress={() => setShowPointsBreakdown(!showPointsBreakdown)}
140
140
  >
141
141
  <Text style={styles.pointsText}>
142
- {viewMyCartData?.amount_cents / 100} Points
142
+ {viewMyCartData?.amount_cents / 100}
143
143
  </Text>
144
144
  <CustomImage
145
145
  source={
@@ -230,7 +230,7 @@ const PaymentMethod: React.FC<{
230
230
  <View style={styles.bottomCTA}>
231
231
  <View style={styles.priceSection}>
232
232
  <Text style={styles.priceText}>
233
- {viewMyCartData?.total_amount_cents / 100} points
233
+ {viewMyCartData?.total_amount_cents / 100}
234
234
  </Text>
235
235
  <TouchableOpacity
236
236
  activeOpacity={0.7}
@@ -111,7 +111,7 @@ const PointsLog: React.FC<Props> = ({ navigation }) => {
111
111
  <Text
112
112
  style={[styles.tabText, activeTab === "log" && styles.activeTabText]}
113
113
  >
114
- Points Log
114
+ Amount Log
115
115
  </Text>
116
116
  {activeTab === "log" && <View style={styles.tabIndicator} />}
117
117
  </TouchableOpacity>
@@ -222,7 +222,7 @@ const PointsLog: React.FC<Props> = ({ navigation }) => {
222
222
  <View key={index}>
223
223
  <View style={styles.redemptionItem}>
224
224
  <View style={styles.itemDetails}>
225
- <Text style={styles.itemTitle}>Points Redeemed</Text>
225
+ <Text style={styles.itemTitle}>Amount Redeemed</Text>
226
226
  <Text style={styles.itemOrderNo}>
227
227
  Order No : {item?.order_no}
228
228
  </Text>
@@ -255,7 +255,7 @@ const PointsLog: React.FC<Props> = ({ navigation }) => {
255
255
  return (
256
256
  <View style={styles.container}>
257
257
  <MobileHeader
258
- title="Apna Points"
258
+ title="Apna Amount"
259
259
  showBack
260
260
  onBack={() => {
261
261
  if (navigation.canGoBack()) {
@@ -1,4 +1,4 @@
1
1
  export const RedemptionHistoryTitle = "Redemption History"
2
- export const PointsRedeemed = "Points Redeemed"
2
+ export const PointsRedeemed = "Amount Redeemed"
3
3
  export const OrderNo = "Order No :"
4
4
  export const Error = "Error";
@@ -1,12 +1,12 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
5
- ScrollView,
5
+ FlatList,
6
6
  TouchableOpacity,
7
7
  SafeAreaView,
8
- Dimensions,
9
8
  TextInput,
9
+ ActivityIndicator,
10
10
  } from 'react-native';
11
11
  import Svg, { Path, G, Defs, ClipPath, Rect } from 'react-native-svg';
12
12
  import { useFocusEffect, useNavigation } from '@react-navigation/native';
@@ -31,9 +31,6 @@ import {
31
31
  import Images from '../../../src-app/constants/Images';
32
32
  import { colors } from '../../../src-app/styles/colors';
33
33
  import { CATEGORY_LABELS } from '../../../src-app/constants/Labels';
34
- import { formatPointsRange } from '../../../src-app/constants/Formatter';
35
- import { NUMBER } from '../../../src-app/constants/Messages';
36
- import { MAP, PUSH, REDUCE } from '../../../src-app/utils/filterPins';
37
34
  import { ROUTES } from '../../../src-app/constants/Routes';
38
35
 
39
36
  const AmbraneIcon: React.FC = () => (
@@ -137,7 +134,7 @@ const ProductCard: React.FC<{
137
134
  <View style={styles.productInfo}>
138
135
  <Text style={styles.productTitle}>{title}</Text>
139
136
  <Text style={styles.productPoints}>
140
- {((priceCents ?? 0) / 100)} Points
137
+ {((priceCents ?? 0) / 100)} Amount
141
138
  </Text>
142
139
 
143
140
  </View>
@@ -159,12 +156,11 @@ const Redeem: React.FC = () => {
159
156
  const [selectedFilters, setSelectedFilters] = useState(DEFAULT_FILTERS);
160
157
 
161
158
 
162
- const { myProductsListError, viewMyCartData } = useAppSelector(
159
+ const { myProductsListError, viewMyCartData, myProductsListLoadingMore, myProductsListPage, myProductsListHasNextPage } = useAppSelector(
163
160
  (state: RootState) => state.auth,
164
161
  );
165
162
 
166
- const screenWidth = Dimensions.get('window').width;
167
- const cardWidth = (screenWidth - 60) / 2;
163
+ const currentFiltersRef = useRef<{ name?: string; product_types?: string; category_ids?: string }>({});
168
164
 
169
165
  const handleProductPress = (product: ProductData) => {
170
166
  console.log('🚀 Redeem navigating to ProductDetail with id:', product?.id);
@@ -185,6 +181,7 @@ const Redeem: React.FC = () => {
185
181
  console.log('🏪 Redeem Screen: useFocusEffect triggered - refreshing data');
186
182
  setSelectedFilters(DEFAULT_FILTERS);
187
183
  setSearchQuery('');
184
+ currentFiltersRef.current = {};
188
185
  dispatch(myProductsListRequest({}));
189
186
  }, [dispatch])
190
187
  );
@@ -197,19 +194,66 @@ const Redeem: React.FC = () => {
197
194
  (state: RootState) => state.auth,
198
195
  );
199
196
 
197
+ // Log response data
198
+ useEffect(() => {
199
+ console.log('📦 SDK Redeem Response:', JSON.stringify(myProductsListData, null, 2));
200
+ }, [myProductsListData]);
201
+
200
202
  const handleSearch = (text: string) => {
201
203
  console.log('🔍 Redeem Screen: Search triggered with text:', text);
202
204
  setSearchQuery(text);
203
205
  if (text.length >= 3) {
204
206
  console.log('🔍 Redeem Screen: Dispatching search request with name:', text);
207
+ currentFiltersRef.current = { ...currentFiltersRef.current, name: text };
205
208
  dispatch(myProductsListRequest({ name: text }));
206
209
  }
207
210
  if (text.length === 0) {
208
211
  console.log('🔍 Redeem Screen: Dispatching clear search request');
212
+ currentFiltersRef.current = { ...currentFiltersRef.current, name: undefined };
209
213
  dispatch(myProductsListRequest({}));
210
214
  }
211
215
  };
212
216
 
217
+ const handleLoadMore = () => {
218
+ if (!myProductsListHasNextPage || myProductsListLoadingMore || myProductsListLoading) {
219
+ return;
220
+ }
221
+ const nextPage = myProductsListPage + 1;
222
+ dispatch(myProductsListRequest({ ...currentFiltersRef.current, page: nextPage }));
223
+ };
224
+
225
+ const renderProductItem = ({ item, index }: { item: any; index: number }) => (
226
+ <View style={{ flex: 1, marginHorizontal: 8, marginBottom: 28 }}>
227
+ <ProductCard
228
+ key={index}
229
+ title={item?.name}
230
+ priceCents={item?.price?.price_cents}
231
+ imageUrl={item?.image?.url}
232
+ category={item?.product_type}
233
+ logo={
234
+ item?.logo === 'titan' ? (
235
+ <TitanLogo />
236
+ ) : item?.logo === 'ambrane' ? (
237
+ <AmbraneIcon />
238
+ ) : item?.logo === 'rayban' ? (
239
+ <RayBanLogo />
240
+ ) : undefined
241
+ }
242
+ backgroundColor={item?.backgroundColor}
243
+ onPress={() => handleProductPress(item)}
244
+ />
245
+ </View>
246
+ );
247
+
248
+ const renderFooter = () => {
249
+ if (!myProductsListLoadingMore) return null;
250
+ return (
251
+ <View style={styles.paginationFooter}>
252
+ <ActivityIndicator size="small" color={colors.primary} />
253
+ </View>
254
+ );
255
+ };
256
+
213
257
  const navigateToCart = () => {
214
258
  // In SDK, MyCart is a hidden tab screen, navigate directly to it
215
259
  navigation.navigate(ROUTES.MyCart as any, { from: ROUTES.REDEEM });
@@ -261,52 +305,22 @@ const Redeem: React.FC = () => {
261
305
 
262
306
  {myProductsListLoading ? (
263
307
  <CustomLoader loading={myProductsListLoading} />
264
- ) : (
265
- <ScrollView
308
+ ) : Array.isArray(myProductsListData) && myProductsListData?.length > 0 ? (
309
+ <FlatList
310
+ data={myProductsListData}
311
+ renderItem={renderProductItem}
312
+ keyExtractor={(item, index) => `${item?.id || index}`}
313
+ numColumns={2}
314
+ contentContainerStyle={styles.productsGrid}
315
+ columnWrapperStyle={styles.productRow}
266
316
  style={styles.scrollContainer}
267
317
  showsVerticalScrollIndicator={false}
268
- >
269
- {Array.isArray(myProductsListData) &&
270
- myProductsListData?.length > 0 ? (
271
- <View style={styles.productsGrid}>
272
- {myProductsListData
273
- ?.[REDUCE]((rows: any[], product, index) => {
274
- if (index % 2 === 0) {
275
- rows?.[PUSH]([product]);
276
- } else {
277
- rows?.[rows.length - 1]?.[PUSH](product);
278
- }
279
- return rows;
280
- }, [])
281
- ?.[MAP]((row: any[], rowIndex: number) => (
282
- <View key={rowIndex} style={styles.productRow}>
283
- {row?.[MAP]((product, productIndex) => (
284
- <ProductCard
285
- key={`${rowIndex}-${productIndex}`}
286
- title={product?.name}
287
- priceCents={product?.price?.price_cents} imageUrl={product?.image?.url}
288
- category={product?.product_type}
289
- logo={
290
- product?.logo === 'titan' ? (
291
- <TitanLogo />
292
- ) : product?.logo === 'ambrane' ? (
293
- <AmbraneIcon />
294
- ) : product?.logo === 'rayban' ? (
295
- <RayBanLogo />
296
- ) : undefined
297
- }
298
- backgroundColor={product?.backgroundColor}
299
- onPress={() => handleProductPress(product)}
300
- />
301
- ))}
302
- {row?.length === 1 && <View style={{ width: cardWidth }} />}
303
- </View>
304
- ))}
305
- </View>
306
- ) : (
307
- <NoDataFound containerstyle={styles.nodataFound} />
308
- )}
309
- </ScrollView>
318
+ onEndReached={handleLoadMore}
319
+ onEndReachedThreshold={0.5}
320
+ ListFooterComponent={renderFooter}
321
+ />
322
+ ) : (
323
+ <NoDataFound containerstyle={styles.nodataFound} />
310
324
  )}
311
325
 
312
326
  <FilterModal
@@ -318,10 +332,15 @@ const Redeem: React.FC = () => {
318
332
  onApply={(filters) => {
319
333
  setSelectedFilters(filters);
320
334
  setIsFilterModalVisible(false);
335
+ const ref: any = {};
336
+ if (filters?.productType) ref.product_types = filters.productType;
337
+ if (filters?.categories?.length > 0) ref.category_ids = filters.categories.join(',');
338
+ currentFiltersRef.current = ref;
321
339
  }}
322
340
  onClear={() => {
323
341
  setSelectedFilters(DEFAULT_FILTERS);
324
342
  setIsFilterModalVisible(false);
343
+ currentFiltersRef.current = {};
325
344
  dispatch(myProductsListRequest({}));
326
345
  }}
327
346
  />
@@ -110,8 +110,6 @@ const styles = StyleSheet.create({
110
110
  },
111
111
  productsGrid: {
112
112
  paddingHorizontal: SCALE(20),
113
- flexDirection: 'column',
114
- gap: VSCALE(28),
115
113
  paddingBottom: VSCALE(100),
116
114
  },
117
115
  productRow: {
@@ -263,7 +261,12 @@ const styles = StyleSheet.create({
263
261
  },
264
262
  nodataFound:{
265
263
  minHeight:450
266
- }
264
+ },
265
+ paginationFooter: {
266
+ paddingVertical: VSCALE(20),
267
+ alignItems: 'center',
268
+ justifyContent: 'center',
269
+ },
267
270
  });
268
271
 
269
272
  export default styles;
@@ -60,7 +60,7 @@ const TransactionSuccessful: React.FC<Props> = ({ navigation }) => {
60
60
  <View style={styles.contentContainer}>
61
61
  <CustomImage source={Images.successTick} imgStyle={styles.icon} />
62
62
 
63
- <Text style={styles.title}>{points} Points</Text>
63
+ <Text style={styles.title}>{points} Amount</Text>
64
64
  <Text style={styles.subtitle}>Transaction Successful</Text>
65
65
  </View>
66
66
  <CustomButton
@@ -147,7 +147,7 @@ const CustomCard: React.FC<CustomCardProps> = ({ data, loading }) => {
147
147
  <Text style={styles.productQuantity}>Qty : {quantity}</Text>
148
148
  </View>
149
149
  </View>
150
- <Text style={styles.productPoints}>{item / 100} Points</Text>
150
+ <Text style={styles.productPoints}>{item / 100} Amount</Text>
151
151
  </View>
152
152
  );
153
153
  };
@@ -168,7 +168,7 @@ const CustomCard: React.FC<CustomCardProps> = ({ data, loading }) => {
168
168
  </Text>
169
169
  </View>
170
170
 
171
- <Text style={styles.totalPoints}>{pointsFromCentsFixed(item.grand_total, 2)} Points</Text>
171
+ <Text style={styles.totalPoints}>{pointsFromCentsFixed(item.grand_total, 2)} Amount</Text>
172
172
  </View>
173
173
  </View>
174
174
 
@@ -103,6 +103,6 @@ export const DATE = "Date :";
103
103
  export const ORDER_NO = "Order No :";
104
104
 
105
105
  export const AMOUNT_TO_BE_PAID = "Amount to be Paid";
106
- export const POINTS = "Points";
106
+ export const POINTS = "Amount";
107
107
  export const AMOUNT_BREAKDOWN = "Amount Breakdown"
108
108
 
@@ -166,14 +166,26 @@ function* handleMyProductList(
166
166
  const name = action.payload?.name;
167
167
  const product_types = action.payload?.product_types;
168
168
  const category_ids = action.payload?.category_ids;
169
+ const page = action.payload?.page || 1;
169
170
  try {
170
171
  const response = yield call(
171
172
  myProductListApi,
172
173
  name,
173
174
  product_types,
174
- category_ids
175
+ category_ids,
176
+ page
175
177
  );
176
- yield put(myProductsListSuccess(response.data));
178
+ const perPage = 12;
179
+ const totalRecords = response?.data?.total_records || 0;
180
+ const totalPages = Math.ceil(totalRecords / perPage);
181
+ const hasNextPage = page < totalPages;
182
+
183
+ yield put(myProductsListSuccess({
184
+ ...response.data,
185
+ page,
186
+ totalPages,
187
+ hasNextPage,
188
+ }));
177
189
  } catch (error: any) {
178
190
  yield put(myProductsListFailure(error?.response?.data?.message));
179
191
  }
@@ -19,6 +19,10 @@ interface AuthState {
19
19
  myProductsListLoading: boolean;
20
20
  myProductsListError: string | null;
21
21
  myProductsListData: any;
22
+ myProductsListLoadingMore: boolean;
23
+ myProductsListPage: number;
24
+ myProductsListTotalPages: number;
25
+ myProductsListHasNextPage: boolean;
22
26
  myProfileLoading: boolean;
23
27
  myProfileError: string | null;
24
28
  myProfileData: any;
@@ -118,6 +122,10 @@ const initialState: AuthState = {
118
122
  myProductsListLoading: false,
119
123
  myProductsListError: null,
120
124
  myProductsListData: null,
125
+ myProductsListLoadingMore: false,
126
+ myProductsListPage: 1,
127
+ myProductsListTotalPages: 1,
128
+ myProductsListHasNextPage: false,
121
129
  myProfileLoading: false,
122
130
  myProfileError: null,
123
131
  myProfileData: null,
@@ -278,17 +286,35 @@ const authSlice = createSlice({
278
286
  state.myOrdersError = action.payload;
279
287
  },
280
288
  myProductsListRequest(state, _action: PayloadAction<any>) {
281
- state.myProductsListLoading = true;
289
+ const page = _action.payload?.page || 1;
290
+ if (page > 1) {
291
+ state.myProductsListLoadingMore = true;
292
+ } else {
293
+ state.myProductsListLoading = true;
294
+ state.myProductsListData = null;
295
+ state.myProductsListPage = 1;
296
+ state.myProductsListTotalPages = 1;
297
+ state.myProductsListHasNextPage = false;
298
+ }
282
299
  state.myProductsListError = null;
283
- state.myProductsListData = null;
284
300
  },
285
301
  myProductsListSuccess(state, action: PayloadAction<any>) {
302
+ const { response, page, totalPages, hasNextPage } = action.payload;
286
303
  state.myProductsListLoading = false;
287
- state.myProductsListData = action.payload.response;
304
+ state.myProductsListLoadingMore = false;
305
+ state.myProductsListPage = page || 1;
306
+ state.myProductsListTotalPages = totalPages || 1;
307
+ state.myProductsListHasNextPage = hasNextPage || false;
308
+ if (page > 1 && Array.isArray(state.myProductsListData) && Array.isArray(response)) {
309
+ state.myProductsListData = [...state.myProductsListData, ...response];
310
+ } else {
311
+ state.myProductsListData = response;
312
+ }
288
313
  state.myProductsListError = null;
289
314
  },
290
315
  myProductsListFailure(state, action: PayloadAction<any>) {
291
316
  state.myProductsListLoading = false;
317
+ state.myProductsListLoadingMore = false;
292
318
  state.myProductsListError = action.payload;
293
319
  },
294
320
  myProfileRequest(state, _action: PayloadAction<any>) {
@@ -133,7 +133,8 @@ export const myOrdersApi = async () => {
133
133
  export const myProductListApi = async (
134
134
  name?: string,
135
135
  product_types?: string,
136
- category_ids?: string
136
+ category_ids?: string,
137
+ page?: number
137
138
  ) => {
138
139
  try {
139
140
  const headers = await getAuthHeader();
@@ -143,6 +144,10 @@ export const myProductListApi = async (
143
144
  payment_method: "zaggle_card",
144
145
  };
145
146
 
147
+ if (page) {
148
+ params.page = page;
149
+ }
150
+
146
151
  if (name) {
147
152
  params.by_name = name;
148
153
  }
@@ -160,6 +165,12 @@ export const myProductListApi = async (
160
165
  params,
161
166
  });
162
167
 
168
+ console.log('=== MY PRODUCT LIST API RESPONSE ===');
169
+ console.log('Status:', response.status);
170
+ console.log('record count:', response?.data?.total_records);
171
+ console.log('Response Data:', JSON.stringify(response.data, null, 2));
172
+ console.log('===================================');
173
+
163
174
  return response;
164
175
  } catch (error: any) {
165
176
  throw error;