feat: implement order details screen and product review submission functionality

This commit is contained in:
Tamojit Biswas 2026-07-20 17:15:47 +05:30
parent b6af3c8d24
commit bed28f2310
17 changed files with 785 additions and 30 deletions

View File

@ -7,3 +7,4 @@ export * from './paymentMethodsApi';
export * from './customerDetailsApi';
export * from './orderApi';
export * from './offerApi';
export * from './reviewApi';

View File

@ -0,0 +1,7 @@
import { giveRatingPayload } from '@interfaces';
import { apiClient } from '@services';
export const giveRatingApi = async (id: string, payload: giveRatingPayload) => {
const response = await apiClient.post(`/products/${id}/ratings`, payload);
return response;
};

View File

@ -15,4 +15,4 @@ export * from './orderHistoryCard';
export * from './paymentOption';
export * from './OrderStatusTimeline';
export * from './TrackingMap';
export * from './productRatingModal';

View File

@ -0,0 +1 @@
export * from './productRatingModal';

View File

@ -0,0 +1,231 @@
import { StyleSheet, Dimensions, Platform } from 'react-native';
import { typography } from '@theme';
const { width } = Dimensions.get('window');
export const getStyles = (colors: any) =>
StyleSheet.create({
// ── Backdrop ──────────────────────────────────────────────────────────────
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.55)',
justifyContent: 'flex-end',
},
// ── Sheet ─────────────────────────────────────────────────────────────────
sheet: {
backgroundColor: colors.cardBg ?? '#FFFFFF',
borderTopLeftRadius: 28,
borderTopRightRadius: 28,
paddingHorizontal: 24,
paddingBottom: Platform.OS === 'ios' ? 40 : 28,
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: -4 },
shadowOpacity: 0.12,
shadowRadius: 16,
},
android: { elevation: 16 },
}),
},
// ── Drag handle ───────────────────────────────────────────────────────────
dragHandle: {
width: 40,
height: 4,
borderRadius: 2,
backgroundColor: colors.border ?? '#DEDEDE',
alignSelf: 'center',
marginTop: 12,
marginBottom: 20,
},
// ── Product hero ──────────────────────────────────────────────────────────
productHero: {
alignItems: 'center',
marginBottom: 20,
},
productEmoji: {
fontSize: 52,
marginBottom: 10,
},
productName: {
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.bold,
color: colors.text,
textAlign: 'center',
marginBottom: 4,
},
productMeta: {
fontSize: typography.fontSize.sm,
color: colors.textSecondary,
textAlign: 'center',
},
// ── Rating section ────────────────────────────────────────────────────────
ratingSection: {
alignItems: 'center',
marginBottom: 24,
},
ratingLabel: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.semibold,
color: colors.textSecondary,
marginBottom: 14,
textTransform: 'uppercase',
letterSpacing: 0.6,
},
ratingHint: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
marginTop: 10,
},
// ── Divider ───────────────────────────────────────────────────────────────
divider: {
height: 1,
backgroundColor: colors.border ?? '#ECECEC',
marginBottom: 20,
},
// ── Comment input ─────────────────────────────────────────────────────────
inputSection: {
marginBottom: 20,
},
inputLabel: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
marginBottom: 8,
},
optionalTag: {
fontWeight: typography.fontWeight.regular ?? '400',
color: colors.textSecondary,
fontSize: typography.fontSize.xs,
},
commentInput: {
backgroundColor: colors.surface ?? '#F5F6F8',
borderRadius: 12,
borderWidth: 1.5,
borderColor: colors.border ?? '#ECECEC',
paddingHorizontal: 14,
paddingVertical: 12,
fontSize: typography.fontSize.sm,
color: colors.text,
minHeight: 88,
textAlignVertical: 'top',
},
commentInputFocused: {
borderColor: colors.primary ?? '#05824C',
},
charCount: {
fontSize: typography.fontSize.xs,
color: colors.textSecondary,
textAlign: 'right',
marginTop: 4,
},
// ── Image upload ──────────────────────────────────────────────────────────
imageSection: {
marginBottom: 24,
},
imageLabel: {
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
marginBottom: 10,
},
imageScrollContent: {
gap: 10,
},
addImageBtn: {
width: 72,
height: 72,
borderRadius: 12,
borderWidth: 1.5,
borderColor: colors.primary ?? '#05824C',
borderStyle: 'dashed',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.primaryMuted ?? '#E9F7EF',
},
addImageIcon: {
fontSize: 22,
marginBottom: 2,
},
addImageText: {
fontSize: 10,
color: colors.primary ?? '#05824C',
fontWeight: typography.fontWeight.semibold,
},
imageThumb: {
width: 72,
height: 72,
borderRadius: 12,
position: 'relative',
},
imageThumbnail: {
width: 72,
height: 72,
borderRadius: 12,
},
removeImageBtn: {
position: 'absolute',
top: -6,
right: -6,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: colors.error ?? '#E53935',
alignItems: 'center',
justifyContent: 'center',
},
removeImageText: {
color: '#FFF',
fontSize: 11,
fontWeight: 'bold',
lineHeight: 14,
},
// ── Submit button ─────────────────────────────────────────────────────────
submitBtn: {
borderRadius: 14,
paddingVertical: 15,
alignItems: 'center',
justifyContent: 'center',
},
submitBtnActive: {
backgroundColor: colors.primary ?? '#05824C',
},
submitBtnDisabled: {
backgroundColor: colors.border ?? '#DEDEDE',
},
submitBtnText: {
fontSize: typography.fontSize.md,
fontWeight: typography.fontWeight.bold,
color: '#FFFFFF',
},
submitBtnTextDisabled: {
color: colors.textSecondary ?? '#9E9E9E',
},
// ── Already-rated banner ──────────────────────────────────────────────────
ratedBanner: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.primaryMuted ?? '#E9F7EF',
borderRadius: 12,
padding: 14,
marginBottom: 20,
},
ratedBannerEmoji: {
fontSize: 20,
marginRight: 10,
},
ratedBannerText: {
fontSize: typography.fontSize.sm,
color: colors.primary ?? '#05824C',
fontWeight: typography.fontWeight.semibold,
},
});

View File

@ -0,0 +1,288 @@
import React, { useState, useRef, useEffect, useCallback } from 'react';
import {
Modal,
View,
Text,
TextInput,
TouchableOpacity,
Animated,
ScrollView,
Image,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
} from 'react-native';
import { useAppTheme } from '@theme';
import { RatingStars } from '../ratingStars/ratingStars';
import { getStyles } from './productRatingModal.styles';
import { giveRatingPayload } from '@interfaces';
// ─── Types ────────────────────────────────────────────────────────────────────
export interface ProductRatingModalProps {
visible: boolean;
/** Product id being rated */
productId: string;
/** Display name of the product */
productName: string;
/** Optional: qty badge text, e.g. "2x" */
quantity?: string | number;
/** The parent order id — required by the rating API */
orderId?: string;
/** Called when the user submits their rating */
onSubmit: (payload: giveRatingPayload) => void;
/** Called when the modal is dismissed without submitting */
onClose: () => void;
}
// ─── Rating hint labels ───────────────────────────────────────────────────────
const RATING_LABELS: Record<number, string> = {
1: 'Terrible 😞',
2: 'Bad 😕',
3: 'Okay 😐',
4: 'Good 😊',
5: 'Excellent 🤩',
};
const MAX_COMMENT = 300;
// ─── Component ────────────────────────────────────────────────────────────────
export const ProductRatingModal: React.FC<ProductRatingModalProps> = ({
visible,
orderId,
productId,
productName,
quantity,
onSubmit,
onClose,
}) => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
// ── State ────────────────────────────────────────────────────────────────
const [rating, setRating] = useState(0);
const [comment, setComment] = useState('');
const [commentFocused, setCommentFocused] = useState(false);
const [imageUris] = useState<string[]>([]); // placeholder wire launchImageLibrary here
const [submitted, setSubmitted] = useState(false);
// ── Animation ────────────────────────────────────────────────────────────
const slideY = useRef(new Animated.Value(400)).current;
useEffect(() => {
if (visible) {
// reset state each time the modal opens for a new product
setRating(0);
setComment('');
setSubmitted(false);
Animated.spring(slideY, {
toValue: 0,
useNativeDriver: true,
damping: 18,
stiffness: 160,
}).start();
} else {
slideY.setValue(400);
}
}, [visible, slideY]);
// ── Handlers ─────────────────────────────────────────────────────────────
const handleClose = useCallback(() => {
Animated.timing(slideY, {
toValue: 400,
duration: 200,
useNativeDriver: true,
}).start(onClose);
}, [slideY, onClose]);
const handleSubmit = useCallback(() => {
if (rating === 0) return;
onSubmit({
rating,
comment,
images: imageUris,
orderId: orderId ?? '',
});
setSubmitted(true);
}, [rating, comment, imageUris, orderId, onSubmit]);
const canSubmit = rating > 0;
// ── Render ───────────────────────────────────────────────────────────────
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={handleClose}
statusBarTranslucent
>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
{/* Backdrop — tap to dismiss */}
<TouchableWithoutFeedback onPress={handleClose}>
<View style={styles.overlay}>
<TouchableWithoutFeedback>
<Animated.View
style={[styles.sheet, { transform: [{ translateY: slideY }] }]}
>
{/* Drag handle */}
<View style={styles.dragHandle} />
{/* Product hero */}
<View style={styles.productHero}>
<Text style={styles.productEmoji}>🛍</Text>
<Text style={styles.productName} numberOfLines={2}>
{productName}
</Text>
{quantity !== undefined && (
<Text style={styles.productMeta}>Qty: {quantity}</Text>
)}
</View>
{/* Already-submitted banner */}
{submitted ? (
<>
<View style={styles.ratedBanner}>
<Text style={styles.ratedBannerEmoji}></Text>
<Text style={styles.ratedBannerText}>
Thanks for your rating!
</Text>
</View>
{/* Show submitted stars (read-only) */}
<View style={styles.ratingSection}>
<RatingStars rating={rating} size={34} />
</View>
<TouchableOpacity
style={[styles.submitBtn, styles.submitBtnActive]}
onPress={handleClose}
activeOpacity={0.8}
>
<Text style={styles.submitBtnText}>Done</Text>
</TouchableOpacity>
</>
) : (
<ScrollView
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* Star rating */}
<View style={styles.ratingSection}>
<Text style={styles.ratingLabel}>Your Rating</Text>
<RatingStars
rating={rating}
onRate={setRating}
size={40}
/>
<Text style={styles.ratingHint}>
{rating > 0
? RATING_LABELS[rating]
: 'Tap a star to rate'}
</Text>
</View>
<View style={styles.divider} />
{/* Comment (optional) */}
<View style={styles.inputSection}>
<Text style={styles.inputLabel}>
Review{' '}
<Text style={styles.optionalTag}>(optional)</Text>
</Text>
<TextInput
style={[
styles.commentInput,
commentFocused && styles.commentInputFocused,
]}
placeholder="Share your experience with this product..."
placeholderTextColor={colors.textSecondary}
value={comment}
onChangeText={t => setComment(t.slice(0, MAX_COMMENT))}
multiline
onFocus={() => setCommentFocused(true)}
onBlur={() => setCommentFocused(false)}
returnKeyType="done"
blurOnSubmit
/>
<Text style={styles.charCount}>
{comment.length}/{MAX_COMMENT}
</Text>
</View>
{/* Image upload strip (optional) */}
<View style={styles.imageSection}>
<Text style={styles.imageLabel}>
Photos{' '}
<Text style={styles.optionalTag}>(optional)</Text>
</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.imageScrollContent}
>
{/* Add photo button */}
<TouchableOpacity
style={styles.addImageBtn}
activeOpacity={0.75}
/* TODO: wire react-native-image-picker here */
>
<Text style={styles.addImageIcon}>📷</Text>
<Text style={styles.addImageText}>Add</Text>
</TouchableOpacity>
{/* Thumbnail previews */}
{imageUris.map((uri, i) => (
<View key={i} style={styles.imageThumb}>
<Image
source={{ uri }}
style={styles.imageThumbnail}
/>
<TouchableOpacity
style={styles.removeImageBtn}
hitSlop={{ top: 4, right: 4, bottom: 4, left: 4 }}
>
<Text style={styles.removeImageText}></Text>
</TouchableOpacity>
</View>
))}
</ScrollView>
</View>
{/* Submit */}
<TouchableOpacity
style={[
styles.submitBtn,
canSubmit
? styles.submitBtnActive
: styles.submitBtnDisabled,
]}
onPress={handleSubmit}
disabled={!canSubmit}
activeOpacity={0.85}
>
<Text
style={[
styles.submitBtnText,
!canSubmit && styles.submitBtnTextDisabled,
]}
>
Submit Review
</Text>
</TouchableOpacity>
</ScrollView>
)}
</Animated.View>
</TouchableWithoutFeedback>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
</Modal>
);
};

View File

@ -1 +1,3 @@
export * from './orderDetailsScreen';
export * from './thunk';
export * from './reducer';

View File

@ -191,4 +191,65 @@ export const getStyles = (colors: any) =>
color: colors.text,
fontWeight: typography.fontWeight.medium,
},
// ── Rating integration ──────────────────────────────────────────────────
/** Subtle highlight applied to item rows when the order is DELIVERED */
itemRowTappable: {
backgroundColor: colors.surface ?? '#F8F9FA',
borderRadius: 10,
paddingHorizontal: 10,
paddingVertical: 8,
marginHorizontal: -10,
},
ratingPillRow: {
flexDirection: 'row',
marginTop: 4,
},
/** Green "✓ Rated" pill */
ratedPill: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: (colors.primaryMuted ?? '#E9F7EF'),
borderRadius: 20,
paddingHorizontal: 8,
paddingVertical: 3,
},
ratedPillText: {
fontSize: 11,
color: colors.primary ?? '#05824C',
fontWeight: typography.fontWeight.semibold,
},
/** Amber "⭐ Rate this" pill */
ratePill: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFF8E1',
borderRadius: 20,
paddingHorizontal: 8,
paddingVertical: 3,
},
ratePillText: {
fontSize: 11,
color: '#F59E0B',
fontWeight: typography.fontWeight.semibold,
},
/** Banner shown at the top of Order Items when DELIVERED */
rateHintBanner: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFF8E1',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 14,
},
rateHintEmoji: {
fontSize: 16,
marginRight: 8,
},
rateHintText: {
fontSize: typography.fontSize.sm,
color: '#92400E',
fontWeight: typography.fontWeight.medium,
},
});

View File

@ -1,15 +1,23 @@
import React, { useEffect } from 'react';
import { View, Text, ScrollView, ActivityIndicator } from 'react-native';
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
ScrollView,
ActivityIndicator,
TouchableOpacity,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { Header, OrderStatusTimeline } from '@components';
import { Header, OrderStatusTimeline, ProductRatingModal } from '@components';
import { useAppTheme } from '@theme';
import { getStyles } from './orderDetailsScreen.styles';
import { RootState, useAppDispatch, useAppSelector } from '@store';
import { getOrderByIdThunk } from '../checkoutPaymentScreen';
import { formatDate, formatTime } from '@utils/helper';
import { AppStackParamList } from '../../../navigation/appStack';
import { submitReview } from './thunk';
import { giveRatingPayload, OrderItem } from '@interfaces';
import { AppStackParamList } from '@navigation';
type OrderDetailsRouteProp = RouteProp<AppStackParamList, 'OrderDetailsScreen'>;
type OrderDetailsNavProp = StackNavigationProp<AppStackParamList>;
@ -26,12 +34,53 @@ export const OrderDetailsScreen: React.FC = () => {
const { orderDetails, orderDetailsLoading, orderDetailsError } =
useAppSelector((state: RootState) => state.paymentMethods);
// ── Rating modal state ────────────────────────────────────────────────────
const [ratingModalItem, setRatingModalItem] = useState<OrderItem | null>(
null,
);
/** Tracks which productIds have already been rated this session */
const [ratedProductIds, setRatedProductIds] = useState<Set<string>>(
new Set(),
);
const isDelivered = orderDetails?.status === 'DELIVERED';
useEffect(() => {
if (orderId) {
dispatch(getOrderByIdThunk(orderId));
}
}, [dispatch, orderId]);
const handleRatingSubmit = useCallback(
(payload: giveRatingPayload) => {
if (!ratingModalItem) return;
// Dispatch the API call: POST /products/:productId/ratings
dispatch(
submitReview({
id: ratingModalItem.productId,
payload: {
rating: payload.rating,
comment: payload.comment,
images: payload.images,
orderId: orderDetails?.id ?? '',
},
}),
)
.unwrap()
.then(() => {
setRatedProductIds(prev =>
new Set(prev).add(ratingModalItem.productId),
);
});
},
[dispatch, ratingModalItem, orderDetails?.id],
);
const handleModalClose = useCallback(() => {
setRatingModalItem(null);
}, []);
// ── Loading ───────────────────────────────────────────────────────────────
if (orderDetailsLoading) {
return (
<View style={styles.container}>
@ -105,17 +154,61 @@ export const OrderDetailsScreen: React.FC = () => {
{/* Order Items Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Order Items</Text>
{orderDetails.orderItems?.map((item, index) => (
<View key={item.id || index} style={styles.itemRow}>
{/* Rate all hint — shown only when delivered */}
{isDelivered && (
<View style={styles.rateHintBanner}>
<Text style={styles.rateHintEmoji}></Text>
<Text style={styles.rateHintText}>
Tap any item below to rate it
</Text>
</View>
)}
{orderDetails.orderItems?.map((item, index) => {
const isRated = ratedProductIds.has(item.productId);
return (
<TouchableOpacity
key={item.id || index}
style={[styles.itemRow, isDelivered && styles.itemRowTappable]}
activeOpacity={isDelivered ? 0.65 : 1}
onPress={() => {
if (isDelivered) {
setRatingModalItem(item);
}
}}
disabled={!isDelivered}
>
<View style={styles.itemInfo}>
<View style={styles.itemQuantityBadge}>
<Text style={styles.itemQuantityText}>{item.quantity}x</Text>
<Text style={styles.itemQuantityText}>
{item.quantity}x
</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.itemName}>{item.name}</Text>
{/* Rating status pill — only shown when DELIVERED */}
{isDelivered && (
<View style={styles.ratingPillRow}>
{isRated ? (
<View style={styles.ratedPill}>
<Text style={styles.ratedPillText}> Rated</Text>
</View>
) : (
<View style={styles.ratePill}>
<Text style={styles.ratePillText}>
Rate this
</Text>
</View>
)}
</View>
)}
</View>
</View>
<Text style={styles.itemPrice}>{item.totalAmount}</Text>
</View>
))}
</TouchableOpacity>
);
})}
</View>
{/* Bill Summary Section */}
@ -184,6 +277,17 @@ export const OrderDetailsScreen: React.FC = () => {
</View>
)}
</ScrollView>
{/* Product Rating Modal */}
<ProductRatingModal
visible={ratingModalItem !== null}
productId={ratingModalItem?.productId ?? ''}
productName={ratingModalItem?.name ?? ''}
orderId={orderDetails?.id}
quantity={ratingModalItem?.quantity}
onSubmit={handleRatingSubmit}
onClose={handleModalClose}
/>
</View>
);
};

View File

@ -0,0 +1,25 @@
import { createReducer } from '@reduxjs/toolkit';
import { submitReview } from './thunk';
export interface OrderDetailsState {
loading: boolean;
error: string | null;
}
const initialState: OrderDetailsState = {
loading: false,
error: null,
};
export const orderDetailsReducer = createReducer(initialState, builder => {
builder.addCase(submitReview.pending, state => {
state.loading = true;
});
builder.addCase(submitReview.fulfilled, state => {
state.loading = false;
});
builder.addCase(submitReview.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
});

View File

@ -0,0 +1,18 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { giveRatingPayload } from '@interfaces';
import { giveRatingApi } from '@api';
export const submitReview = createAsyncThunk(
'order/submitReview',
async (
{ id, payload }: { id: string; payload: giveRatingPayload },
{ rejectWithValue },
) => {
try {
const response = await giveRatingApi(id, payload);
return response;
} catch (error: any) {
return rejectWithValue(error.response?.data);
}
},
);

View File

@ -36,13 +36,13 @@ const { width } = Dimensions.get('window');
// TODO: move these into your shared Product type once the backend
// response includes them.
interface ProductReview {
id: string;
userName?: string;
rating: number;
comment?: string;
createdAt?: string;
}
// interface ProductReview {
// id: string;
// userName?: string;
// rating: number;
// comment?: string;
// createdAt?: string;
// }
type RatingBreakdown = Partial<Record<1 | 2 | 3 | 4 | 5, number>>;
@ -256,9 +256,8 @@ export const ProviderDetailsScreen: React.FC = () => {
}
const avgRating = product?.avgRating ? parseFloat(product.avgRating) : 0;
const reviews: ProductReview[] = (product as any)?.reviews ?? [];
const totalRatings: number =
(product as any)?.ratingsCount ?? reviews.length ?? 0;
const reviews = product?.recentRatings ?? [];
const totalRatings = reviews.length;
const breakdown: RatingBreakdown | undefined = (product as any)
?.ratingBreakdown;
const hasRatings = totalRatings > 0;
@ -331,12 +330,12 @@ export const ProviderDetailsScreen: React.FC = () => {
<View style={styles.reviewCardHeader}>
<View style={styles.reviewerAvatar}>
<Text style={styles.reviewerAvatarText}>
{(review.userName || 'U').charAt(0).toUpperCase()}
{(review?.user?.name || 'U').charAt(0).toUpperCase()}
</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.reviewerName}>
{review.userName || 'Anonymous'}
{review?.user?.name || 'Anonymous'}
</Text>
{renderStars(review.rating, 12)}
</View>
@ -349,8 +348,8 @@ export const ProviderDetailsScreen: React.FC = () => {
: ''}
</Text>
</View>
{!!review.comment && (
<Text style={styles.reviewComment}>{review.comment}</Text>
{!!review.review && (
<Text style={styles.reviewComment}>{review.review}</Text>
)}
</View>
))}

View File

@ -97,3 +97,4 @@ export * from './paymentMethods';
export * from './customerProfile';
export * from './order';
export * from './offers';
export * from './productRating';

View File

@ -128,6 +128,11 @@ export interface ProductRating {
rating: number;
review: string;
createdAt: string;
user: {
id: string;
name: string;
profileImage: string;
};
}
export enum ProductType {

View File

@ -0,0 +1,6 @@
export interface giveRatingPayload {
rating: number;
comment?: string;
images?: string[];
orderId: string;
}

5
app/navigation/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './appStack';
export * from './mainTabNavigator';
export * from './rootNavigator';
export * from './authStack';
export * from './onboardingStack';

View File

@ -11,7 +11,7 @@ import providerDetailsReducer from '@features/screens/providerDetailsScreen/redu
import paymentMethodsReducer from '@features/screens/checkoutPaymentScreen/reducer';
import customerProfileReducer from './commonreducers/customerProfile/reducer';
import { offerReducer } from './commonreducers/offer';
import { accountReducer } from '@features/screens';
import { accountReducer, orderDetailsReducer } from '@features/screens';
import searchReducer from '@features/screens/searchScreen/reducer';
const rootReducer = combineReducers({
@ -28,6 +28,7 @@ const rootReducer = combineReducers({
offer: offerReducer,
account: accountReducer,
search: searchReducer,
orderDetails: orderDetailsReducer,
});
export type RootState = ReturnType<typeof rootReducer>;