inital commit

This commit is contained in:
2023-10-12 23:45:46 +03:00
parent 9b16b14a4c
commit 6bcc0fd3cc
86 changed files with 13784 additions and 34 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ node_modules/
# Expo
.expo/
.idea/
dist/
web-build/

20
App.tsx
View File

@@ -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
View File

@@ -0,0 +1,4 @@
import registerRootComponent from 'expo/build/launch/registerRootComponent';
import App from "App";
registerRootComponent(App);

View File

@@ -4,27 +4,27 @@
"slug": "Assemblr",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"icon": "./src/assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"image": "./src/assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
"src/**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"foregroundImage": "./src/assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
"favicon": "./src/assets/favicon.png"
}
}
}

12
metro.config.js Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "assemblr",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"main": "AppEntry.ts",
"scripts": {
"start": "expo start",
"android": "expo start --android",
@@ -9,14 +9,34 @@
"web": "expo start --web"
},
"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-secure-store": "~12.3.1",
"expo-status-bar": "~1.6.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": {
"@babel/core": "^7.20.0",
"@types/react": "~18.0.14",
"@types/react": "~18.2.14",
"typescript": "^5.1.3"
},
"private": true

32
src/App.tsx Normal file
View 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
View 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;

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/fonts/SF-Pro.ttf Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/icons/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
src/assets/icons/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/icons/money.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/icons/scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View 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;

View 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;

View 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;

View 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

View 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;

View 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
View 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>();

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -1,6 +1,7 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
"strict": true,
"baseUrl": "./src"
}
}

8996
yarn.lock Normal file

File diff suppressed because it is too large Load Diff