inital commit
1
.gitignore
vendored
@@ -5,6 +5,7 @@ node_modules/
|
|||||||
|
|
||||||
# Expo
|
# Expo
|
||||||
.expo/
|
.expo/
|
||||||
|
.idea/
|
||||||
dist/
|
dist/
|
||||||
web-build/
|
web-build/
|
||||||
|
|
||||||
|
|||||||
20
App.tsx
@@ -1,20 +0,0 @@
|
|||||||
import { StatusBar } from 'expo-status-bar';
|
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text>Open up App.tsx to start working on your app!</Text>
|
|
||||||
<StatusBar style="auto" />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
4
AppEntry.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import registerRootComponent from 'expo/build/launch/registerRootComponent';
|
||||||
|
import App from "App";
|
||||||
|
|
||||||
|
registerRootComponent(App);
|
||||||
10
app.json
@@ -4,27 +4,27 @@
|
|||||||
"slug": "Assemblr",
|
"slug": "Assemblr",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/icon.png",
|
"icon": "./src/assets/icon.png",
|
||||||
"userInterfaceStyle": "light",
|
"userInterfaceStyle": "light",
|
||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/splash.png",
|
"image": "./src/assets/splash.png",
|
||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"assetBundlePatterns": [
|
"assetBundlePatterns": [
|
||||||
"**/*"
|
"src/**/*"
|
||||||
],
|
],
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": true
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
"foregroundImage": "./src/assets/adaptive-icon.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"favicon": "./assets/favicon.png"
|
"favicon": "./src/assets/favicon.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
metro.config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
resolver: {
|
||||||
|
extraNodeModules: new Proxy({}, {
|
||||||
|
get: (target, name) => path.join(process.cwd(), `src/${name}`)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
watchFolders: [
|
||||||
|
path.resolve(__dirname, 'src')
|
||||||
|
],
|
||||||
|
};
|
||||||
3928
package-lock.json
generated
26
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "assemblr",
|
"name": "assemblr",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "node_modules/expo/AppEntry.js",
|
"main": "AppEntry.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
@@ -9,14 +9,34 @@
|
|||||||
"web": "expo start --web"
|
"web": "expo start --web"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@expo/webpack-config": "^19.0.0",
|
||||||
|
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||||
|
"@react-navigation/native": "^6.1.7",
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
|
"@rneui/base": "^4.0.0-rc.8",
|
||||||
|
"@rneui/themed": "^4.0.0-rc.8",
|
||||||
|
"axios": "^1.5.0",
|
||||||
|
"babel-plugin-module-resolver": "^5.0.0",
|
||||||
"expo": "~49.0.8",
|
"expo": "~49.0.8",
|
||||||
|
"expo-secure-store": "~12.3.1",
|
||||||
"expo-status-bar": "~1.6.0",
|
"expo-status-bar": "~1.6.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-native": "0.72.4"
|
"react-dom": "18.2.0",
|
||||||
|
"react-native": "0.72.4",
|
||||||
|
"react-native-modal": "^13.0.1",
|
||||||
|
"react-native-responsive-dimensions": "^3.1.1",
|
||||||
|
"react-native-responsive-fontsize": "^0.5.1",
|
||||||
|
"react-native-safe-area-context": "^4.7.2",
|
||||||
|
"react-native-screens": "~3.22.0",
|
||||||
|
"react-native-vector-icons": "^10.0.0",
|
||||||
|
"react-native-web": "~0.19.6",
|
||||||
|
"react-native-webview": "^13.6.0",
|
||||||
|
"react-redux": "^8.1.2",
|
||||||
|
"redux": "^4.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
"@types/react": "~18.0.14",
|
"@types/react": "~18.2.14",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
|||||||
32
src/App.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {StyleSheet, Text, View} from 'react-native';
|
||||||
|
import {Provider} from "react-redux";
|
||||||
|
|
||||||
|
import {useFonts} from 'expo-font';
|
||||||
|
import {store} from "./redux/store";
|
||||||
|
import React from "react";
|
||||||
|
import CommonPage from "./screens/CommonPage/CommonPage";
|
||||||
|
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
let [fontsLoading] = useFonts({
|
||||||
|
'SF Pro Text': require('./assets/fonts/SF-Pro.ttf')
|
||||||
|
})
|
||||||
|
if (!fontsLoading)
|
||||||
|
return <View><Text>Loading...</Text></View>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<CommonPage/>
|
||||||
|
</Provider>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
25
src/api/apiClient.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import axios, {AxiosHeaders, AxiosRequestConfig, AxiosRequestHeaders, InternalAxiosRequestConfig} from 'axios';
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
|
||||||
|
const apiClient = axios.create({
|
||||||
|
baseURL: 'http://192.168.1.101:5000',
|
||||||
|
});
|
||||||
|
|
||||||
|
apiClient.interceptors.request.use(async (config) => {
|
||||||
|
const accessToken = await SecureStore.getItemAsync('access_token');
|
||||||
|
if (!config.headers) {
|
||||||
|
config.headers = new AxiosHeaders();
|
||||||
|
}
|
||||||
|
if (accessToken) {
|
||||||
|
config.headers.set('Authorization', `Bearer ${accessToken}`, true);
|
||||||
|
}
|
||||||
|
config.validateStatus = (status) => {
|
||||||
|
return true
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}, function (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default apiClient;
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/fonts/SF-Pro-Display-Black.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-BlackItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Bold.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-BoldItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Heavy.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-HeavyItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Light.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-LightItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Medium.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-MediumItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Regular.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-RegularItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Semibold.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-SemiboldItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Thin.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-ThinItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-Ultralight.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Display-UltralightItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Italic.ttf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Black.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Bold.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Heavy.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Light.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Medium.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Regular.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Semibold.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Thin.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Rounded-Ultralight.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Black.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-BlackItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Bold.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-BoldItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Heavy.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-HeavyItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Light.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-LightItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Medium.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-MediumItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Regular.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-RegularItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Semibold.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-SemiboldItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Thin.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-ThinItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-Ultralight.otf
Normal file
BIN
src/assets/fonts/SF-Pro-Text-UltralightItalic.otf
Normal file
BIN
src/assets/fonts/SF-Pro.ttf
Normal file
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
src/assets/icons/barcode.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/icons/box.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
src/assets/icons/home.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/icons/money.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/icons/profile.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/icons/scan.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/img/login/backgroud.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/assets/img/login/telegram.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
43
src/components/BasicButton/BasicButton.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import {FC} from "react";
|
||||||
|
import {StyleSheet, TouchableOpacity, Text, View, StyleProp, ViewStyle, GestureResponderEvent} from "react-native";
|
||||||
|
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
import DText from "../DText/DText";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
|
isUnset?: boolean;
|
||||||
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
|
};
|
||||||
|
|
||||||
|
const BasicButton: FC<Props> = ({label, onPress, style, isUnset = false}) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={onPress}>
|
||||||
|
<View style={[styles.container, style]}>
|
||||||
|
<DText style={styles.text}>{label}</DText>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignContent: "center",
|
||||||
|
backgroundColor: '#2478F8',
|
||||||
|
borderRadius: responsiveWidth(1),
|
||||||
|
padding: responsiveWidth(2),
|
||||||
|
flex: 1,
|
||||||
|
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: "white",
|
||||||
|
fontSize: RFPercentage(2),
|
||||||
|
textAlignVertical:"center",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BasicButton;
|
||||||
19
src/components/DText/DText.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React, {FC, ReactElement} from "react";
|
||||||
|
import {StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native';
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: string;
|
||||||
|
style?: StyleProp<TextStyle>;
|
||||||
|
}
|
||||||
|
const DText: FC<Props> = ({children, style}) => {
|
||||||
|
return (
|
||||||
|
<Text style={[styles.text, style]}>{children}</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
fontFamily: 'SF Pro Text',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default DText;
|
||||||
60
src/components/SearchBar/ScanModal.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import {FC, useEffect, useRef} from "react";
|
||||||
|
import {GestureResponderEvent, StyleSheet, Text, TextInput, View} from "react-native";
|
||||||
|
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
import Modal from "react-native-modal";
|
||||||
|
import BasicButton from "../BasicButton/BasicButton";
|
||||||
|
import DText from "../DText/DText";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
visible: boolean
|
||||||
|
onCancelButtonPress?: (event: GestureResponderEvent) => void,
|
||||||
|
onChanged: (text: string) => void
|
||||||
|
}
|
||||||
|
const ScanModal: FC<Props> = ({visible, onCancelButtonPress, onChanged}) => {
|
||||||
|
const inputRef = useRef<TextInput | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) inputRef.current?.focus();
|
||||||
|
}, [visible]);
|
||||||
|
return (
|
||||||
|
<Modal isVisible={visible}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<DText style={styles.text}>Наведите сканер на штрихкод товара или заказа</DText>
|
||||||
|
<BasicButton onPress={onCancelButtonPress} style={styles.cancelButton} label={"Отмена"}/>
|
||||||
|
<TextInput onEndEditing={(e) => {
|
||||||
|
onChanged(e.nativeEvent.text);
|
||||||
|
}} style={styles.pseudoInput} ref={inputRef}/>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
borderRadius: responsiveWidth(1),
|
||||||
|
backgroundColor: "white",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingHorizontal: responsiveWidth(18),
|
||||||
|
paddingVertical: responsiveHeight(10),
|
||||||
|
gap: responsiveHeight(3)
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: "#2B2D3A",
|
||||||
|
fontSize: RFPercentage(3),
|
||||||
|
textAlign: "center"
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
flex: 0,
|
||||||
|
width: responsiveWidth(30)
|
||||||
|
},
|
||||||
|
pseudoInput: {
|
||||||
|
backgroundColor: "red",
|
||||||
|
opacity: 0,
|
||||||
|
position: "absolute",
|
||||||
|
zIndex: -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default ScanModal;
|
||||||
84
src/components/SearchBar/SearchBar.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React, {FC, useRef, useState} from "react";
|
||||||
|
import {Image, StyleSheet, TextInput, TouchableOpacity, View} from "react-native";
|
||||||
|
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
import BasicButton from "../BasicButton/BasicButton";
|
||||||
|
import ScanModal from "./ScanModal";
|
||||||
|
import telegramAuthButton from "../TelegramAuthButton/TelegramAuthButton";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSearch: (text: string) => void;
|
||||||
|
}
|
||||||
|
const SearchBar: FC<Props> = ({onSearch}) => {
|
||||||
|
const [isScanModalVisible, setIsScanModalVisible] = useState<boolean>(false);
|
||||||
|
const [searchInput, setSearchInput] = useState<string>("");
|
||||||
|
const textInputRef = useRef<TextInput>(null);
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScanModal
|
||||||
|
onChanged={(text) => {
|
||||||
|
setIsScanModalVisible(false);
|
||||||
|
onSearch(text);
|
||||||
|
}}
|
||||||
|
onCancelButtonPress={() => setIsScanModalVisible(false)}
|
||||||
|
visible={isScanModalVisible}/>
|
||||||
|
<BasicButton onPress={() => {
|
||||||
|
onSearch(searchInput);
|
||||||
|
if (textInputRef.current) {
|
||||||
|
textInputRef.current.clear();
|
||||||
|
}
|
||||||
|
}} style={styles.scanButton} label={"Поиск"}/>
|
||||||
|
<View style={styles.scanImageWrapper}>
|
||||||
|
<TouchableOpacity onPress={() => setIsScanModalVisible(true)}>
|
||||||
|
<Image style={styles.scanImage} source={require('assets/icons/scan.png')}/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<TextInput ref={textInputRef} onChangeText={(text) => {
|
||||||
|
setSearchInput(text);
|
||||||
|
}} style={styles.textInput} placeholder={"Введите запрос"}/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = 6;
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
display: "flex",
|
||||||
|
marginHorizontal: responsiveWidth(5),
|
||||||
|
flexDirection: "row-reverse",
|
||||||
|
height: responsiveHeight(height),
|
||||||
|
alignItems: "flex-end"
|
||||||
|
},
|
||||||
|
scanImage: {
|
||||||
|
height: responsiveHeight(5),
|
||||||
|
width: responsiveHeight(5),
|
||||||
|
|
||||||
|
},
|
||||||
|
scanButton: {
|
||||||
|
borderRadius: 0,
|
||||||
|
borderTopRightRadius: responsiveWidth(1),
|
||||||
|
borderBottomRightRadius: responsiveWidth(1),
|
||||||
|
width: responsiveWidth(25)
|
||||||
|
},
|
||||||
|
scanImageWrapper: {
|
||||||
|
paddingHorizontal: responsiveWidth(1),
|
||||||
|
height: responsiveHeight(height),
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#A5A5A5",
|
||||||
|
},
|
||||||
|
textInput: {
|
||||||
|
height: responsiveHeight(height),
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#A5A5A5",
|
||||||
|
borderTopLeftRadius: responsiveWidth(1),
|
||||||
|
borderBottomLeftRadius: responsiveWidth(1),
|
||||||
|
paddingLeft: responsiveHeight(2),
|
||||||
|
fontSize: RFPercentage(2),
|
||||||
|
fontFamily: 'SF Pro Text'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SearchBar
|
||||||
49
src/components/TelegramAuthButton/TelegramAuthButton.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import {FC, useCallback} from "react";
|
||||||
|
import {
|
||||||
|
GestureResponderEvent,
|
||||||
|
Linking,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableNativeFeedback,
|
||||||
|
TouchableOpacity,
|
||||||
|
View
|
||||||
|
} from "react-native";
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const TelegramAuthButton: FC<Props> = ({onPress}) => {
|
||||||
|
return (
|
||||||
|
<TouchableNativeFeedback onPress={onPress}>
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<View style={styles.buttonContent}>
|
||||||
|
<Text style={styles.buttonText}>Войти</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableNativeFeedback>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttonContainer: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
buttonContent: {
|
||||||
|
backgroundColor: '#0090c9',
|
||||||
|
borderRadius: RFPercentage(1),
|
||||||
|
paddingHorizontal: 50,
|
||||||
|
flexDirection: 'row', // Сохранено, так как возможно добавление иконки или другого элемента в будущем
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: RFPercentage(3),
|
||||||
|
color: 'white',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TelegramAuthButton;
|
||||||
129
src/features/auth/authSlice.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import {createSlice, createAsyncThunk, PayloadAction, ThunkDispatch} from '@reduxjs/toolkit';
|
||||||
|
import apiClient from 'api/apiClient';
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
import {AppDispatch} from "../../redux/store";
|
||||||
|
import {useDispatch} from "react-redux";
|
||||||
|
import {AxiosError} from "axios";
|
||||||
|
import {RejectedAction} from "@reduxjs/toolkit/dist/query/core/buildThunks";
|
||||||
|
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
|
||||||
|
import {EndpointBuilder} from "@reduxjs/toolkit/dist/query/endpointDefinitions";
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
accessToken: string | null;
|
||||||
|
status: 'idle' | 'loading' | 'succeeded' | 'failed';
|
||||||
|
errorMessage: string | null;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: AuthState = {
|
||||||
|
accessToken: null,
|
||||||
|
status: 'idle',
|
||||||
|
errorMessage: null,
|
||||||
|
isAuthenticated: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pokemonApi = createApi({
|
||||||
|
reducerPath: 'pokemonApi',
|
||||||
|
baseQuery: fetchBaseQuery({baseUrl: 'https://pokeapi.co/api/v2/'}),
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getPokemonByName: builder.query<any, string>({
|
||||||
|
query: (name) => `pokemon/${name}`,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const {useGetPokemonByNameQuery} = pokemonApi;
|
||||||
|
|
||||||
|
export const loginUser = createAsyncThunk(
|
||||||
|
'auth/login',
|
||||||
|
async (credentials: { login: string; password: string }, {rejectWithValue}) => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post<{ access_token: string }>('/auth/login', credentials);
|
||||||
|
if (response.status == 401) {
|
||||||
|
throw Error("Invalid credentials");
|
||||||
|
}
|
||||||
|
return response.data.access_token;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error instanceof AxiosError)
|
||||||
|
return rejectWithValue('Server response error');
|
||||||
|
else if (error instanceof Error)
|
||||||
|
return rejectWithValue('Invalid credentials');
|
||||||
|
return rejectWithValue('Unexpected error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const loadToken = createAsyncThunk<
|
||||||
|
void,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
dispatch: AppDispatch // Тип для dispatch
|
||||||
|
}
|
||||||
|
>('auth/loadToken', async (_, {dispatch}) => {
|
||||||
|
try {
|
||||||
|
const token = await SecureStore.getItemAsync('access_token');
|
||||||
|
if (token) {
|
||||||
|
dispatch(authSlice.actions.setToken(token));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Couldn't read token", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const logoutUser = createAsyncThunk<
|
||||||
|
void,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
dispatch: AppDispatch // Тип для dispatch
|
||||||
|
}
|
||||||
|
>('auth/loadToken', async (_, {dispatch}) => {
|
||||||
|
try {
|
||||||
|
await SecureStore.deleteItemAsync('access_token');
|
||||||
|
dispatch(authSlice.actions.logout())
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Couldn't read token", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const authSlice = createSlice({
|
||||||
|
name: 'auth',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setToken: (state, action: PayloadAction<string>) => {
|
||||||
|
state.accessToken = action.payload;
|
||||||
|
state.isAuthenticated = true;
|
||||||
|
state.status = "succeeded";
|
||||||
|
},
|
||||||
|
logout: (state) => {
|
||||||
|
state.accessToken = null;
|
||||||
|
state.status = "idle";
|
||||||
|
state.isAuthenticated = false
|
||||||
|
state.errorMessage = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(loginUser.pending, (state) => {
|
||||||
|
state.accessToken = "";
|
||||||
|
state.status = 'loading';
|
||||||
|
state.errorMessage = null;
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
}).addCase(loginUser.fulfilled, (state, action: PayloadAction<string>) => {
|
||||||
|
let accessToken = action.payload;
|
||||||
|
|
||||||
|
state.accessToken = accessToken;
|
||||||
|
state.status = 'succeeded';
|
||||||
|
state.errorMessage = null
|
||||||
|
state.isAuthenticated = true;
|
||||||
|
|
||||||
|
SecureStore.setItemAsync('access_token', accessToken);
|
||||||
|
}).addCase(loginUser.rejected, (state, action) => {
|
||||||
|
if (action.payload)
|
||||||
|
state.errorMessage = action.payload as string;
|
||||||
|
state.status = "failed";
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
state.accessToken = "";
|
||||||
|
})
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default authSlice.reducer;
|
||||||
16
src/redux/store.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {configureStore} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import authReducer, {pokemonApi} from 'features/auth/authSlice';
|
||||||
|
import {useDispatch} from "react-redux";
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
auth: authReducer,
|
||||||
|
[pokemonApi.reducerPath]: pokemonApi.reducer
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(pokemonApi.middleware)
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
16
src/screens/BarcodeScreen/BarcodeScreen.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {Button, Text, View} from "react-native";
|
||||||
|
import {useAppDispatch} from "../../redux/store";
|
||||||
|
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||||
|
import * as process from "process";
|
||||||
|
|
||||||
|
function BarcodeScreen() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text style={{fontSize: 36}}>Barcode</Text>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BarcodeScreen;
|
||||||
14
src/screens/BoxScreen/BoxScreen.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import {Button, Text, View} from "react-native";
|
||||||
|
import {useAppDispatch} from "../../redux/store";
|
||||||
|
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||||
|
import * as process from "process";
|
||||||
|
|
||||||
|
function BoxScreen() {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text style={{fontSize: 36}}>Box</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BoxScreen;
|
||||||
27
src/screens/CommonPage/CommonPage.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import {SafeAreaView, StyleSheet, View} from "react-native";
|
||||||
|
import LoginScreen from "../LoginScreen/LoginScreen";
|
||||||
|
import MainScreen from "../MainScreen/MainScreen";
|
||||||
|
import SearchBar from "../../components/SearchBar/SearchBar";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function CommonPage() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<View style={styles.main}>
|
||||||
|
<MainScreen/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
main: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flexGrow: 1,
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CommonPage;
|
||||||
23
src/screens/HomeScreen/HomeScreen.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import {Button, Modal, SafeAreaView, Text, View} from "react-native";
|
||||||
|
import {useAppDispatch} from "../../redux/store";
|
||||||
|
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||||
|
import * as process from "process";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import BasicButton from "../../components/BasicButton/BasicButton";
|
||||||
|
import SearchBar from "components/SearchBar/SearchBar";
|
||||||
|
import ScanModal from "components/SearchBar/ScanModal";
|
||||||
|
import DText from "../../components/DText/DText";
|
||||||
|
|
||||||
|
function HomeScreen() {
|
||||||
|
return (
|
||||||
|
<View style={{backgroundColor: "white"}}>
|
||||||
|
<SearchBar onSearch={(text) => {
|
||||||
|
console.log(`From scanner: ${text}`)
|
||||||
|
}}/>
|
||||||
|
<DText>Хуй</DText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeScreen;
|
||||||
90
src/screens/LoginScreen/LoginScreen.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import {FC, useCallback, useEffect, useState} from "react";
|
||||||
|
import {StyleSheet, Text, View, ImageBackground, Linking} from 'react-native';
|
||||||
|
import TelegramAuthButton from "components/TelegramAuthButton/TelegramAuthButton";
|
||||||
|
import WebView from "react-native-webview";
|
||||||
|
import InputField from "./components/InputField";
|
||||||
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
import {loadToken, loginUser} from "features/auth/authSlice";
|
||||||
|
import {AppDispatch, RootState, useAppDispatch} from "redux/store";
|
||||||
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
import {initializeUseSelector} from "react-redux/es/hooks/useSelector";
|
||||||
|
import HomeScreen from "../HomeScreen/HomeScreen";
|
||||||
|
import {RFPercentage, RFValue} from "react-native-responsive-fontsize";
|
||||||
|
import {responsiveWidth} from "react-native-responsive-dimensions";
|
||||||
|
|
||||||
|
function LoginScreen() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [login, setLogin] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const {status, errorMessage, isAuthenticated} = useSelector((state: RootState) => state.auth);
|
||||||
|
const handleLogin = async () => {
|
||||||
|
dispatch(loginUser({login: login, password: password}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(loadToken());
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{<View style={styles.container}>
|
||||||
|
<ImageBackground source={require('assets/img/login/backgroud.png')} resizeMode="cover"
|
||||||
|
style={styles.image}>
|
||||||
|
<View style={styles.block}>
|
||||||
|
<Text style={styles.authText}>Авторизация</Text>
|
||||||
|
<InputField onChange={setLogin} placeholder={"Логин"}/>
|
||||||
|
<InputField onChange={setPassword} secureTextEntry={false} placeholder={"Пароль"}/>
|
||||||
|
<TelegramAuthButton onPress={handleLogin}/>
|
||||||
|
<Text style={{fontSize: 36}}>{errorMessage}</Text>
|
||||||
|
</View>
|
||||||
|
</ImageBackground>
|
||||||
|
</View>}
|
||||||
|
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 42,
|
||||||
|
lineHeight: 84,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#000000c0',
|
||||||
|
},
|
||||||
|
authText: {
|
||||||
|
color: '#2478F8',
|
||||||
|
fontFamily: 'SF Pro Text',
|
||||||
|
fontSize: RFPercentage(3),
|
||||||
|
fontStyle: 'normal',
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
marginHorizontal: responsiveWidth(10),
|
||||||
|
paddingVertical: "5%",
|
||||||
|
paddingHorizontal: "5%",
|
||||||
|
borderRadius: RFPercentage(5),
|
||||||
|
borderColor: "#2478F8",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
backgroundColor: "#C6E0EA",
|
||||||
|
alignSelf: "center",
|
||||||
|
opacity: 0.8,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: 30
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default LoginScreen;
|
||||||
53
src/screens/LoginScreen/components/InputField.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import {FC, useCallback} from "react";
|
||||||
|
import {StyleSheet, Text, TextInput, TouchableOpacity, View} from "react-native";
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
import {responsiveWidth} from "react-native-responsive-dimensions";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
placeholder?: string;
|
||||||
|
onChange?: (text: string) => void,
|
||||||
|
secureTextEntry?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputField: FC<Props> = ({placeholder, onChange, secureTextEntry = false}) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.fieldContainer}>
|
||||||
|
<View style={styles.textInputWrapper}>
|
||||||
|
<TextInput
|
||||||
|
placeholder={placeholder}
|
||||||
|
autoCorrect={false}
|
||||||
|
autoCapitalize={"none"}
|
||||||
|
secureTextEntry={secureTextEntry}
|
||||||
|
style={styles.textInput}
|
||||||
|
onChangeText={onChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
textInputWrapper: {
|
||||||
|
backgroundColor: '#0090c9',
|
||||||
|
borderRadius: RFPercentage(1),
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingHorizontal: responsiveWidth(3),
|
||||||
|
},
|
||||||
|
fieldLabel: {
|
||||||
|
marginLeft: 40,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
fontSize: RFPercentage(3),
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
fieldContainer: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
textInput: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: RFPercentage(3),
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InputField;
|
||||||
108
src/screens/MainScreen/MainScreen.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import {StyleSheet, Text, View, ImageBackground, Linking, Image} from 'react-native';
|
||||||
|
import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
|
||||||
|
import HomeScreen from "../HomeScreen/HomeScreen";
|
||||||
|
import BoxScreen from "../BoxScreen/BoxScreen";
|
||||||
|
import BarcodeScreen from "../BarcodeScreen/BarcodeScreen";
|
||||||
|
import MoneyScreen from "../MoneyScreen/MoneyScreen";
|
||||||
|
import ProfileScreen from "../ProfileScreen/ProfileScreen";
|
||||||
|
import {NavigationContainer} from "@react-navigation/native";
|
||||||
|
import moneyScreen from "../MoneyScreen/MoneyScreen";
|
||||||
|
import profileScreen from "../ProfileScreen/ProfileScreen";
|
||||||
|
import {responsiveHeight, responsiveWidth} from "react-native-responsive-dimensions";
|
||||||
|
import {RFPercentage} from "react-native-responsive-fontsize";
|
||||||
|
import LoginScreen from "../LoginScreen/LoginScreen";
|
||||||
|
|
||||||
|
|
||||||
|
interface CustomTabProps {
|
||||||
|
name: string;
|
||||||
|
component: React.ComponentType<any>;
|
||||||
|
icon: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomTab = ({name, component, icon}: CustomTabProps) => ({
|
||||||
|
name,
|
||||||
|
component,
|
||||||
|
options: {
|
||||||
|
tabBarIcon: ({focused, color, size}: { focused: boolean; color: string; size: number }) => (
|
||||||
|
<Image
|
||||||
|
source={icon}
|
||||||
|
style={{
|
||||||
|
width: RFPercentage(4),
|
||||||
|
height: RFPercentage(4),
|
||||||
|
tintColor: color
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
tabBarLabel: () => null,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function MainScreen() {
|
||||||
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
|
const tabScreens = [
|
||||||
|
{
|
||||||
|
name: "Home",
|
||||||
|
component: HomeScreen,
|
||||||
|
icon: require('assets/icons/home.png')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Box",
|
||||||
|
component: BoxScreen,
|
||||||
|
icon: require('assets/icons/box.png')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Barcode",
|
||||||
|
component: BarcodeScreen,
|
||||||
|
icon: require('assets/icons/barcode.png')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Money",
|
||||||
|
component: moneyScreen,
|
||||||
|
icon: require('assets/icons/money.png')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Profile",
|
||||||
|
component: profileScreen,
|
||||||
|
icon: require('assets/icons/profile.png')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavigationContainer>
|
||||||
|
|
||||||
|
<Tab.Navigator screenOptions={{
|
||||||
|
tabBarStyle: styles.tabBarStyle,
|
||||||
|
headerTitle: ""
|
||||||
|
}}>
|
||||||
|
{tabScreens.map(tabScreen =>
|
||||||
|
<Tab.Screen key={tabScreen.name} {...CustomTab({
|
||||||
|
name: tabScreen.name,
|
||||||
|
component: tabScreen.component,
|
||||||
|
icon: tabScreen.icon,
|
||||||
|
})}/>
|
||||||
|
)}
|
||||||
|
</Tab.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
tabBarStyle: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: responsiveWidth(10),
|
||||||
|
right: responsiveWidth(10),
|
||||||
|
bottom: 0,
|
||||||
|
borderTopLeftRadius: 18,
|
||||||
|
borderTopRightRadius: 18,
|
||||||
|
elevation: 10,
|
||||||
|
height: responsiveHeight(8),
|
||||||
|
paddingHorizontal: responsiveWidth(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
export default MainScreen;
|
||||||
15
src/screens/MoneyScreen/MoneyScreen.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {Button, Text, View} from "react-native";
|
||||||
|
import {useAppDispatch} from "../../redux/store";
|
||||||
|
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||||
|
import * as process from "process";
|
||||||
|
|
||||||
|
function MoneyScreen() {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text style={{fontSize: 36}}>Money</Text>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoneyScreen;
|
||||||
15
src/screens/ProfileScreen/ProfileScreen.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {Button, Text, View} from "react-native";
|
||||||
|
import {useAppDispatch} from "../../redux/store";
|
||||||
|
import {logoutUser, useGetPokemonByNameQuery} from "../../features/auth/authSlice";
|
||||||
|
import * as process from "process";
|
||||||
|
|
||||||
|
function ProfileScreen() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text style={{fontSize: 36}}>Profile</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileScreen;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true
|
"strict": true,
|
||||||
|
"baseUrl": "./src"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||