feat: implement wallet feature with transaction history and navigation integration
This commit is contained in:
parent
bed28f2310
commit
40b21a4446
@ -1,4 +1,8 @@
|
|||||||
import { CustomerResponse, WalletResponse } from '@interfaces';
|
import {
|
||||||
|
CustomerResponse,
|
||||||
|
WalletResponse,
|
||||||
|
WalletTransaction,
|
||||||
|
} from '@interfaces';
|
||||||
import { apiClient } from '@services';
|
import { apiClient } from '@services';
|
||||||
|
|
||||||
export const getCustomerDetails = async () => {
|
export const getCustomerDetails = async () => {
|
||||||
@ -8,3 +12,7 @@ export const getCustomerDetails = async () => {
|
|||||||
export const getWalletDetails = async (): Promise<WalletResponse> => {
|
export const getWalletDetails = async (): Promise<WalletResponse> => {
|
||||||
return await apiClient.get<WalletResponse>('/wallets/balance');
|
return await apiClient.get<WalletResponse>('/wallets/balance');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getWalletTransactions = async (): Promise<WalletTransaction[]> => {
|
||||||
|
return await apiClient.get<WalletTransaction[]>('/wallets/transactions');
|
||||||
|
};
|
||||||
|
|||||||
@ -48,6 +48,7 @@ const MENU_SECTIONS: MenuSectionData[] = [
|
|||||||
label: 'Wallet',
|
label: 'Wallet',
|
||||||
subLabel: 'Cards, UPI & wallets',
|
subLabel: 'Cards, UPI & wallets',
|
||||||
tint: '#FFF4E5',
|
tint: '#FFF4E5',
|
||||||
|
onPress: navigation => navigation.navigate('WalletScreen'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '🔔',
|
icon: '🔔',
|
||||||
|
|||||||
@ -21,3 +21,4 @@ export * from './helpSupportScreen';
|
|||||||
export * from './accountScreen';
|
export * from './accountScreen';
|
||||||
export * from './writeReviewScreen';
|
export * from './writeReviewScreen';
|
||||||
export * from './orderDetailsScreen';
|
export * from './orderDetailsScreen';
|
||||||
|
export * from './walletScreen';
|
||||||
|
|||||||
3
app/features/screens/walletScreen/index.ts
Normal file
3
app/features/screens/walletScreen/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './walletScreen';
|
||||||
|
export * from './reducer';
|
||||||
|
export * from './thunk';
|
||||||
30
app/features/screens/walletScreen/reducer.ts
Normal file
30
app/features/screens/walletScreen/reducer.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { WalletTransaction } from '@interfaces';
|
||||||
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
|
import { getAllWalletTransactions } from './thunk';
|
||||||
|
|
||||||
|
export interface walletState {
|
||||||
|
walletTransactions: WalletTransaction[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: walletState = {
|
||||||
|
walletTransactions: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const walletReducer = createReducer(initialState, builder => {
|
||||||
|
builder.addCase(getAllWalletTransactions.pending, state => {
|
||||||
|
state.loading = true;
|
||||||
|
state.error = null;
|
||||||
|
});
|
||||||
|
builder.addCase(getAllWalletTransactions.fulfilled, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.walletTransactions = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(getAllWalletTransactions.rejected, (state, action) => {
|
||||||
|
state.loading = false;
|
||||||
|
state.error = action.payload as string | null;
|
||||||
|
});
|
||||||
|
});
|
||||||
14
app/features/screens/walletScreen/thunk.ts
Normal file
14
app/features/screens/walletScreen/thunk.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { getWalletTransactions } from '@api';
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const getAllWalletTransactions = createAsyncThunk(
|
||||||
|
'wallet/getAllWalletTransactions',
|
||||||
|
async (_, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await getWalletTransactions();
|
||||||
|
return response;
|
||||||
|
} catch (error: any) {
|
||||||
|
return rejectWithValue(error.response.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
128
app/features/screens/walletScreen/walletScreen.styles.ts
Normal file
128
app/features/screens/walletScreen/walletScreen.styles.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
export const getStyles = (theme: any) =>
|
||||||
|
StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#F6F7FB',
|
||||||
|
},
|
||||||
|
|
||||||
|
balanceCard: {
|
||||||
|
margin: 16,
|
||||||
|
borderRadius: 18,
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: '#2F80ED',
|
||||||
|
},
|
||||||
|
|
||||||
|
balanceLabel: {
|
||||||
|
color: '#E6F0FF',
|
||||||
|
fontSize: 15,
|
||||||
|
},
|
||||||
|
|
||||||
|
balance: {
|
||||||
|
fontSize: 34,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#FFF',
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
|
||||||
|
addMoneyButton: {
|
||||||
|
marginTop: 20,
|
||||||
|
backgroundColor: '#FFF',
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 12,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
|
||||||
|
addMoneyText: {
|
||||||
|
color: '#2F80ED',
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: 6,
|
||||||
|
},
|
||||||
|
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginHorizontal: 16,
|
||||||
|
marginBottom: 10,
|
||||||
|
color: '#222',
|
||||||
|
},
|
||||||
|
|
||||||
|
listContainer: {
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingBottom: 20,
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionCard: {
|
||||||
|
backgroundColor: '#FFF',
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 15,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
elevation: 2,
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 5,
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
},
|
||||||
|
|
||||||
|
iconContainer: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 24,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionInfo: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 14,
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionTitle: {
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#222',
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionRef: {
|
||||||
|
color: '#777',
|
||||||
|
fontSize: 12,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionDate: {
|
||||||
|
color: '#999',
|
||||||
|
fontSize: 12,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
amountContainer: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
|
||||||
|
amount: {
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
|
||||||
|
balanceText: {
|
||||||
|
color: '#888',
|
||||||
|
fontSize: 12,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 80,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyText: {
|
||||||
|
marginTop: 12,
|
||||||
|
color: '#888',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
});
|
||||||
149
app/features/screens/walletScreen/walletScreen.tsx
Normal file
149
app/features/screens/walletScreen/walletScreen.tsx
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
SafeAreaView,
|
||||||
|
FlatList,
|
||||||
|
TouchableOpacity,
|
||||||
|
} from 'react-native';
|
||||||
|
// import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
import { Header } from '@components';
|
||||||
|
import { useAppTheme } from '@theme';
|
||||||
|
import { getStyles } from './walletScreen.styles';
|
||||||
|
import { WalletTransaction } from '@interfaces';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@store';
|
||||||
|
import { getAllWalletTransactions } from './thunk';
|
||||||
|
import { useFocusEffect } from '@react-navigation/native';
|
||||||
|
import { getWalletBalanceThunk } from '../accountScreen';
|
||||||
|
|
||||||
|
// const balance = 17496.05;
|
||||||
|
|
||||||
|
// const transactions: WalletTransaction[] = [
|
||||||
|
// {
|
||||||
|
// id: '1',
|
||||||
|
// walletId: '',
|
||||||
|
// transactionReference: 'TXN-1784543790637-2387',
|
||||||
|
// direction: 'OUT',
|
||||||
|
// amount: '1408.95',
|
||||||
|
// balanceBefore: '18905',
|
||||||
|
// balanceAfter: '17496.05',
|
||||||
|
// type: 'PAYMENT',
|
||||||
|
// referenceId: null,
|
||||||
|
// description: null,
|
||||||
|
// idempotencyKey: null,
|
||||||
|
// createdAt: '2026-07-20T10:36:30.639Z',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: '2',
|
||||||
|
// walletId: '',
|
||||||
|
// transactionReference: 'TXN-1784199810512-8502',
|
||||||
|
// direction: 'OUT',
|
||||||
|
// amount: '1095',
|
||||||
|
// balanceBefore: '20000',
|
||||||
|
// balanceAfter: '18905',
|
||||||
|
// type: 'PAYMENT',
|
||||||
|
// referenceId: null,
|
||||||
|
// description: null,
|
||||||
|
// idempotencyKey: null,
|
||||||
|
// createdAt: '2026-07-16T11:03:30.515Z',
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
const WalletScreen = () => {
|
||||||
|
const { colors } = useAppTheme();
|
||||||
|
const styles = getStyles(colors);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { walletTransactions } = useAppSelector(state => state.wallet);
|
||||||
|
const { wallet } = useAppSelector(state => state.account);
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
dispatch(getAllWalletTransactions());
|
||||||
|
dispatch(getWalletBalanceThunk());
|
||||||
|
}, []),
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderItem = ({ item }: { item: WalletTransaction }) => {
|
||||||
|
const isCredit = item.direction === 'IN';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.transactionCard}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.iconContainer,
|
||||||
|
{
|
||||||
|
backgroundColor: isCredit ? '#DFF7E4' : '#FFE5E5',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{/* <Icon
|
||||||
|
name={isCredit ? 'arrow-bottom-left' : 'arrow-top-right'}
|
||||||
|
size={22}
|
||||||
|
color={isCredit ? '#18A558' : '#F04438'}
|
||||||
|
/> */}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.transactionInfo}>
|
||||||
|
<Text style={styles.transactionTitle}>{item.type}</Text>
|
||||||
|
|
||||||
|
<Text style={styles.transactionRef}>{item.transactionReference}</Text>
|
||||||
|
|
||||||
|
<Text style={styles.transactionDate}>
|
||||||
|
{new Date(item.createdAt).toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.amountContainer}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.amount,
|
||||||
|
{
|
||||||
|
color: isCredit ? '#18A558' : '#F04438',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{isCredit ? '+' : '-'}₹{item.amount}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text style={styles.balanceText}>Bal ₹{item.balanceAfter}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.container}>
|
||||||
|
<Header title="Wallet" />
|
||||||
|
|
||||||
|
<View style={styles.balanceCard}>
|
||||||
|
<Text style={styles.balanceLabel}>Available Balance</Text>
|
||||||
|
|
||||||
|
<Text style={styles.balance}>₹{wallet?.balance.toFixed(2)}</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.addMoneyButton}>
|
||||||
|
{/* <Icon name="plus" color="#FFF" size={20} /> */}
|
||||||
|
<Text style={styles.addMoneyText}>Add Money</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.sectionTitle}>Recent Transactions</Text>
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={walletTransactions}
|
||||||
|
keyExtractor={item => item.id}
|
||||||
|
renderItem={renderItem}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.listContainer}
|
||||||
|
ListEmptyComponent={
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
{/* <Icon name="wallet-outline" size={70} color="#BDBDBD" /> */}
|
||||||
|
<Text style={styles.emptyText}>No Transactions Yet</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WalletScreen;
|
||||||
@ -98,3 +98,4 @@ export * from './customerProfile';
|
|||||||
export * from './order';
|
export * from './order';
|
||||||
export * from './offers';
|
export * from './offers';
|
||||||
export * from './productRating';
|
export * from './productRating';
|
||||||
|
export * from './wallet';
|
||||||
|
|||||||
24
app/interfaces/wallet.ts
Normal file
24
app/interfaces/wallet.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export interface WalletTransaction {
|
||||||
|
id: string;
|
||||||
|
walletId: string;
|
||||||
|
transactionReference: string;
|
||||||
|
direction: TransactionDirection;
|
||||||
|
amount: string;
|
||||||
|
balanceBefore: string;
|
||||||
|
balanceAfter: string;
|
||||||
|
type: TransactionType;
|
||||||
|
referenceId: string | null;
|
||||||
|
description: string | null;
|
||||||
|
idempotencyKey: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransactionDirection = 'IN' | 'OUT';
|
||||||
|
|
||||||
|
export type TransactionType =
|
||||||
|
| 'PAYMENT'
|
||||||
|
| 'REFUND'
|
||||||
|
| 'TOPUP'
|
||||||
|
| 'WITHDRAWAL'
|
||||||
|
| 'TRANSFER'
|
||||||
|
| 'ADJUSTMENT';
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
WriteReviewScreen,
|
WriteReviewScreen,
|
||||||
OrderDetailsScreen,
|
OrderDetailsScreen,
|
||||||
} from '@features/screens';
|
} from '@features/screens';
|
||||||
|
import WalletScreen from '@features/screens/walletScreen/walletScreen';
|
||||||
|
|
||||||
export type AppStackParamList = {
|
export type AppStackParamList = {
|
||||||
MainTabs: NavigatorScreenParams<MainTabParamList> | undefined;
|
MainTabs: NavigatorScreenParams<MainTabParamList> | undefined;
|
||||||
@ -31,6 +32,7 @@ export type AppStackParamList = {
|
|||||||
HelpSupportScreen: undefined;
|
HelpSupportScreen: undefined;
|
||||||
WriteReviewScreen: { productId: string } | undefined;
|
WriteReviewScreen: { productId: string } | undefined;
|
||||||
OrderDetailsScreen: { orderId: string } | undefined;
|
OrderDetailsScreen: { orderId: string } | undefined;
|
||||||
|
WalletScreen: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Stack = createStackNavigator<AppStackParamList>();
|
const Stack = createStackNavigator<AppStackParamList>();
|
||||||
@ -69,6 +71,7 @@ export const AppStack: React.FC = () => {
|
|||||||
<Stack.Screen name="HelpSupportScreen" component={HelpSupportScreen} />
|
<Stack.Screen name="HelpSupportScreen" component={HelpSupportScreen} />
|
||||||
<Stack.Screen name="WriteReviewScreen" component={WriteReviewScreen} />
|
<Stack.Screen name="WriteReviewScreen" component={WriteReviewScreen} />
|
||||||
<Stack.Screen name="OrderDetailsScreen" component={OrderDetailsScreen} />
|
<Stack.Screen name="OrderDetailsScreen" component={OrderDetailsScreen} />
|
||||||
|
<Stack.Screen name="WalletScreen" component={WalletScreen} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,11 @@ import providerDetailsReducer from '@features/screens/providerDetailsScreen/redu
|
|||||||
import paymentMethodsReducer from '@features/screens/checkoutPaymentScreen/reducer';
|
import paymentMethodsReducer from '@features/screens/checkoutPaymentScreen/reducer';
|
||||||
import customerProfileReducer from './commonreducers/customerProfile/reducer';
|
import customerProfileReducer from './commonreducers/customerProfile/reducer';
|
||||||
import { offerReducer } from './commonreducers/offer';
|
import { offerReducer } from './commonreducers/offer';
|
||||||
import { accountReducer, orderDetailsReducer } from '@features/screens';
|
import {
|
||||||
|
accountReducer,
|
||||||
|
orderDetailsReducer,
|
||||||
|
walletReducer,
|
||||||
|
} from '@features/screens';
|
||||||
import searchReducer from '@features/screens/searchScreen/reducer';
|
import searchReducer from '@features/screens/searchScreen/reducer';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
@ -29,6 +33,7 @@ const rootReducer = combineReducers({
|
|||||||
account: accountReducer,
|
account: accountReducer,
|
||||||
search: searchReducer,
|
search: searchReducer,
|
||||||
orderDetails: orderDetailsReducer,
|
orderDetails: orderDetailsReducer,
|
||||||
|
wallet: walletReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user