| @@ -1,86 +1,108 @@ | |||
| import React, {useContext} from 'react'; | |||
| import { View, Text, ImageBackground, Image, TouchableOpacity, StyleSheet, Linking } from 'react-native'; | |||
| import { DrawerContentScrollView, DrawerItemList } from '@react-navigation/drawer'; | |||
| import { AuthContext } from '../../context/AuthContext'; | |||
| import Spinner from 'react-native-loading-spinner-overlay/lib'; | |||
| import Loader from '../Loader'; | |||
| import React, { useContext } from "react"; | |||
| import { | |||
| View, | |||
| Text, | |||
| ImageBackground, | |||
| Image, | |||
| TouchableOpacity, | |||
| StyleSheet, | |||
| Linking, | |||
| } from "react-native"; | |||
| import { | |||
| DrawerContentScrollView, | |||
| DrawerItemList, | |||
| } from "@react-navigation/drawer"; | |||
| import { AuthContext } from "../../context/AuthContext"; | |||
| import Loader from "../Loader"; | |||
| import { MaterialIcons } from "@expo/vector-icons"; | |||
| import { Ionicons } from "@expo/vector-icons"; | |||
| const CustomDrawer = props => { | |||
| const CustomDrawer = (props) => { | |||
| const { logout, isLoading } = useContext(AuthContext); | |||
| const {logout, isLoading} = useContext(AuthContext); | |||
| return ( | |||
| <View style={styles.container}> | |||
| <Loader visible={isLoading} /> | |||
| <DrawerContentScrollView {...props} contentContainerStyle={styles.drawer}> | |||
| <ImageBackground source={require('../../assets/images/menu-bg.jpeg')} style={styles.backgroundImage}> | |||
| <Image source={require('../../assets/images/diligent-purple.png')} style={styles.profileImage} /> | |||
| <Text style={styles.userName}>Diligent Software</Text> | |||
| </ImageBackground> | |||
| <View style={styles.drawerItems}> | |||
| <DrawerItemList {...props} /> | |||
| </View> | |||
| </DrawerContentScrollView> | |||
| <View style={styles.footer}> | |||
| <TouchableOpacity onPress={() => Linking.openURL('mailto:office@dilig.net')} style={styles.footerButton}> | |||
| <View style={styles.buttonContent}> | |||
| <Text style={styles.footerButtonText}>Contact Us</Text> | |||
| </View> | |||
| </TouchableOpacity> | |||
| <TouchableOpacity onPress={logout} style={styles.footerButton}> | |||
| <View style={styles.buttonContent}> | |||
| <Text style={styles.footerButtonText}>Sign Out</Text> | |||
| </View> | |||
| </TouchableOpacity> | |||
| </View> | |||
| return ( | |||
| <View style={styles.container}> | |||
| <Loader visible={isLoading} /> | |||
| <DrawerContentScrollView {...props} contentContainerStyle={styles.drawer}> | |||
| <ImageBackground | |||
| source={require("../../assets/images/menu-bg.jpeg")} | |||
| style={styles.backgroundImage} | |||
| > | |||
| <Image | |||
| source={require("../../assets/images/diligent-purple.png")} | |||
| style={styles.profileImage} | |||
| /> | |||
| <Text style={styles.userName}>Diligent Software</Text> | |||
| </ImageBackground> | |||
| <View style={styles.drawerItems}> | |||
| <DrawerItemList {...props} /> | |||
| </View> | |||
| ) | |||
| } | |||
| </DrawerContentScrollView> | |||
| <View style={styles.footer}> | |||
| <TouchableOpacity | |||
| onPress={() => Linking.openURL("mailto:office@dilig.net")} | |||
| style={styles.footerButton} | |||
| > | |||
| <View style={styles.buttonContent}> | |||
| <Ionicons name="mail-outline" size={24} color="#333" /> | |||
| <Text style={styles.footerButtonText}>Contact Us</Text> | |||
| </View> | |||
| </TouchableOpacity> | |||
| <TouchableOpacity onPress={logout} style={styles.footerButton}> | |||
| <View style={styles.buttonContent}> | |||
| <MaterialIcons name="logout" size={22} color="#333" /> | |||
| <Text style={styles.footerButtonText}>Sign Out</Text> | |||
| </View> | |||
| </TouchableOpacity> | |||
| </View> | |||
| </View> | |||
| ); | |||
| }; | |||
| const styles = StyleSheet.create({ | |||
| container: { | |||
| flex: 1 | |||
| }, | |||
| drawer: { | |||
| backgroundColor: '#8200d6' | |||
| }, | |||
| backgroundImage: { | |||
| padding: 20 | |||
| }, | |||
| profileImage: { | |||
| height: 80, | |||
| width: 80, | |||
| borderRadius: 40, | |||
| marginBottom: 10 | |||
| }, | |||
| userName: { | |||
| color: '#fff', | |||
| fontSize: 18, | |||
| marginBottom: 5, | |||
| fontFamily: 'poppins-regular' | |||
| }, | |||
| drawerItems: { | |||
| flex: 1, | |||
| backgroundColor: '#fff', | |||
| paddingTop: 10, | |||
| }, | |||
| footer: { | |||
| padding: 20, | |||
| borderTopWidth: 1, | |||
| borderTopColor: '#ccc' | |||
| }, | |||
| footerButton: { | |||
| paddingVertical: 15 | |||
| }, | |||
| buttonContent: { | |||
| flexDirection: 'row', | |||
| alignItems: 'center' | |||
| }, | |||
| footerButtonText: { | |||
| fontSize: 15, | |||
| marginLeft: 5, | |||
| fontFamily: 'poppins-regular' | |||
| } | |||
| }) | |||
| container: { | |||
| flex: 1, | |||
| }, | |||
| drawer: { | |||
| backgroundColor: "#8200d6", | |||
| }, | |||
| backgroundImage: { | |||
| padding: 20, | |||
| }, | |||
| profileImage: { | |||
| height: 80, | |||
| width: 80, | |||
| borderRadius: 40, | |||
| marginBottom: 10, | |||
| }, | |||
| userName: { | |||
| color: "#fff", | |||
| fontSize: 18, | |||
| marginBottom: 5, | |||
| fontFamily: "poppins-regular", | |||
| }, | |||
| drawerItems: { | |||
| flex: 1, | |||
| backgroundColor: "#fff", | |||
| paddingTop: 10, | |||
| }, | |||
| footer: { | |||
| padding: 20, | |||
| borderTopWidth: 1, | |||
| borderTopColor: "#ccc", | |||
| }, | |||
| footerButton: { | |||
| paddingVertical: 15, | |||
| }, | |||
| buttonContent: { | |||
| flexDirection: "row", | |||
| alignItems: "center", | |||
| }, | |||
| footerButtonText: { | |||
| fontSize: 15, | |||
| marginLeft: 5, | |||
| fontFamily: "poppins-regular", | |||
| }, | |||
| }); | |||
| export default CustomDrawer; | |||
| export default CustomDrawer; | |||
| @@ -0,0 +1,82 @@ | |||
| import React from "react"; | |||
| import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native"; | |||
| import { globalStyles } from "../../styles/global"; | |||
| import { windowWidth } from "../../utils/Dimensions"; | |||
| const ListItem = ({ photo, title, onPress, publishedAt }) => { | |||
| const formatDate = (date) => { | |||
| const tempDate = new Date(date); | |||
| const fDate = | |||
| tempDate.getDate() + | |||
| "/" + | |||
| (tempDate.getMonth() + 1) + | |||
| "/" + | |||
| tempDate.getFullYear(); | |||
| return fDate; | |||
| }; | |||
| return ( | |||
| <View style={styles.cardContainer}> | |||
| <View style={styles.imageContainer}> | |||
| <Image | |||
| style={styles.image} | |||
| source={{ uri: `http://localhost:1337${photo.small.url}` }} | |||
| /> | |||
| <View style={{ width: windowWidth - 220 }}> | |||
| <Text | |||
| style={{ | |||
| color: "#333", | |||
| fontFamily: "poppins-regular", | |||
| fontSize: 14, | |||
| textTransform: "uppercase", | |||
| }} | |||
| > | |||
| {title} | |||
| </Text> | |||
| <Text | |||
| style={{ | |||
| color: "#333", | |||
| fontFamily: "poppins-regular", | |||
| fontSize: 14, | |||
| textTransform: "uppercase", | |||
| }} | |||
| > | |||
| Published At {formatDate(publishedAt)} | |||
| </Text> | |||
| </View> | |||
| </View> | |||
| <TouchableOpacity style={globalStyles.button} onPress={onPress}> | |||
| <Text style={styles.buttonText}>Read More</Text> | |||
| </TouchableOpacity> | |||
| </View> | |||
| ); | |||
| }; | |||
| const styles = StyleSheet.create({ | |||
| cardContainer: { | |||
| flexDirection: "row", | |||
| justifyContent: "space-between", | |||
| alignItems: "center", | |||
| marginBottom: 20, | |||
| }, | |||
| imageContainer: { | |||
| flexDirection: "row", | |||
| alignItems: "center", | |||
| flex: 1, | |||
| }, | |||
| image: { | |||
| width: 55, | |||
| height: 55, | |||
| borderRadius: 10, | |||
| marginRight: 8, | |||
| }, | |||
| buttonText: { | |||
| color: "#fff", | |||
| textAlign: "center", | |||
| fontFamily: "poppins-regular", | |||
| fontSize: 14, | |||
| }, | |||
| }); | |||
| export default ListItem; | |||
| @@ -2,7 +2,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; | |||
| import * as Google from "expo-auth-session/providers/google"; | |||
| import React, { createContext, useEffect, useState } from "react"; | |||
| import { googleApi, loginApi, registerApi } from "../service/user"; | |||
| import {revokeAsync} from 'expo-auth-session' | |||
| import { revokeAsync } from "expo-auth-session"; | |||
| export const AuthContext = createContext(); | |||
| @@ -47,6 +47,7 @@ export const AuthProvider = ({ children }) => { | |||
| if (res.user) { | |||
| setUserInfo(res); | |||
| AsyncStorage.setItem("userInfo", JSON.stringify(res)); | |||
| AsyncStorage.setItem("jwt-token", res.jwt); | |||
| setIsLoading(false); | |||
| } else { | |||
| callback(res); | |||
| @@ -70,6 +71,7 @@ export const AuthProvider = ({ children }) => { | |||
| if (res.user) { | |||
| setUserInfo(res); | |||
| AsyncStorage.setItem("userInfo", JSON.stringify(res)); | |||
| AsyncStorage.setItem("jwt-token", res.jwt); | |||
| setIsLoading(false); | |||
| } else { | |||
| callback(res); | |||
| @@ -85,12 +87,16 @@ export const AuthProvider = ({ children }) => { | |||
| const logout = async () => { | |||
| setIsLoading(true); | |||
| AsyncStorage.removeItem("userInfo"); | |||
| AsyncStorage.removeItem("jwt-token"); | |||
| setUserInfo({}); | |||
| setIsLoading(false); | |||
| if (userInfo.user.provider === 'google') { | |||
| await revokeAsync({token: response.authentication.accessToken}, {revocationEndpoint: 'https://oauth2.googleapis.com/revoke'}) | |||
| if (userInfo.user.provider === "google") { | |||
| await revokeAsync( | |||
| { token: response.authentication.accessToken }, | |||
| { revocationEndpoint: "https://oauth2.googleapis.com/revoke" } | |||
| ); | |||
| } | |||
| setIsLoading(false); | |||
| }; | |||
| const isLoggedIn = async () => { | |||
| @@ -6,6 +6,7 @@ import { getFocusedRouteNameFromRoute } from "@react-navigation/native"; | |||
| import HomeScreen from "../screens/HomeScreen"; | |||
| import Ionicons from "@expo/vector-icons/Ionicons"; | |||
| import FavoriteScreen from "../screens/FavoriteScreen"; | |||
| import PostDetailsScreen from "../screens/PostDetailsScreen"; | |||
| const Tab = createBottomTabNavigator(); | |||
| const Stack = createNativeStackNavigator(); | |||
| @@ -18,6 +19,13 @@ const HomeStack = () => { | |||
| component={HomeScreen} | |||
| options={{ headerShown: false }} | |||
| /> | |||
| <Stack.Screen | |||
| name="PostDetails" | |||
| component={PostDetailsScreen} | |||
| options={({route}) => ({ | |||
| title: route.params.title | |||
| })} | |||
| /> | |||
| </Stack.Navigator> | |||
| ); | |||
| }; | |||
| @@ -23,6 +23,7 @@ | |||
| "expo-status-bar": "~1.4.2", | |||
| "expo-web-browser": "~12.0.0", | |||
| "formik": "^2.2.9", | |||
| "lodash.filter": "^4.6.0", | |||
| "react": "18.1.0", | |||
| "react-dom": "18.1.0", | |||
| "react-native": "0.70.5", | |||
| @@ -1,48 +1,151 @@ | |||
| import React from 'react'; | |||
| import { View, Text, SafeAreaView, ScrollView, ImageBackground, TextInput, TouchableOpacity, StyleSheet } from 'react-native'; | |||
| import Feather from '@expo/vector-icons/Feather'; | |||
| import { windowWidth } from '../utils/Dimensions'; | |||
| const HomeScreen = ({navigation}) => { | |||
| return ( | |||
| <SafeAreaView style={{flex: 1, backgroundColor: '#fff'}}> | |||
| <ScrollView style={{padding: 20}}> | |||
| <View style={styles.wrapper}> | |||
| <Text style={{fontSize: 18}}> | |||
| Hello Diligent | |||
| </Text> | |||
| <TouchableOpacity onPress={() => navigation.openDrawer()}> | |||
| <ImageBackground source={require('../assets/images/diligent-purple.png')} style={styles.imageBackground} imageStyle={{borderRadius:25}} /> | |||
| </TouchableOpacity> | |||
| </View> | |||
| <View style={styles.search}> | |||
| <Feather name='search' size={20} color='#C6C6C6' style={{marginRight: 5}} /> | |||
| <TextInput placeholder='Search' placeholderTextColor='#C6C6C6' /> | |||
| </View> | |||
| </ScrollView> | |||
| </SafeAreaView> | |||
| ) | |||
| } | |||
| import React, { useContext, useEffect, useState } from "react"; | |||
| import { | |||
| View, | |||
| Text, | |||
| SafeAreaView, | |||
| ScrollView, | |||
| ImageBackground, | |||
| TextInput, | |||
| TouchableOpacity, | |||
| StyleSheet, | |||
| } from "react-native"; | |||
| import Feather from "@expo/vector-icons/Feather"; | |||
| import { postsApi } from "../service/post"; | |||
| import { AuthContext } from "../context/AuthContext"; | |||
| import ListItem from "../components/ListItem/ListItem"; | |||
| import filter from "lodash.filter"; | |||
| import { globalStyles } from "../styles/global"; | |||
| const styles = StyleSheet.create({ | |||
| wrapper: { | |||
| flexDirection: 'row', | |||
| justifyContent: 'space-between', | |||
| marginBottom: 20 | |||
| }, | |||
| imageBackground: { | |||
| width: 35, | |||
| height: 35 | |||
| }, | |||
| search: { | |||
| flexDirection: 'row', | |||
| borderColor: '#C6C6C6', | |||
| borderWidth: 1, | |||
| borderRadius: 8, | |||
| paddingHorizontal: 10, | |||
| paddingVertical: 8 | |||
| const HomeScreen = ({ navigation }) => { | |||
| const [posts, setPosts] = useState([]); | |||
| const { userInfo } = useContext(AuthContext); | |||
| const [query, setQuery] = useState(""); | |||
| const [filteredData, setFilteredData] = useState(posts); | |||
| const contains = (name, search) => { | |||
| if (name.includes(search)) { | |||
| return true; | |||
| } | |||
| return false; | |||
| }; | |||
| const searchFilter = (text) => { | |||
| const formatedText = text.toLowerCase(); | |||
| const data = filter(posts, (item) => { | |||
| return contains(item?.attributes.title.toLowerCase(), formatedText); | |||
| }); | |||
| setFilteredData(data); | |||
| setQuery(formatedText); | |||
| }; | |||
| const fetchAll = () => { | |||
| if (userInfo.jwt) { | |||
| postsApi(userInfo.jwt) | |||
| .then((res) => setPosts(res.data)) | |||
| .catch((e) => console.log(e)); | |||
| } | |||
| }; | |||
| useEffect(() => { | |||
| fetchAll(); | |||
| }, []); | |||
| useEffect(() => { | |||
| if (posts) { | |||
| setFilteredData(posts); | |||
| } | |||
| }) | |||
| }, [posts]) | |||
| return ( | |||
| <SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}> | |||
| <ScrollView style={{ padding: 20 }}> | |||
| <View style={styles.wrapper}> | |||
| <Text style={{ fontSize: 18 }}>Hello Diligent</Text> | |||
| <TouchableOpacity onPress={() => navigation.openDrawer()}> | |||
| <ImageBackground | |||
| source={require("../assets/images/diligent-purple.png")} | |||
| style={styles.imageBackground} | |||
| imageStyle={{ borderRadius: 25 }} | |||
| /> | |||
| </TouchableOpacity> | |||
| </View> | |||
| <View style={styles.search}> | |||
| <Feather | |||
| name="search" | |||
| size={20} | |||
| color="#C6C6C6" | |||
| style={{ marginRight: 5 }} | |||
| /> | |||
| <TextInput | |||
| onChangeText={(text) => searchFilter(text)} | |||
| autoCapitalize="none" | |||
| autoCorrect={false} | |||
| placeholder="Search" | |||
| value={query} | |||
| placeholderTextColor="#C6C6C6" | |||
| /> | |||
| </View> | |||
| <Text> | |||
| {filteredData.length === 0 && ( | |||
| <View> | |||
| <Text style={globalStyles.boldText}>No Results Found</Text> | |||
| </View> | |||
| )} | |||
| </Text> | |||
| {query.length === 0 | |||
| ? posts.map((post) => ( | |||
| <ListItem | |||
| key={post.id} | |||
| title={post.attributes.title} | |||
| photo={post.attributes.profileImage.data.attributes.formats} | |||
| publishedAt={post.attributes.publishedAt} | |||
| onPress={() => | |||
| navigation.navigate("PostDetails", { | |||
| title: post.attributes.title, | |||
| id: post.id, | |||
| }) | |||
| } | |||
| /> | |||
| )) | |||
| : filteredData.map((post) => ( | |||
| <ListItem | |||
| key={post.id} | |||
| title={post.attributes.title} | |||
| photo={post.attributes.profileImage.data.attributes.formats} | |||
| publishedAt={post.attributes.publishedAt} | |||
| onPress={() => | |||
| navigation.navigate("PostDetails", { | |||
| title: post.attributes.title, | |||
| id: post.id, | |||
| }) | |||
| } | |||
| /> | |||
| ))} | |||
| </ScrollView> | |||
| </SafeAreaView> | |||
| ); | |||
| }; | |||
| const styles = StyleSheet.create({ | |||
| wrapper: { | |||
| flexDirection: "row", | |||
| justifyContent: "space-between", | |||
| marginBottom: 20, | |||
| }, | |||
| imageBackground: { | |||
| width: 35, | |||
| height: 35, | |||
| }, | |||
| search: { | |||
| flexDirection: "row", | |||
| borderColor: "#C6C6C6", | |||
| borderWidth: 1, | |||
| borderRadius: 8, | |||
| paddingHorizontal: 10, | |||
| paddingVertical: 8, | |||
| marginBottom: 20, | |||
| }, | |||
| }); | |||
| export default HomeScreen; | |||
| export default HomeScreen; | |||
| @@ -28,7 +28,7 @@ const styles = StyleSheet.create({ | |||
| }, | |||
| headText: { | |||
| fontWeight: 'bold', | |||
| fontSize: 30, | |||
| fontSize: 70, | |||
| color: '#20315f', | |||
| fontFamily: 'poppins-semibold' | |||
| }, | |||
| @@ -0,0 +1,54 @@ | |||
| import React, { useContext, useEffect, useState } from "react"; | |||
| import { View, Text, Image, StyleSheet } from "react-native"; | |||
| import { AuthContext } from "../context/AuthContext"; | |||
| import { singlePostApi } from "../service/post"; | |||
| import { globalStyles } from "../styles/global"; | |||
| import { windowWidth } from "../utils/Dimensions"; | |||
| const PostDetailsScreen = ({ navigation, route }) => { | |||
| const { userInfo } = useContext(AuthContext); | |||
| const [post, setPost] = useState({}); | |||
| const fetchPost = async () => { | |||
| if (userInfo.jwt && route.params.id) { | |||
| singlePostApi(userInfo.jwt, route.params?.id) | |||
| .then((res) => setPost(res.data)) | |||
| .catch((e) => console.log(e)); | |||
| } | |||
| }; | |||
| useEffect(() => { | |||
| fetchPost(); | |||
| }, []); | |||
| return ( | |||
| <View style={{ flex: 1 }}> | |||
| <Image | |||
| style={styles.image} | |||
| source={{ | |||
| uri: `http://localhost:1337${post?.attributes?.profileImage.data.attributes.url}`, | |||
| }} | |||
| /> | |||
| <Text style={[globalStyles.boldText, styles.title]}> | |||
| {post?.attributes?.title} | |||
| </Text> | |||
| <Text style={[globalStyles.regularText ,styles.description]}>{post?.attributes?.description}</Text> | |||
| </View> | |||
| ); | |||
| }; | |||
| const styles = StyleSheet.create({ | |||
| image: { | |||
| width: windowWidth, | |||
| height: 300, | |||
| }, | |||
| title: { | |||
| marginTop: 20, | |||
| textAlign: "center", | |||
| }, | |||
| description: { | |||
| marginHorizontal: 20 | |||
| } | |||
| }); | |||
| export default PostDetailsScreen; | |||
| @@ -1,3 +1,6 @@ | |||
| export default { | |||
| login: 'api/auth/local' | |||
| login: 'api/auth/local', | |||
| register: 'api/auth/local/register', | |||
| googleAuth: 'api/auth/google/callback?access_token=', | |||
| fetchPosts: 'api/posts?populate=*', | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| import { API_ENDPOINT } from "./endpointDef"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const postsApi = async (jwt) => { | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.fetchPosts}`, { | |||
| method: "GET", | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| 'Authorization': "Bearer " + jwt, | |||
| }, | |||
| }); | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| export const singlePostApi = async (jwt, postId) => { | |||
| const result = await fetch(`${API_ENDPOINT}api/posts/${postId}?populate=*`, { | |||
| method: "GET", | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| 'Authorization': "Bearer " + jwt, | |||
| }, | |||
| }); | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| @@ -16,7 +16,7 @@ export const loginApi = async (payload) => { | |||
| }; | |||
| export const registerApi = async (payload) => { | |||
| const result = await fetch(API_ENDPOINT + 'api/auth/local/register', { | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.register}`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json' | |||
| @@ -30,7 +30,7 @@ export const registerApi = async (payload) => { | |||
| }; | |||
| export const googleApi = async (accessToken) => { | |||
| const result = await fetch('http://localhost:1337/' + `api/auth/google/callback?access_token=${accessToken}`); | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.googleAuth}${accessToken}`); | |||
| const res = await result.json(); | |||
| @@ -41,4 +41,10 @@ export const globalStyles = StyleSheet.create({ | |||
| flex: 1, | |||
| justifyContent: "center", | |||
| }, | |||
| button: { | |||
| backgroundColor: primary, | |||
| padding: 10, | |||
| width: 100, | |||
| borderRadius: 10 | |||
| } | |||
| }); | |||
| @@ -4770,6 +4770,11 @@ lodash.debounce@^4.0.8: | |||
| resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" | |||
| integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== | |||
| lodash.filter@^4.6.0: | |||
| version "4.6.0" | |||
| resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" | |||
| integrity sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ== | |||
| lodash.isequal@^4.5.0: | |||
| version "4.5.0" | |||
| resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" | |||