From c7dbdd3b8a417dd154e1e6d5ad0ecd94abfd9cb5 Mon Sep 17 00:00:00 2001 From: Tamojit Biswas Date: Tue, 14 Jul 2026 18:52:58 +0530 Subject: [PATCH] feat: implement core architecture, Redux store, product interfaces, and foundational UI components for Home and Login screens --- .env | 3 ++ app/api/index.ts | 1 + app/api/offerApi.ts | 9 +++++ app/api/productApi.ts | 11 ++++- .../screens/homeScreen/homeScreen.tsx | 20 ++++++++-- app/features/screens/homeScreen/reducer.ts | 14 ++++++- app/features/screens/homeScreen/thunk.ts | 14 ++++++- .../screens/offersScreen/offersScreen.tsx | 40 ++++++++++++++----- app/interfaces/index.ts | 1 + app/interfaces/offers.ts | 33 +++++++++++++++ app/interfaces/product.ts | 13 ++++++ app/services/apiClient.ts | 2 +- app/store/commonreducers/offer/index.ts | 2 + app/store/commonreducers/offer/reducer.ts | 29 ++++++++++++++ app/store/commonreducers/offer/thunk.ts | 14 +++++++ app/store/rootReducer.ts | 2 + app/utils/helper.ts | 2 +- package.json | 2 + yarn.lock | 10 +++++ 19 files changed, 203 insertions(+), 19 deletions(-) create mode 100644 .env create mode 100644 app/api/offerApi.ts create mode 100644 app/interfaces/offers.ts create mode 100644 app/store/commonreducers/offer/index.ts create mode 100644 app/store/commonreducers/offer/reducer.ts create mode 100644 app/store/commonreducers/offer/thunk.ts diff --git a/.env b/.env new file mode 100644 index 0000000..31a3900 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +STRIPE_SECRET_KEY=sk_test_51Ox04DSHlFQYe8R5HXy6nj0eQqtqAP4ynF7ODFg71ork78B38MPsDV3gQEo2EYaFL9OG75L8tG7bKxptsmVeONrS00ji44eUIl +RAZORPAY_KEY_ID=rzp_test_TCxbW8AxcXgMCj +RAZORPAY_KEY_SECRET=rLlM87yFehhsVNSqjCE2qRz5 \ No newline at end of file diff --git a/app/api/index.ts b/app/api/index.ts index 642c92b..fdf96c5 100644 --- a/app/api/index.ts +++ b/app/api/index.ts @@ -6,3 +6,4 @@ export * from './cartApi'; export * from './paymentMethodsApi'; export * from './customerDetailsApi'; export * from './orderApi'; +export * from './offerApi'; diff --git a/app/api/offerApi.ts b/app/api/offerApi.ts new file mode 100644 index 0000000..987f157 --- /dev/null +++ b/app/api/offerApi.ts @@ -0,0 +1,9 @@ +import { apiClient } from '@services'; +import { PromotionResponse } from '@interfaces'; + +export const fetchOffers = async (): Promise => { + const response = await apiClient.get( + '/promotions/customer/offers', + ); + return response; +}; diff --git a/app/api/productApi.ts b/app/api/productApi.ts index a201f5e..2218636 100644 --- a/app/api/productApi.ts +++ b/app/api/productApi.ts @@ -1,4 +1,9 @@ -import { Products, PaginationMeta, Product } from '@interfaces'; +import { + Products, + PaginationMeta, + Product, + CategoriesResponse, +} from '@interfaces'; import { apiClient } from '@services'; export interface GetProductsResponse { @@ -15,3 +20,7 @@ export const getProductDetailsApi = async ( ): Promise => { return await apiClient.get(`/products/${productId}`); }; + +export const getCategoriesApi = async (): Promise => { + return await apiClient.get(`/categories/selected`); +}; diff --git a/app/features/screens/homeScreen/homeScreen.tsx b/app/features/screens/homeScreen/homeScreen.tsx index fb5df2f..8cbcd04 100644 --- a/app/features/screens/homeScreen/homeScreen.tsx +++ b/app/features/screens/homeScreen/homeScreen.tsx @@ -13,6 +13,7 @@ import { import { useNavigation, CompositeNavigationProp, + useFocusEffect, } from '@react-navigation/native'; import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { StackNavigationProp } from '@react-navigation/stack'; @@ -22,8 +23,13 @@ import { useAppTheme } from '@theme'; import { Product } from '../../../interfaces'; import { AppStackParamList } from '../../../navigation/appStack'; import { MainTabParamList } from '../../../navigation/mainTabNavigator'; -import { fetchCustomerDetails, useAppDispatch, useAppSelector } from '@store'; -import { getAllProductsThunk } from './thunk'; +import { + fetchCustomerDetails, + getAllOffersThunk, + useAppDispatch, + useAppSelector, +} from '@store'; +import { getAllCategoriesThunk, getAllProductsThunk } from './thunk'; type NavProp = CompositeNavigationProp< BottomTabNavigationProp, @@ -76,7 +82,9 @@ export const HomeScreen: React.FC = () => { const navigation = useNavigation(); const dispatch = useAppDispatch(); - const { products, isLoading } = useAppSelector(state => state.home); + const { products, isLoading, categories } = useAppSelector( + state => state.home, + ); const [selectedCategory, setSelectedCategory] = useState('all'); const [activeBanner, setActiveBanner] = useState(0); @@ -84,7 +92,13 @@ export const HomeScreen: React.FC = () => { useEffect(() => { dispatch(getAllProductsThunk()); dispatch(fetchCustomerDetails()); + dispatch(getAllCategoriesThunk()); }, [dispatch]); + useFocusEffect( + useCallback(() => { + dispatch(getAllOffersThunk()); + }, [dispatch]), + ); const filteredProducts = selectedCategory === 'all' diff --git a/app/features/screens/homeScreen/reducer.ts b/app/features/screens/homeScreen/reducer.ts index dcadc1b..44616b0 100644 --- a/app/features/screens/homeScreen/reducer.ts +++ b/app/features/screens/homeScreen/reducer.ts @@ -1,17 +1,19 @@ import { createReducer } from '@reduxjs/toolkit'; -import { Product, Products } from '@interfaces'; -import { getAllProductsThunk } from './thunk'; +import { Categories, Product, Products } from '@interfaces'; +import { getAllCategoriesThunk, getAllProductsThunk } from './thunk'; export interface HomeState { products: Products[]; isLoading: boolean; error: string | null; + categories: Categories[]; } const initialState: HomeState = { products: [], isLoading: false, error: null, + categories: [], }; const homeReducer = createReducer(initialState, builder => { @@ -30,6 +32,14 @@ const homeReducer = createReducer(initialState, builder => { .addCase(getAllProductsThunk.rejected, (state, action) => { state.isLoading = false; state.error = action.payload || 'Failed to fetch products'; + }) + .addCase(getAllCategoriesThunk.fulfilled, (state, action) => { + state.categories = action.payload || []; + state.isLoading = false; + }) + .addCase(getAllCategoriesThunk.rejected, (state, action) => { + state.isLoading = false; + state.error = (action.payload as string) || 'Failed to fetch categories'; }); }); diff --git a/app/features/screens/homeScreen/thunk.ts b/app/features/screens/homeScreen/thunk.ts index c137142..fd76cf3 100644 --- a/app/features/screens/homeScreen/thunk.ts +++ b/app/features/screens/homeScreen/thunk.ts @@ -1,5 +1,5 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { getProductsApi, GetProductsResponse } from '@api'; +import { getCategoriesApi, getProductsApi, GetProductsResponse } from '@api'; export const getAllProductsThunk = createAsyncThunk< GetProductsResponse, @@ -15,3 +15,15 @@ export const getAllProductsThunk = createAsyncThunk< ); } }); + +export const getAllCategoriesThunk = createAsyncThunk( + 'categories/getAll', + async (_, { rejectWithValue }) => { + try { + const response = await getCategoriesApi(); + return response; + } catch (error) { + return rejectWithValue(error); + } + }, +); diff --git a/app/features/screens/offersScreen/offersScreen.tsx b/app/features/screens/offersScreen/offersScreen.tsx index c72a682..45d2e1b 100644 --- a/app/features/screens/offersScreen/offersScreen.tsx +++ b/app/features/screens/offersScreen/offersScreen.tsx @@ -3,29 +3,49 @@ import { View, Text, FlatList, TouchableOpacity } from 'react-native'; import { getStyles } from './offersScreen.styles'; import { Header } from '@components'; import { useAppTheme } from '@theme'; +import { useAppSelector } from '@store'; -const OFFERS = [ - { id: '1', code: 'WELCOME50', description: '50% off on your first order', expiry: '30 Jul 2026' }, - { id: '2', code: 'FLAT20', description: 'Flat ₹20 off on orders above ₹199', expiry: '15 Aug 2026' }, - { id: '3', code: 'FREEDEL', description: 'Free delivery on orders above ₹299', expiry: '31 Jul 2026' }, -]; +// const OFFERS = [ +// { +// id: '1', +// code: 'WELCOME50', +// description: '50% off on your first order', +// expiry: '30 Jul 2026', +// }, +// { +// id: '2', +// code: 'FLAT20', +// description: 'Flat ₹20 off on orders above ₹199', +// expiry: '15 Aug 2026', +// }, +// { +// id: '3', +// code: 'FREEDEL', +// description: 'Free delivery on orders above ₹299', +// expiry: '31 Jul 2026', +// }, +// ]; export const OffersScreen: React.FC = () => { const { colors } = useAppTheme(); const styles = getStyles(colors); + const { offers } = useAppSelector(state => state.offer); + // console.log(offers); return (
item.id} + data={offers} + keyExtractor={item => item.id} renderItem={({ item }) => ( - {item.code} - {item.description} - Expires: {item.expiry} + {item?.title} + {item?.description} + {/* + Expires: {new Date(item?.).toLocaleDateString()} + */} { + builder + .addCase(getAllOffersThunk.fulfilled, (state, action) => { + state.offers = action.payload?.promotions; + state.isLoading = false; + }) + .addCase(getAllOffersThunk.pending, state => { + state.isLoading = true; + }) + .addCase(getAllOffersThunk.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); +}); diff --git a/app/store/commonreducers/offer/thunk.ts b/app/store/commonreducers/offer/thunk.ts new file mode 100644 index 0000000..c9fa2a8 --- /dev/null +++ b/app/store/commonreducers/offer/thunk.ts @@ -0,0 +1,14 @@ +import { fetchOffers } from '@api'; +import { createAsyncThunk } from '@reduxjs/toolkit'; + +export const getAllOffersThunk = createAsyncThunk( + 'offers/getAll', + async (_, { rejectWithValue }) => { + try { + const response = await fetchOffers(); + return response; + } catch (error) { + return rejectWithValue(error); + } + }, +); diff --git a/app/store/rootReducer.ts b/app/store/rootReducer.ts index 38e0ff6..fae5a19 100644 --- a/app/store/rootReducer.ts +++ b/app/store/rootReducer.ts @@ -10,6 +10,7 @@ import homeReducer from '@features/screens/homeScreen/reducer'; import providerDetailsReducer from '@features/screens/providerDetailsScreen/reducer'; import paymentMethodsReducer from '@features/screens/checkoutPaymentScreen/reducer'; import customerProfileReducer from './commonreducers/customerProfile/reducer'; +import { offerReducer } from './commonreducers/offer'; const rootReducer = combineReducers({ auth: authReducer, @@ -22,6 +23,7 @@ const rootReducer = combineReducers({ providerDetails: providerDetailsReducer, paymentMethods: paymentMethodsReducer, customerProfile: customerProfileReducer, + offer: offerReducer, }); export type RootState = ReturnType; diff --git a/app/utils/helper.ts b/app/utils/helper.ts index 3c393c7..22b2e6e 100644 --- a/app/utils/helper.ts +++ b/app/utils/helper.ts @@ -1,5 +1,5 @@ export const getFullUrl = (url?: string) => { - const BASE_URL = 'https://db1d-202-8-116-13.ngrok-free.app'; + const BASE_URL = 'https://157a-202-8-116-13.ngrok-free.app'; if (!url) return ''; return url.startsWith('/') ? `${BASE_URL}${url}` : url; }; diff --git a/package.json b/package.json index 7ac003a..60b8bb3 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,14 @@ "@react-navigation/native": "^7.3.4", "@react-navigation/stack": "^7.10.6", "@reduxjs/toolkit": "^2.12.0", + "@stripe/stripe-react-native": "^0.68.0", "axios": "^1.18.1", "react": "19.2.3", "react-native": "0.86.0", "react-native-geolocation-service": "^5.3.1", "react-native-gesture-handler": "^2.32.0", "react-native-maps": "^1.29.0", + "react-native-razorpay": "^3.0.0", "react-native-reanimated": "^4.5.0", "react-native-safe-area-context": "^5.8.0", "react-native-screens": "^4.25.2", diff --git a/yarn.lock b/yarn.lock index ecec587..644c061 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1847,6 +1847,11 @@ resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== +"@stripe/stripe-react-native@^0.68.0": + version "0.68.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-react-native/-/stripe-react-native-0.68.0.tgz#1c95930943e7538e9930dc19f75b0a5e435b94cc" + integrity sha512-5Sv0bUX9ABxttRL2qWGfxzaq5mVLNFJ3XTOmyWw0IpgrL1vXqVHzx8d6qWX8iHbosu1985x8BxbquIns+P6g3w== + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -5829,6 +5834,11 @@ react-native-maps@^1.29.0: dependencies: "@types/geojson" "^7946.0.13" +react-native-razorpay@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/react-native-razorpay/-/react-native-razorpay-3.0.0.tgz#05fca397b132be97aac3f8d6c54576b3fcf269fc" + integrity sha512-p7bcEJd8P3/FuNuV/kDvJAhF2QCHXaOwkMA7hkZV3XnmU2IJRap3dF8BYuSBAb+OEvzAm5RQbRHlZAnkr/+h+g== + react-native-reanimated@^4.5.0: version "4.5.1" resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-4.5.1.tgz#be81ed3a96bf70baeeccdc24d3350883099b7dd3"