🎨 🧭 🗃️ Improve UI, use redux to fetch data, add nav options

pull/11/head
Alexis Drai 2 years ago
parent 96d7fc200e
commit bb986a8929

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

@ -1,7 +1,13 @@
import React from 'react';
import Navigation from "./navigation/Navigation";
// App.tsx
import React from 'react';
import Navigation from "./navigation/Navigation";
import store from "./redux/store";
import { Provider } from "react-redux";
export default function App() {
return <Navigation/>;
// TODO Send to homescreen instead, and include a bottom bar to navigate to Moves, Pokemongs, Trainers
return (
<Provider store={store}>
<Navigation/>
</Provider>);
}

@ -44,5 +44,6 @@ This app will contain several "master/detail" tabs. They are as follows.
## Using the app
This app is linked to a backend that is set up to accept CORS from [`http://localhost:19006`](http://localhost:19006), so please make sure you're
not overriding that default port number when running it.
This app is linked to a backend that is set up to accept CORS from [`http://localhost:19006`](http://localhost:19006).
If you want to use the dedicated API, please make sure you're not overriding that default port number when running this
app.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@ -0,0 +1,32 @@
// components/MoveListItem.test.ts
import { Move } from "../entities/Move";
import React from "react";
import { StyleSheet, Text, TouchableOpacity } from "react-native";
type MoveListItemProps = {
move: Move;
onPress: () => void;
};
const MoveListItem: React.FC<MoveListItemProps> = ({ move, onPress }) => (
<TouchableOpacity style={styles.listItem} onPress={onPress}>
<Text style={styles.listItemText}>{move.name}, {move.type.name}: {move.power}</Text>
</TouchableOpacity>
);
const styles = StyleSheet.create({
listItem: {
backgroundColor: '#DDD',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 10,
},
listItemText: {
color: '#333',
fontSize: 18,
},
});
export default MoveListItem;

@ -1,11 +1,13 @@
import React from 'react';
import {render} from '@testing-library/react-native';
// components/TypeTacticsInfoList.test.ts
import React from 'react';
import { render } from '@testing-library/react-native';
import TypeTacticsInfoList from './TypeTacticsInfoList';
describe('TypeTacticsInfoList component', () => {
it('renders types correctly', () => {
const types = ['FIRE', 'WATER', 'GRASS'];
const {getByText} = render(<TypeTacticsInfoList isWeakness={true} types={types}/>);
const { getByText } = render(<TypeTacticsInfoList isWeakness={true} types={types}/>);
types.forEach(type => {
expect(getByText(type)).toBeTruthy();
@ -13,13 +15,13 @@ describe('TypeTacticsInfoList component', () => {
});
it('renders "Nothing" when types array is empty', () => {
const {getByText} = render(<TypeTacticsInfoList isWeakness={false} types={[]}/>);
const { getByText } = render(<TypeTacticsInfoList isWeakness={false} types={[]}/>);
expect(getByText('Nothing')).toBeTruthy();
});
it('renders "Nothing" when types is undefined', () => {
// @ts-ignore
const {getByText} = render(<TypeTacticsInfoList isWeakness={true} types={undefined}/>);
const { getByText } = render(<TypeTacticsInfoList isWeakness={true} types={undefined}/>);
expect(getByText('Nothing')).toBeTruthy();
});
});

@ -1,39 +1,56 @@
import React from "react";
import {StyleSheet, View, Text, ScrollView} from "react-native";
// components/TypeTacticsInfoList.ts
import React from "react";
import { ScrollView, StyleSheet, Text, View } from "react-native";
type TypeListProps = {
isWeakness: boolean;
types: string[];
};
const TypeTacticsInfoList = ({isWeakness, types}: TypeListProps) => {
const TypeTacticsInfoList = ({ isWeakness, types }: TypeListProps) => {
if (!types || types.length === 0) {
types = ['Nothing'];
}
return (
<ScrollView style={isWeakness ? styles.weakAgainst : styles.effectiveAgainst}>
<View style={styles.list}>
<Text>{isWeakness ? 'weak against' : 'effective against'}:</Text>
{types.map((type, index) => (
<Text key={index}>{type}</Text>
))}
</View>
</ScrollView>
<View style={isWeakness ? styles.weakAgainst : styles.effectiveAgainst}>
<Text style={styles.title}>{isWeakness ? 'Weak Against' : 'Effective Against'}:</Text>
<ScrollView>
<View style={styles.list}>
{types.map((type, index) => (
<Text key={index} style={styles.type}>{type}</Text>
))}
</View>
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
list: {
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 5,
},
type: {
fontSize: 16,
marginBottom: 5,
},
weakAgainst: {
backgroundColor: '#FF6961',
borderRadius: 10,
marginBottom: 10,
padding: 10,
},
effectiveAgainst: {
backgroundColor: '#77DD77',
borderRadius: 10,
marginBottom: 10,
padding: 10,
},
});

@ -0,0 +1,2 @@
// config.ts
export const API_BASE_URL = 'http://localhost:8080';

@ -1,4 +1,6 @@
import {Type} from "./Type";
// entities/Move.ts
import { Type } from "./Type";
export interface Move {
name: string;

@ -1,3 +1,5 @@
// entities/Type.ts
export interface Type {
name: string;
weakAgainst: string[];

@ -1,22 +1,67 @@
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import MoveListScreen from '../screens/MoveListScreen';
import MoveDetailScreen from '../screens/MoveDetailScreen';
import {RootStackParamList} from "./navigationTypes";
// navigation/Navigation.tsx
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { NavigationContainer } from '@react-navigation/native';
import MoveListScreen from '../screens/moves/MoveListScreen';
import MoveDetailScreen from '../screens/moves/MoveDetailScreen';
import HomeScreen from '../screens/HomeScreen';
import { createStackNavigator } from '@react-navigation/stack';
import { RootStackParamList, RootTabParamList } from "./navigationTypes";
import { Image, StyleSheet } from 'react-native';
const Stack = createStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<RootTabParamList>();
const MoveStack = () => {
return (
<Stack.Navigator initialRouteName="MoveList">
<Stack.Screen name="MoveList" component={MoveListScreen} options={{ title: 'Moves' }}/>
<Stack.Screen
name="MoveDetail"
component={MoveDetailScreen}
options={({ route }) => ({ title: route.params.move.name })}
/>
</Stack.Navigator>
);
};
const Navigation = () => {
// TODO replace 'Move Detail' by the move name itself
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="MoveList">
<Stack.Screen name="MoveList" component={MoveListScreen} options={{title: 'Moves'}}/>
<Stack.Screen name="MoveDetail" component={MoveDetailScreen} options={{title: 'Move Detail'}}/>
</Stack.Navigator>
<Tab.Navigator initialRouteName="Home"
screenOptions={{ headerShown: false }}>
<Tab.Screen name="Home" component={HomeScreen} options={{
title: 'Home',
tabBarIcon: () => <Image source={require('../assets/home.png')}
style={styles.icon}/>
}}/>
<Tab.Screen name="Pokemongs" component={MoveStack} options={{
title: 'Pkmng',
tabBarIcon: () => <Image source={require('../assets/pokemongs.png')}
style={styles.icon}/>
}}/>
<Tab.Screen name="Moves" component={MoveStack} options={{
title: 'Moves',
tabBarIcon: () => <Image source={require('../assets/moves.png')}
style={styles.icon}/>
}}/>
<Tab.Screen name="Trainers" component={MoveStack} options={{
title: 'Trainers',
tabBarIcon: () => <Image source={require('../assets/trainers.png')}
style={styles.icon}/>
}}/>
</Tab.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
icon: {
width: 24,
height: 24,
padding: 8,
},
});
export default Navigation;

@ -1,6 +1,15 @@
import {Move} from "../entities/Move";
// navigation/navigationTypes.ts
import { Move } from "../entities/Move";
export type RootStackParamList = {
MoveList: undefined;
MoveDetail: { move: Move };
};
export type RootTabParamList = {
Home: undefined;
Pokemongs: undefined;
Moves: undefined;
Trainers: undefined;
};

@ -39,6 +39,7 @@
"@react-navigation/bottom-tabs": "^6.5.7",
"@react-navigation/native": "^6.1.6",
"@react-navigation/stack": "^6.3.16",
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/react-native": "^12.1.2",
"@types/react": "~18.0.27",
"expo": "^48.0.0",
@ -50,6 +51,9 @@
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-web": "~0.18.11",
"react-redux": "^8.0.7",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"typescript": "^4.9.4"
},
"devDependencies": {

@ -0,0 +1,26 @@
// redux/actions/moveAction.ts
import { FETCH_MOVES } from '../constants';
import { Move } from "../../entities/Move";
import { Dispatch } from "redux";
import { API_BASE_URL } from "../../config";
export const setMoves = (moves: Move[]) => {
return {
type: FETCH_MOVES,
payload: moves,
};
}
export const getMoves = () => {
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move`);
const data = await response.json();
dispatch(setMoves(data));
}
catch (error) {
console.error(error);
}
}
}

@ -1 +1,3 @@
// redux/constants.ts
export const FETCH_MOVES = 'FETCH_MOVES';

@ -0,0 +1,27 @@
// redux/reducers/moveReducer.ts
import { FETCH_MOVES } from '../constants';
import { Move } from "../../entities/Move";
import { AnyAction } from "redux";
export type MoveState = {
moves: Move[];
};
type MoveAction = AnyAction & {
type: typeof FETCH_MOVES;
payload?: Move[];
};
const initialState: MoveState = {
moves: [],
}
export default function moveReducer(state = initialState, action: MoveAction): MoveState {
switch (action.type) {
case FETCH_MOVES:
return { ...state, moves: action.payload || [] };
default:
return state;
}
}

@ -0,0 +1,15 @@
// redux/store.ts
import { configureStore } from '@reduxjs/toolkit'
import moveReducer from './reducers/moveReducer';
export type AppDispatch = typeof store.dispatch;
const store = configureStore({
reducer: {
move: moveReducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});
export default store;

@ -0,0 +1,14 @@
// screens/HomeScreen.tsx
import React from 'react';
import { Image, View } from 'react-native';
const HomeScreen = () => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Image source={require('../assets/logo.png')} style={{ width: 500, height: 500 }}/>
</View>
);
};
export default HomeScreen;

@ -1,54 +0,0 @@
import React from 'react';
import {ScrollView, StyleSheet, Text, View} from 'react-native';
import {RouteProp} from '@react-navigation/native';
import {RootStackParamList} from "../navigation/navigationTypes";
import TypeTacticsInfoList from "../components/TypeTacticsInfoList"
type MoveDetailScreenRouteProp = RouteProp<RootStackParamList, 'MoveDetail'>;
type Props = {
route: MoveDetailScreenRouteProp;
};
const MoveDetailScreen = ({route}: Props) => {
const {move} = route.params;
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>{move.name}</Text>
<Text>Name: {move.name}</Text>
<Text>Category: {move.category}</Text>
<Text>Power: {move.power}</Text>
<Text>Accuracy: {move.accuracy}</Text>
<Text>Type: {move.type.name}</Text>
<View style={styles.typeListsContainer}>
<TypeTacticsInfoList isWeakness={true} types={move.type.weakAgainst}/>
<TypeTacticsInfoList isWeakness={false} types={move.type.effectiveAgainst}/>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
list: {
backgroundColor: '#F0F0F0',
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
typeListsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});
export default MoveDetailScreen;

@ -1,41 +0,0 @@
import React, {useEffect, useState} from 'react';
import {FlatList, ScrollView, Text, TouchableOpacity, View} from 'react-native';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from "../navigation/navigationTypes";
import {Move} from "../entities/Move";
type MoveListScreenNavigationProp = StackNavigationProp<RootStackParamList, 'MoveList'>;
type Props = {
navigation: MoveListScreenNavigationProp;
};
const MoveListScreen = ({navigation}: Props) => {
const [moves, setMoves] = useState<Move[]>([]);
// FIXME LATER use a redux store to fetch the data
useEffect(() => {
fetch('http://localhost:8080/move')
.then(response => response.json())
.then(data => setMoves(data))
.catch(error => console.error(error));
}, []);
return (
<ScrollView>
<View>
<FlatList
data={moves}
renderItem={({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('MoveDetail', {move: item})}>
<Text>{item.name}, {item.type.name}: {item.power}</Text>
</TouchableOpacity>
)}
keyExtractor={(item) => item.name}
/>
</View>
</ScrollView>
);
};
export default MoveListScreen;

@ -0,0 +1,55 @@
// screens/moves/MoveDetailScreen.tsx
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import { RouteProp } from '@react-navigation/native';
import { RootStackParamList } from "../../navigation/navigationTypes";
import TypeTacticsInfoList from "../../components/TypeTacticsInfoList"
type MoveDetailScreenRouteProp = RouteProp<RootStackParamList, 'MoveDetail'>;
type Props = {
route: MoveDetailScreenRouteProp;
};
const MoveDetailScreen = ({ route }: Props) => {
const { move } = route.params;
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>Name: {move.name}</Text>
<Text style={styles.detail}>Category: {move.category}</Text>
<Text style={styles.detail}>Power: {move.power}</Text>
<Text style={styles.detail}>Accuracy: {move.accuracy}</Text>
<Text style={styles.detail}>Type: {move.type.name}</Text>
<View style={styles.typeListsContainer}>
<TypeTacticsInfoList isWeakness={true} types={move.type.weakAgainst}/>
<TypeTacticsInfoList isWeakness={false} types={move.type.effectiveAgainst}/>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
detail: {
fontSize: 18,
marginBottom: 5,
},
typeListsContainer: {
flexDirection: 'row',
justifyContent: 'space-evenly',
marginTop: 10,
},
});
export default MoveDetailScreen;

@ -0,0 +1,51 @@
// screens/moves/MoveListScreen.tsx
import React, { useEffect } from 'react';
import { FlatList, ScrollView, View } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from "../../navigation/navigationTypes";
import { useDispatch, useSelector } from 'react-redux';
import { getMoves } from '../../redux/actions/moveActions';
import { MoveState } from "../../redux/reducers/moveReducer";
import { AppDispatch } from "../../redux/store";
import MoveListItem from "../../components/MoveListItem";
type MoveListScreenNavigationProp = StackNavigationProp<RootStackParamList, 'MoveList'>;
type Props = {
navigation: MoveListScreenNavigationProp;
};
type RootState = {
move: MoveState;
};
const MoveListScreen = ({ navigation }: Props) => {
const dispatch = useDispatch();
const moves = useSelector((state: RootState) => state.move.moves);
useEffect(() => {
const loadMoves = async () => {
await (dispatch as AppDispatch)(getMoves());
};
loadMoves();
}, [dispatch]);
return (
<ScrollView>
<View>
<FlatList
data={moves}
renderItem={({ item }) => (
<MoveListItem
move={item}
onPress={() => navigation.navigate('MoveDetail', { move: item })}
/>
)}
keyExtractor={(item) => item.name}
/>
</View>
</ScrollView>
);
};
export default MoveListScreen;

@ -1044,6 +1044,13 @@
dependencies:
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
version "7.22.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3":
version "7.20.7"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz"
@ -2141,6 +2148,16 @@
color "^4.2.3"
warn-once "^0.1.0"
"@reduxjs/toolkit@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4"
integrity sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==
dependencies:
immer "^9.0.21"
redux "^4.2.1"
redux-thunk "^2.4.2"
reselect "^4.1.8"
"@segment/loosely-validate-event@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz"
@ -2326,6 +2343,14 @@
resolved "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz"
integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==
"@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz"
@ -2421,6 +2446,15 @@
dependencies:
"@types/react" "^17"
"@types/react@*":
version "18.2.8"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.8.tgz#a77dcffe4e9af148ca4aa8000c51a1e8ed99e2c8"
integrity sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^17":
version "17.0.59"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.59.tgz"
@ -2489,6 +2523,11 @@
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
"@types/ws@^8.5.1":
version "8.5.4"
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz"
@ -5365,7 +5404,7 @@ hermes-profile-transformer@^0.0.6:
dependencies:
source-map "^0.7.3"
hoist-non-react-statics@^3.3.0:
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -5552,6 +5591,11 @@ image-size@^0.6.0:
resolved "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz"
integrity sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==
immer@^9.0.21:
version "9.0.21"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz"
@ -8494,6 +8538,18 @@ react-native@0.71.8:
whatwg-fetch "^3.0.0"
ws "^6.2.2"
react-redux@^8.0.7:
version "8.0.7"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.7.tgz#b74ef2f7ce2076e354540aa3511d3670c2b62571"
integrity sha512-1vRQuCQI5Y2uNmrMXg81RXKiBHY3jBzvCvNmZF437O/Z9/pZ+ba2uYHbemYXb3g8rjsacBGo+/wmfrQKzMhJsg==
dependencies:
"@babel/runtime" "^7.12.1"
"@types/hoist-non-react-statics" "^3.3.1"
"@types/use-sync-external-store" "^0.0.3"
hoist-non-react-statics "^3.3.2"
react-is "^18.0.0"
use-sync-external-store "^1.0.0"
react-refresh@^0.4.0:
version "0.4.3"
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz"
@ -8567,6 +8623,18 @@ recast@^0.20.4:
source-map "~0.6.1"
tslib "^2.0.1"
redux-thunk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
redux@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
dependencies:
"@babel/runtime" "^7.9.2"
regenerate-unicode-properties@^10.1.0:
version "10.1.0"
resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz"
@ -8693,7 +8761,7 @@ requires-port@^1.0.0:
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
reselect@^4.0.0:
reselect@^4.0.0, reselect@^4.1.8:
version "4.1.8"
resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz"
integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==

Loading…
Cancel
Save