Compare commits
	
		
			2 Commits
		
	
	
		
			7bca25a30c
			...
			a6531ca88a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a6531ca88a | |||
| 9010574b59 | 
							
								
								
									
										2
									
								
								app.json
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								app.json
									
									
									
									
									
								
							@@ -15,7 +15,7 @@
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "name": "Assemblr",
 | 
					    "name": "Assemblr",
 | 
				
			||||||
    "slug": "Assemblr",
 | 
					    "slug": "Assemblr",
 | 
				
			||||||
    "version": "1.1.7",
 | 
					    "version": "1.3.4",
 | 
				
			||||||
    "orientation": "portrait",
 | 
					    "orientation": "portrait",
 | 
				
			||||||
    "icon": "./src/assets/icon.png",
 | 
					    "icon": "./src/assets/icon.png",
 | 
				
			||||||
    "userInterfaceStyle": "light",
 | 
					    "userInterfaceStyle": "light",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,7 @@
 | 
				
			|||||||
    "react": "18.2.0",
 | 
					    "react": "18.2.0",
 | 
				
			||||||
    "react-dom": "18.2.0",
 | 
					    "react-dom": "18.2.0",
 | 
				
			||||||
    "react-native": "0.72.6",
 | 
					    "react-native": "0.72.6",
 | 
				
			||||||
 | 
					    "react-native-animatable": "^1.4.0",
 | 
				
			||||||
    "react-native-gesture-handler": "~2.12.0",
 | 
					    "react-native-gesture-handler": "~2.12.0",
 | 
				
			||||||
    "react-native-image-zoom-viewer": "^3.0.1",
 | 
					    "react-native-image-zoom-viewer": "^3.0.1",
 | 
				
			||||||
    "react-native-keyevent": "^0.3.1",
 | 
					    "react-native-keyevent": "^0.3.1",
 | 
				
			||||||
@@ -58,7 +59,8 @@
 | 
				
			|||||||
    "react-native-webview": "13.2.2",
 | 
					    "react-native-webview": "13.2.2",
 | 
				
			||||||
    "react-redux": "^8.1.2",
 | 
					    "react-redux": "^8.1.2",
 | 
				
			||||||
    "redux": "^4.2.1",
 | 
					    "redux": "^4.2.1",
 | 
				
			||||||
    "rn-openapp": "^2.1.2"
 | 
					    "rn-openapp": "^2.1.2",
 | 
				
			||||||
 | 
					    "expo-av": "~13.4.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@babel/core": "^7.20.0",
 | 
					    "@babel/core": "^7.20.0",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/App.tsx
									
									
									
									
									
								
							@@ -1,28 +1,18 @@
 | 
				
			|||||||
import {StyleSheet, Text, View} from 'react-native';
 | 
					import {StyleSheet} from 'react-native';
 | 
				
			||||||
import {Provider} from "react-redux";
 | 
					import {Provider} from "react-redux";
 | 
				
			||||||
 | 
					 | 
				
			||||||
import {useFonts} from 'expo-font';
 | 
					 | 
				
			||||||
import {store} from "./redux/store";
 | 
					import {store} from "./redux/store";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import CommonPage from "./screens/CommonPage/CommonPage";
 | 
					import CommonPage from "./screens/CommonPage/CommonPage";
 | 
				
			||||||
import {BottomSheetModalProvider} from "@gorhom/bottom-sheet";
 | 
					import {BottomSheetModalProvider} from "@gorhom/bottom-sheet";
 | 
				
			||||||
import {GestureHandlerRootView} from "react-native-gesture-handler";
 | 
					import {GestureHandlerRootView} from "react-native-gesture-handler";
 | 
				
			||||||
import Toast from "react-native-toast-message";
 | 
					 | 
				
			||||||
import Constants from "expo-constants";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function App() {
 | 
					export default function App() {
 | 
				
			||||||
    let [fontsLoading] = useFonts({
 | 
					 | 
				
			||||||
        // 'SF Pro Text': require('./assets/fonts/SF-Pro-Text-Regular.otf')
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!fontsLoading)
 | 
					 | 
				
			||||||
        return <View><Text>Loading...</Text></View>
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Provider store={store}>
 | 
					        <Provider store={store}>
 | 
				
			||||||
            <GestureHandlerRootView style={{flex: 1}}>
 | 
					            <GestureHandlerRootView style={{flex: 1}}>
 | 
				
			||||||
                <BottomSheetModalProvider>
 | 
					                <BottomSheetModalProvider>
 | 
				
			||||||
                    <CommonPage/>
 | 
					                        <CommonPage/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                </BottomSheetModalProvider>
 | 
					                </BottomSheetModalProvider>
 | 
				
			||||||
            </GestureHandlerRootView>
 | 
					            </GestureHandlerRootView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,33 +1,10 @@
 | 
				
			|||||||
import axios, {AxiosHeaders, AxiosRequestConfig, AxiosRequestHeaders, InternalAxiosRequestConfig} from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
import * as SecureStore from 'expo-secure-store';
 | 
					
 | 
				
			||||||
import {useDispatch} from "react-redux";
 | 
					 | 
				
			||||||
import {logout} from "../features/auth/authSlice";
 | 
					 | 
				
			||||||
import {store} from "../redux/store";
 | 
					 | 
				
			||||||
export const baseUrl = 'https://assemblr.denco.store';
 | 
					export const baseUrl = 'https://assemblr.denco.store';
 | 
				
			||||||
// export const baseUrl = 'http://192.168.1.101:5000';
 | 
					// export const baseUrl = 'http://192.168.1.101:5000';
 | 
				
			||||||
const apiClient = axios.create({
 | 
					const apiClient = axios.create({
 | 
				
			||||||
    baseURL: baseUrl
 | 
					    baseURL: baseUrl
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apiClient.interceptors.request.use(async (config) => {
 | 
					 | 
				
			||||||
    const accessToken = await SecureStore.getItemAsync('accessToken');
 | 
					 | 
				
			||||||
    if (!config.headers) {
 | 
					 | 
				
			||||||
        config.headers = new AxiosHeaders();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (accessToken) {
 | 
					 | 
				
			||||||
        config.headers.set('Authorization', `Bearer ${accessToken}`, true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    config.validateStatus = (status) => {
 | 
					 | 
				
			||||||
        if (status == 401) {
 | 
					 | 
				
			||||||
            SecureStore.deleteItemAsync('accessToken');
 | 
					 | 
				
			||||||
            store.dispatch(logout());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return config;
 | 
					 | 
				
			||||||
}, function (error) {
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default apiClient;
 | 
					export default apiClient;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ const assemblyApi = {
 | 
				
			|||||||
        let response = await apiClient.post(`${router}/create`, {orderId});
 | 
					        let response = await apiClient.post(`${router}/create`, {orderId});
 | 
				
			||||||
        return response.data;
 | 
					        return response.data;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    close: async (assemblyId: number): Promise<{ ok: boolean, message: string }> => {
 | 
					    close: async (assemblyId: number): Promise<{ ok: boolean, message: string, reward: number }> => {
 | 
				
			||||||
        let response = await apiClient.post(`${router}/close`, {assemblyId});
 | 
					        let response = await apiClient.post(`${router}/close`, {assemblyId});
 | 
				
			||||||
        return response.data;
 | 
					        return response.data;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								src/api/balanceApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/api/balanceApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import {BalanceInfo, BalanceTransaction} from "../types/balance";
 | 
				
			||||||
 | 
					import apiClient from "./apiClient";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const router = '/balance';
 | 
				
			||||||
 | 
					const balanceApi = {
 | 
				
			||||||
 | 
					    getTransactions: async (page: number): Promise<{ balanceTransactions: BalanceTransaction[] }> => {
 | 
				
			||||||
 | 
					        const response = await apiClient.get(`${router}/transactions`, {params: {page}});
 | 
				
			||||||
 | 
					        return response.data;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    getBalanceInfo: async (): Promise<BalanceInfo> => {
 | 
				
			||||||
 | 
					        const response = await apiClient.get(`${router}/info`);
 | 
				
			||||||
 | 
					        return response.data;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default balanceApi;
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/icons/reward.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/icons/reward.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 18 KiB  | 
							
								
								
									
										93
									
								
								src/components/Animations/AnimationsView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/components/Animations/AnimationsView.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					import {FC, useEffect, useState} from "react";
 | 
				
			||||||
 | 
					import {StyleSheet, View, Image} from "react-native";
 | 
				
			||||||
 | 
					import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
 | 
				
			||||||
 | 
					import * as Animatable from 'react-native-animatable';
 | 
				
			||||||
 | 
					import DText from "../DText/DText";
 | 
				
			||||||
 | 
					import {useSelector} from "react-redux";
 | 
				
			||||||
 | 
					import {RootState, useAppDispatch} from "../../redux/store";
 | 
				
			||||||
 | 
					import {hideReward} from "../../features/animations/animationsSlice";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// const width = responsiveWidth(20)
 | 
				
			||||||
 | 
					const AnimationsView: FC = () => {
 | 
				
			||||||
 | 
					    const dispatch = useAppDispatch();
 | 
				
			||||||
 | 
					    const state = useSelector((state: RootState) => state.animations);
 | 
				
			||||||
 | 
					    const [currentAnimation, setCurrentAnimation] = useState('bounceInRight');
 | 
				
			||||||
 | 
					    const [animationStage, setAnimationStage] = useState('in'); // Управляет текущей стадией анимации
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        // После завершения анимации входа, начинаем анимацию выхода
 | 
				
			||||||
 | 
					        if (animationStage === 'out') {
 | 
				
			||||||
 | 
					            setCurrentAnimation('bounceOutRight');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [animationStage]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onAnimationEnd = () => {
 | 
				
			||||||
 | 
					        if (animationStage === 'in') {
 | 
				
			||||||
 | 
					            // После завершения анимации входа, переходим к анимации выхода
 | 
				
			||||||
 | 
					            setAnimationStage('out');
 | 
				
			||||||
 | 
					        } else if (animationStage === "out") {
 | 
				
			||||||
 | 
					            dispatch(hideReward());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const startAnimation = () => {
 | 
				
			||||||
 | 
					        setCurrentAnimation("bounceInRight");
 | 
				
			||||||
 | 
					        setAnimationStage('in');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (state.isRewardAnimationVisible)
 | 
				
			||||||
 | 
					            startAnimation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }, [state.isRewardAnimationVisible]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <View style={{...style.container, display: state.isRewardAnimationVisible ? "flex" : "none"}}>
 | 
				
			||||||
 | 
					            <Animatable.View
 | 
				
			||||||
 | 
					                style={{
 | 
				
			||||||
 | 
					                    backgroundColor: "white",
 | 
				
			||||||
 | 
					                    borderRadius: responsiveWidth(3),
 | 
				
			||||||
 | 
					                    // width: width,
 | 
				
			||||||
 | 
					                    paddingHorizontal: responsiveWidth(2),
 | 
				
			||||||
 | 
					                    display: "flex",
 | 
				
			||||||
 | 
					                    flexDirection: "row",
 | 
				
			||||||
 | 
					                    alignItems: "center",
 | 
				
			||||||
 | 
					                    gap: responsiveWidth(1),
 | 
				
			||||||
 | 
					                    zIndex: 1,
 | 
				
			||||||
 | 
					                    elevation: 10,
 | 
				
			||||||
 | 
					                    justifyContent: "center"
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                animation={currentAnimation}
 | 
				
			||||||
 | 
					                duration={1000} // Продолжительность анимации
 | 
				
			||||||
 | 
					                easing="ease-in-out" // Тип анимации
 | 
				
			||||||
 | 
					                iterationCount={1} // Бесконечное повторение
 | 
				
			||||||
 | 
					                onAnimationEnd={onAnimationEnd}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <View style={{
 | 
				
			||||||
 | 
					                    height: responsiveHeight(5)
 | 
				
			||||||
 | 
					                }}>
 | 
				
			||||||
 | 
					                    <Image
 | 
				
			||||||
 | 
					                        style={{
 | 
				
			||||||
 | 
					                            height: responsiveHeight(5),
 | 
				
			||||||
 | 
					                            width:responsiveWidth(5),
 | 
				
			||||||
 | 
					                            resizeMode: "contain"
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                        source={require('assets/icons/reward.png')}/>
 | 
				
			||||||
 | 
					                </View>
 | 
				
			||||||
 | 
					                <DText>+{state.rewardAmount.toLocaleString('ru-RU')}₽</DText>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </Animatable.View>
 | 
				
			||||||
 | 
					        </View>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const style = StyleSheet.create({
 | 
				
			||||||
 | 
					    container: {
 | 
				
			||||||
 | 
					        position: "absolute",
 | 
				
			||||||
 | 
					        right: 0,
 | 
				
			||||||
 | 
					        top: 0,
 | 
				
			||||||
 | 
					        marginTop: responsiveHeight(10),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export default AnimationsView;
 | 
				
			||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
import React, {FC, ReactElement} from "react";
 | 
					import React, {FC, ReactNode} from "react";
 | 
				
			||||||
import {StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native';
 | 
					import {StyleProp, StyleSheet, TextStyle} from 'react-native';
 | 
				
			||||||
import {RFPercentage} from "react-native-responsive-fontsize";
 | 
					 | 
				
			||||||
import DText from "../DText/DText";
 | 
					import DText from "../DText/DText";
 | 
				
			||||||
import {responsiveScreenFontSize, responsiveWidth} from "react-native-responsive-dimensions";
 | 
					import {responsiveScreenFontSize} from "react-native-responsive-dimensions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    children: string;
 | 
					    children: ReactNode;
 | 
				
			||||||
    style?: StyleProp<TextStyle>;
 | 
					    style?: StyleProp<TextStyle>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
const DTitle: FC<Props> = ({children, style}) => {
 | 
					const DTitle: FC<Props> = ({children, style}) => {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								src/contexts/apiContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/contexts/apiContext.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import {createContext, FC, ReactNode, useContext} from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ApiState = {
 | 
				
			||||||
 | 
					    onLogout: () => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const apiContext = createContext<ApiState | undefined>(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useApiContext = () => {
 | 
				
			||||||
 | 
					    const context = useContext(apiContext);
 | 
				
			||||||
 | 
					    if (!context) throw new Error('useApiContext must be used within ApiContextProvider');
 | 
				
			||||||
 | 
					    return context;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ApiContextProviderProps = {
 | 
				
			||||||
 | 
					    children: ReactNode;
 | 
				
			||||||
 | 
					    state: ApiState;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export const ApiContextProvider: FC<ApiContextProviderProps> = ({children, state}) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <ApiContextProvider state={state}>
 | 
				
			||||||
 | 
					            {children}
 | 
				
			||||||
 | 
					        </ApiContextProvider>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/features/animations/animationsSlice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/features/animations/animationsSlice.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					import {createSlice, PayloadAction} from "@reduxjs/toolkit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AnimationsSlice {
 | 
				
			||||||
 | 
					    isRewardAnimationVisible: boolean;
 | 
				
			||||||
 | 
					    rewardAmount: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initialState: AnimationsSlice = {
 | 
				
			||||||
 | 
					    isRewardAnimationVisible: false,
 | 
				
			||||||
 | 
					    rewardAmount: 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const animationsSlice = createSlice({
 | 
				
			||||||
 | 
					    name: 'animations',
 | 
				
			||||||
 | 
					    initialState,
 | 
				
			||||||
 | 
					    reducers: {
 | 
				
			||||||
 | 
					        showReward: (state, action: PayloadAction<{ reward: number }>) => {
 | 
				
			||||||
 | 
					            state.rewardAmount = action.payload.reward;
 | 
				
			||||||
 | 
					            state.isRewardAnimationVisible = true;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        hideReward: (state) => {
 | 
				
			||||||
 | 
					            state.isRewardAnimationVisible = false;
 | 
				
			||||||
 | 
					            state.rewardAmount = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const {showReward, hideReward} = animationsSlice.actions;
 | 
				
			||||||
 | 
					export default animationsSlice.reducer;
 | 
				
			||||||
							
								
								
									
										71
									
								
								src/features/balance/balanceSlice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/features/balance/balanceSlice.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					import {BalanceInfo, BalanceTransaction} from "../../types/balance";
 | 
				
			||||||
 | 
					import {createAsyncThunk, createSlice} from "@reduxjs/toolkit";
 | 
				
			||||||
 | 
					import balanceApi from "../../api/balanceApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const name = 'balance';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TransactionsState {
 | 
				
			||||||
 | 
					    items: BalanceTransaction[];
 | 
				
			||||||
 | 
					    isLoading: boolean;
 | 
				
			||||||
 | 
					    currentPage: number;
 | 
				
			||||||
 | 
					    hasNext: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface BalanceState {
 | 
				
			||||||
 | 
					    balance: number;
 | 
				
			||||||
 | 
					    transactions: TransactionsState;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const transactionsInitialState: TransactionsState = {
 | 
				
			||||||
 | 
					    currentPage: 1,
 | 
				
			||||||
 | 
					    hasNext: true,
 | 
				
			||||||
 | 
					    isLoading: false,
 | 
				
			||||||
 | 
					    items: []
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const initialState: BalanceState = {
 | 
				
			||||||
 | 
					    balance: 0,
 | 
				
			||||||
 | 
					    transactions: transactionsInitialState
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fetchTransactions = createAsyncThunk(
 | 
				
			||||||
 | 
					    `${name}/fetchTransactions`,
 | 
				
			||||||
 | 
					    async (page: number, _): Promise<BalanceTransaction[]> => {
 | 
				
			||||||
 | 
					        const response = await balanceApi.getTransactions(page);
 | 
				
			||||||
 | 
					        return response.balanceTransactions;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fetchBalance = createAsyncThunk(
 | 
				
			||||||
 | 
					    `${name}/fetchBalance`,
 | 
				
			||||||
 | 
					    async (_): Promise<BalanceInfo> => {
 | 
				
			||||||
 | 
					        return await balanceApi.getBalanceInfo();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					export const balanceSlice = createSlice({
 | 
				
			||||||
 | 
					    name: name,
 | 
				
			||||||
 | 
					    initialState,
 | 
				
			||||||
 | 
					    reducers: {
 | 
				
			||||||
 | 
					        refreshTransactions: (state) => {
 | 
				
			||||||
 | 
					            state.transactions = transactionsInitialState;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    extraReducers: (builder) => {
 | 
				
			||||||
 | 
					        builder.addCase(fetchTransactions.pending, (state, action) => {
 | 
				
			||||||
 | 
					            state.transactions.isLoading = true;
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        builder.addCase(fetchTransactions.fulfilled, (state, action) => {
 | 
				
			||||||
 | 
					            state.transactions.isLoading = false;
 | 
				
			||||||
 | 
					            state.transactions.hasNext = action.payload.length > 0;
 | 
				
			||||||
 | 
					            state.transactions.items = [...state.transactions.items, ...action.payload];
 | 
				
			||||||
 | 
					            state.transactions.currentPage = state.transactions.currentPage + 1;
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        builder.addCase(fetchBalance.fulfilled, (state, action) => {
 | 
				
			||||||
 | 
					            state.balance = action.payload.balance;
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const {refreshTransactions} = balanceSlice.actions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default balanceSlice.reducer;
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/redux/general.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/redux/general.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import {AxiosError, AxiosRequestConfig} from "axios";
 | 
				
			||||||
 | 
					import {BaseQueryFn} from "@reduxjs/toolkit/query";
 | 
				
			||||||
 | 
					import apiClient from "../api/apiClient";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const axiosBaseQuery =
 | 
				
			||||||
 | 
					    (): BaseQueryFn<
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            url: string
 | 
				
			||||||
 | 
					            method?: AxiosRequestConfig['method']
 | 
				
			||||||
 | 
					            data?: AxiosRequestConfig['data']
 | 
				
			||||||
 | 
					            params?: AxiosRequestConfig['params']
 | 
				
			||||||
 | 
					            headers?: AxiosRequestConfig['headers']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    > =>
 | 
				
			||||||
 | 
					        async ({method, data, params, headers}) => {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const result = await apiClient({
 | 
				
			||||||
 | 
					                    method,
 | 
				
			||||||
 | 
					                    data,
 | 
				
			||||||
 | 
					                    params,
 | 
				
			||||||
 | 
					                    headers,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                return {data: result.data}
 | 
				
			||||||
 | 
					            } catch (axiosError) {
 | 
				
			||||||
 | 
					                const err = axiosError as AxiosError
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    error: {
 | 
				
			||||||
 | 
					                        status: err.response?.status,
 | 
				
			||||||
 | 
					                        data: err.response?.data || err.message,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
@@ -12,6 +12,8 @@ import ordersFilterReducer from 'features/ordersFilter/ordersFilterSlice';
 | 
				
			|||||||
import shippingWarehouseSelectReducer from 'features/shippingWarehouseSelect/shippingWarehouseSelectSlice';
 | 
					import shippingWarehouseSelectReducer from 'features/shippingWarehouseSelect/shippingWarehouseSelectSlice';
 | 
				
			||||||
import citySelectReducer from 'features/citySelect/citySelectSlice';
 | 
					import citySelectReducer from 'features/citySelect/citySelectSlice';
 | 
				
			||||||
import cancelAssemblyModal from 'features/cancelAssemblyModal/cancelAssemblyModalSlice';
 | 
					import cancelAssemblyModal from 'features/cancelAssemblyModal/cancelAssemblyModalSlice';
 | 
				
			||||||
 | 
					import balanceReducer from 'features/balance/balanceSlice';
 | 
				
			||||||
 | 
					import animationsReducer from 'features/animations/animationsSlice';
 | 
				
			||||||
import {useDispatch} from "react-redux";
 | 
					import {useDispatch} from "react-redux";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const store = configureStore({
 | 
					export const store = configureStore({
 | 
				
			||||||
@@ -27,7 +29,9 @@ export const store = configureStore({
 | 
				
			|||||||
        ordersFilter: ordersFilterReducer,
 | 
					        ordersFilter: ordersFilterReducer,
 | 
				
			||||||
        shippingWarehouseSelect: shippingWarehouseSelectReducer,
 | 
					        shippingWarehouseSelect: shippingWarehouseSelectReducer,
 | 
				
			||||||
        citySelect: citySelectReducer,
 | 
					        citySelect: citySelectReducer,
 | 
				
			||||||
        cancelAssemblyModal: cancelAssemblyModal
 | 
					        cancelAssemblyModal: cancelAssemblyModal,
 | 
				
			||||||
 | 
					        balance: balanceReducer,
 | 
				
			||||||
 | 
					        animations: animationsReducer
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import React, {useEffect} from "react";
 | 
				
			|||||||
import {background} from "../../css/colors";
 | 
					import {background} from "../../css/colors";
 | 
				
			||||||
import {useDispatch, useSelector} from "react-redux";
 | 
					import {useDispatch, useSelector} from "react-redux";
 | 
				
			||||||
import {RootState} from "../../redux/store";
 | 
					import {RootState} from "../../redux/store";
 | 
				
			||||||
import {login} from "../../features/auth/authSlice";
 | 
					import {login, logout} from "../../features/auth/authSlice";
 | 
				
			||||||
import * as SecureStore from 'expo-secure-store';
 | 
					import * as SecureStore from 'expo-secure-store';
 | 
				
			||||||
import Toast from "react-native-toast-message";
 | 
					import Toast from "react-native-toast-message";
 | 
				
			||||||
import toastConfig from "../../components/Toast/Toast";
 | 
					import toastConfig from "../../components/Toast/Toast";
 | 
				
			||||||
@@ -21,9 +21,7 @@ import {ASSEMBLY_STATE} from "../../types/assembly";
 | 
				
			|||||||
import Constants from "expo-constants";
 | 
					import Constants from "expo-constants";
 | 
				
			||||||
import * as FileSystem from 'expo-file-system';
 | 
					import * as FileSystem from 'expo-file-system';
 | 
				
			||||||
import applicationApi from "../../api/applicationApi";
 | 
					import applicationApi from "../../api/applicationApi";
 | 
				
			||||||
import {ActivityAction, startActivityAsync} from "expo-intent-launcher";
 | 
					import {startActivityAsync} from "expo-intent-launcher";
 | 
				
			||||||
import {RenderTargetOptions} from "@shopify/flash-list";
 | 
					 | 
				
			||||||
import KeyEvent from 'react-native-keyevent';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    openLoadingModal,
 | 
					    openLoadingModal,
 | 
				
			||||||
    setIndeterminate,
 | 
					    setIndeterminate,
 | 
				
			||||||
@@ -31,8 +29,12 @@ import {
 | 
				
			|||||||
    setProgress
 | 
					    setProgress
 | 
				
			||||||
} from "../../features/loadingModal/loadingModalSlice";
 | 
					} from "../../features/loadingModal/loadingModalSlice";
 | 
				
			||||||
import CancelAssemblyModal from "../../components/Modals/CancelAssemblyModal/CancelAssemblyModal";
 | 
					import CancelAssemblyModal from "../../components/Modals/CancelAssemblyModal/CancelAssemblyModal";
 | 
				
			||||||
 | 
					import {AxiosHeaders} from "axios";
 | 
				
			||||||
 | 
					import apiClient from "../../api/apiClient";
 | 
				
			||||||
 | 
					import AnimationsView from "../../components/Animations/AnimationsView";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function CommonPage() {
 | 
					function CommonPage() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dim = useSelector((state: RootState) => state.interface.dim);
 | 
					    const dim = useSelector((state: RootState) => state.interface.dim);
 | 
				
			||||||
    const isAuthorized = useSelector((state: RootState) => state.auth.isAuthorized);
 | 
					    const isAuthorized = useSelector((state: RootState) => state.auth.isAuthorized);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,13 +87,42 @@ function CommonPage() {
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const configApi = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        apiClient.interceptors.request.use(async (config) => {
 | 
				
			||||||
 | 
					            const accessToken = await SecureStore.getItemAsync('accessToken');
 | 
				
			||||||
 | 
					            if (!config.headers) {
 | 
				
			||||||
 | 
					                config.headers = new AxiosHeaders();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (accessToken) {
 | 
				
			||||||
 | 
					                config.headers.set('Authorization', `Bearer ${accessToken}`, true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            config.validateStatus = (status) => {
 | 
				
			||||||
 | 
					                if (status == 401) {
 | 
				
			||||||
 | 
					                    SecureStore.deleteItemAsync('accessToken');
 | 
				
			||||||
 | 
					                    dispatch(logout());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return config;
 | 
				
			||||||
 | 
					        }, function (error) {
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        configApi();
 | 
				
			||||||
        checkUpdates();
 | 
					        checkUpdates();
 | 
				
			||||||
 | 
					        loadSettings();
 | 
				
			||||||
    }, []);
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <View style={styles.main}>
 | 
					        <View style={styles.main}>
 | 
				
			||||||
            {isAuthorized ? <MainScreen/> : <LoginScreen/>}
 | 
					            {isAuthorized ? <MainScreen/> : <LoginScreen/>}
 | 
				
			||||||
 | 
					            <AnimationsView/>
 | 
				
			||||||
            <View style={[styles.overlay, {display: dim ? 'flex' : 'none'}]}/>
 | 
					            <View style={[styles.overlay, {display: dim ? 'flex' : 'none'}]}/>
 | 
				
			||||||
            <LoadingModal/>
 | 
					            <LoadingModal/>
 | 
				
			||||||
            <ScanModal/>
 | 
					            <ScanModal/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import {StyleSheet, Text, View, ImageBackground, Image} from 'react-native';
 | 
					import {StyleSheet, Image} from 'react-native';
 | 
				
			||||||
import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
 | 
					import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
 | 
				
			||||||
import BarcodeScreen from "../BarcodeScreen/BarcodeScreen";
 | 
					import BarcodeScreen from "../BarcodeScreen/BarcodeScreen";
 | 
				
			||||||
import {DefaultTheme, NavigationContainer} from "@react-navigation/native";
 | 
					import {DefaultTheme, NavigationContainer} from "@react-navigation/native";
 | 
				
			||||||
@@ -6,10 +6,10 @@ import moneyScreen from "../MoneyScreen/MoneyScreen";
 | 
				
			|||||||
import profileScreen from "../ProfileScreen/ProfileScreen";
 | 
					import profileScreen from "../ProfileScreen/ProfileScreen";
 | 
				
			||||||
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
 | 
					import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
 | 
				
			||||||
import {RFPercentage} from "react-native-responsive-fontsize";
 | 
					import {RFPercentage} from "react-native-responsive-fontsize";
 | 
				
			||||||
import OrderScreen, {OrderScreenController} from "../OrderScreen/OrderScreen";
 | 
					import {OrderScreenController} from "../OrderScreen/OrderScreen";
 | 
				
			||||||
import OrdersScreen from "../OrdersScreen/OrdersScreen";
 | 
					import OrdersScreen from "../OrdersScreen/OrdersScreen";
 | 
				
			||||||
import {background} from "../../css/colors";
 | 
					import {background} from "../../css/colors";
 | 
				
			||||||
import * as fs from "fs";
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export  type TabNavigatorParamList = {
 | 
					export  type TabNavigatorParamList = {
 | 
				
			||||||
    Home: undefined;
 | 
					    Home: undefined;
 | 
				
			||||||
@@ -30,16 +30,16 @@ const CustomTab = ({name, component, icon, hidden}: CustomTabProps) => ({
 | 
				
			|||||||
    name,
 | 
					    name,
 | 
				
			||||||
    component,
 | 
					    component,
 | 
				
			||||||
    options: {
 | 
					    options: {
 | 
				
			||||||
        tabBarIcon: ({focused, color, size}: { focused: boolean; color: string; size: number }) => (
 | 
					        tabBarIcon: ({focused, color, size}: { focused: boolean; color: string; size: number }) => {
 | 
				
			||||||
            <Image
 | 
					            return (<Image
 | 
				
			||||||
                source={icon}
 | 
					                source={icon}
 | 
				
			||||||
                style={{
 | 
					                style={{
 | 
				
			||||||
                    width: RFPercentage(4),
 | 
					                    width: RFPercentage(4),
 | 
				
			||||||
                    height: RFPercentage(4),
 | 
					                    height: RFPercentage(4),
 | 
				
			||||||
                    tintColor: color,
 | 
					                    tintColor: color,
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
            />
 | 
					            />)
 | 
				
			||||||
        ),
 | 
					        },
 | 
				
			||||||
        tabBarLabel: () => null,
 | 
					        tabBarLabel: () => null,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -90,6 +90,7 @@ function MainScreen() {
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
        }}>
 | 
					        }}>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <Tab.Navigator screenOptions={{
 | 
					            <Tab.Navigator screenOptions={{
 | 
				
			||||||
                tabBarStyle: styles.tabBarStyle,
 | 
					                tabBarStyle: styles.tabBarStyle,
 | 
				
			||||||
                headerTitle: "",
 | 
					                headerTitle: "",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,8 @@ import {RFPercentage} from "react-native-responsive-fontsize";
 | 
				
			|||||||
import DTitle from "../../components/DTitle/DTitle";
 | 
					import DTitle from "../../components/DTitle/DTitle";
 | 
				
			||||||
import BasicButton from "../../components/BasicButton/BasicButton";
 | 
					import BasicButton from "../../components/BasicButton/BasicButton";
 | 
				
			||||||
import React, {FC, useEffect, useState} from "react";
 | 
					import React, {FC, useEffect, useState} from "react";
 | 
				
			||||||
import {useDispatch, useSelector} from "react-redux";
 | 
					import {useSelector} from "react-redux";
 | 
				
			||||||
import {RootState} from "../../redux/store";
 | 
					import {RootState, useAppDispatch} from "../../redux/store";
 | 
				
			||||||
import {closeLoadingModal, openLoadingModal, setLoadingText} from "../../features/loadingModal/loadingModalSlice";
 | 
					import {closeLoadingModal, openLoadingModal, setLoadingText} from "../../features/loadingModal/loadingModalSlice";
 | 
				
			||||||
import {NavigationProp, useNavigation} from "@react-navigation/native";
 | 
					import {NavigationProp, useNavigation} from "@react-navigation/native";
 | 
				
			||||||
import printingApi from "../../api/printingApi";
 | 
					import printingApi from "../../api/printingApi";
 | 
				
			||||||
@@ -31,6 +31,8 @@ import {TabNavigatorParamList} from "../MainScreen/MainScreen";
 | 
				
			|||||||
import {openImageZoomModal, setImages} from "../../features/imageZoomModal/loadingModalSlice";
 | 
					import {openImageZoomModal, setImages} from "../../features/imageZoomModal/loadingModalSlice";
 | 
				
			||||||
import {OrderStatus, OrderStatusDictionary} from "../../features/ordersFilter/ordersFilterSlice";
 | 
					import {OrderStatus, OrderStatusDictionary} from "../../features/ordersFilter/ordersFilterSlice";
 | 
				
			||||||
import {openCancelAssemblyModal} from "../../features/cancelAssemblyModal/cancelAssemblyModalSlice";
 | 
					import {openCancelAssemblyModal} from "../../features/cancelAssemblyModal/cancelAssemblyModalSlice";
 | 
				
			||||||
 | 
					import {fetchBalance, refreshTransactions} from "../../features/balance/balanceSlice";
 | 
				
			||||||
 | 
					import {showReward} from "../../features/animations/animationsSlice";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AssemblyPeriod = {
 | 
					type AssemblyPeriod = {
 | 
				
			||||||
@@ -68,7 +70,7 @@ type OrderScreenProps = {
 | 
				
			|||||||
const OrderScreen: FC<OrderScreenProps> = ({order}) => {
 | 
					const OrderScreen: FC<OrderScreenProps> = ({order}) => {
 | 
				
			||||||
    const navigator = useNavigation<NavigationProp<TabNavigatorParamList, 'Barcode'>>();
 | 
					    const navigator = useNavigation<NavigationProp<TabNavigatorParamList, 'Barcode'>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const dispatch = useDispatch();
 | 
					    const dispatch = useAppDispatch();
 | 
				
			||||||
    const assembly = useSelector((state: RootState) => state.assembly.assembly);
 | 
					    const assembly = useSelector((state: RootState) => state.assembly.assembly);
 | 
				
			||||||
    const assemblyState = useSelector((state: RootState) => state.assembly.localState);
 | 
					    const assemblyState = useSelector((state: RootState) => state.assembly.localState);
 | 
				
			||||||
    const initialOrder = useSelector((state: RootState) => state.assembly.initialOrder);
 | 
					    const initialOrder = useSelector((state: RootState) => state.assembly.initialOrder);
 | 
				
			||||||
@@ -144,12 +146,19 @@ const OrderScreen: FC<OrderScreenProps> = ({order}) => {
 | 
				
			|||||||
                            label={"Завершить сборку"}
 | 
					                            label={"Завершить сборку"}
 | 
				
			||||||
                            onPress={() => {
 | 
					                            onPress={() => {
 | 
				
			||||||
                                if (!assembly) return;
 | 
					                                if (!assembly) return;
 | 
				
			||||||
                                assemblyApi.close(assembly.databaseId).then(({ok, message}) => {
 | 
					                                assemblyApi.close(assembly.databaseId).then(({ok, message, reward}) => {
 | 
				
			||||||
                                    Toast.show({
 | 
					                                    Toast.show({
 | 
				
			||||||
                                        type: ok ? 'success' : 'error',
 | 
					                                        type: ok ? 'success' : 'error',
 | 
				
			||||||
                                        text1: 'Завершение сборки',
 | 
					                                        text1: 'Завершение сборки',
 | 
				
			||||||
                                        text2: message
 | 
					                                        text2: message
 | 
				
			||||||
                                    });
 | 
					                                    });
 | 
				
			||||||
 | 
					                                    if (ok) {
 | 
				
			||||||
 | 
					                                        dispatch(showReward({reward}));
 | 
				
			||||||
 | 
					                                        dispatch(fetchBalance())
 | 
				
			||||||
 | 
					                                        dispatch(refreshTransactions())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    dispatch(endAssembly());
 | 
					                                    dispatch(endAssembly());
 | 
				
			||||||
                                    navigator.navigate('Barcode');
 | 
					                                    navigator.navigate('Barcode');
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,130 +1,54 @@
 | 
				
			|||||||
import {View, StyleSheet, TouchableOpacity, Image, ScrollView, GestureResponderEvent} from "react-native";
 | 
					import {View, StyleSheet} from "react-native";
 | 
				
			||||||
import {responsiveFontSize, responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
 | 
					import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
 | 
				
			||||||
import {blue, gray} from "../../css/colors";
 | 
					import {blue} from "../../css/colors";
 | 
				
			||||||
import {RFPercentage} from "react-native-responsive-fontsize";
 | 
					import {RFPercentage} from "react-native-responsive-fontsize";
 | 
				
			||||||
import DText from "../../components/DText/DText";
 | 
					 | 
				
			||||||
import DTitle from "../../components/DTitle/DTitle";
 | 
					import DTitle from "../../components/DTitle/DTitle";
 | 
				
			||||||
import Separator from "../../components/Separator/Separator";
 | 
					import Separator from "../../components/Separator/Separator";
 | 
				
			||||||
import {BottomSheetModal} from "@gorhom/bottom-sheet";
 | 
					import {useEffect} from "react";
 | 
				
			||||||
import {useMemo, useRef, useState} from "react";
 | 
					import {useSelector} from "react-redux";
 | 
				
			||||||
import assemblyApi from "../../api/assemblyApi";
 | 
					import SettingsView from "./SettingsView";
 | 
				
			||||||
import Toast from "react-native-toast-message";
 | 
					import TransactionsView from "./TransactionView";
 | 
				
			||||||
import {useDispatch} from "react-redux";
 | 
					import {RootState, useAppDispatch} from "../../redux/store";
 | 
				
			||||||
import {reset} from "../../features/assembly/assemblySlice";
 | 
					import {fetchBalance, fetchTransactions, refreshTransactions} from "../../features/balance/balanceSlice";
 | 
				
			||||||
 | 
					import {formatBalanceNumber} from "../../utils/formatters";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SettingsElementProps = {
 | 
					 | 
				
			||||||
    icon: any;
 | 
					 | 
				
			||||||
    title: string;
 | 
					 | 
				
			||||||
    onPress?: (event: GestureResponderEvent) => void
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const SettingsElement: React.FC<SettingsElementProps> = ({icon, title, onPress}) => {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <TouchableOpacity onPress={onPress}>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <View style={styles.actionsCarouselElementContainer}>
 | 
					 | 
				
			||||||
                <View style={styles.actionsCarouselImageWrapper}>
 | 
					 | 
				
			||||||
                    <Image style={styles.actionsCarouselImage} source={icon}/>
 | 
					 | 
				
			||||||
                </View>
 | 
					 | 
				
			||||||
                <DText>{title}</DText>
 | 
					 | 
				
			||||||
            </View>
 | 
					 | 
				
			||||||
        </TouchableOpacity>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
type HistoryElementProps = {
 | 
					 | 
				
			||||||
    cost: number;
 | 
					 | 
				
			||||||
    description: string;
 | 
					 | 
				
			||||||
    color: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
const HistoryElement: React.FC<HistoryElementProps> = ({cost, description, color}) => {
 | 
					 | 
				
			||||||
    const formatNumber = (n: number): string => n >= 0 ? `+${n}` : `${n}`;
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <View style={styles.historyElementContainer}>
 | 
					 | 
				
			||||||
            <DText style={{
 | 
					 | 
				
			||||||
                fontSize: responsiveFontSize(2),
 | 
					 | 
				
			||||||
                color: color,
 | 
					 | 
				
			||||||
                fontWeight: "500"
 | 
					 | 
				
			||||||
            }}>{formatNumber(cost)} руб</DText>
 | 
					 | 
				
			||||||
            <DText style={{fontSize: responsiveFontSize(1.5), color: gray, fontWeight: "400"}}>{description}</DText>
 | 
					 | 
				
			||||||
        </View>
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function ProfileScreen() {
 | 
					function ProfileScreen() {
 | 
				
			||||||
 | 
					    const dispatch = useAppDispatch();
 | 
				
			||||||
    const bottomSheetModalRef = useRef<BottomSheetModal>(null);
 | 
					    const balanceState = useSelector((state: RootState) => state.balance);
 | 
				
			||||||
    const snapPoints = useMemo(() => ['25%', '40%'], []);
 | 
					    const onTransactionsEndReached = () => {
 | 
				
			||||||
    const [modalVisible, setModalVisible] = useState(false);
 | 
					        if (balanceState.transactions.isLoading || !balanceState.transactions.hasNext) return;
 | 
				
			||||||
    const dispatch = useDispatch();
 | 
					        dispatch(fetchTransactions(balanceState.transactions.currentPage));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const onTransactionsRefresh = () => {
 | 
				
			||||||
 | 
					        dispatch(refreshTransactions());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        dispatch(fetchBalance());
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <View style={styles.container}>
 | 
					        <View style={styles.container}>
 | 
				
			||||||
            <View style={styles.header}>
 | 
					            <View style={styles.header}>
 | 
				
			||||||
                <View style={styles.headerInfo}>
 | 
					                <View style={styles.headerInfo}>
 | 
				
			||||||
                    <DTitle style={styles.headerText}>Ваш баланс: 228 руб</DTitle>
 | 
					                    <DTitle style={styles.headerText}>Ваш
 | 
				
			||||||
                    <DTitle style={styles.headerText}>Собрано товаров: 1337 шт</DTitle>
 | 
					                        баланс: {formatBalanceNumber(balanceState.balance, false)} руб</DTitle>
 | 
				
			||||||
                </View>
 | 
					                </View>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            </View>
 | 
					            </View>
 | 
				
			||||||
            <View style={styles.content}>
 | 
					            <View style={styles.content}>
 | 
				
			||||||
                <View style={styles.actionsCarouselContainer}>
 | 
					                <View style={styles.actionsCarouselContainer}>
 | 
				
			||||||
                    <ScrollView horizontal={true}
 | 
					                    <SettingsView/>
 | 
				
			||||||
                                showsVerticalScrollIndicator={false}
 | 
					 | 
				
			||||||
                                showsHorizontalScrollIndicator={false}
 | 
					 | 
				
			||||||
                                contentContainerStyle={styles.actionsCarousel}
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                        <SettingsElement icon={require('assets/icons/settings/withdraw.png')} title={'Вывод'}/>
 | 
					 | 
				
			||||||
                        <SettingsElement icon={require('assets/icons/settings/statistics.png')} title={'Статистика'}/>
 | 
					 | 
				
			||||||
                        <SettingsElement icon={require('assets/icons/settings/printer.png')} title={'Принтеры'}/>
 | 
					 | 
				
			||||||
                        <SettingsElement onPress={() => {
 | 
					 | 
				
			||||||
                            assemblyApi.cancel().then(response => {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                Toast.show({
 | 
					 | 
				
			||||||
                                    type: response.ok ? "success" : "error",
 | 
					 | 
				
			||||||
                                    text1: "Отмена сборки",
 | 
					 | 
				
			||||||
                                    text2: response.message,
 | 
					 | 
				
			||||||
                                });
 | 
					 | 
				
			||||||
                                dispatch(reset());
 | 
					 | 
				
			||||||
                            })
 | 
					 | 
				
			||||||
                        }} icon={require('assets/icons/settings/close.png')} title={'Отменить сборку'}/>
 | 
					 | 
				
			||||||
                    </ScrollView>
 | 
					 | 
				
			||||||
                </View>
 | 
					                </View>
 | 
				
			||||||
                <Separator/>
 | 
					                <Separator/>
 | 
				
			||||||
                <View style={styles.historyContainer}>
 | 
					                <View style={styles.historyContainer}>
 | 
				
			||||||
                    <DTitle>История операций</DTitle>
 | 
					                    <DTitle>История операций</DTitle>
 | 
				
			||||||
                    <ScrollView
 | 
					                    <TransactionsView
 | 
				
			||||||
                        showsHorizontalScrollIndicator={false}
 | 
					                        onRefresh={onTransactionsRefresh}
 | 
				
			||||||
                        showsVerticalScrollIndicator={false}
 | 
					                        isLoading={balanceState.transactions.isLoading}
 | 
				
			||||||
                        style={styles.historyElements}
 | 
					                        transactions={balanceState.transactions.items}
 | 
				
			||||||
                        contentContainerStyle={styles.historyElementsContainer}>
 | 
					                        onEndReached={onTransactionsEndReached}/>
 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                        <HistoryElement cost={25} description={"сбор товара №36243869"} color={"#2478F8"}/>
 | 
					 | 
				
			||||||
                    </ScrollView>
 | 
					 | 
				
			||||||
                </View>
 | 
					                </View>
 | 
				
			||||||
            </View>
 | 
					            </View>
 | 
				
			||||||
            <BottomSheetModal ref={bottomSheetModalRef}
 | 
					 | 
				
			||||||
                              snapPoints={snapPoints}
 | 
					 | 
				
			||||||
                              onDismiss={() => {
 | 
					 | 
				
			||||||
                                  setModalVisible(false);
 | 
					 | 
				
			||||||
                              }}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                <View style={{flex: 1}}>
 | 
					 | 
				
			||||||
                    <DText>хуй</DText>
 | 
					 | 
				
			||||||
                </View>
 | 
					 | 
				
			||||||
            </BottomSheetModal>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <View style={[styles.overlay, {display: modalVisible ? 'flex' : 'none'}]}/>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        </View>
 | 
					        </View>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -151,7 +75,8 @@ const styles = StyleSheet.create({
 | 
				
			|||||||
    historyContainer: {
 | 
					    historyContainer: {
 | 
				
			||||||
        paddingVertical: responsiveHeight(2),
 | 
					        paddingVertical: responsiveHeight(2),
 | 
				
			||||||
        flex: 1,
 | 
					        flex: 1,
 | 
				
			||||||
        // backgroundColor: "red"
 | 
					        gap: responsiveHeight(1),
 | 
				
			||||||
 | 
					        marginBottom: responsiveHeight(6)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    historyElements: {
 | 
					    historyElements: {
 | 
				
			||||||
        marginTop: responsiveHeight(3),
 | 
					        marginTop: responsiveHeight(3),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										87
									
								
								src/screens/ProfileScreen/SettingsView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/screens/ProfileScreen/SettingsView.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					import {GestureResponderEvent, Image, ScrollView, StyleSheet, TouchableOpacity, View} from "react-native";
 | 
				
			||||||
 | 
					import DText from "../../components/DText/DText";
 | 
				
			||||||
 | 
					import {RFPercentage} from "react-native-responsive-fontsize";
 | 
				
			||||||
 | 
					import {FC} from "react";
 | 
				
			||||||
 | 
					import assemblyApi from "../../api/assemblyApi";
 | 
				
			||||||
 | 
					import Toast from "react-native-toast-message";
 | 
				
			||||||
 | 
					import {reset} from "../../features/assembly/assemblySlice";
 | 
				
			||||||
 | 
					import {useDispatch} from "react-redux";
 | 
				
			||||||
 | 
					import {responsiveWidth} from "react-native-responsive-dimensions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SettingsElementProps = {
 | 
				
			||||||
 | 
					    icon: any;
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    onPress?: (event: GestureResponderEvent) => void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const SettingsElement: FC<SettingsElementProps> = ({icon, title, onPress}) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <TouchableOpacity onPress={onPress}>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <View style={styles.actionsCarouselElementContainer}>
 | 
				
			||||||
 | 
					                <View style={styles.actionsCarouselImageWrapper}>
 | 
				
			||||||
 | 
					                    <Image style={styles.actionsCarouselImage} source={icon}/>
 | 
				
			||||||
 | 
					                </View>
 | 
				
			||||||
 | 
					                <DText style={{textAlign: "center"}}>{title}</DText>
 | 
				
			||||||
 | 
					            </View>
 | 
				
			||||||
 | 
					        </TouchableOpacity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SettingsView: FC = () => {
 | 
				
			||||||
 | 
					    const dispatch = useDispatch();
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <ScrollView horizontal={true}
 | 
				
			||||||
 | 
					                    showsVerticalScrollIndicator={false}
 | 
				
			||||||
 | 
					                    showsHorizontalScrollIndicator={false}
 | 
				
			||||||
 | 
					                    contentContainerStyle={styles.actionsCarousel}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <SettingsElement icon={require('assets/icons/settings/withdraw.png')} title={'Вывод'}/>
 | 
				
			||||||
 | 
					            <SettingsElement icon={require('assets/icons/settings/statistics.png')} title={'Статистика'}/>
 | 
				
			||||||
 | 
					            <SettingsElement icon={require('assets/icons/settings/printer.png')} title={'Принтеры'}/>
 | 
				
			||||||
 | 
					            <SettingsElement onPress={() => {
 | 
				
			||||||
 | 
					                assemblyApi.cancel().then(response => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Toast.show({
 | 
				
			||||||
 | 
					                        type: response.ok ? "success" : "error",
 | 
				
			||||||
 | 
					                        text1: "Отмена сборки",
 | 
				
			||||||
 | 
					                        text2: response.message,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    dispatch(reset());
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }} icon={require('assets/icons/settings/close.png')} title={'Отменить сборку'}/>
 | 
				
			||||||
 | 
					        </ScrollView>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const styles = StyleSheet.create({
 | 
				
			||||||
 | 
					    actionsCarouselElementContainer: {
 | 
				
			||||||
 | 
					        display: "flex",
 | 
				
			||||||
 | 
					        alignItems: "center",
 | 
				
			||||||
 | 
					        maxWidth: RFPercentage(10),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    actionsCarouselImageWrapper: {
 | 
				
			||||||
 | 
					        width: RFPercentage(10),
 | 
				
			||||||
 | 
					        height: RFPercentage(10),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        backgroundColor: "white",
 | 
				
			||||||
 | 
					        borderRadius: 100,
 | 
				
			||||||
 | 
					        borderWidth: RFPercentage(0.3),
 | 
				
			||||||
 | 
					        padding: RFPercentage(1.5),
 | 
				
			||||||
 | 
					        justifyContent: "center",
 | 
				
			||||||
 | 
					        alignItems: "center"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    actionsCarouselImage: {
 | 
				
			||||||
 | 
					        resizeMode: "contain",
 | 
				
			||||||
 | 
					        flex: 1,
 | 
				
			||||||
 | 
					        width: "100%",
 | 
				
			||||||
 | 
					        height: "100%",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    actionsCarousel: {
 | 
				
			||||||
 | 
					        flexDirection: "row",
 | 
				
			||||||
 | 
					        columnGap: responsiveWidth(3),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default SettingsView;
 | 
				
			||||||
							
								
								
									
										74
									
								
								src/screens/ProfileScreen/TransactionView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/screens/ProfileScreen/TransactionView.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					import {ScrollView, StyleSheet, View} from "react-native";
 | 
				
			||||||
 | 
					import DText from "../../components/DText/DText";
 | 
				
			||||||
 | 
					import {responsiveFontSize, responsiveHeight} from "react-native-responsive-dimensions";
 | 
				
			||||||
 | 
					import {gray} from "../../css/colors";
 | 
				
			||||||
 | 
					import {BalanceTransaction} from "../../types/balance";
 | 
				
			||||||
 | 
					import {FC} from "react";
 | 
				
			||||||
 | 
					import {RFPercentage} from "react-native-responsive-fontsize";
 | 
				
			||||||
 | 
					import {FlashList} from "@shopify/flash-list";
 | 
				
			||||||
 | 
					import flashListSeparator from "../../components/FlashListSeparator/FlashListSeparator";
 | 
				
			||||||
 | 
					import {formatBalanceNumber} from "../../utils/formatters";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TransactionElementProps = {
 | 
				
			||||||
 | 
					    transaction: BalanceTransaction;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const TransactionElement: FC<TransactionElementProps> = ({transaction}) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <View style={styles.historyElementContainer}>
 | 
				
			||||||
 | 
					            <DText style={{
 | 
				
			||||||
 | 
					                fontSize: responsiveFontSize(2),
 | 
				
			||||||
 | 
					                fontWeight: "500"
 | 
				
			||||||
 | 
					            }}>{formatBalanceNumber(transaction.amount)} руб</DText>
 | 
				
			||||||
 | 
					            <DText style={{
 | 
				
			||||||
 | 
					                fontSize: responsiveFontSize(1.5),
 | 
				
			||||||
 | 
					                color: gray,
 | 
				
			||||||
 | 
					                fontWeight: "400"
 | 
				
			||||||
 | 
					            }}>{transaction.description}</DText>
 | 
				
			||||||
 | 
					        </View>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TransactionsViewProps = {
 | 
				
			||||||
 | 
					    transactions: BalanceTransaction[];
 | 
				
			||||||
 | 
					    onEndReached: () => void;
 | 
				
			||||||
 | 
					    onRefresh: () => void;
 | 
				
			||||||
 | 
					    isLoading: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const TransactionsView: FC<TransactionsViewProps> = ({transactions, isLoading, onEndReached, onRefresh}) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <FlashList
 | 
				
			||||||
 | 
					            onRefresh={onRefresh}
 | 
				
			||||||
 | 
					            showsHorizontalScrollIndicator={false}
 | 
				
			||||||
 | 
					            showsVerticalScrollIndicator={false}
 | 
				
			||||||
 | 
					            ItemSeparatorComponent={flashListSeparator}
 | 
				
			||||||
 | 
					            data={transactions}
 | 
				
			||||||
 | 
					            keyExtractor={item => item.id.toString()}
 | 
				
			||||||
 | 
					            keyboardShouldPersistTaps={"never"}
 | 
				
			||||||
 | 
					            onEndReachedThreshold={0.5}
 | 
				
			||||||
 | 
					            estimatedItemSize={100}
 | 
				
			||||||
 | 
					            renderItem={item =>
 | 
				
			||||||
 | 
					                <TransactionElement
 | 
				
			||||||
 | 
					                    transaction={item.item}
 | 
				
			||||||
 | 
					                />}
 | 
				
			||||||
 | 
					            onEndReached={onEndReached}
 | 
				
			||||||
 | 
					            refreshing={isLoading}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					        </FlashList>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const styles = StyleSheet.create({
 | 
				
			||||||
 | 
					    historyElementContainer: {
 | 
				
			||||||
 | 
					        width: "100%",
 | 
				
			||||||
 | 
					        backgroundColor: "white",
 | 
				
			||||||
 | 
					        borderRadius: RFPercentage(2),
 | 
				
			||||||
 | 
					        padding: RFPercentage(2),
 | 
				
			||||||
 | 
					        elevation: 1
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    historyElementsContainer: {
 | 
				
			||||||
 | 
					        rowGap: responsiveHeight(2),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default TransactionsView;
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/screens/ProfileScreen/useBalance.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/screens/ProfileScreen/useBalance.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					const useBalance = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/types/balance.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/types/balance.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					export enum BalanceTransactionType {
 | 
				
			||||||
 | 
					    TOP_UP,
 | 
				
			||||||
 | 
					    WITHDRAW
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type BalanceTransaction = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    type: BalanceTransactionType;
 | 
				
			||||||
 | 
					    userId: number;
 | 
				
			||||||
 | 
					    amount: number;
 | 
				
			||||||
 | 
					    description: string;
 | 
				
			||||||
 | 
					    jsonData?: object;
 | 
				
			||||||
 | 
					    createdAt: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type BalanceInfo = {
 | 
				
			||||||
 | 
					    balance: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/utils/formatters.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/utils/formatters.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					export const formatBalanceNumber = (n: number, addSign = true) => {
 | 
				
			||||||
 | 
					    const roundNumber = (n: number, digits: number): number => parseInt(n.toFixed(digits))
 | 
				
			||||||
 | 
					    const formatNumber = (n: number): string => n
 | 
				
			||||||
 | 
					    >= 0 ? `${addSign ? '+' : ''}${roundNumber(n, 2).toLocaleString('ru-RU')}` :
 | 
				
			||||||
 | 
					        `${roundNumber(n, 2).toLocaleString('ru-RU')}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return formatNumber(n);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user