remake app ohome setup /notworking
This commit is contained in:
51
App.js
51
App.js
@@ -1,21 +1,42 @@
|
|||||||
import { StatusBar } from 'expo-status-bar';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
// import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
export default function App() {
|
import App from './src/App';
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
// //styles
|
||||||
<Text>Open up App.js to start working on your app!</Text>
|
import { ThemeProvider } from 'styled-components'
|
||||||
<StatusBar style="auto" />
|
|
||||||
</View>
|
// import GlobalStyle from './src/styles/globalStyles'
|
||||||
);
|
import Theme from './src/styles/theme'
|
||||||
|
// //redux
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import store from './src/redux/store';
|
||||||
|
|
||||||
|
import { fetchAll } from './src/utils'
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
|
|
||||||
|
fetchAll()
|
||||||
|
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<ThemeProvider theme={Theme}>
|
||||||
|
{/* <GlobalStyle /> */}
|
||||||
|
<App />
|
||||||
|
<StatusBar style="auto" />
|
||||||
|
</ThemeProvider>
|
||||||
|
</Provider>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
alignItems: 'center',
|
},
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
21
index.js
Normal file
21
index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text>Open up App.js to start working on your app!</Text>
|
||||||
|
<StatusBar style="auto" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
16730
package-lock.json
generated
Normal file
16730
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -8,12 +8,26 @@
|
|||||||
"eject": "expo eject"
|
"eject": "expo eject"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-community/masked-view": "^0.1.11",
|
||||||
|
"@react-navigation/elements": "^1.2.1",
|
||||||
|
"@react-navigation/native": "^6.0.6",
|
||||||
|
"@react-navigation/native-stack": "^6.2.5",
|
||||||
|
"@react-navigation/stack": "^6.0.11",
|
||||||
|
"@reduxjs/toolkit": "^1.6.2",
|
||||||
"expo": "~43.0.0",
|
"expo": "~43.0.0",
|
||||||
"expo-status-bar": "~1.1.0",
|
"expo-status-bar": "~1.1.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
|
"react-icons": "^4.3.1",
|
||||||
"react-native": "0.64.2",
|
"react-native": "0.64.2",
|
||||||
"react-native-web": "0.17.1"
|
"react-native-gesture-handler": "~1.10.2",
|
||||||
|
"react-native-modal": "^13.0.0",
|
||||||
|
"react-native-reanimated": "~2.2.0",
|
||||||
|
"react-native-safe-area-context": "3.3.2",
|
||||||
|
"react-native-screens": "~3.8.0",
|
||||||
|
"react-native-web": "0.17.1",
|
||||||
|
"react-redux": "^7.2.5",
|
||||||
|
"styled-components": "^5.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.9"
|
"@babel/core": "^7.12.9"
|
||||||
|
|||||||
99
src/App.js
Normal file
99
src/App.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
|
//components
|
||||||
|
import HomePage from './pages/HomePage';
|
||||||
|
import GroceryListPage from './pages/groceryList/GroceryListPage';
|
||||||
|
import RecipePage from './pages/recipes/RecipesPage';
|
||||||
|
import AddRecipe from './pages/recipes/AddRecipe';
|
||||||
|
import theme from './styles/theme';
|
||||||
|
//dependencies
|
||||||
|
//navigation
|
||||||
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
|
|
||||||
|
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const MyTheme = {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
background: theme.colors.dp00,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// const ScrollToTop = () => {
|
||||||
|
// const { pathname } = useLocation();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// window.scrollTo(0, 0);
|
||||||
|
// }, [pathname]);
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
const Stack = createNativeStackNavigator();
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const statusRecipes = useSelector(state => state.recipes.status)
|
||||||
|
return (
|
||||||
|
<NavigationContainer theme={MyTheme}>
|
||||||
|
<Stack.Navigator screenOptions={{
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: theme.colors.dp00 + 'bb'
|
||||||
|
}, //COLORS.dp08
|
||||||
|
headerShadowVisible: false,
|
||||||
|
headerTintColor: theme.colors.textW0,
|
||||||
|
headerTitleStyle: { fontSize: 32 },
|
||||||
|
cardOverlayEnabled: true,
|
||||||
|
headerTransparent: true,
|
||||||
|
}}>
|
||||||
|
<Stack.Screen name="Home" component={HomePage} options={{
|
||||||
|
headerLeft: () => (
|
||||||
|
< TouchableOpacity
|
||||||
|
onPress={() => alert('This is a button!')}
|
||||||
|
>
|
||||||
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
|
}} />
|
||||||
|
<Stack.Screen name="Groceries" component={GroceryListPage} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
{/* <Router>
|
||||||
|
<View>
|
||||||
|
<ScrollToTop />
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/groceryList">
|
||||||
|
<GroceryListPage />
|
||||||
|
</Route>
|
||||||
|
<Route path="/recipes/addRecipe/:id">
|
||||||
|
{statusRecipes === 'idle' &&
|
||||||
|
< AddRecipe />
|
||||||
|
}
|
||||||
|
</Route>
|
||||||
|
<Route exact path="/recipes/addRecipe">
|
||||||
|
<AddRecipe />
|
||||||
|
</Route>
|
||||||
|
<Route path="/recipes/:id">
|
||||||
|
{statusRecipes === 'idle' &&
|
||||||
|
< Recipe />
|
||||||
|
}
|
||||||
|
</Route>
|
||||||
|
<Route path="/recipes">
|
||||||
|
<RecipePage />
|
||||||
|
</Route>
|
||||||
|
<Route path="/">
|
||||||
|
<HomePage />
|
||||||
|
</Route>
|
||||||
|
<Route exact path="/">
|
||||||
|
<Redirect to="/" />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</View>
|
||||||
|
</Router> */}
|
||||||
68
src/components/Dropdown.js
Normal file
68
src/components/Dropdown.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { Text, View } from 'react-native'
|
||||||
|
|
||||||
|
const WrapperDropdown = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
z-index:2;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp01 + 'cc'};
|
||||||
|
width: 200px;
|
||||||
|
max-height: 100px;
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
border-bottom-right-radius: 10px;
|
||||||
|
`
|
||||||
|
const DropdownItem = styled(Text)`
|
||||||
|
color: ${({ theme }) => theme.colors.textW2};
|
||||||
|
font-size: 20px;
|
||||||
|
&:hover{
|
||||||
|
background-color: #0001;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const HorizontalSeperator = styled(View)`
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp02};
|
||||||
|
`
|
||||||
|
|
||||||
|
const Dropdown = (props) => {
|
||||||
|
let text = props.text
|
||||||
|
text = text.replace(/\W/g, '')
|
||||||
|
let regex = new RegExp(text, "i");
|
||||||
|
|
||||||
|
let newArray = props.array.filter((element) =>
|
||||||
|
regex.test(element)
|
||||||
|
)
|
||||||
|
if (newArray.length === 1 && newArray[0] === text) {
|
||||||
|
newArray = [];
|
||||||
|
}
|
||||||
|
let dropdownList = newArray.map((element, index) => {
|
||||||
|
return (
|
||||||
|
<View key={index}>
|
||||||
|
<DropdownItem onClick={() => props.setElement(element)}>{element}</DropdownItem>
|
||||||
|
<HorizontalSeperator />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<WrapperDropdown>
|
||||||
|
{dropdownList}
|
||||||
|
</WrapperDropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QtDropdown = (props) => {
|
||||||
|
const quantities = ["g", "kg", "mL", "L"]
|
||||||
|
return <Dropdown
|
||||||
|
array={quantities}
|
||||||
|
text={props.text}
|
||||||
|
setElement={props.setElement} />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Dropdown
|
||||||
28
src/components/GroceryList/TabMenu.js
Normal file
28
src/components/GroceryList/TabMenu.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react'
|
||||||
|
//styles
|
||||||
|
import {Wrapper,WrapperSelected, WrapperGroceries,IconGroceries, WrapperProducts, IconProducts} from './styles/tabMenu'
|
||||||
|
|
||||||
|
const calcPos = (pos) => {
|
||||||
|
return pos === 'Groceries' ? '-75px'
|
||||||
|
: '75px'
|
||||||
|
}
|
||||||
|
let previousPos = '-150px'
|
||||||
|
const TabMenu = (props) => {
|
||||||
|
const handleTabPress = (tab) => {
|
||||||
|
previousPos = calcPos(props.currentTab)
|
||||||
|
props.setCurrentTab(tab)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<WrapperSelected previousPos = {previousPos} currenttab={props.currentTab}/>
|
||||||
|
<WrapperGroceries currenttab={props.currentTab} onClick={()=>handleTabPress("Groceries")}>
|
||||||
|
<IconGroceries currenttab={props.currentTab}/>
|
||||||
|
</WrapperGroceries>
|
||||||
|
<WrapperProducts currenttab={props.currentTab} onClick={()=>handleTabPress("Products")}>
|
||||||
|
<IconProducts currenttab={props.currentTab}/>
|
||||||
|
</WrapperProducts>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabMenu
|
||||||
77
src/components/GroceryList/groceries/GroceryButtons.js
Normal file
77
src/components/GroceryList/groceries/GroceryButtons.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import ModalAddItem from '../../modals/groceries/ModalAddItem'
|
||||||
|
import ModalAddList from '../../modals/groceries/ModalAddList'
|
||||||
|
//styles
|
||||||
|
import { WrapperButtons, WrapperAddItem, WrapperAddList, WrapperRemove, WrapperSelect, PlusIcon, MenuIcon, RemoveIcon, CheckIcon, ListIcon } from './styles/buttons'
|
||||||
|
//redux
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { itemsRemoved, checkAll } from '../../../redux/slices/groceryList/itemsSlice'
|
||||||
|
import { selectOpenListId } from '../../../redux/slices/groceryList/listsSlice'
|
||||||
|
|
||||||
|
const SelectAllItemsButton = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const listId = useSelector(selectOpenListId)
|
||||||
|
const [toggleAnim, setToggleAnim] = useState(false)
|
||||||
|
const handlePress = () => {
|
||||||
|
listId && dispatch(checkAll(listId));
|
||||||
|
listId && setToggleAnim(!toggleAnim)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperSelect toggle={toggleAnim} visible={props.visible} onClick={handlePress}>
|
||||||
|
<CheckIcon />
|
||||||
|
</WrapperSelect>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const RemoveItemsButton = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const [toggleAnim, setToggleAnim] = useState(false)
|
||||||
|
const handlePress = () => {
|
||||||
|
if (window.confirm("Do you really want to remove the selected items?")) {
|
||||||
|
dispatch(itemsRemoved())
|
||||||
|
setToggleAnim(!toggleAnim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperRemove toggle={toggleAnim} visible={props.visible} onClick={handlePress}>
|
||||||
|
<RemoveIcon />
|
||||||
|
</WrapperRemove>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContainerButtons = (props) => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SelectAllItemsButton visible={visible} />
|
||||||
|
<RemoveItemsButton visible={visible} />
|
||||||
|
<WrapperButtons toggle={visible} onClick={() => setVisible(!visible)}>
|
||||||
|
<MenuIcon />
|
||||||
|
</WrapperButtons >
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddItemButton = (props) => {
|
||||||
|
const [visibleItem, setVisibleItem] = useState(false);
|
||||||
|
const [visibleList, setVisibleList] = useState(false);
|
||||||
|
const lists = useSelector(state => state.lists.entities)
|
||||||
|
if (lists.find((list) => list.open === true)) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WrapperAddItem toggle={visibleItem} onClick={() => setVisibleItem(true)}>
|
||||||
|
<PlusIcon />
|
||||||
|
</WrapperAddItem>
|
||||||
|
<ModalAddItem visible={visibleItem} closeModal={() => setVisibleItem(false)} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WrapperAddList toggle={visibleList} onClick={() => setVisibleList(true)}>
|
||||||
|
<ListIcon />
|
||||||
|
</WrapperAddList>
|
||||||
|
<ModalAddList visible={visibleList} closeModal={() => setVisibleList(false)} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
src/components/GroceryList/groceries/GroceryList.js
Normal file
35
src/components/GroceryList/groceries/GroceryList.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react'
|
||||||
|
//components
|
||||||
|
import List from './List'
|
||||||
|
//redux
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { selectAllSortedItems } from '../../../redux/slices/groceryList/itemsSlice'
|
||||||
|
//styles
|
||||||
|
import { WrapperGroceryList, TextTotalAmount } from './styles/list'
|
||||||
|
import { selectAllProducts } from '../../../redux/slices/groceryList/productsSlice';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
const GroceryList = (props) => {
|
||||||
|
const items = useSelector(selectAllSortedItems)
|
||||||
|
const products = useSelector(selectAllProducts)
|
||||||
|
const lists = useSelector(state => state.lists.entities)
|
||||||
|
let Lists = lists.map((list, index) => {
|
||||||
|
return <List key={index} listId={list._id} />
|
||||||
|
})
|
||||||
|
const totalPrice = Math.round(items.reduce((accumulator, item) => {
|
||||||
|
let product = products.find(product => product._id === item._id && item.checked)
|
||||||
|
accumulator += (product ? product.price : 0) * item.amount.am
|
||||||
|
accumulator += (item.price && item.checked ? item.price : 0) * item.amount.am
|
||||||
|
return accumulator
|
||||||
|
}, 0) * 100) / 100
|
||||||
|
return (
|
||||||
|
<WrapperGroceryList>
|
||||||
|
<TextTotalAmount>total: € {totalPrice}</TextTotalAmount>
|
||||||
|
{Lists}
|
||||||
|
<View style={{ height: 150 }} />
|
||||||
|
</WrapperGroceryList>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default GroceryList
|
||||||
69
src/components/GroceryList/groceries/List.js
Normal file
69
src/components/GroceryList/groceries/List.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
//components
|
||||||
|
import ListedItem from './ListedItem'
|
||||||
|
import ModalEditItem from '../../modals/groceries/ModalEditItem';
|
||||||
|
import ModalEditList from '../../modals/groceries/ModalEditList';
|
||||||
|
// Styling
|
||||||
|
import { Wrapper, WrapperList, WrapperListTitle, ListTitle, ListSubtitle, ButtonRemoveList, ButtonEditList, IconArrowDown, ListSizeWrapper } from './styles/list'
|
||||||
|
//redux
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { selectAllSortedItems, itemsRemovedByList } from '../../../redux/slices/groceryList/itemsSlice'
|
||||||
|
import { toggleOpen, listRemoved } from '../../../redux/slices/groceryList/listsSlice'
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
export default React.memo((props) => {
|
||||||
|
const items = useSelector(selectAllSortedItems)
|
||||||
|
const list = useSelector(state => state.lists.entities.find((list) => list._id === props.listId))
|
||||||
|
|
||||||
|
const [height, setHeight] = useState(0);
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHeight(ref.current.clientHeight);
|
||||||
|
}, [list.open, items]);
|
||||||
|
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
|
||||||
|
const [modalItemVisible, setModalItemVisible] = useState(false);
|
||||||
|
const [modalListVisible, setModalListVisible] = useState(false);
|
||||||
|
|
||||||
|
const itemList = items.filter(item => item.listId === props.listId).map((item, index) => {
|
||||||
|
return (
|
||||||
|
<ListedItem key={index} item={item} setVisible={setModalItemVisible} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const removeList = () => {
|
||||||
|
if (window.confirm("Do you really want to remove this list and the groceries within")) {
|
||||||
|
dispatch(itemsRemovedByList(props.listId))
|
||||||
|
dispatch(listRemoved(props.listId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Wrapper >
|
||||||
|
<WrapperListTitle onClick={() => { dispatch(toggleOpen(props.listId)) }}>
|
||||||
|
<View id="left">
|
||||||
|
<ListTitle>{list.listName}</ListTitle>
|
||||||
|
<ListSubtitle>{list.listDescription && list.listDescription}</ListSubtitle>
|
||||||
|
</View>
|
||||||
|
<View id="right">
|
||||||
|
{list.open && <>
|
||||||
|
<ButtonEditList onClick={() => setModalListVisible(true)} />
|
||||||
|
<ButtonRemoveList onClick={removeList} />
|
||||||
|
</>}
|
||||||
|
<IconArrowDown visible={list.open.toString()} />
|
||||||
|
</View>
|
||||||
|
</WrapperListTitle>
|
||||||
|
<ListSizeWrapper height={height} visible={list.open} >
|
||||||
|
< WrapperList listLength={items.length} ref={ref}>
|
||||||
|
{list.open && itemList}
|
||||||
|
{
|
||||||
|
itemList.length === 0 && <ListSubtitle style={{ fontSize: 22 }} >Add a grocery</ListSubtitle>
|
||||||
|
}
|
||||||
|
{items.find(item => item.modalVisible) && <ModalEditItem visible={modalItemVisible} setVisible={setModalItemVisible} />}
|
||||||
|
</WrapperList >
|
||||||
|
<ModalEditList id={props.listId} visible={modalListVisible} setVisible={setModalListVisible} />
|
||||||
|
</ListSizeWrapper>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
})
|
||||||
34
src/components/GroceryList/groceries/ListedItem.js
Normal file
34
src/components/GroceryList/groceries/ListedItem.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react'
|
||||||
|
//components
|
||||||
|
//styling
|
||||||
|
import { Wrapper,DarkLayer, WrapperItem, WrapperButton, TextProductName, TextDetails, TextAmount, TextPerson } from './styles/listedItem'
|
||||||
|
import { CheckButton } from '../../../styles/componentBlueprints';
|
||||||
|
//redux
|
||||||
|
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||||
|
import { selectItemById, checkToggle, modalToggle } from '../../../redux/slices/groceryList/itemsSlice'
|
||||||
|
import { findTag } from '../../../redux/slices/groceryList/tagsSlice'
|
||||||
|
|
||||||
|
const ListedItem = React.memo((props) => {
|
||||||
|
console.log("Rendering item")
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const item = useSelector(state => selectItemById(state, props.item._id), shallowEqual)
|
||||||
|
const tag = useSelector(state => findTag(state, item.tag))
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<DarkLayer>
|
||||||
|
<WrapperItem person={item.person} color={tag.color} checked={item.checked} onClick={() => { props.setVisible(true); return dispatch(modalToggle(item._id)) }}>
|
||||||
|
<TextProductName >{item.productName}</TextProductName>
|
||||||
|
<TextDetails>{item.details}</TextDetails>
|
||||||
|
<TextAmount>{item.amount.am}{item.amount.qt && " " + item.amount.qt}</TextAmount>
|
||||||
|
{item.person && <TextPerson>{item.person}</TextPerson>}
|
||||||
|
{/* {product.image && <Image src={product.image} />} */}
|
||||||
|
</WrapperItem>
|
||||||
|
</DarkLayer>
|
||||||
|
<WrapperButton onClick={() => dispatch(checkToggle(item._id))} >
|
||||||
|
<CheckButton checked = {item.checked} />
|
||||||
|
</WrapperButton>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ListedItem
|
||||||
123
src/components/GroceryList/groceries/styles/buttons.js
Normal file
123
src/components/GroceryList/groceries/styles/buttons.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
//dependencies
|
||||||
|
import { MdAddShoppingCart, MdDeleteForever} from 'react-icons/md'
|
||||||
|
import { FiMenu,FiCheckSquare } from 'react-icons/fi'
|
||||||
|
import { CgFileAdd } from 'react-icons/cg'
|
||||||
|
//blueprints
|
||||||
|
import { Button } from '../../../../styles/componentBlueprints'
|
||||||
|
|
||||||
|
|
||||||
|
export const WrapperButtons = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
bottom:50px;
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
bottom:70px;
|
||||||
|
left:10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const WrapperAddItem = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
bottom:50px;
|
||||||
|
right:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
bottom:70px;
|
||||||
|
right:10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperAddList = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.secondary};
|
||||||
|
bottom:50px;
|
||||||
|
right:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
bottom:70px;
|
||||||
|
right:10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperRemove = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.error};
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
left:10px;
|
||||||
|
${(props) =>
|
||||||
|
props.visible
|
||||||
|
? css`
|
||||||
|
bottom:140px;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
bottom:70px;
|
||||||
|
`}
|
||||||
|
}
|
||||||
|
${(props) =>
|
||||||
|
props.visible
|
||||||
|
? css`
|
||||||
|
bottom:140px;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
bottom:50px;
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
export const WrapperSelect = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
left:10px;
|
||||||
|
${(props) =>
|
||||||
|
props.visible
|
||||||
|
? css`
|
||||||
|
bottom:210px;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
bottom:70px;
|
||||||
|
`};
|
||||||
|
}
|
||||||
|
${(props) =>
|
||||||
|
props.visible
|
||||||
|
? css`
|
||||||
|
bottom:230px;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
bottom:50px;
|
||||||
|
`};
|
||||||
|
`
|
||||||
|
export const MenuIcon = styled(FiMenu)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 47px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const PlusIcon = styled(MdAddShoppingCart)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 45px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const RemoveIcon = styled(MdDeleteForever)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 45px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const CheckIcon = styled(FiCheckSquare)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 50px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const ListIcon = styled(CgFileAdd)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 50px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
`
|
||||||
100
src/components/GroceryList/groceries/styles/list.js
Normal file
100
src/components/GroceryList/groceries/styles/list.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { Text, View } from 'react-native'
|
||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
import { MdClose } from 'react-icons/md'
|
||||||
|
import { BiEditAlt } from 'react-icons/bi'
|
||||||
|
import { IoChevronDown } from 'react-icons/io5'
|
||||||
|
|
||||||
|
export const WrapperGroceryList = styled(View)`
|
||||||
|
width:100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
border-bottom-color: ${({ theme }) => theme.colors.dp01};
|
||||||
|
border-bottom-width: 3px;
|
||||||
|
/* border-radius: 15px; */
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 768px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const ListSizeWrapper = styled(View)`
|
||||||
|
height: ${(props) => props.visible ? props.height + 'px' : css`0px`};
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
export const WrapperList = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
/* height: ${props => props.height}; */
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
export const WrapperListTitle = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 60px;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px;
|
||||||
|
#left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
#right {
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
export const ListTitle = styled(Text)`
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
font-size: 20px;
|
||||||
|
`
|
||||||
|
export const ListSubtitle = styled(Text)`
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
color: ${({ theme }) => theme.colors.textB5};
|
||||||
|
font-size: 17px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ButtonRemoveList = styled(MdClose)`
|
||||||
|
color: ${({ theme }) => theme.colors.textW5};
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
margin: 4px 10px 4px 0px;
|
||||||
|
`
|
||||||
|
export const ButtonEditList = styled(BiEditAlt)`
|
||||||
|
color: ${({ theme }) => theme.colors.textW5};
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
margin: 4px 10px 4px 0px;
|
||||||
|
`
|
||||||
|
export const IconArrowDown = styled(IoChevronDown)`
|
||||||
|
transform: rotate(${props => props.visible === 'true' ? css`-180deg` : css`0deg`});
|
||||||
|
|
||||||
|
color: ${({ theme }) => theme.colors.textW5};
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
margin: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const TextTotalAmount = styled(Text)`
|
||||||
|
position: absolute;
|
||||||
|
align-self: flex-end;
|
||||||
|
z-index: 2;
|
||||||
|
top:60px;
|
||||||
|
right:0px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: ${({ theme }) => theme.colors.textW1};
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp00 + '88'};
|
||||||
|
padding : 2px 20px;
|
||||||
|
margin: 20px 0px 10px 0px;
|
||||||
|
border-bottom-right-radius: 20px;
|
||||||
|
`
|
||||||
107
src/components/GroceryList/groceries/styles/listedItem.js
Normal file
107
src/components/GroceryList/groceries/styles/listedItem.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { Text, View, Image} from 'react-native'
|
||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 768px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const DarkLayer = styled(View)`
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp00};
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom:7px;
|
||||||
|
margin-left: 7px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const WrapperItem = styled(View)`
|
||||||
|
box-shadow: ${({ theme }) => theme.colors.shadow};
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: ${props => props.person ? css`60px` : css`30px`};
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.itemSelected : props.color + '66'};
|
||||||
|
&:hover{
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.itemSelected : props.color + '77'};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperButton = styled(View)`
|
||||||
|
height: 30px;
|
||||||
|
width: 40px;
|
||||||
|
`
|
||||||
|
export const TextProductName = styled(Text)`
|
||||||
|
/* word-wrap: break-word; */
|
||||||
|
word-break: break-all;
|
||||||
|
margin: 2px 0px 0px 8px;
|
||||||
|
|
||||||
|
max-width: 70%;
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
color: ${props => props.theme.colors.textW1};
|
||||||
|
`
|
||||||
|
export const TextDetails = styled(Text)`
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 2px 0px 3px 8px;
|
||||||
|
|
||||||
|
max-width: 65%;
|
||||||
|
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${({ theme }) => theme.colors.primary};
|
||||||
|
`
|
||||||
|
export const TextAmount = styled(Text)`
|
||||||
|
position: absolute;
|
||||||
|
max-width: 100px;
|
||||||
|
max-height: 30px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow:hidden;
|
||||||
|
white-space:nowrap;
|
||||||
|
right: 0px;
|
||||||
|
margin: 3px 3px 3px 3px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
border-top-right-radius: 15px;
|
||||||
|
padding:2px 8px 2px 6px;
|
||||||
|
background-color:${({ theme }) => theme.colors.dp00 + '88'};
|
||||||
|
color: ${({ theme }) => theme.colors.textW1};
|
||||||
|
`
|
||||||
|
export const TextPerson = styled(Text)`
|
||||||
|
position: absolute;
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-width: 100px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow:hidden;
|
||||||
|
white-space:nowrap;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-right-radius: 15px;
|
||||||
|
padding:2px 8px 0px 6px;
|
||||||
|
background-color:${({ theme }) => theme.colors.dp00 + '88'};
|
||||||
|
color: ${({ theme }) => theme.colors.textW1};
|
||||||
|
`
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
position: absolute;
|
||||||
|
object-fit: cover;
|
||||||
|
opacity: 0.05;
|
||||||
|
height: 100%;
|
||||||
|
width: 200px;
|
||||||
|
border-top-left-radius: 30px;
|
||||||
|
border-top-right-radius: 15px;
|
||||||
|
border-bottom-left-radius: 15px;
|
||||||
|
border-bottom-right-radius: 30px;
|
||||||
|
right:0px;
|
||||||
|
`
|
||||||
33
src/components/GroceryList/products/ListedProduct.js
Normal file
33
src/components/GroceryList/products/ListedProduct.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
//components
|
||||||
|
//styling
|
||||||
|
import { Wrapper, WrapperProduct, WrapperButton, CheckButton, WrapperText, TextProductName, TextTag, TextPrice, StyledImage, IconCheck} from './styles/listedProduct'
|
||||||
|
//redux
|
||||||
|
import { useSelector, useDispatch, shallowEqual} from 'react-redux';
|
||||||
|
import { selectProductById, checkToggle, modalToggle} from '../../../redux/slices/groceryList/productsSlice'
|
||||||
|
import { findTag } from '../../../redux/slices/groceryList/tagsSlice'
|
||||||
|
|
||||||
|
const ListedProduct = React.memo((props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const product = useSelector(state => selectProductById(state, props._id), shallowEqual)
|
||||||
|
const tag = useSelector(state => findTag(state, product.tag))
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<WrapperProduct color={tag.color} checked={product.checked} onClick={()=>{props.setVisible(true); return dispatch(modalToggle(product._id))}}>
|
||||||
|
<WrapperText>
|
||||||
|
<TextProductName >{product.productName}</TextProductName>
|
||||||
|
<TextTag color={tag.color}>{tag.tagName}</TextTag>
|
||||||
|
{product.price !== 0&& <TextPrice>€ {product.price}</TextPrice>}
|
||||||
|
</WrapperText>
|
||||||
|
{product.image && <StyledImage src={product.image}/>}
|
||||||
|
</WrapperProduct>
|
||||||
|
<WrapperButton onClick={()=>dispatch(checkToggle(product._id))} >
|
||||||
|
<CheckButton checked={product.checked}>
|
||||||
|
<IconCheck checked={product.checked} />
|
||||||
|
</CheckButton>
|
||||||
|
</WrapperButton>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ListedProduct
|
||||||
88
src/components/GroceryList/products/ProductButtons.js
Normal file
88
src/components/GroceryList/products/ProductButtons.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import { ModalAddProduct } from '../../modals/groceries/ModalAddProduct'
|
||||||
|
//styles
|
||||||
|
import { WrapperButtons, WrapperAddProduct, WrapperUpload, UploadInput, WrapperDownload, WrapperRemove, MenuIcon, PlusIcon, UploadIcon, DownloadIcon, RemoveIcon } from './styles/buttons'
|
||||||
|
//redux
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { productsRemoved, productsUploaded } from '../../../redux/slices/groceryList/productsSlice'
|
||||||
|
// var FileSaver = require('file-saver');
|
||||||
|
|
||||||
|
const UploadProductsButton = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const [toggleAnim, setToggleAnim] = useState(false)
|
||||||
|
const openFile = (evt) => {
|
||||||
|
setToggleAnim(!toggleAnim)
|
||||||
|
const fileObj = evt.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = () => { dispatch(productsUploaded(JSON.parse(reader.result)['products'])) }
|
||||||
|
reader.readAsText(fileObj);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperUpload toggle={toggleAnim} visible={props.visible}>
|
||||||
|
<UploadInput type="file"
|
||||||
|
onChange={evt => openFile(evt)}
|
||||||
|
multiple={false} />
|
||||||
|
<UploadIcon />
|
||||||
|
</WrapperUpload>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const DownloadProductsButton = (props) => {
|
||||||
|
const products = useSelector(state => state.products.entities)
|
||||||
|
const [toggleAnim, setToggleAnim] = useState(false)
|
||||||
|
const handlePress = () => {
|
||||||
|
if (window.confirm("Do you want to download all of your products?")) {
|
||||||
|
setToggleAnim(!toggleAnim)
|
||||||
|
let productsJSON = JSON.stringify({ products: products }, null, 4)
|
||||||
|
let blob = new Blob([productsJSON], { type: "application/json" });
|
||||||
|
// FileSaver.saveAs(blob, "productList" + Date.now() + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperDownload toggle={toggleAnim} visible={props.visible} onClick={handlePress}>
|
||||||
|
<DownloadIcon />
|
||||||
|
</WrapperDownload>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const RemoveProductsButton = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const [toggleAnim, setToggleAnim] = useState(false)
|
||||||
|
const handlePress = () => {
|
||||||
|
if (window.confirm("Do you really want to remove the selected products?")) {
|
||||||
|
dispatch(productsRemoved());
|
||||||
|
setToggleAnim(!toggleAnim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperRemove toggle={toggleAnim} visible={props.visible} onClick={handlePress}>
|
||||||
|
<RemoveIcon />
|
||||||
|
</WrapperRemove>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export const ContainerButtons = (props) => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UploadProductsButton visible={visible} />
|
||||||
|
<DownloadProductsButton visible={visible} />
|
||||||
|
<RemoveProductsButton visible={visible} />
|
||||||
|
<WrapperButtons toggle={visible} onClick={() => setVisible(!visible)}>
|
||||||
|
<MenuIcon />
|
||||||
|
</WrapperButtons >
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddProductButton = (props) => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const closeModal = () => setVisible(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WrapperAddProduct toggle={visible} onClick={() => setVisible(true)}>
|
||||||
|
<PlusIcon />
|
||||||
|
</WrapperAddProduct>
|
||||||
|
<ModalAddProduct visible={visible} closeModal={closeModal} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
43
src/components/GroceryList/products/ProductsList.js
Normal file
43
src/components/GroceryList/products/ProductsList.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import ListedProduct from './ListedProduct'
|
||||||
|
//redux
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { selectAllSortedProducts } from '../../../redux/slices/groceryList/productsSlice'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import { WrapperList } from './styles/list'
|
||||||
|
import ModalEditProduct from '../../modals/groceries/ModalEditProduct';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
const ProductsList = (props) => {
|
||||||
|
const products = useSelector(selectAllSortedProducts)
|
||||||
|
|
||||||
|
const [count, setCount] = useState(11)
|
||||||
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const productList = products.slice(0, count).map((product, index) => {
|
||||||
|
return (
|
||||||
|
<ListedProduct key={index} _id={product._id} setVisible={setVisible} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// const [current, setCurrent] = useState())
|
||||||
|
const getMoreData = () => {
|
||||||
|
if (productList.length === products.length) {
|
||||||
|
setHasMore(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCount((prevState) => prevState + 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WrapperList>
|
||||||
|
{productList}
|
||||||
|
<View style={{ height: 150 }} />
|
||||||
|
{products.find(product => product.modalVisible) && <ModalEditProduct visible={visible} setVisible={setVisible} />}
|
||||||
|
</WrapperList>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ProductsList
|
||||||
106
src/components/GroceryList/products/styles/buttons.js
Normal file
106
src/components/GroceryList/products/styles/buttons.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
//dependencies
|
||||||
|
import { FiPlus, FiMenu , FiUploadCloud, FiDownloadCloud } from 'react-icons/fi'
|
||||||
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
|
//blueprints
|
||||||
|
import { Button } from '../../../../styles/componentBlueprints'
|
||||||
|
import { TextInput } from 'react-native'
|
||||||
|
|
||||||
|
export const WrapperButtons = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
bottom:50px;
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
bottom:70px;
|
||||||
|
left:10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperAddProduct = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
bottom:50px;
|
||||||
|
right:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
bottom:70px;
|
||||||
|
right:10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperRemove = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.error};
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
left:10px;
|
||||||
|
bottom: ${(props) => props.visible ? '140px' : '70px'};
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom: ${(props) => props.visible ? '140px' : '50px'};
|
||||||
|
|
||||||
|
`
|
||||||
|
export const WrapperUpload = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: #ddb5e4;
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
left:10px;
|
||||||
|
bottom: ${(props) => props.visible ? '280px' : '70px'};
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom: ${(props) => props.visible ? '320px' : '50px'};
|
||||||
|
|
||||||
|
`
|
||||||
|
export const UploadInput = styled(TextInput)`
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
border-radius: 50px;
|
||||||
|
width: 80px;
|
||||||
|
height:80px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const WrapperDownload = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: #fff7ad;
|
||||||
|
left:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
left:10px;
|
||||||
|
bottom: ${(props) => props.visible ? '210px' : '70px'};
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom: ${(props) => props.visible ? '230px' : '50px'};
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
export const MenuIcon = styled(FiMenu)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const PlusIcon = styled(FiPlus)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const UploadIcon = styled(FiUploadCloud)`
|
||||||
|
font-size: 50px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const DownloadIcon = styled(FiDownloadCloud)`
|
||||||
|
font-size: 50px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const RemoveIcon = styled(MdDeleteForever)`
|
||||||
|
color: ${({theme})=> theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 45px;
|
||||||
|
}
|
||||||
|
`
|
||||||
9
src/components/GroceryList/products/styles/list.js
Normal file
9
src/components/GroceryList/products/styles/list.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { View } from 'react-native'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const WrapperList = styled(View)`
|
||||||
|
width:100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
101
src/components/GroceryList/products/styles/listedProduct.js
Normal file
101
src/components/GroceryList/products/styles/listedProduct.js
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { View, Text, Image } from 'react-native'
|
||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
//dependencies
|
||||||
|
import { HiCheck } from 'react-icons/hi'
|
||||||
|
//functions
|
||||||
|
import LightenDarken from '../../../../functions'
|
||||||
|
|
||||||
|
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 768px;
|
||||||
|
align-items: flex-start;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperProduct = styled(View)`
|
||||||
|
box-shadow: ${({theme})=> theme.colors.shadow};
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom:7px;
|
||||||
|
margin-left: 7px;
|
||||||
|
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.itemSelected : props.color + '66'};
|
||||||
|
&:hover{
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.itemSelected : props.color + '77'};
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
export const WrapperButton = styled(View)`
|
||||||
|
height: 60px;
|
||||||
|
width: 40px;
|
||||||
|
`
|
||||||
|
export const CheckButton = styled(View)`
|
||||||
|
display:flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
border: 1px solid ${props => props.checked ? props.theme.colors.selected : props.theme.colors.dp04};
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 0px 5px ;
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.selected : props.theme.colors.buttonGrey};
|
||||||
|
&:hover{
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.selected : LightenDarken(props.theme.colors.buttonGrey, 5)}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const WrapperText = styled(View)`
|
||||||
|
flex:1;
|
||||||
|
max-width:600px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
max-width: 60vw;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const TextProductName = styled(Text)`
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: 0px 0px 0px 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${props => props.theme.colors.textW1};
|
||||||
|
`
|
||||||
|
export const TextTag = styled(Text)`
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 2px 0px 0px 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${props => LightenDarken(props.color,-30)};
|
||||||
|
`
|
||||||
|
export const TextPrice = styled(Text)`
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0px 6px 3px 3px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
border-bottom-left-radius: 15px;
|
||||||
|
padding:2px;
|
||||||
|
background-color:${({ theme }) => theme.colors.dp00 + '88'};
|
||||||
|
color: ${({ theme }) => theme.colors.textW1};
|
||||||
|
`
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
position: absolute;
|
||||||
|
top:0;
|
||||||
|
right:0;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100px;
|
||||||
|
height: 100%;
|
||||||
|
max-height:120px;
|
||||||
|
border-radius: 15px;
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
export const IconCheck = styled(HiCheck)`
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${({ theme }) => theme.colors.dp00};
|
||||||
|
`
|
||||||
88
src/components/GroceryList/styles/tabMenu.js
Normal file
88
src/components/GroceryList/styles/tabMenu.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { View } from 'react-native'
|
||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
import { RiShoppingBasketLine } from 'react-icons/ri'
|
||||||
|
import { FaAppleAlt } from 'react-icons/fa'
|
||||||
|
|
||||||
|
|
||||||
|
const iconSize = '2.0px'
|
||||||
|
const iconSizeSelected = '2.5px'
|
||||||
|
|
||||||
|
const WrapperIcon = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: ${props => props.theme.colors.textW5};;
|
||||||
|
`
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
/* background-color: #1A2C31cc; */
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp00 +'88'};
|
||||||
|
width: 250px;
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
bottom: 0px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
left: 50%;
|
||||||
|
border-top-left-radius: 25px;
|
||||||
|
border-top-right-radius: 25px;
|
||||||
|
margin-left: -125px;
|
||||||
|
`
|
||||||
|
export const WrapperSelected = styled(View)`
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
background-color: black;
|
||||||
|
opacity: 0.7;
|
||||||
|
width: 125px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius:25px;
|
||||||
|
//anims
|
||||||
|
${(props) =>
|
||||||
|
props.currenttab === 'Groceries'
|
||||||
|
? css`
|
||||||
|
right: 125px;
|
||||||
|
background-color: #59D8E6;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
right: 0px;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
//groceries
|
||||||
|
export const WrapperGroceries = styled(WrapperIcon)`
|
||||||
|
flex:1;
|
||||||
|
height: 65px;
|
||||||
|
/* &:hover{
|
||||||
|
background-color: #fffa;
|
||||||
|
} */
|
||||||
|
`
|
||||||
|
export const IconGroceries = styled(RiShoppingBasketLine)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
${props => props.currenttab === 'Groceries' &&
|
||||||
|
css`
|
||||||
|
color: ${props => props.theme.colors.textW0};
|
||||||
|
font-size: ${iconSizeSelected};`
|
||||||
|
};
|
||||||
|
`
|
||||||
|
//products
|
||||||
|
export const WrapperProducts = styled(WrapperIcon)`
|
||||||
|
flex:1;
|
||||||
|
height: 65px;
|
||||||
|
/* &:hover{
|
||||||
|
background-color: #fff4;
|
||||||
|
} */
|
||||||
|
|
||||||
|
`
|
||||||
|
export const IconProducts = styled(FaAppleAlt)`
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top:-5px;
|
||||||
|
${props => props.currenttab === 'Products' &&
|
||||||
|
css`
|
||||||
|
color: ${props => props.theme.colors.textW0};
|
||||||
|
font-size: 16px;`
|
||||||
|
};
|
||||||
|
|
||||||
|
`
|
||||||
11
src/components/Header.js
Normal file
11
src/components/Header.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { useHeaderHeight } from '@react-navigation/elements';
|
||||||
|
|
||||||
|
const HeaderPadding = (props) => {
|
||||||
|
const headerHeight = useHeaderHeight();
|
||||||
|
return (
|
||||||
|
<View style={{ height: headerHeight + 10 }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default HeaderPadding;
|
||||||
19
src/components/Home/GroceryCard.js
Normal file
19
src/components/Home/GroceryCard.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TouchableOpacity, View } from 'react-native';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
//dependencies
|
||||||
|
//styles
|
||||||
|
import { WrapperCard, TextCard, IconCoffee } from './styles/groceryCard'
|
||||||
|
|
||||||
|
const GroceryCard = (props) => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WrapperCard onPress={() => navigation.navigate('Groceries')}>
|
||||||
|
<TextCard>Groceries</TextCard>
|
||||||
|
<View style={{ height: 100, width: '100%' }} />
|
||||||
|
</WrapperCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroceryCard
|
||||||
19
src/components/Home/RecipeCard.js
Normal file
19
src/components/Home/RecipeCard.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
//dependencies
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {WrapperCard, TextCard, IconRecipe} from './styles/groceryCard'
|
||||||
|
|
||||||
|
const RecipeCard = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
// <Link to="/recipes">
|
||||||
|
<WrapperCard>
|
||||||
|
<TextCard>Recipes</TextCard>
|
||||||
|
{/* <IconRecipe/> */}
|
||||||
|
</WrapperCard>
|
||||||
|
// </Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecipeCard
|
||||||
42
src/components/Home/styles/groceryCard.js
Normal file
42
src/components/Home/styles/groceryCard.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Text, TouchableOpacity, View } from 'react-native'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { FiCoffee } from 'react-icons/fi'
|
||||||
|
import { GiHotMeal } from 'react-icons/gi'
|
||||||
|
|
||||||
|
export const WrapperLink = styled(View)`
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
`
|
||||||
|
export const WrapperCard = styled(TouchableOpacity)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 20px;
|
||||||
|
align-items: center;
|
||||||
|
background-color: ${props => props.theme.colors.dp02};
|
||||||
|
`
|
||||||
|
export const TextCard = styled(Text)`
|
||||||
|
text-decoration:none;
|
||||||
|
font-size: 32px;
|
||||||
|
align-self: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 50px;
|
||||||
|
border-top-right-radius: 20px;
|
||||||
|
border-top-left-radius: 20px;
|
||||||
|
width:100%;
|
||||||
|
color: ${props => props.theme.colors.textW2};
|
||||||
|
background-color: #0003;
|
||||||
|
`
|
||||||
|
export const IconCoffee = styled(FiCoffee)`
|
||||||
|
margin-top: 15px;
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
font-size: 50px;
|
||||||
|
`
|
||||||
|
export const IconRecipe = styled(GiHotMeal)`
|
||||||
|
margin-top: 15px;
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
font-size: 50px;
|
||||||
|
`
|
||||||
141
src/components/modals/groceries/ModalAddItem.js
Normal file
141
src/components/modals/groceries/ModalAddItem.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import Dropdown, { QtDropdown } from '../../Dropdown'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, ModalHeader, WrapperInput, WrapperDropdown, WrapperButtons, VerticalSeperator, Input, Button, IconWrong, IconCheck, InputSmall
|
||||||
|
} from '../styles/modal'
|
||||||
|
import { IconProduct, IconAmount, IconPerson, IconDetails } from './styles/addItem'
|
||||||
|
import { IconTag, IconDollar } from './styles/addProduct'
|
||||||
|
//redux
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { itemAdded } from '../../../redux/slices/groceryList/itemsSlice';
|
||||||
|
|
||||||
|
const ModalAddItem = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const products = useSelector(state => state.products.entities)
|
||||||
|
const tags = useSelector(state => state.tags.entities)
|
||||||
|
const lists = useSelector(state => state.lists.entities)
|
||||||
|
|
||||||
|
const [productName, setProductName] = useState('');
|
||||||
|
const [amount, setAmount] = useState('');
|
||||||
|
const [qt, setQt] = useState('')
|
||||||
|
const [person, setPerson] = useState('');
|
||||||
|
const [details, setDetails] = useState('');
|
||||||
|
const [tag, setTag] = useState('');
|
||||||
|
const [price, setPrice] = useState('');
|
||||||
|
|
||||||
|
const [focusedProduct, setFocusedProduct] = React.useState(false)
|
||||||
|
const [focusedQt, setFocusedQt] = React.useState(false)
|
||||||
|
const [focusedTag, setFocusedTag] = React.useState(false)
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (props.visible && productName !== '') {
|
||||||
|
let amountObj = {
|
||||||
|
am: amount ? Number(amount) : 1,
|
||||||
|
qt: qt
|
||||||
|
}
|
||||||
|
console.log(amountObj)
|
||||||
|
let listId = lists.find((list) => list.open === true)._id
|
||||||
|
if (tags.find(t => t.tagName === tag)) {
|
||||||
|
dispatch(itemAdded({ productName, amount: amountObj, person, details, tag, price, listId }));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dispatch(itemAdded({ productName, amount: amountObj, person, details, tag: 'uncategorized', price, listId }));
|
||||||
|
}
|
||||||
|
setProductName('');
|
||||||
|
setAmount('');
|
||||||
|
setQt('');
|
||||||
|
setPerson('');
|
||||||
|
setDetails('');
|
||||||
|
setTag('');
|
||||||
|
setPrice('');
|
||||||
|
props.closeModal();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Fill in a valid product name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDropdownPress = (pName) => {
|
||||||
|
let product = products.find((product) => product.productName === pName);
|
||||||
|
setProductName(product.productName)
|
||||||
|
setTag(product.tag)
|
||||||
|
setPrice(product.price)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={props.closeModal} animation={false}>
|
||||||
|
<ModalHeader>Add product</ModalHeader>
|
||||||
|
{/* {product && product.image && <Image src={product.image} />} */}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconProduct />
|
||||||
|
<Input onFocus={() => setFocusedProduct(true)} onBlur={() => { setTimeout(() => { setFocusedProduct(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={productName}
|
||||||
|
onChange={(text) => setProductName(text.target.value)}
|
||||||
|
placeholder="Product name" />
|
||||||
|
{productName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
{focusedProduct && <WrapperDropdown>
|
||||||
|
<Dropdown array={products.map(product => product.productName)} text={productName} setElement={handleDropdownPress} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconAmount />
|
||||||
|
<Input style={{ width: '69%' }}
|
||||||
|
type="number"
|
||||||
|
value={amount}
|
||||||
|
onChange={(text) => setAmount(text.target.value)}
|
||||||
|
placeholder="Amount" />
|
||||||
|
{amount ? <IconCheck /> : <IconWrong />}
|
||||||
|
<InputSmall style={{ marginLeft: "1%", width: '10%' }}
|
||||||
|
onFocus={() => setFocusedQt(true)} onBlur={() => { setTimeout(() => { setFocusedQt(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={qt}
|
||||||
|
onChange={(text) => setQt(text.target.value)}
|
||||||
|
placeholder="Qt." />
|
||||||
|
</WrapperInput>
|
||||||
|
{focusedQt && <WrapperDropdown style={{ marginLeft: "60%"}}>
|
||||||
|
<QtDropdown text={qt} setElement={setQt} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconPerson />
|
||||||
|
<Input type="text"
|
||||||
|
value={person}
|
||||||
|
onChange={(text) => setPerson(text.target.value)}
|
||||||
|
placeholder="For" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconDetails />
|
||||||
|
<Input type="text"
|
||||||
|
value={details}
|
||||||
|
onChange={(text) => setDetails(text.target.value)}
|
||||||
|
placeholder="Additional details" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconTag />
|
||||||
|
<Input type="text"
|
||||||
|
onFocus={() => setFocusedTag(true)} onBlur={() => { setTimeout(() => { setFocusedTag(false) }, 100) }}
|
||||||
|
value={tag}
|
||||||
|
onChange={(text) => setTag(text.target.value)}
|
||||||
|
placeholder="Tag" />
|
||||||
|
</WrapperInput>
|
||||||
|
{focusedTag && <WrapperDropdown>
|
||||||
|
<Dropdown array={tags.map(tag => tag.tagName)} text={tag} setElement={setTag} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconDollar />
|
||||||
|
<Input type="text"
|
||||||
|
value={price}
|
||||||
|
onChange={(text) => setPrice(text.target.value)}
|
||||||
|
placeholder="Price" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={props.closeModal}>Close</Button>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<Button onClick={() => handleSubmit()}>Add Item</Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ModalAddItem
|
||||||
54
src/components/modals/groceries/ModalAddList.js
Normal file
54
src/components/modals/groceries/ModalAddList.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, ModalHeader, WrapperInput, WrapperButtons, VerticalSeperator, Input, Button, IconWrong, IconCheck
|
||||||
|
} from '../styles/modal'
|
||||||
|
//redux
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { listAdded } from '../../../redux/slices/groceryList/listsSlice';
|
||||||
|
|
||||||
|
const ModalAddList = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const [listName, setListName] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (props.visible && listName !== '') {
|
||||||
|
dispatch(listAdded({ listName, listDescription: description }));
|
||||||
|
setListName('');
|
||||||
|
setDescription('');
|
||||||
|
props.closeModal();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Give a valid name for your grocery list!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={props.closeModal} animation={false}>
|
||||||
|
<ModalHeader>Add a grocery list</ModalHeader>
|
||||||
|
<WrapperInput>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={listName}
|
||||||
|
onChange={(text) => setListName(text.target.value)}
|
||||||
|
// onKeyDown={(event) => handleKeyDown(event)}
|
||||||
|
placeholder="List name" />
|
||||||
|
{listName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<Input type="text"
|
||||||
|
value={description}
|
||||||
|
onChange={(text) => setDescription(text.target.value)}
|
||||||
|
placeholder="List description" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={props.closeModal}>Close</Button>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<Button onClick={() => handleSubmit()}>Add List</Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ModalAddList
|
||||||
90
src/components/modals/groceries/ModalAddProduct.js
Normal file
90
src/components/modals/groceries/ModalAddProduct.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import Dropdown from '../../Dropdown'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, ModalHeader, WrapperInput, WrapperDropdown, WrapperButtons, VerticalSeperator, InputSmall, Button, IconWrong, IconCheck
|
||||||
|
} from '../styles/modal'
|
||||||
|
import {
|
||||||
|
StyledImage, IconProduct, IconTag, IconDollar, IconLink
|
||||||
|
} from './styles/addProduct'
|
||||||
|
//redux
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { productAdded } from '../../../redux/slices/groceryList/productsSlice';
|
||||||
|
|
||||||
|
export const ModalAddProduct = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const tags = useSelector(state => state.tags.entities)
|
||||||
|
const [productName, setProductName] = useState('');
|
||||||
|
const [tag, setTag] = useState('');
|
||||||
|
const [price, setPrice] = useState('');
|
||||||
|
const [image, setImage] = useState('');
|
||||||
|
|
||||||
|
const [focused, setFocused] = React.useState(false)
|
||||||
|
// const handleKeyDown = (event) => {
|
||||||
|
// if (event.key === 'Enter' && productName !== '') {
|
||||||
|
// setProductName('')
|
||||||
|
// dispatch(addNewProduct({ productName, tag: 'Vegetables & fruit', price: 5, image: 'https://media-cdn.tripadvisor.com/media/photo-s/15/2d/23/07/domino-s-pizza.jpg' }))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (props.visible && productName) {
|
||||||
|
dispatch(productAdded({ productName, tag: tags.find(t => t.tagName === tag) ? tag : "uncategorized", price, image }));
|
||||||
|
setProductName('');
|
||||||
|
setTag('');
|
||||||
|
setPrice('');
|
||||||
|
setImage('');
|
||||||
|
props.closeModal();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('You should give a valid product name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={props.closeModal} animation={false}>
|
||||||
|
<ModalHeader>Add product</ModalHeader>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconProduct />
|
||||||
|
<InputSmall type="text"
|
||||||
|
value={productName}
|
||||||
|
onChange={(text) => setProductName(text.target.value)}
|
||||||
|
// onKeyDown={(event) => handleKeyDown(event)}
|
||||||
|
placeholder="Product name" />
|
||||||
|
{productName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconTag />
|
||||||
|
<InputSmall onFocus={() => setFocused(true)} onBlur={() => { setTimeout(() => { setFocused(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(text) => setTag(text.target.value)}
|
||||||
|
placeholder="Tag" />
|
||||||
|
{tag ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
{focused && <WrapperDropdown>
|
||||||
|
<Dropdown array={tags.map(tag => tag.tagName)} text={tag} setElement={setTag} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconDollar />
|
||||||
|
<InputSmall type="number"
|
||||||
|
value={price}
|
||||||
|
onChange={(text) => setPrice(text.target.value)}
|
||||||
|
placeholder="Price" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconLink />
|
||||||
|
<InputSmall type="text"
|
||||||
|
value={image}
|
||||||
|
onChange={(text) => setImage(text.target.value)}
|
||||||
|
placeholder="Image link" />
|
||||||
|
</WrapperInput>
|
||||||
|
{image && <StyledImage src={image} alt={'No link...'} />}
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={props.closeModal}>Close</Button>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<Button onClick={() => handleSubmit()}>Add </Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
141
src/components/modals/groceries/ModalEditItem.js
Normal file
141
src/components/modals/groceries/ModalEditItem.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import Dropdown, { QtDropdown } from '../../Dropdown'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, WrapperInput, WrapperDropdown, WrapperButtons, Input, Button, IconWrong, IconCheck, InputSmall
|
||||||
|
} from '../styles/modal'
|
||||||
|
import { IconProduct, IconAmount, IconPerson, IconDetails } from './styles/addItem'
|
||||||
|
import { WrapperProduct, WrapperProductInfo, TextPrice, TextTag, StyledImage } from './styles/edit'
|
||||||
|
import { IconTag, IconDollar } from './styles/addProduct'
|
||||||
|
//redux
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { itemAdded, itemRemoved, modalToggle, selectItemModalVisible } from '../../../redux/slices/groceryList/itemsSlice';
|
||||||
|
|
||||||
|
const ModalEditItem = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const item = useSelector(selectItemModalVisible)
|
||||||
|
const products = useSelector(state => state.products.entities)
|
||||||
|
const tags = useSelector(state => state.tags.entities)
|
||||||
|
|
||||||
|
const [productName, setProductName] = useState(item.productName);
|
||||||
|
const [amount, setAmount] = useState(item.amount.am);
|
||||||
|
const [qt, setQt] = useState(item.amount.qt)
|
||||||
|
const [person, setPerson] = useState(item.person);
|
||||||
|
const [details, setDetails] = useState(item.details);
|
||||||
|
const [tag, setTag] = useState(item.tag);
|
||||||
|
const [price, setPrice] = useState(item.price);
|
||||||
|
|
||||||
|
const [focusedProduct, setFocusedProduct] = React.useState(false)
|
||||||
|
const [focusedQt, setFocusedQt] = React.useState(false)
|
||||||
|
const [focusedTag, setFocusedTag] = React.useState(false)
|
||||||
|
|
||||||
|
let product = products.find((product) => product.productName === productName);
|
||||||
|
let tagState = tags.find(t=>t.tagName===tag)
|
||||||
|
tagState = tagState ? tagState : tags[0]
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
let amountObj = {
|
||||||
|
am: amount ? Number(amount) : 1,
|
||||||
|
qt: qt
|
||||||
|
}
|
||||||
|
if (props.visible && productName !== '') {
|
||||||
|
await dispatch(itemRemoved(item._id))
|
||||||
|
await dispatch(itemAdded({ productName, amount: amountObj, person, details, tag: tagState.tagName, price, listId: item.listId }));
|
||||||
|
props.setVisible(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('Fill in a valid product name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDropdownPress = (pName) => {
|
||||||
|
let product = products.find((product) => product.productName === pName);
|
||||||
|
setProductName(product.productName)
|
||||||
|
setTag(product.tag)
|
||||||
|
setPrice(product.price)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
dispatch(modalToggle(item._id))
|
||||||
|
props.setVisible(false)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={closeModal} animation={false}>
|
||||||
|
<WrapperProduct color={tagState.color}>
|
||||||
|
{product && product.image && <StyledImage src={product.image} />}
|
||||||
|
<WrapperProductInfo>
|
||||||
|
<TextPrice>€ {price}</TextPrice>
|
||||||
|
<TextTag color={tagState.color}>{tagState.tagName}</TextTag>
|
||||||
|
</WrapperProductInfo>
|
||||||
|
</WrapperProduct>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconProduct />
|
||||||
|
<Input onFocus={() => setFocusedProduct(true)} onBlur={() => { setTimeout(() => { setFocusedProduct(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={productName}
|
||||||
|
onChange={(text) => setProductName(text.target.value)}
|
||||||
|
// onKeyDown={(event) => handleKeyDown(event)}
|
||||||
|
placeholder="Product name" />
|
||||||
|
{productName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
{focusedProduct && <WrapperDropdown>
|
||||||
|
<Dropdown array={products.map(product => product.productName)} text={productName} setElement={handleDropdownPress} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconAmount />
|
||||||
|
<Input style={{ width: '67%' }}
|
||||||
|
type="number"
|
||||||
|
value={amount}
|
||||||
|
onChange={(text) => setAmount(text.target.value)}
|
||||||
|
placeholder="Amount" />
|
||||||
|
{amount ? <IconCheck /> : <IconWrong />}
|
||||||
|
<InputSmall style={{ marginLeft: "1%", width: '10%' }}
|
||||||
|
onFocus={() => setFocusedQt(true)} onBlur={() => { setTimeout(() => { setFocusedQt(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={qt}
|
||||||
|
onChange={(text) => setQt(text.target.value)}
|
||||||
|
placeholder="Qt." />
|
||||||
|
</WrapperInput>
|
||||||
|
{focusedQt && <WrapperDropdown style={{ marginLeft: "60%"}}>
|
||||||
|
<QtDropdown text={qt} setElement={setQt} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconPerson />
|
||||||
|
<Input type="text"
|
||||||
|
value={person}
|
||||||
|
onChange={(text) => setPerson(text.target.value)}
|
||||||
|
placeholder="For" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconDetails />
|
||||||
|
<Input type="text"
|
||||||
|
value={details}
|
||||||
|
onChange={(text) => setDetails(text.target.value)}
|
||||||
|
placeholder="Additional details" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconTag />
|
||||||
|
<Input type="text"
|
||||||
|
onFocus={() => setFocusedTag(true)} onBlur={() => { setTimeout(() => { setFocusedTag(false) }, 100) }}
|
||||||
|
value={tag}
|
||||||
|
onChange={(text) => setTag(text.target.value)}
|
||||||
|
placeholder="Tag" />
|
||||||
|
</WrapperInput>
|
||||||
|
{focusedTag && <WrapperDropdown>
|
||||||
|
<Dropdown array={tags.map(t => t.tagName)} text={tag} setElement={setTag} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconDollar />
|
||||||
|
<Input type="text"
|
||||||
|
value={price}
|
||||||
|
onChange={(text) => setPrice(text.target.value)}
|
||||||
|
placeholder="Price" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={() => handleSubmit()}>Save</Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ModalEditItem
|
||||||
50
src/components/modals/groceries/ModalEditList.js
Normal file
50
src/components/modals/groceries/ModalEditList.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, ModalHeader, WrapperInput, WrapperButtons, VerticalSeperator, Input, Button, IconWrong, IconCheck
|
||||||
|
} from '../styles/modal'
|
||||||
|
//redux
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { listEdited } from '../../../redux/slices/groceryList/listsSlice';
|
||||||
|
|
||||||
|
const ModalEditList = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const list = useSelector(state => state.lists.entities.find(list=>list._id === props.id))
|
||||||
|
const [listName, setListName] = useState(list.listName);
|
||||||
|
const [description, setDescription] = useState(list.listDescription);
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (props.visible && listName !== '') {
|
||||||
|
dispatch(listEdited({listName, _id: props.id, listDescription: description }));
|
||||||
|
props.setVisible(false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('You need to give a list name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={() => props.setVisible(false)} animation={false}>
|
||||||
|
<ModalHeader>Edit the list</ModalHeader>
|
||||||
|
<WrapperInput>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={listName}
|
||||||
|
onChange={(text) => setListName(text.target.value)}
|
||||||
|
placeholder="List name" />
|
||||||
|
{listName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<Input type="text"
|
||||||
|
value={description}
|
||||||
|
onChange={(text) => setDescription(text.target.value)}
|
||||||
|
placeholder="List description" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={() => props.setVisible(false)}>Close</Button>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<Button onClick={() => handleSubmit()}>Edit List</Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ModalEditList
|
||||||
98
src/components/modals/groceries/ModalEditProduct.js
Normal file
98
src/components/modals/groceries/ModalEditProduct.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import Dropdown from '../../Dropdown'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, WrapperInput, WrapperDropdown, WrapperButtons, Input, Button, IconWrong, IconCheck
|
||||||
|
} from '../styles/modal'
|
||||||
|
import {
|
||||||
|
IconProduct, IconTag, IconDollar, IconLink
|
||||||
|
} from './styles/addProduct'
|
||||||
|
import { WrapperProduct, WrapperProductInfo, TextPrice, TextTag, StyledImage } from './styles/edit'
|
||||||
|
//redux
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { modalToggle, productAdded, productRemoved } from '../../../redux/slices/groceryList/productsSlice';
|
||||||
|
import { selectProductModalVisible } from '../../../redux/slices/groceryList/productsSlice'
|
||||||
|
|
||||||
|
const ModalEditProduct = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const product = useSelector(selectProductModalVisible)
|
||||||
|
const tags = useSelector(state => state.tags.entities)
|
||||||
|
|
||||||
|
const [productName, setProductName] = useState(product.productName);
|
||||||
|
const [tag, setTag] = useState(product.tag);
|
||||||
|
const [price, setPrice] = useState(product.price);
|
||||||
|
const [image, setImage] = useState(product.image);
|
||||||
|
|
||||||
|
const [focused, setFocused] = React.useState(false)
|
||||||
|
let usedTag = tags.find(t => t.tagName === tag)
|
||||||
|
if (!usedTag) {
|
||||||
|
usedTag = tags[0]
|
||||||
|
}
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (props.visible && productName) {
|
||||||
|
dispatch(productRemoved(product._id))
|
||||||
|
dispatch(productAdded({ productName, tag: tags.find(t=>t.tagName===tag) ? tag : "uncategorized", price, image}))
|
||||||
|
|
||||||
|
props.setVisible(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('You should give product name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const closeModal = () => {
|
||||||
|
dispatch(modalToggle(product._id))
|
||||||
|
props.setVisible(false)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={closeModal} animation={false}>
|
||||||
|
<WrapperProduct color={usedTag.color}>
|
||||||
|
{image && <StyledImage src={image} alt="No image found" />}
|
||||||
|
<WrapperProductInfo>
|
||||||
|
<TextPrice>€ {price}</TextPrice>
|
||||||
|
<TextTag color={usedTag.color}>{usedTag.tagName}</TextTag>
|
||||||
|
</WrapperProductInfo>
|
||||||
|
</WrapperProduct>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconProduct />
|
||||||
|
<Input type="text"
|
||||||
|
value={productName}
|
||||||
|
onChange={(text) => setProductName(text.target.value)}
|
||||||
|
// onKeyDown={(event) => handleKeyDown(event)}
|
||||||
|
placeholder="Product name" />
|
||||||
|
{productName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconTag />
|
||||||
|
<Input onFocus={() => setFocused(true)} onBlur={() => { setTimeout(() => { setFocused(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(text) => setTag(text.target.value)}
|
||||||
|
placeholder="Tag" />
|
||||||
|
{tag ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
{focused && <WrapperDropdown>
|
||||||
|
<Dropdown array={tags.map(tag => tag.tagName)} text={tag} setElement={setTag} />
|
||||||
|
</WrapperDropdown>}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconDollar />
|
||||||
|
<Input type="number"
|
||||||
|
value={price}
|
||||||
|
onChange={(text) => setPrice(text.target.value)}
|
||||||
|
placeholder="Price" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconLink />
|
||||||
|
<Input type="text"
|
||||||
|
value={image}
|
||||||
|
onChange={(text) => setImage(text.target.value)}
|
||||||
|
placeholder="Image link" />
|
||||||
|
</WrapperInput>
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={handleSubmit}>Save</Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ModalEditProduct
|
||||||
37
src/components/modals/groceries/styles/addItem.js
Normal file
37
src/components/modals/groceries/styles/addItem.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { IoFastFood } from 'react-icons/io5'
|
||||||
|
import {AiOutlineFieldNumber} from 'react-icons/ai'
|
||||||
|
import {IoPersonCircleSharp} from 'react-icons/io5'
|
||||||
|
import {BiCommentAdd} from 'react-icons/bi'
|
||||||
|
import { Image } from 'react-native'
|
||||||
|
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
position: absolute;
|
||||||
|
object-fit: cover;
|
||||||
|
z-index:0;
|
||||||
|
opacity: 0.1;
|
||||||
|
width: 70%;
|
||||||
|
height: 100%;
|
||||||
|
right:0;
|
||||||
|
border-top-left-radius: 100px;
|
||||||
|
border-top-right-radius: 15px;
|
||||||
|
border-bottom-left-radius: 15px;
|
||||||
|
border-bottom-right-radius: 100px;
|
||||||
|
`
|
||||||
|
const iconSize = '32px'
|
||||||
|
export const IconProduct = styled(IoFastFood)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
|
export const IconAmount = styled(AiOutlineFieldNumber)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
|
export const IconPerson = styled(IoPersonCircleSharp)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
|
export const IconDetails = styled(BiCommentAdd)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
32
src/components/modals/groceries/styles/addProduct.js
Normal file
32
src/components/modals/groceries/styles/addProduct.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { IoFastFood } from 'react-icons/io5'
|
||||||
|
import { BsTag } from 'react-icons/bs'
|
||||||
|
import { BiDollar ,BiLinkAlt} from 'react-icons/bi'
|
||||||
|
import { Image } from 'react-native'
|
||||||
|
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
position: absolute;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 35%;
|
||||||
|
height: 50%;
|
||||||
|
right:0;
|
||||||
|
border-radius: 15px;
|
||||||
|
`
|
||||||
|
const iconSize = '32px'
|
||||||
|
export const IconProduct = styled(IoFastFood)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
|
export const IconTag = styled(BsTag)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
|
export const IconDollar = styled(BiDollar)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
|
export const IconLink = styled(BiLinkAlt)`
|
||||||
|
font-size: ${iconSize};
|
||||||
|
color: ${({theme})=>theme.colors.iconGrey};
|
||||||
|
`
|
||||||
42
src/components/modals/groceries/styles/edit.js
Normal file
42
src/components/modals/groceries/styles/edit.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Image, Text, View } from 'react-native'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import LightenDarkenColor from '../../../../functions'
|
||||||
|
|
||||||
|
|
||||||
|
export const WrapperProduct = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: ${(props) => props.color + '22'};
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height:100px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`
|
||||||
|
export const WrapperProductInfo = styled(View)`
|
||||||
|
flex:1;
|
||||||
|
`
|
||||||
|
export const TextPrice = styled(Text)`
|
||||||
|
background-color: #0004;
|
||||||
|
color: ${({ theme }) => theme.colors.textW2};
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
height:30px;
|
||||||
|
|
||||||
|
padding-left: 5px;
|
||||||
|
`
|
||||||
|
export const TextTag = styled(Text)`
|
||||||
|
color: ${(props) => LightenDarkenColor(props.color,-30)};
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
margin: 0px 5px;
|
||||||
|
`
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
object-fit: cover;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
margin-top:-50px;
|
||||||
|
border-top-left-radius: 15px;
|
||||||
|
border-top-right-radius: 15px;
|
||||||
|
`
|
||||||
104
src/components/modals/recipes/ModalAddIngredients.js
Normal file
104
src/components/modals/recipes/ModalAddIngredients.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import styled from "styled-components"
|
||||||
|
import { CgFile } from "react-icons/cg"
|
||||||
|
import { GiMeal } from "react-icons/gi"
|
||||||
|
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { findRecipeById } from '../../../redux/slices/recipesSlice'
|
||||||
|
|
||||||
|
//styles
|
||||||
|
import {
|
||||||
|
StyledModal, ModalHeader, WrapperButtons, VerticalSeperator, Button, Input, WrapperDropdown, ModalDescription, WrapperInput, IconCheck, IconWrong
|
||||||
|
} from '../styles/modal'
|
||||||
|
import Ingredient from '../../recipes/Ingredient&Button'
|
||||||
|
import { itemAdded } from '../../../redux/slices/groceryList/itemsSlice'
|
||||||
|
import Dropdown from '../../Dropdown'
|
||||||
|
|
||||||
|
const IconList = styled(CgFile)`
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
`
|
||||||
|
const IconMeal = styled(GiMeal)`
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
const ModalAddIngredients = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const recipe = useSelector(state => findRecipeById(state, props.id))
|
||||||
|
let amountOfServings = recipe.servings ? recipe.servings : 1
|
||||||
|
const lists = useSelector(state => state.lists.entities)
|
||||||
|
const [listName, setListName] = useState('')
|
||||||
|
const [focused, setFocused] = useState(false)
|
||||||
|
const [servings, setServings] = useState(amountOfServings)
|
||||||
|
let servingsMultiplier = Math.ceil(servings / amountOfServings)
|
||||||
|
const IngredientsList = recipe.ingredients.map((ingredient, index) => {
|
||||||
|
return (
|
||||||
|
<Ingredient key={index} recipeId={recipe._id} multiplier={servingsMultiplier} ingredient={ingredient} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (lists.find(list => list.listName === listName) && recipe.ingredients.find(ingredient => ingredient.checked)) {
|
||||||
|
console.log('were sending the ingredients baby')
|
||||||
|
recipe.ingredients.forEach(ingredient => ingredient.checked === true && dispatch(itemAdded({
|
||||||
|
productName: ingredient.productName,
|
||||||
|
amount: {
|
||||||
|
am: ingredient.amount * servingsMultiplier,
|
||||||
|
qt: ingredient.portion,
|
||||||
|
},
|
||||||
|
|
||||||
|
tag: ingredient.tag,
|
||||||
|
price: ingredient.price,
|
||||||
|
person: '',
|
||||||
|
details: `For ${recipe.name} (${ingredient.portion}) `,
|
||||||
|
listId: lists.find(list => list.listName === listName)._id
|
||||||
|
})))
|
||||||
|
props.closeModal()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert('You should fill in a valid grocery list and/or select at least 1 ingredient')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StyledModal show={props.visible} centered={true} onHide={props.closeModal} animation={false}>
|
||||||
|
<ModalHeader>Add to grocerylist</ModalHeader>
|
||||||
|
<ModalDescription>Choose a grocery list and select the ingredients you would like to add to it</ModalDescription>
|
||||||
|
<WrapperInput>
|
||||||
|
<IconList />
|
||||||
|
<Input
|
||||||
|
style={{ fontSize: 20 }}
|
||||||
|
type="text"
|
||||||
|
value={listName}
|
||||||
|
onChange={(text) => setListName(text.target.value)}
|
||||||
|
onFocus={() => setFocused(true)} onBlur={() => { setTimeout(() => { setFocused(false) }, 100) }}
|
||||||
|
placeholder="Grocery list" />
|
||||||
|
{listName ? <IconCheck /> : <IconWrong />}
|
||||||
|
</WrapperInput>
|
||||||
|
{focused && <WrapperDropdown>
|
||||||
|
<Dropdown array={lists.map(list => list.listName)} text={listName} setElement={setListName} />
|
||||||
|
</WrapperDropdown>
|
||||||
|
}
|
||||||
|
<WrapperInput>
|
||||||
|
<IconMeal />
|
||||||
|
<Input
|
||||||
|
style={{ fontSize: 20, width: 100 }}
|
||||||
|
type="number"
|
||||||
|
value={servings}
|
||||||
|
onChange={(text) => setServings(Number(text.target.value))}
|
||||||
|
min={amountOfServings}
|
||||||
|
step={amountOfServings}
|
||||||
|
placeholder="Multiplier" />
|
||||||
|
<p style={{ marginLeft: 5 }}>servings</p>
|
||||||
|
</WrapperInput>
|
||||||
|
{IngredientsList}
|
||||||
|
<WrapperButtons>
|
||||||
|
<Button onClick={props.closeModal}>Close</Button>
|
||||||
|
<VerticalSeperator />
|
||||||
|
<Button onClick={() => handleSubmit()}>Add</Button>
|
||||||
|
</WrapperButtons>
|
||||||
|
</StyledModal >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ModalAddIngredients
|
||||||
96
src/components/modals/styles/modal.js
Normal file
96
src/components/modals/styles/modal.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { Text, View, TextInput } from 'react-native'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import Modal from 'react-native-modal';
|
||||||
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
|
import { HiCheck } from 'react-icons/hi'
|
||||||
|
|
||||||
|
export const StyledModal = styled(Modal)`
|
||||||
|
.modal-content{
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp01};
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperInput = styled(View)`
|
||||||
|
padding-left: 5px;
|
||||||
|
z-index:1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom:5px;
|
||||||
|
`
|
||||||
|
export const WrapperDropdown = styled(View)`
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-top: -5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const WrapperButtons = styled(View)`
|
||||||
|
z-index:1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
margin-top: 5px;
|
||||||
|
`
|
||||||
|
export const VerticalSeperator = styled(View)`
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp12};
|
||||||
|
`
|
||||||
|
export const InputSmall = styled(TextInput)`
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontS};
|
||||||
|
|
||||||
|
|
||||||
|
width: 50%;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 5px;
|
||||||
|
`
|
||||||
|
export const Input = styled(TextInput)`
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontS};
|
||||||
|
|
||||||
|
|
||||||
|
width: 80%;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 5px;
|
||||||
|
padding-right: 20px;
|
||||||
|
`
|
||||||
|
export const Button = styled(Text)`
|
||||||
|
color: ${props => props.theme.colors.primary};
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontS};
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ModalHeader = styled(Text)`
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontM};
|
||||||
|
margin: 5px;
|
||||||
|
color: ${({ theme }) => theme.colors.textW1};
|
||||||
|
`
|
||||||
|
export const ModalDescription = styled(Text)`
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp24};
|
||||||
|
color: ${({ theme }) => theme.colors.textW2};
|
||||||
|
margin: 5px 0px;
|
||||||
|
padding: 2px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const IconWrong = styled(FaAsterisk)`
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontS};
|
||||||
|
color: ${({ theme }) => theme.colors.error + 'aa'};
|
||||||
|
`
|
||||||
|
export const IconCheck = styled(HiCheck)`
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-top: -3px;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontM};
|
||||||
|
color: ${({ theme }) => theme.colors.primary};
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
32
src/components/recipes/Ingredient&Button.js
Normal file
32
src/components/recipes/Ingredient&Button.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { View } from 'react-native'
|
||||||
|
import styled from "styled-components"
|
||||||
|
|
||||||
|
import { Ingredient } from "./addRecipe/Ingredient"
|
||||||
|
import { CheckButton } from "../../styles/componentBlueprints"
|
||||||
|
import { ingredientCheckToggle } from "../../redux/slices/recipesSlice"
|
||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom-color: ${props => props.theme.colors.dp1};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: ${props => props.checked === true && props.theme.colors.selected + '33'};
|
||||||
|
`
|
||||||
|
|
||||||
|
const IngredientButton = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
let ingredient = props.ingredient
|
||||||
|
return (
|
||||||
|
<Wrapper checked={ingredient.checked}>
|
||||||
|
<Ingredient ingredient={ingredient} multiplier={props.multiplier}/>
|
||||||
|
<View onClick={() => dispatch(ingredientCheckToggle({ id: props.recipeId, productName: ingredient.productName }))} >
|
||||||
|
<CheckButton checked={ingredient.checked} />
|
||||||
|
</View>
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IngredientButton
|
||||||
14
src/components/recipes/RecipeCard.js
Normal file
14
src/components/recipes/RecipeCard.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
import { WrapperRecipeCard, RecipeName, ImageRecipe } from "./styles/recipeCard"
|
||||||
|
|
||||||
|
const RecipeCard = (props) => {
|
||||||
|
let recipe = props.recipe
|
||||||
|
return (
|
||||||
|
<WrapperRecipeCard to={"/recipes/"+recipe._id}>
|
||||||
|
{recipe.image && <ImageRecipe src={recipe.image} />}
|
||||||
|
<RecipeName>{recipe.name}</RecipeName>
|
||||||
|
</WrapperRecipeCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecipeCard
|
||||||
17
src/components/recipes/RecipeList.js
Normal file
17
src/components/recipes/RecipeList.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { WrapperRecipeList } from "./styles/recipeList"
|
||||||
|
import RecipeCard from "./RecipeCard"
|
||||||
|
import { useSelector } from "react-redux"
|
||||||
|
|
||||||
|
const RecipeList = () => {
|
||||||
|
const recipes = useSelector(state=> state.recipes.entities)
|
||||||
|
let recipeList = recipes.map((recipe, index) => {
|
||||||
|
return (<RecipeCard key={index} recipe={recipe} />)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<WrapperRecipeList>
|
||||||
|
{recipeList}
|
||||||
|
</WrapperRecipeList>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecipeList
|
||||||
51
src/components/recipes/addRecipe/Buttons.js
Normal file
51
src/components/recipes/addRecipe/Buttons.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
import {
|
||||||
|
WrapperAddRecipe, IconPlus,
|
||||||
|
WrapperOptions, IconOptions, WrapperOptionButtons, IconRemove, IconEdit
|
||||||
|
} from "./styles/buttons"
|
||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { removeRecipe } from "../../../redux/slices/recipesSlice"
|
||||||
|
|
||||||
|
import { PlusIcon, WrapperAddItem } from "../../GroceryList/groceries/styles/buttons"
|
||||||
|
import ModalAddIngredients from "../../modals/recipes/ModalAddIngredients"
|
||||||
|
|
||||||
|
export const AddRecipeButton = () => {
|
||||||
|
return (
|
||||||
|
// < Link to="/recipes/addRecipe">
|
||||||
|
<WrapperAddRecipe>
|
||||||
|
<IconPlus />
|
||||||
|
</WrapperAddRecipe>
|
||||||
|
// </Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddIngredientsButton = (props) => {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
return (<>
|
||||||
|
<WrapperAddItem style={{ bottom: 10 }} onClick={()=>setVisible(true)}>
|
||||||
|
<PlusIcon />
|
||||||
|
</WrapperAddItem>
|
||||||
|
<ModalAddIngredients id={props.id} visible={visible} closeModal={() => setVisible(false)} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptionsButtonRecipe = (props) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const [toggled, setToggled] = useState(false)
|
||||||
|
const handleRemove = () => {
|
||||||
|
if (window.confirm("Do you really want to remove this recipe?")) {
|
||||||
|
dispatch(removeRecipe(props.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperOptions toggled={toggled} >
|
||||||
|
<IconOptions toggled={toggled} onClick={() => setToggled(!toggled)} />
|
||||||
|
<WrapperOptionButtons toggled={toggled}>
|
||||||
|
<IconRemove onClick={() => handleRemove()} />
|
||||||
|
{/* <IconEdit onClick={() => history.push("/recipes/addRecipe/" + props.id)} /> */}
|
||||||
|
</WrapperOptionButtons>
|
||||||
|
</WrapperOptions>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
src/components/recipes/addRecipe/Ingredient.js
Normal file
22
src/components/recipes/addRecipe/Ingredient.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { View } from "react-native"
|
||||||
|
import { useSelector } from "react-redux"
|
||||||
|
|
||||||
|
import { WrapperIngredient, TagCircle, IngredientName, IngredientPortion, IngredientAmount, IconEdit, IconRemove } from './styles/ingredient'
|
||||||
|
|
||||||
|
export const Ingredient = (props) => {
|
||||||
|
let ingredient = props.ingredient
|
||||||
|
let tag = useSelector(state => state.tags.entities.find(tag => tag.tagName === ingredient.tag))
|
||||||
|
let multiplier = props.multiplier ? props.multiplier : 1
|
||||||
|
return (
|
||||||
|
<WrapperIngredient>
|
||||||
|
<View id="left">
|
||||||
|
<TagCircle tagColor={tag.color} />
|
||||||
|
<IngredientPortion>{ingredient.portion}</IngredientPortion>
|
||||||
|
<IngredientName>{ingredient.productName}</IngredientName>
|
||||||
|
</View>
|
||||||
|
<IngredientAmount>{ingredient.amount*multiplier}</IngredientAmount>
|
||||||
|
{props.EditIngredient && <IconEdit onClick={() => props.EditIngredient(props.index)} />}
|
||||||
|
{props.RemoveIngredient && <IconRemove onClick={() => props.RemoveIngredient(props.index)} />}
|
||||||
|
</WrapperIngredient>
|
||||||
|
)
|
||||||
|
}
|
||||||
119
src/components/recipes/addRecipe/Inputs.js
Normal file
119
src/components/recipes/addRecipe/Inputs.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
import { View } from "react-native"
|
||||||
|
import { useSelector } from "react-redux"
|
||||||
|
import { selectAllProducts } from "../../../redux/slices/groceryList/productsSlice"
|
||||||
|
import Dropdown from "../../Dropdown"
|
||||||
|
import { Input } from "../../modals/styles/modal"
|
||||||
|
import { Ingredient } from "./Ingredient"
|
||||||
|
import {
|
||||||
|
WrapperIngredients, InputIngredientName, InputAmount, InputPortion, Button
|
||||||
|
} from "./styles/inputs"
|
||||||
|
export const Ingredients = (props) => {
|
||||||
|
let ingredients = props.ingredients
|
||||||
|
|
||||||
|
const products = useSelector(selectAllProducts)
|
||||||
|
const tags = useSelector(state => state.tags.entities)
|
||||||
|
const [ingredientName, setIngredientName] = useState('')
|
||||||
|
const [amount, setAmount] = useState('')
|
||||||
|
const [portion, setPortion] = useState('')
|
||||||
|
const [tag, setTag] = useState('')
|
||||||
|
const [price, setPrice] = useState('')
|
||||||
|
|
||||||
|
const [focused, setFocused] = useState(false)
|
||||||
|
const [focusedTag, setFocusedTag] = useState(false)
|
||||||
|
const [index, setIndex] = useState(-1)
|
||||||
|
|
||||||
|
const EditIngredient = (i) => {
|
||||||
|
setIndex(i)
|
||||||
|
setIngredientName(ingredients[i].productName)
|
||||||
|
setAmount(ingredients[i].amount)
|
||||||
|
setPortion(ingredients[i].portion)
|
||||||
|
}
|
||||||
|
const RemoveIngredient = (i) => {
|
||||||
|
let ingredientsEdited = ingredients.slice()
|
||||||
|
ingredientsEdited.splice(i, 1)
|
||||||
|
props.setIngredients(ingredientsEdited)
|
||||||
|
}
|
||||||
|
const IngredientList = ingredients.map((ingredient, key) => {
|
||||||
|
return (<Ingredient ingredient={ingredient} EditIngredient={EditIngredient} RemoveIngredient={RemoveIngredient} index={key} key={key} />)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const submitIngredient = () => {
|
||||||
|
let ingredientTag = tags.find(t => t.tagName === tag)
|
||||||
|
ingredientTag = ingredientTag ? ingredientTag.tagName : 'uncategorized'
|
||||||
|
if (ingredientName !== '' && index === -1) {
|
||||||
|
props.setIngredients([...ingredients, { productName: ingredientName, amount: Number(amount), portion, tag: ingredientTag, price }])
|
||||||
|
}
|
||||||
|
else if (index !== -1) {
|
||||||
|
let ingredientsEdited = ingredients.slice()
|
||||||
|
ingredientsEdited.splice(index, 1, { productName: ingredientName, amount: Number(amount), portion, tag: ingredientTag, price })
|
||||||
|
props.setIngredients(ingredientsEdited)
|
||||||
|
setIndex(-1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("Fill in the name of the ingredient")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIngredientName('')
|
||||||
|
setAmount('')
|
||||||
|
setPortion('')
|
||||||
|
setTag('')
|
||||||
|
setPrice('')
|
||||||
|
|
||||||
|
}
|
||||||
|
const handleDropdownPress = (pName) => {
|
||||||
|
let product = products.find((product) => product.productName === pName);
|
||||||
|
setIngredientName(product.productName)
|
||||||
|
setTag(product.tag)
|
||||||
|
setPrice(product.price)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<WrapperIngredients>
|
||||||
|
<h2>Ingredients</h2>
|
||||||
|
<View id="row">
|
||||||
|
<View style={{ width: '100%' }}>
|
||||||
|
{IngredientList}
|
||||||
|
<InputIngredientName onFocus={() => setFocused(true)} onBlur={() => { setTimeout(() => { setFocused(false) }, 100) }}
|
||||||
|
type="text"
|
||||||
|
value={ingredientName}
|
||||||
|
onChange={(text) => setIngredientName(text.target.value)}
|
||||||
|
placeholder="Name ingredient" />
|
||||||
|
{focused &&
|
||||||
|
<Dropdown array={products.map(product => product.productName)} text={ingredientName} setElement={handleDropdownPress} />}
|
||||||
|
<View id="row">
|
||||||
|
<View>
|
||||||
|
<Input
|
||||||
|
onFocus={() => setFocusedTag(true)} onBlur={() => { setTimeout(() => { setFocusedTag(false) }, 100) }}
|
||||||
|
style={{ marginLeft: 0 }}
|
||||||
|
type="text"
|
||||||
|
value={tag}
|
||||||
|
onChange={(text) => setTag(text.target.value)}
|
||||||
|
placeholder="tag" />
|
||||||
|
{focusedTag &&
|
||||||
|
<Dropdown array={tags.map(t => t.tagName)} text={tag} setElement={setTag} />}
|
||||||
|
</View>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={price}
|
||||||
|
onChange={(text) => setPrice(text.target.value)}
|
||||||
|
placeholder="Price" />
|
||||||
|
</View>
|
||||||
|
<View id="row">
|
||||||
|
<InputAmount
|
||||||
|
type="number"
|
||||||
|
value={amount}
|
||||||
|
onChange={(text) => setAmount(text.target.value)}
|
||||||
|
placeholder="amt." />
|
||||||
|
<InputPortion
|
||||||
|
type="text"
|
||||||
|
value={portion}
|
||||||
|
onChange={(text) => setPortion(text.target.value)}
|
||||||
|
placeholder="Portion (1/2 teaspoon, ...)" />
|
||||||
|
<Button onClick={submitIngredient} >{index === -1 ? 'Add' : 'Edit'}</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</WrapperIngredients>
|
||||||
|
)
|
||||||
|
}
|
||||||
62
src/components/recipes/addRecipe/styles/buttons.js
Normal file
62
src/components/recipes/addRecipe/styles/buttons.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { View } from "react-native"
|
||||||
|
import styled, {css} from 'styled-components'
|
||||||
|
import { Button } from '../../../../styles/componentBlueprints'
|
||||||
|
|
||||||
|
import { FiPlus, FiSettings } from 'react-icons/fi'
|
||||||
|
import { GoTrashcan } from 'react-icons/go'
|
||||||
|
import { BiEditAlt } from 'react-icons/bi'
|
||||||
|
|
||||||
|
export const WrapperAddRecipe = styled(Button)`
|
||||||
|
position:absolute;
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary};
|
||||||
|
bottom:20px;
|
||||||
|
right:20px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
bottom:30px;
|
||||||
|
right:10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const IconPlus = styled(FiPlus)`
|
||||||
|
color: ${({ theme }) => theme.colors.textB2};
|
||||||
|
font-size: 60px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperOptions = styled(View)`
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const WrapperOptionButtons = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
top: 60px;
|
||||||
|
width: 55px;
|
||||||
|
right: 0px;
|
||||||
|
height: ${props => props.toggled ? css`100px` : css`0px` };
|
||||||
|
background-color: ${({theme}) => theme.colors.dp00 +'bb'};
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
export const IconOptions = styled(FiSettings)`
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: ${({ theme }) => theme.colors.textW2};
|
||||||
|
transform: rotate(${props => props.toggled ? css`135deg` : css`0deg` });
|
||||||
|
font-size: 40px;
|
||||||
|
`
|
||||||
|
export const IconRemove = styled(GoTrashcan)`
|
||||||
|
position: absolute;
|
||||||
|
font-size: 40px;
|
||||||
|
color: ${({ theme }) => theme.colors.error};
|
||||||
|
`
|
||||||
|
export const IconEdit = styled(BiEditAlt)`
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
color: ${({ theme }) => theme.colors.textW2};
|
||||||
|
font-size: 40px;
|
||||||
|
`
|
||||||
61
src/components/recipes/addRecipe/styles/ingredient.js
Normal file
61
src/components/recipes/addRecipe/styles/ingredient.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Text, View } from "react-native"
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { GoTrashcan } from 'react-icons/go'
|
||||||
|
import { BiEditAlt } from 'react-icons/bi'
|
||||||
|
|
||||||
|
export const WrapperIngredient = styled(View)`
|
||||||
|
margin: 5px 0px;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
#left{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const TagCircle = styled(View)`
|
||||||
|
background-color: ${(props) => props.tagColor};
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
align-self: center;
|
||||||
|
margin: 0px 5px;
|
||||||
|
`
|
||||||
|
export const IngredientName = styled(Text)`
|
||||||
|
margin-left: 10px;
|
||||||
|
width: 60%;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontS};
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
color: ${props => props.theme.colors.textW4};
|
||||||
|
`
|
||||||
|
export const IngredientPortion = styled(Text)`
|
||||||
|
width: 35%;
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontS};
|
||||||
|
font-weight: 900;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
color: ${props => props.theme.colors.textW5};
|
||||||
|
`
|
||||||
|
export const IngredientAmount = styled(Text)`
|
||||||
|
max-width: 12%;
|
||||||
|
margin-right: 48px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
color: ${props => props.theme.colors.textW5};
|
||||||
|
`
|
||||||
|
export const IconRemove = styled(GoTrashcan)`
|
||||||
|
position:absolute;
|
||||||
|
right: 0px;
|
||||||
|
color: ${({ theme }) => theme.colors.error + 'aa'};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
`
|
||||||
|
export const IconEdit = styled(BiEditAlt)`
|
||||||
|
position:absolute;
|
||||||
|
right: 20px;
|
||||||
|
color: ${({ theme }) => theme.colors.textW5};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
`
|
||||||
43
src/components/recipes/addRecipe/styles/inputs.js
Normal file
43
src/components/recipes/addRecipe/styles/inputs.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Text, TextInput, View } from "react-native"
|
||||||
|
import styled from "styled-components"
|
||||||
|
export const WrapperIngredients = styled(View)`
|
||||||
|
margin-top: 10px;
|
||||||
|
width:100%;
|
||||||
|
border-left: solid ${props => props.theme.colors.primary + '55'} 1px;
|
||||||
|
padding-left: 5px;
|
||||||
|
/* #row{
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex:1;
|
||||||
|
width:100%;
|
||||||
|
} */
|
||||||
|
`
|
||||||
|
export const InputIngredientName = styled(TextInput)`
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
width:100%;
|
||||||
|
`
|
||||||
|
export const InputAmount = styled(TextInput)`
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
width: 50px;
|
||||||
|
`
|
||||||
|
export const InputPortion = styled(TextInput)`
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
width: 60%;
|
||||||
|
margin-left: 10px;
|
||||||
|
`
|
||||||
|
export const Button = styled(Text)`
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
bottom: -8px;
|
||||||
|
color: ${props => props.theme.colors.primary + 'bb'};
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
`
|
||||||
39
src/components/recipes/styles/recipeCard.js
Normal file
39
src/components/recipes/styles/recipeCard.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Image, Text, View } from 'react-native'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const WrapperRecipeCard = styled(View)`
|
||||||
|
box-shadow: ${({theme})=> theme.colors.shadow};
|
||||||
|
display: flex;
|
||||||
|
flex:1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin: 5px;
|
||||||
|
height: 130px;
|
||||||
|
min-width: 130px;
|
||||||
|
max-width: 200px;
|
||||||
|
background-color: ${props => props.theme.colors.dp01 + 'dd'};
|
||||||
|
border-radius: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const RecipeName = styled(Text)`
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100%;
|
||||||
|
flex:1;
|
||||||
|
max-height: 70px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${props => props.theme.colors.textW4};
|
||||||
|
background-color: ${props => props.theme.colors.dp00 + 'dd'};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ImageRecipe = styled(Image)`
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 20px;
|
||||||
|
`
|
||||||
8
src/components/recipes/styles/recipeList.js
Normal file
8
src/components/recipes/styles/recipeList.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { View } from 'react-native'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const WrapperRecipeList = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`
|
||||||
31
src/components/styles/header.js
Normal file
31
src/components/styles/header.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { View } from "react-native"
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const WrapperHeader = styled(View)`
|
||||||
|
z-index: 2;
|
||||||
|
position:absolute;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 7px 0px 7px 5px;
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp00 + 'bb'};
|
||||||
|
/* h1{
|
||||||
|
width: 80%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0px;
|
||||||
|
margin-left: 20px;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
color: ${props => props.theme.colors.textW1};
|
||||||
|
} */
|
||||||
|
`
|
||||||
|
// export const TextPeers = styled(Text)`
|
||||||
|
// position: absolute;
|
||||||
|
// right: 10px;
|
||||||
|
// top: 5px;
|
||||||
|
// background-color: #0002;
|
||||||
|
// color: ${props => props.theme.colors.textW5};
|
||||||
|
// padding: 0px 7px;
|
||||||
|
// border-radius: 8px;
|
||||||
|
// font-size: 1rem;
|
||||||
|
// `
|
||||||
BIN
src/fonts/Comfortaa-Regular.ttf
Normal file
BIN
src/fonts/Comfortaa-Regular.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Sansation_Light.ttf
Normal file
BIN
src/fonts/Sansation_Light.ttf
Normal file
Binary file not shown.
BIN
src/fonts/Sansation_Regular.ttf
Normal file
BIN
src/fonts/Sansation_Regular.ttf
Normal file
Binary file not shown.
30
src/functions.js
Normal file
30
src/functions.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
function LightenDarkenColor(col, nAmt) {
|
||||||
|
let amt = nAmt *2.55;
|
||||||
|
var usePound = false;
|
||||||
|
|
||||||
|
if (col[0] === "#") {
|
||||||
|
col = col.slice(1);
|
||||||
|
usePound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var num = parseInt(col,16);
|
||||||
|
|
||||||
|
var r = (num >> 16) + amt;
|
||||||
|
|
||||||
|
if (r > 255) r = 255;
|
||||||
|
else if (r < 0) r = 0;
|
||||||
|
|
||||||
|
var b = ((num >> 8) & 0x00FF) + amt;
|
||||||
|
|
||||||
|
if (b > 255) b = 255;
|
||||||
|
else if (b < 0) b = 0;
|
||||||
|
|
||||||
|
var g = (num & 0x0000FF) + amt;
|
||||||
|
|
||||||
|
if (g > 255) g = 255;
|
||||||
|
else if (g < 0) g = 0;
|
||||||
|
|
||||||
|
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default LightenDarkenColor
|
||||||
32
src/pages/HomePage.js
Normal file
32
src/pages/HomePage.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { View } from 'react-native'
|
||||||
|
import HeaderPadding from '../components/Header'
|
||||||
|
|
||||||
|
//components
|
||||||
|
import Header from '../components/Header'
|
||||||
|
import GroceryCard from '../components/Home/GroceryCard'
|
||||||
|
import RecipeCard from '../components/Home/RecipeCard'
|
||||||
|
|
||||||
|
//styling
|
||||||
|
import { Wrapper, WrapperBoard, WrapperRight, WrapperLeft } from './styles/HomePage'
|
||||||
|
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
return (
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<HeaderPadding/>
|
||||||
|
<Wrapper>
|
||||||
|
<WrapperBoard>
|
||||||
|
<WrapperLeft>
|
||||||
|
<GroceryCard />
|
||||||
|
</WrapperLeft>
|
||||||
|
<WrapperRight>
|
||||||
|
<RecipeCard />
|
||||||
|
</WrapperRight>
|
||||||
|
</WrapperBoard>
|
||||||
|
</Wrapper>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage
|
||||||
20
src/pages/groceryList/GroceryListPage.js
Normal file
20
src/pages/groceryList/GroceryListPage.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
//components
|
||||||
|
import TabMenu from '../../components/GroceryList/TabMenu'
|
||||||
|
import Groceries from './storageManagement/Groceries'
|
||||||
|
import Products from './storageManagement/Products'
|
||||||
|
//styling
|
||||||
|
import { WrapperGroceryPage } from './styles/GroceryListPage'
|
||||||
|
|
||||||
|
const GroceryListPage = () => {
|
||||||
|
const [currentTab, setCurrentTab] = useState("Groceries")
|
||||||
|
return (
|
||||||
|
<WrapperGroceryPage>
|
||||||
|
{
|
||||||
|
currentTab === "Groceries" ? <Groceries /> : <Products />
|
||||||
|
}
|
||||||
|
<TabMenu currentTab={currentTab} setCurrentTab={setCurrentTab} />
|
||||||
|
</WrapperGroceryPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default GroceryListPage
|
||||||
26
src/pages/groceryList/storageManagement/Groceries.js
Normal file
26
src/pages/groceryList/storageManagement/Groceries.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Text, View } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
//components
|
||||||
|
import Header from '../../../components/Header'
|
||||||
|
import GroceryList from '../../../components/GroceryList/groceries/GroceryList'
|
||||||
|
import { LoadAnimation } from '../../../styles/componentBlueprints'
|
||||||
|
|
||||||
|
import { ContainerButtons, AddItemButton } from '../../../components/GroceryList/groceries/GroceryButtons'
|
||||||
|
//styling
|
||||||
|
import { ArrowBack } from '../../styles/page'
|
||||||
|
//redux
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
const Groceries = () => {
|
||||||
|
const statusItems = useSelector(state => state.items.status)
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{statusItems === 'loading' ? <LoadAnimation/>
|
||||||
|
: <GroceryList />}
|
||||||
|
<ContainerButtons />
|
||||||
|
<AddItemButton />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Groceries
|
||||||
28
src/pages/groceryList/storageManagement/Products.js
Normal file
28
src/pages/groceryList/storageManagement/Products.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Text, View } from 'react-native'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
//components
|
||||||
|
import Header from '../../../components/Header'
|
||||||
|
import ProductsList from '../../../components/GroceryList/products/ProductsList'
|
||||||
|
|
||||||
|
import { AddProductButton, ContainerButtons } from '../../../components/GroceryList/products/ProductButtons'
|
||||||
|
|
||||||
|
import { LoadAnimation } from '../../../styles/componentBlueprints'
|
||||||
|
//styling
|
||||||
|
import { ArrowBack } from '../../styles/page'
|
||||||
|
//redux
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
const Products = () => {
|
||||||
|
const statusProducts = useSelector(state => state.products.status)
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{statusProducts === 'loading' ? <LoadAnimation />
|
||||||
|
: <ProductsList />}
|
||||||
|
<AddProductButton />
|
||||||
|
<ContainerButtons />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Products
|
||||||
6
src/pages/groceryList/styles/GroceryListPage.js
Normal file
6
src/pages/groceryList/styles/GroceryListPage.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { View } from "react-native"
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const WrapperGroceryPage = styled(View)`
|
||||||
|
width:100%;
|
||||||
|
`
|
||||||
113
src/pages/recipes/AddRecipe.js
Normal file
113
src/pages/recipes/AddRecipe.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { Text, View } from 'react-native'
|
||||||
|
//components
|
||||||
|
import Header from "../../components/Header"
|
||||||
|
import { ArrowBack } from "../styles/page";
|
||||||
|
import { Ingredients } from "../../components/recipes/addRecipe/Inputs";
|
||||||
|
//styling
|
||||||
|
import { Wrapper, WrapperRecipe, Input, InputInstructions, IconRecipe, IconPot, IconMeal, IconImage, IconCheck } from "./styles/addRecipe"
|
||||||
|
import { useState } from "react";
|
||||||
|
import { addRecipe, findRecipeById, updateRecipe } from "../../redux/slices/recipesSlice";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { StyledImage } from "./styles/recipe";
|
||||||
|
import HeaderPadding from '../../components/Header';
|
||||||
|
|
||||||
|
const AddRecipe = () => {
|
||||||
|
let { id } = useParams()
|
||||||
|
const foundRecipe = useSelector(state => findRecipeById(state, id))
|
||||||
|
let recipe = foundRecipe ? foundRecipe : { name: '', prepTime: '', servings: '', image: '', ingredients: [], instructions: '' }
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const [name, setName] = useState(recipe.name)
|
||||||
|
const [prepTime, setPrepTime] = useState(recipe.prepTime)
|
||||||
|
const [servings, setServings] = useState(recipe.servings)
|
||||||
|
const [image, setImage] = useState(recipe.image)
|
||||||
|
const [ingredients, setIngredients] = useState(recipe.ingredients)
|
||||||
|
const [instructions, setInstructions] = useState(recipe.instructions)
|
||||||
|
|
||||||
|
const handleImageInput = (input) => {
|
||||||
|
if (input.target.files && input.target.files[0] && input.target.files[0].size < 1000000) {
|
||||||
|
let reader = new FileReader()
|
||||||
|
reader.onload = function (e) {
|
||||||
|
setImage(e.target.result)
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(input.target.files[0])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("That file is too big! Choose an image with a size lower than 1MB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const submitRecipe = () => {
|
||||||
|
if (name && ingredients && instructions) {
|
||||||
|
foundRecipe ? dispatch(updateRecipe({ _id: foundRecipe._id, name, prepTime: Number(prepTime), servings: Number(servings), image, ingredients, instructions }))
|
||||||
|
: dispatch(addRecipe({ name, prepTime: Number(prepTime), servings: Number(servings), image, ingredients, instructions }))
|
||||||
|
console.log(typeof (image))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("You should add a name, at least one ingredient and the instructions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleInput = (event) => {
|
||||||
|
let lastChar = event.target.value.substr(-1).charCodeAt(0);
|
||||||
|
let penultimateChar = event.target.value.substr(-2).charCodeAt(0);
|
||||||
|
|
||||||
|
if (instructions === '') {
|
||||||
|
setInstructions("- " + event.target.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (penultimateChar === 10 && lastChar === 45) {
|
||||||
|
setInstructions(instructions.slice(0, instructions.length - 3))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (lastChar === 10) {
|
||||||
|
setInstructions(event.target.value + "- ")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setInstructions(event.target.value)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderPadding/>
|
||||||
|
<Wrapper >
|
||||||
|
{image && <StyledImage src={image} alt="Recipe" />}
|
||||||
|
<WrapperRecipe>
|
||||||
|
<View id="row">
|
||||||
|
<IconRecipe />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(text) => setName(text.target.value)}
|
||||||
|
placeholder="Recipe name" />
|
||||||
|
</View>
|
||||||
|
<View id="row">
|
||||||
|
<IconPot />
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={prepTime}
|
||||||
|
onChange={(text) => setPrepTime(text.target.value)}
|
||||||
|
placeholder="Prep time (min)" />
|
||||||
|
<IconMeal />
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={servings}
|
||||||
|
onChange={(text) => setServings(text.target.value)}
|
||||||
|
placeholder="Servings" />
|
||||||
|
</View>
|
||||||
|
<View id="row">
|
||||||
|
<IconImage />
|
||||||
|
<Input
|
||||||
|
style={{ borderBottom: 'none' }}
|
||||||
|
type="file" accept="image/*"
|
||||||
|
onChange={input => handleImageInput(input)} />
|
||||||
|
</View>
|
||||||
|
<Ingredients ingredients={ingredients} setIngredients={setIngredients} />
|
||||||
|
<InputInstructions
|
||||||
|
type="text"
|
||||||
|
value={instructions}
|
||||||
|
onChange={(text) => handleInput(text)}
|
||||||
|
placeholder='Instructions' />
|
||||||
|
</WrapperRecipe>
|
||||||
|
</Wrapper>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddRecipe
|
||||||
72
src/pages/recipes/Recipe.js
Normal file
72
src/pages/recipes/Recipe.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { useSelector } from "react-redux"
|
||||||
|
import { useParams } from "react-router"
|
||||||
|
//redux
|
||||||
|
import { findRecipeById } from "../../redux/slices/recipesSlice"
|
||||||
|
|
||||||
|
import Header from "../../components/Header"
|
||||||
|
import { ArrowBack } from "../styles/page"
|
||||||
|
import { Wrapper, WrapperRecipe } from "./styles/addRecipe"
|
||||||
|
import { Ingredient } from "../../components/recipes/addRecipe/Ingredient"
|
||||||
|
import { Title, InstructionWrapper, InstructionNumber, StyledImage, TextPrep, TextTime, TextMinutes, WrapperServings, TextServings, IconPotLarge, IconMeal, Hr } from "./styles/recipe"
|
||||||
|
import { OptionsButtonRecipe, AddIngredientsButton } from "../../components/recipes/addRecipe/Buttons"
|
||||||
|
import { Text, View } from "react-native"
|
||||||
|
import HeaderPadding from "../../components/Header"
|
||||||
|
|
||||||
|
const Recipe = () => {
|
||||||
|
let { id } = useParams()
|
||||||
|
const recipe = useSelector(state => findRecipeById(state, id))
|
||||||
|
const IngredientList = recipe.ingredients.map((ingredient, index) => {
|
||||||
|
return (<Ingredient ingredient={ingredient} index={index} key={index} />)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let strArr = recipe.instructions.split("- ")
|
||||||
|
const InstructionList = strArr.map((instruction, index) => {
|
||||||
|
return index !== 0 && (<InstructionWrapper key={index} >
|
||||||
|
<View><InstructionNumber>{index}</InstructionNumber></View>
|
||||||
|
<Text id="instruction">{instruction}</Text>
|
||||||
|
</InstructionWrapper>)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderPadding/>
|
||||||
|
<Wrapper >
|
||||||
|
{recipe.image && <StyledImage src={recipe.image} />}
|
||||||
|
<WrapperRecipe>
|
||||||
|
<Text>{recipe.name}</Text>
|
||||||
|
<Hr />
|
||||||
|
{recipe.prepTime !== 0 && recipe.prepTime && <>
|
||||||
|
<View id="row" style={{ position: 'relative', marginTop: 0 }} >
|
||||||
|
<IconPotLarge />
|
||||||
|
<TextPrep>READY IN:</TextPrep>
|
||||||
|
<View id="column" >
|
||||||
|
<TextTime>{recipe.prepTime}</TextTime>
|
||||||
|
<TextMinutes>minutes</TextMinutes>
|
||||||
|
</View>
|
||||||
|
{recipe.servings !== 0 && recipe.servings && <>
|
||||||
|
<WrapperServings>
|
||||||
|
<IconMeal />
|
||||||
|
<View>
|
||||||
|
<TextTime>{recipe.servings}</TextTime>
|
||||||
|
<TextServings>servings</TextServings>
|
||||||
|
</View>
|
||||||
|
</WrapperServings>
|
||||||
|
</>}
|
||||||
|
</View>
|
||||||
|
<Hr />
|
||||||
|
</>}
|
||||||
|
|
||||||
|
<View style={{ width: '100%' }}>
|
||||||
|
<Title>Ingredients</Title>
|
||||||
|
{IngredientList}
|
||||||
|
<Hr />
|
||||||
|
<Title>Instructions</Title>
|
||||||
|
{InstructionList}
|
||||||
|
</View>
|
||||||
|
</WrapperRecipe>
|
||||||
|
</Wrapper>
|
||||||
|
<AddIngredientsButton id={id} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Recipe
|
||||||
22
src/pages/recipes/RecipesPage.js
Normal file
22
src/pages/recipes/RecipesPage.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//components
|
||||||
|
import RecipeList from "../../components/recipes/RecipeList"
|
||||||
|
import Header from '../../components/Header'
|
||||||
|
import { ArrowBack } from '../styles/page'
|
||||||
|
import { AddRecipeButton } from "../../components/recipes/addRecipe/Buttons";
|
||||||
|
import { LoadAnimation } from "../../styles/componentBlueprints";
|
||||||
|
//redux
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { Text } from "react-native";
|
||||||
|
import HeaderPadding from "../../components/Header";
|
||||||
|
const RecipesPage = () => {
|
||||||
|
const statusRecipes = useSelector(state => state.recipes.status)
|
||||||
|
return (<>
|
||||||
|
<HeaderPadding/>
|
||||||
|
{statusRecipes === 'loading' ? <LoadAnimation/>
|
||||||
|
: <RecipeList />}
|
||||||
|
<AddRecipeButton />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecipesPage
|
||||||
95
src/pages/recipes/styles/addRecipe.js
Normal file
95
src/pages/recipes/styles/addRecipe.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { Image, Text, TextInput, View } from "react-native"
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { GiHotMeal, GiMeal, GiCookingPot } from 'react-icons/gi'
|
||||||
|
import { HiCheck } from 'react-icons/hi'
|
||||||
|
import { BiImageAdd } from 'react-icons/bi'
|
||||||
|
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const WrapperRecipe = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 500px;
|
||||||
|
padding: 10px;
|
||||||
|
#row{
|
||||||
|
margin-top: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
#column{
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const Input = styled(TextInput)`
|
||||||
|
flex:1;
|
||||||
|
margin-left: 5px;
|
||||||
|
|
||||||
|
color: ${props => props.theme.colors.textW1};
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const InputInstructions = styled(Text)`
|
||||||
|
margin-top: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
color: ${props => props.theme.colors.textW1};
|
||||||
|
border-top: solid ${props => props.theme.colors.primary + 'aa'} 1px;
|
||||||
|
border-bottom-color: ${props => props.theme.colors.primary + 'aa'};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
min-height: 600px;
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
`
|
||||||
|
export const IconRecipe = styled(GiHotMeal)`
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontM};
|
||||||
|
`
|
||||||
|
export const IconPot = styled(GiCookingPot)`
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontM};
|
||||||
|
`
|
||||||
|
export const IconMeal = styled(GiMeal)`
|
||||||
|
margin-left: 5px;
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontM};
|
||||||
|
`
|
||||||
|
export const IconImage = styled(BiImageAdd)`
|
||||||
|
color: ${({ theme }) => theme.colors.primaryVar};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontM};
|
||||||
|
`
|
||||||
|
export const IconCheck = styled(HiCheck)`
|
||||||
|
margin-right: 10px;
|
||||||
|
color: ${({ theme }) => theme.colors.textB3};
|
||||||
|
background-color: ${({ theme }) => theme.colors.primary + 'cc'};
|
||||||
|
border-radius: 30px;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontL};
|
||||||
|
`
|
||||||
93
src/pages/recipes/styles/recipe.js
Normal file
93
src/pages/recipes/styles/recipe.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { Image, Text, View } from "react-native"
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { GiCookingPot, GiMeal } from 'react-icons/gi'
|
||||||
|
|
||||||
|
export const Title = styled(Text)`
|
||||||
|
margin-top: 10px;
|
||||||
|
`
|
||||||
|
export const InstructionWrapper = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 0px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom-color: ${({ theme }) => theme.colors.dp02};
|
||||||
|
border-bottom-width: 3px;
|
||||||
|
#instruction{
|
||||||
|
margin-left: 7px;
|
||||||
|
color: ${({theme}) => theme.colors.textW4};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const InstructionNumber = styled(Text)`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: ${({theme}) => theme.colors.primary + 'dd'};
|
||||||
|
color: ${({theme}) => theme.colors.textB1};
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 34px;
|
||||||
|
width: 34px;
|
||||||
|
border-radius: 17px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const StyledImage = styled(Image)`
|
||||||
|
margin-top: -70px;
|
||||||
|
object-fit: cover;
|
||||||
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
@media ${({theme}) => theme.mediaQueries.above768}{
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const TextPrep = styled(Text)`
|
||||||
|
text-align: right;
|
||||||
|
width: 70px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
line-height: 1.2;
|
||||||
|
`
|
||||||
|
export const TextTime = styled(Text)`
|
||||||
|
margin-bottom: -3px;
|
||||||
|
width: 70px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: ${({ theme }) => theme.colors.primary};
|
||||||
|
`
|
||||||
|
export const TextMinutes = styled(Text)`
|
||||||
|
width: 70px;
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontS};
|
||||||
|
font-weight: normal;
|
||||||
|
color: ${({ theme }) => theme.colors.primary};
|
||||||
|
`
|
||||||
|
export const WrapperServings = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 2%;
|
||||||
|
top: 2px;
|
||||||
|
`
|
||||||
|
export const TextServings = styled(Text)`
|
||||||
|
margin-top: -5px;
|
||||||
|
font-weight: bold;
|
||||||
|
`
|
||||||
|
export const IconPotLarge = styled(GiCookingPot)`
|
||||||
|
margin: -20px 0px -16px 0px;
|
||||||
|
color: ${({ theme }) => theme.colors.primary};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontL};
|
||||||
|
`
|
||||||
|
export const IconMeal = styled(GiMeal)`
|
||||||
|
margin: -20px 0px -16px 0px;
|
||||||
|
color: ${({ theme }) => theme.colors.primary};
|
||||||
|
font-size: ${({theme}) => theme.fontSize.fontL};
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
export const Hr = styled(View)`
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px 0px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: ${({ theme }) => theme.colors.dp08};
|
||||||
|
`
|
||||||
30
src/pages/styles/HomePage.js
Normal file
30
src/pages/styles/HomePage.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { View } from "react-native"
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { FiMenu } from 'react-icons/fi'
|
||||||
|
|
||||||
|
export const Wrapper = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
export const WrapperBoard = styled(View)`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
export const WrapperLeft = styled(View)`
|
||||||
|
width: 50%;
|
||||||
|
padding: 0px 4px 8px 8px;
|
||||||
|
`
|
||||||
|
export const WrapperRight = styled(View)`
|
||||||
|
width: 50%;
|
||||||
|
padding: 0px 8px 8px 4px;
|
||||||
|
`
|
||||||
|
// export const Menu = styled(FiMenu)`
|
||||||
|
// color: ${props=>props.theme.colors.textW2};
|
||||||
|
// font-size: 45px;
|
||||||
|
// `
|
||||||
9
src/pages/styles/page.js
Normal file
9
src/pages/styles/page.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import { BiArrowBack } from 'react-icons/bi'
|
||||||
|
|
||||||
|
export const ArrowBack = styled(BiArrowBack).attrs(props=>({
|
||||||
|
// onClick:() => history.goBack()
|
||||||
|
}))`
|
||||||
|
color: ${({theme}) => theme.colors.textW2};
|
||||||
|
font-size: 45px;
|
||||||
|
`
|
||||||
198
src/redux/slices/groceryList/itemsSlice.js
Normal file
198
src/redux/slices/groceryList/itemsSlice.js
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const fetchItems = createAsyncThunk('items/fetchItems', async () => {
|
||||||
|
let items = []
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceries', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { items = data })
|
||||||
|
items.forEach((item) => { item.checked = false; item.modalVisible = false; })
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
export const itemAdded = createAsyncThunk('items/itemAdded',
|
||||||
|
async (payload, thunkAPI) => {
|
||||||
|
let newItem = {
|
||||||
|
...payload,
|
||||||
|
amount: payload.amount,
|
||||||
|
price: payload.price && Number(payload.price),
|
||||||
|
modalVisible: false,
|
||||||
|
checked: false,
|
||||||
|
}
|
||||||
|
// let existingItem = thunkAPI.getState().items.entities.slice().find(item => item.productName === newItem.productName)
|
||||||
|
// if (existingItem) {
|
||||||
|
// alert("This item was already in " + thunkAPI.getState().lists.entities.find((list) => list._id === existingItem.listId).listName.toString())
|
||||||
|
// existingItem = {
|
||||||
|
// ...existingItem,
|
||||||
|
// person: existingItem.person + ', ' + newItem.person,
|
||||||
|
// details: existingItem.details + ', ' + newItem.details,
|
||||||
|
// amount: existingItem.amount + newItem.amount
|
||||||
|
// }
|
||||||
|
// const requestOptions = {
|
||||||
|
// method: 'PUT',
|
||||||
|
// headers: { 'Content-Type': 'application/json' },
|
||||||
|
// body: JSON.stringify(existingItem)
|
||||||
|
// };
|
||||||
|
// await fetch('https://ohomeapi.familymoyaers.com/grocery', requestOptions)
|
||||||
|
// return { existed: true, newItem: existingItem }
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// const requestOptions = {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: { 'Content-Type': 'application/json' },
|
||||||
|
// body: JSON.stringify(newItem)
|
||||||
|
// };
|
||||||
|
// await fetch('https://ohomeapi.familymoyaers.com/grocery', requestOptions)
|
||||||
|
// return { existed: false, newItem: newItem }
|
||||||
|
// }
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(newItem)
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/grocery', requestOptions)
|
||||||
|
return { existed: false, newItem: newItem }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const itemsRemoved = createAsyncThunk('items/itemsRemoved', async (payload, thunkAPI) => {
|
||||||
|
let productIds = []
|
||||||
|
await thunkAPI.getState().items.entities
|
||||||
|
.forEach(item =>
|
||||||
|
item.checked === true && productIds.push(item._id)
|
||||||
|
)
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ productIds: productIds })
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceries', requestOptions)
|
||||||
|
})
|
||||||
|
export const itemsRemovedByList = createAsyncThunk('items/itemsRemovedByList', async (payload) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ listId: payload })
|
||||||
|
};
|
||||||
|
console.log(payload)
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceriesByList', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
export const itemRemoved = createAsyncThunk('items/itemRemoved', async (payload, thunkAPI) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ productId: payload })
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/grocery', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
const itemsSlice = createSlice({
|
||||||
|
name: 'items',
|
||||||
|
initialState: {
|
||||||
|
status: 'loading',
|
||||||
|
entities: [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
checkToggle: {
|
||||||
|
reducer(state, action) {
|
||||||
|
let item = state.entities.find((item) => item._id === action.payload._id)
|
||||||
|
item.checked = !item.checked
|
||||||
|
},
|
||||||
|
prepare(_id) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
_id: _id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkAll(state, action) {
|
||||||
|
let listItems = state.entities.filter(item => item.listId === action.payload)
|
||||||
|
if (listItems.slice().filter((item) => item.checked === false).length === 0) {
|
||||||
|
listItems.forEach(item => item.checked = false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
listItems.forEach(item => item.checked = true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modalToggle: {
|
||||||
|
reducer(state, action) {
|
||||||
|
let item = state.entities.find((item) => item._id === action.payload._id)
|
||||||
|
if (!state.entities.find((i) => i.modalVisible === true) || item.modalVisible) {
|
||||||
|
item.modalVisible = !item.modalVisible;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prepare(_id) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
_id: _id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: builder => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchItems.fulfilled, (state, action) => {
|
||||||
|
state.entities = action.payload;
|
||||||
|
// state.entities.unshift(...stockProducts);
|
||||||
|
state.status = 'idle'
|
||||||
|
console.log('items fetched')
|
||||||
|
})
|
||||||
|
.addCase(itemAdded.fulfilled, (state, action) => {
|
||||||
|
let newItem = action.payload.newItem
|
||||||
|
state.entities.push(newItem)
|
||||||
|
console.log('sent the new item')
|
||||||
|
// if (action.payload.existed === false) {
|
||||||
|
// state.entities.push(newItem)
|
||||||
|
// console.log('sent the new item')
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// let prevItem = state.entities.find(item => item._id === newItem._id)
|
||||||
|
// Object.assign(prevItem, newItem)
|
||||||
|
// console.log('edited the item')
|
||||||
|
})
|
||||||
|
.addCase(itemsRemoved.fulfilled, (state, action) => {
|
||||||
|
state.entities = state.entities.filter(item => item.checked === false)
|
||||||
|
console.log('items removed')
|
||||||
|
})
|
||||||
|
.addCase(itemsRemovedByList.fulfilled, (state, action) => {
|
||||||
|
state.entities = state.entities.filter(item => item.listId === action.payload)
|
||||||
|
})
|
||||||
|
.addCase(itemRemoved.fulfilled, (state, action) => {
|
||||||
|
state.entities.splice(state.entities.findIndex((item) => item._id === action.payload), 1)
|
||||||
|
console.log('item removed')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//selectors
|
||||||
|
//all items
|
||||||
|
export const selectAllSortedItems = createSelector(
|
||||||
|
state => state.items.entities,
|
||||||
|
state => state.products.entities,
|
||||||
|
(items, products) => {
|
||||||
|
return items.slice().sort((a, b) => {
|
||||||
|
let productA = products.find(product => product._id === a._id)
|
||||||
|
let tagA = productA ? productA.tag : a.tag ? a.tag : 'uncategorized'
|
||||||
|
let productB = products.find(product => product._id === b._id)
|
||||||
|
let tagB = productB ? productB.tag : b.tag ? b.tag : 'uncategorized'
|
||||||
|
return tagA.localeCompare(tagB)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
//product by id
|
||||||
|
export const selectItemById = (state, _id) => state.items.entities.find(item => item._id === _id)
|
||||||
|
//item by modal visible
|
||||||
|
export const selectItemModalVisible = createSelector(
|
||||||
|
state => state.items.entities,
|
||||||
|
items => items.find(item => item.modalVisible === true)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const { checkToggle, checkAll, modalToggle } = itemsSlice.actions;
|
||||||
|
|
||||||
|
export default itemsSlice.reducer;
|
||||||
97
src/redux/slices/groceryList/listsSlice.js
Normal file
97
src/redux/slices/groceryList/listsSlice.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const fetchLists = createAsyncThunk('items/fetchLists', async () => {
|
||||||
|
let lists = []
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceryLists', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { lists = data })
|
||||||
|
console.log(lists)
|
||||||
|
lists.forEach((list) => { list.open = false })
|
||||||
|
return lists
|
||||||
|
})
|
||||||
|
|
||||||
|
export const listAdded = createAsyncThunk('items/listAdded',
|
||||||
|
async (payload) => {
|
||||||
|
let list = {
|
||||||
|
...payload,
|
||||||
|
open: false
|
||||||
|
}
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(list)
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceryList', requestOptions)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { list._id = data })
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
)
|
||||||
|
export const listRemoved = createAsyncThunk('items/listRemoved', async (payload, thunkAPI) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ listId: payload })
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceryList', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
export const listEdited = createAsyncThunk('items/listEdited', async (payload) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceryList', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
const listsSlice = createSlice({
|
||||||
|
name: 'lists',
|
||||||
|
initialState: {
|
||||||
|
status: 'loading',
|
||||||
|
entities: [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
toggleOpen(state, action) {
|
||||||
|
state.entities.forEach(list => list._id !== action.payload && (list.open = false))
|
||||||
|
let list = state.entities.find((list) => list._id === action.payload)
|
||||||
|
list && (list.open = !list.open)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: builder => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchLists.fulfilled, (state, action) => {
|
||||||
|
state.entities = action.payload;
|
||||||
|
// state.entities.unshift(...stockProducts);
|
||||||
|
state.status = 'idle'
|
||||||
|
// console.log('items fetched')
|
||||||
|
})
|
||||||
|
.addCase(listAdded.fulfilled, (state, action) => {
|
||||||
|
state.entities.push(action.payload)
|
||||||
|
// console.log('sent the new item')
|
||||||
|
})
|
||||||
|
.addCase(listRemoved.fulfilled, (state, action) => {
|
||||||
|
state.entities.splice(state.entities.findIndex((list) => list._id === action.payload), 1)
|
||||||
|
// console.log('item removed')
|
||||||
|
})
|
||||||
|
.addCase(listEdited.fulfilled, (state, action) => {
|
||||||
|
let list = state.entities.find(list=> list._id === action.payload._id)
|
||||||
|
list.listName = action.payload.listName
|
||||||
|
list.listDescription = action.payload.listDescription
|
||||||
|
// console.log('item removed')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const selectOpenListId = (state) => {
|
||||||
|
let list = state.lists.entities.find(list => list.open === true)
|
||||||
|
return list && list._id }
|
||||||
|
|
||||||
|
// export const { checkToggle, checkAll, modalToggle } = itemsSlice.actions;
|
||||||
|
export const { toggleOpen } = listsSlice.actions;
|
||||||
|
export default listsSlice.reducer;
|
||||||
171
src/redux/slices/groceryList/productsSlice.js
Normal file
171
src/redux/slices/groceryList/productsSlice.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const fetchProducts = createAsyncThunk('products/fetchProducts', async () => {
|
||||||
|
let products = []
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/products', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { products = data })
|
||||||
|
products.forEach((product) => { product.checked = false; product.modalVisible = false; })
|
||||||
|
return products
|
||||||
|
})
|
||||||
|
|
||||||
|
export const productAdded = createAsyncThunk('products/productAdded',
|
||||||
|
async (payload) => {
|
||||||
|
let newProduct = {
|
||||||
|
...payload,
|
||||||
|
type: 'product',
|
||||||
|
price: Number(payload.price),
|
||||||
|
|
||||||
|
modalVisible: false,
|
||||||
|
checked: false,
|
||||||
|
}
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(newProduct)
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/product', requestOptions)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { newProduct._id = data })
|
||||||
|
return newProduct
|
||||||
|
}
|
||||||
|
)
|
||||||
|
export const productsRemoved = createAsyncThunk('products/productsRemoved', async (payload, thunkAPI) => {
|
||||||
|
let productIds = []
|
||||||
|
await thunkAPI.getState().products.entities
|
||||||
|
.forEach(product =>
|
||||||
|
product.checked === true && productIds.push(product._id)
|
||||||
|
)
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ productIds: productIds })
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/products', requestOptions)
|
||||||
|
})
|
||||||
|
export const productRemoved = createAsyncThunk('products/productRemoved', async (payload) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ productId: payload })
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/product', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
|
||||||
|
export const productsUploaded = createAsyncThunk('products/productsUploaded', async (payload) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
};
|
||||||
|
fetch('https://ohomeapi.familymoyaers.com/products', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
|
||||||
|
const productsSlice = createSlice({
|
||||||
|
name: 'products',
|
||||||
|
initialState: {
|
||||||
|
status: 'loading',
|
||||||
|
entities: [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
checkToggle: {
|
||||||
|
reducer(state, action) {
|
||||||
|
let product = state.entities.find((product) => product._id === action.payload._id)
|
||||||
|
product.checked = !product.checked;
|
||||||
|
},
|
||||||
|
prepare(_id) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
_id: _id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modalToggle: {
|
||||||
|
reducer(state, action) {
|
||||||
|
let product = state.entities.find((product) => product._id === action.payload._id)
|
||||||
|
if (!state.entities.find((p) => p.modalVisible === true) || product.modalVisible) {
|
||||||
|
product.modalVisible = !product.modalVisible;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prepare(_id) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
_id: _id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: builder => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchProducts.fulfilled, (state, action) => {
|
||||||
|
state.entities = action.payload
|
||||||
|
state.status = 'idle'
|
||||||
|
console.log('products fetched')
|
||||||
|
})
|
||||||
|
.addCase(productAdded.fulfilled, (state, action) => {
|
||||||
|
state.entities.push(action.payload);
|
||||||
|
console.log('sended the item')
|
||||||
|
})
|
||||||
|
.addCase(productsRemoved.fulfilled, (state, action) => {
|
||||||
|
state.entities = state.entities.filter(product => product.checked === false)
|
||||||
|
console.log('products removed')
|
||||||
|
})
|
||||||
|
.addCase(productRemoved.fulfilled, (state, action) => {
|
||||||
|
state.entities.splice(state.entities.findIndex((product) => product._id === action.payload), 1)
|
||||||
|
console.log('product removed')
|
||||||
|
})
|
||||||
|
.addCase(productsUploaded.fulfilled, (state, action) => {
|
||||||
|
action.payload.forEach(product => state.entities.push(product))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//selectors
|
||||||
|
//all products
|
||||||
|
export const selectAllProducts = state => state.products.entities
|
||||||
|
export const selectAllSortedProducts = state => state.products.entities.slice().sort((a, b) => a.tag.localeCompare(b.tag))
|
||||||
|
//product by id
|
||||||
|
export const selectProductsByTag = (state, tag) => state.products.entities.filter(product => product.tag === tag)
|
||||||
|
export const selectProductById = (state, _id) => {
|
||||||
|
let product = state.products.entities.find(product => product._id === _id)
|
||||||
|
if (product) {
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
productName: _id,
|
||||||
|
tag: 'uncategorized',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const selectProductByItem = (state, item) => {
|
||||||
|
let product = state.products.entities.find(product => product._id === item._id)
|
||||||
|
if (product) {
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
productName: item._id,
|
||||||
|
tag: item.tag ? item.tag : 'uncategorized',
|
||||||
|
price: item.price && item.price,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const selectProductModalVisible = createSelector(
|
||||||
|
state => state.products.entities,
|
||||||
|
products => products.find(product => product.modalVisible === true)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export const { checkToggle, modalToggle } = productsSlice.actions;
|
||||||
|
|
||||||
|
export default productsSlice.reducer;
|
||||||
49
src/redux/slices/groceryList/tagsSlice.js
Normal file
49
src/redux/slices/groceryList/tagsSlice.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { createSlice} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
status: 'idle',
|
||||||
|
entities: [
|
||||||
|
{ tagName: 'uncategorized', color: '#92a69b' },
|
||||||
|
{ tagName: 'Vegetables', color: '#BDD684' },
|
||||||
|
{ tagName: 'Grains', color: '#FA9600' },
|
||||||
|
{ tagName: 'Meal', color: '#ffe2a3' },
|
||||||
|
{ tagName: 'Meat', color: '#ef5350' },
|
||||||
|
{ tagName: 'Dairy', color: '#E8E8E8' },
|
||||||
|
{ tagName: 'Preservable', color: '#8ac8e6' },
|
||||||
|
{ tagName: 'Spices', color: '#d8eb34' },
|
||||||
|
{ tagName: 'Drinks', color: '#00e0b7' },
|
||||||
|
{ tagName: 'Snack', color: '#9575CD' },
|
||||||
|
{ tagName: 'Fruit', color: '#8fff40' },
|
||||||
|
{ tagName: 'Sauce', color: '#FCCABB' },
|
||||||
|
{ tagName: 'Baking', color: '#edb600' },
|
||||||
|
{ tagName: 'Cleaning', color: '#26EDEA' },
|
||||||
|
{ tagName: 'Hygiene', color: '#FC92F9' },
|
||||||
|
{ tagName: 'Pets', color: '#a0fc92' },
|
||||||
|
{ tagName: 'Equipment', color: '#C8C8E6' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const tagsSlice = createSlice({
|
||||||
|
name: 'tags',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
tagAdded: {
|
||||||
|
reducer(state, action) {
|
||||||
|
state.push(action.payload)
|
||||||
|
},
|
||||||
|
prepare(tagName, color) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
tagName,
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const findTag = (state, tagName) => state.tags.entities.find(tag => tag.tagName === tagName)
|
||||||
|
export const selectAllTags = (state) => state.tags.entities
|
||||||
|
|
||||||
|
export const { tagAdded } = tagsSlice.actions
|
||||||
|
export default tagsSlice.reducer
|
||||||
26
src/redux/slices/informationSlice.js
Normal file
26
src/redux/slices/informationSlice.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { createSlice} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
peers: 0,
|
||||||
|
}
|
||||||
|
const informationSlice = createSlice({
|
||||||
|
name: 'information',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
peersChanged: {
|
||||||
|
reducer(state, action) {
|
||||||
|
state.peers += action.payload.amount
|
||||||
|
},
|
||||||
|
prepare(amount) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { peersChanged } = informationSlice.actions
|
||||||
|
export default informationSlice.reducer
|
||||||
85
src/redux/slices/recipesSlice.js
Normal file
85
src/redux/slices/recipesSlice.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const fetchRecipes = createAsyncThunk('recipes/fetchRecipes', async () => {
|
||||||
|
let recipes = []
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/recipes', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => { recipes = data })
|
||||||
|
recipes.forEach(recipe => recipe.ingredients.forEach(ingredient => ingredient.checked = false))
|
||||||
|
return recipes
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addRecipe = createAsyncThunk('recipes/addRecipe', async (payload) => {
|
||||||
|
const size = new TextEncoder().encode(JSON.stringify(payload)).length
|
||||||
|
console.log(size)
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/recipe', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateRecipe = createAsyncThunk('recipes/updateRecipe', async (payload) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/recipe', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
|
||||||
|
export const removeRecipe = createAsyncThunk('recipes/removeRecipe', async (payload) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: payload })
|
||||||
|
};
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/recipe', requestOptions)
|
||||||
|
return payload
|
||||||
|
})
|
||||||
|
|
||||||
|
const recipesSlice = createSlice({
|
||||||
|
name: 'recipes',
|
||||||
|
initialState: {
|
||||||
|
status: 'loading',
|
||||||
|
entities: []
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
ingredientCheckToggle(state, action) {
|
||||||
|
let ingredient = state.entities.find(recipe => recipe._id === action.payload.id).ingredients.find(ingredient => ingredient.productName === action.payload.productName)
|
||||||
|
ingredient.checked = !ingredient.checked
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraReducers: builder => {
|
||||||
|
builder
|
||||||
|
.addCase(fetchRecipes.fulfilled, (state, action) => {
|
||||||
|
state.entities = action.payload
|
||||||
|
state.status = 'idle'
|
||||||
|
console.log('RECIPES fetched')
|
||||||
|
})
|
||||||
|
.addCase(addRecipe.fulfilled, (state, action) => {
|
||||||
|
state.entities.push(action.payload)
|
||||||
|
console.log('RECIPE added')
|
||||||
|
})
|
||||||
|
.addCase(updateRecipe.fulfilled, (state, action) => {
|
||||||
|
let index = state.entities.findIndex(recipe => recipe._id === action.payload._id)
|
||||||
|
state.entities.splice(index, 1, action.payload)
|
||||||
|
})
|
||||||
|
.addCase(removeRecipe.fulfilled, (state, action) => {
|
||||||
|
state.entities.splice(state.entities.findIndex(recipe => recipe._id === action.payload), 1)
|
||||||
|
console.log('RECIPE removed')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const findRecipeById = (state, id) => state.recipes.entities.find(recipe => recipe._id === id)
|
||||||
|
|
||||||
|
export const { ingredientCheckToggle } = recipesSlice.actions
|
||||||
|
export default recipesSlice.reducer
|
||||||
23
src/redux/store.js
Normal file
23
src/redux/store.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import itemsReducer from './slices/groceryList/itemsSlice';
|
||||||
|
import productsReducer from './slices/groceryList/productsSlice';
|
||||||
|
import lists from './slices/groceryList/listsSlice';
|
||||||
|
|
||||||
|
import tagsReducer from './slices/groceryList/tagsSlice';
|
||||||
|
// import informationReducer from './slices/informationSlice';
|
||||||
|
import recipes from './slices/recipesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default configureStore({
|
||||||
|
reducer: {
|
||||||
|
lists: lists,
|
||||||
|
items: itemsReducer,
|
||||||
|
products: productsReducer,
|
||||||
|
|
||||||
|
recipes: recipes,
|
||||||
|
|
||||||
|
tags: tagsReducer,
|
||||||
|
},
|
||||||
|
})
|
||||||
53
src/styles/componentBlueprints.js
Normal file
53
src/styles/componentBlueprints.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { View } from "react-native"
|
||||||
|
import React from 'react'
|
||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
//deps
|
||||||
|
import { HiCheck } from 'react-icons/hi'
|
||||||
|
//anims
|
||||||
|
|
||||||
|
import LightenDarken from '../functions'
|
||||||
|
//standard button layout
|
||||||
|
export const Button = styled(View)`
|
||||||
|
box-shadow: ${({ theme }) => theme.colors.shadow};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height:80px;
|
||||||
|
border-radius: 50px;
|
||||||
|
@media ${({ theme }) => theme.mediaQueries.below768}{
|
||||||
|
height: 65px;
|
||||||
|
width: 65px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const CheckButtonWrapper = styled(View)`
|
||||||
|
display:flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
border: 1px solid ${props => props.checked ? props.theme.colors.selected : LightenDarken(props.theme.colors.dp24, -5)};
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 0px 5px ;
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.selected : props.theme.colors.buttonGrey};
|
||||||
|
&:hover{
|
||||||
|
background-color: ${props => props.checked ? props.theme.colors.selected : LightenDarken(props.theme.colors.buttonGrey, 5)}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const IconCheck = styled(HiCheck)`
|
||||||
|
font-size: ${({ theme }) => theme.fontSize.fontL};
|
||||||
|
color: ${({ theme }) => theme.colors.dp00};
|
||||||
|
`
|
||||||
|
export const CheckButton = (props) => {
|
||||||
|
return (
|
||||||
|
<CheckButtonWrapper checked={props.checked} >
|
||||||
|
<IconCheck checked={props.checked} />
|
||||||
|
</CheckButtonWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//load anim
|
||||||
|
export const LoadAnimation = () => {
|
||||||
|
return (<View style={{ backgroundColor: "red", width: 100, height: 100 }} />)
|
||||||
|
}
|
||||||
73
src/styles/globalStyles.js
Normal file
73
src/styles/globalStyles.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// import { createGlobalStyle } from 'styled-components'
|
||||||
|
// export default createGlobalStyle`
|
||||||
|
// * {
|
||||||
|
// padding: 0px;
|
||||||
|
// margin: 0px;
|
||||||
|
// font-size: 1rem;
|
||||||
|
// font-family: sansationRegular;
|
||||||
|
// }
|
||||||
|
// a {
|
||||||
|
// text-decoration:none;
|
||||||
|
// }
|
||||||
|
// p{
|
||||||
|
// padding: 0px;
|
||||||
|
// margin:0px;
|
||||||
|
// font-size: 1.2rem;
|
||||||
|
// color: ${({theme}) => theme.colors.textW5};
|
||||||
|
// }
|
||||||
|
// h1{
|
||||||
|
// margin: 0px;
|
||||||
|
// padding: 0px;
|
||||||
|
// font-size: 1.8rem;
|
||||||
|
// color: ${({theme}) => theme.colors.textW1};
|
||||||
|
// font-weight: bold;
|
||||||
|
// }
|
||||||
|
// h2{
|
||||||
|
// margin: 0px;
|
||||||
|
// padding: 0px;
|
||||||
|
// font-size: 1.5rem;
|
||||||
|
// color: ${({theme}) => theme.colors.textW3};
|
||||||
|
// font-weight: bold;
|
||||||
|
// }
|
||||||
|
// button{
|
||||||
|
// padding: 0px;
|
||||||
|
// border: none;
|
||||||
|
// background: none;
|
||||||
|
// }
|
||||||
|
// input{
|
||||||
|
// color: ${props => props.theme.colors.textW1};
|
||||||
|
// background-color: #0000;
|
||||||
|
// resize: none;
|
||||||
|
// outline: none;
|
||||||
|
// border: none;
|
||||||
|
// }
|
||||||
|
// textarea{
|
||||||
|
// background-color: #0000;
|
||||||
|
// resize: none;
|
||||||
|
// outline: none;
|
||||||
|
// border: none;
|
||||||
|
// }
|
||||||
|
// /* width */
|
||||||
|
// ::-webkit-scrollbar {
|
||||||
|
// width: 5px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Track */
|
||||||
|
// ::-webkit-scrollbar-track {
|
||||||
|
// opacity: 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Handle */
|
||||||
|
// ::-webkit-scrollbar-thumb {
|
||||||
|
// background: #555;
|
||||||
|
// }
|
||||||
|
// ::-moz-selection { /* Code for Firefox */
|
||||||
|
// color: white;
|
||||||
|
// background: #7bc97eaa;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ::selection {
|
||||||
|
// color: white;
|
||||||
|
// background: #7bc97eaa;
|
||||||
|
// }
|
||||||
|
// `
|
||||||
106
src/styles/theme.js
Normal file
106
src/styles/theme.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import LightenDarken from '../functions'
|
||||||
|
|
||||||
|
|
||||||
|
let themeStyle = 'light'
|
||||||
|
|
||||||
|
const dp00 = themeStyle === 'dark' ? '#0C0F12' : '#f9f6ef'
|
||||||
|
const textW0 = themeStyle === 'dark' ? '#ffffff': '#524131'
|
||||||
|
const textB0 = themeStyle === 'dark' ? '#000000': '#ffffff'
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
colors: themeStyle === 'dark' ? {
|
||||||
|
//buttons
|
||||||
|
buttonGrey: LightenDarken(dp00, 7), //30%,
|
||||||
|
//standard
|
||||||
|
primary: '#98f8b6', //primary (light: #3e897c) #005c51 52c7b8 b6fcec #98f8b6
|
||||||
|
primaryVar: '#aaf898',
|
||||||
|
secondary: '#F2A8DB', //secondary
|
||||||
|
error: '#ef5350',
|
||||||
|
selected: '#3e8362',
|
||||||
|
itemSelected: '#253f34',
|
||||||
|
|
||||||
|
iconGrey: '#8a8a8a',
|
||||||
|
|
||||||
|
//background
|
||||||
|
dp00: dp00, //0%
|
||||||
|
dp01: LightenDarken(dp00, 5), //5%
|
||||||
|
dp02: LightenDarken(dp00, 7), //7%
|
||||||
|
dp03: LightenDarken(dp00, 8), //8%
|
||||||
|
dp04: LightenDarken(dp00, 9), //9%
|
||||||
|
dp06: LightenDarken(dp00, 11), //11%
|
||||||
|
dp08: LightenDarken(dp00, 12), //12%
|
||||||
|
dp12: LightenDarken(dp00, 14), //14%
|
||||||
|
dp16: LightenDarken(dp00, 15), //15%
|
||||||
|
dp24: LightenDarken(dp00, 16), //16%
|
||||||
|
|
||||||
|
//text
|
||||||
|
textW0: textW0,
|
||||||
|
textW1: LightenDarken(textW0, -5),
|
||||||
|
textW2: LightenDarken(textW0, -10),
|
||||||
|
textW3: LightenDarken(textW0, -15),
|
||||||
|
textW4: LightenDarken(textW0, -20),
|
||||||
|
textW5: LightenDarken(textW0, -25),
|
||||||
|
|
||||||
|
textB0: textB0,
|
||||||
|
textB1: LightenDarken(textB0, 5),
|
||||||
|
textB2: LightenDarken(textB0, 10),
|
||||||
|
textB3: LightenDarken(textB0, 15),
|
||||||
|
textB4: LightenDarken(textB0, 20),
|
||||||
|
textB5: LightenDarken(textB0, 25),
|
||||||
|
} :
|
||||||
|
{
|
||||||
|
//buttons
|
||||||
|
buttonGrey: dp00, //30%,
|
||||||
|
//standard
|
||||||
|
primary: '#f28006', //primary (light: #3e897c) #005c51 52c7b8 b6fcec #98f8b6
|
||||||
|
primaryVar: '#da5d3f',
|
||||||
|
secondary: '#F2A8DB', //secondary
|
||||||
|
error: '#ef5350',
|
||||||
|
selected: '#E09A4F',
|
||||||
|
itemSelected: '#ECC194',
|
||||||
|
|
||||||
|
// iconGrey: '#8a8a8a',
|
||||||
|
iconGrey: '#da5d3f',
|
||||||
|
|
||||||
|
shadow: '2px 2px 5px #0001',
|
||||||
|
//background
|
||||||
|
dp00: dp00, //0%
|
||||||
|
dp01: LightenDarken(dp00, -5 ), //5%
|
||||||
|
dp02: LightenDarken(dp00, -7 ), //7%
|
||||||
|
dp03: LightenDarken(dp00, -8 ), //8%
|
||||||
|
dp04: LightenDarken(dp00, -9 ), //9%
|
||||||
|
dp06: LightenDarken(dp00, -11), //11%
|
||||||
|
dp08: LightenDarken(dp00, -12), //12%
|
||||||
|
dp12: LightenDarken(dp00, -14), //14%
|
||||||
|
dp16: LightenDarken(dp00, -15), //15%
|
||||||
|
dp24: LightenDarken(dp00, -16), //16%
|
||||||
|
|
||||||
|
//text
|
||||||
|
textW0: textW0,
|
||||||
|
textW1: LightenDarken(textW0, 5),
|
||||||
|
textW2: LightenDarken(textW0, 10),
|
||||||
|
textW3: LightenDarken(textW0, 15),
|
||||||
|
textW4: LightenDarken(textW0, 20),
|
||||||
|
textW5: LightenDarken(textW0, 25),
|
||||||
|
|
||||||
|
textB0: textB0,
|
||||||
|
textB1: LightenDarken(textB0, -5),
|
||||||
|
textB2: LightenDarken(textB0, -8),
|
||||||
|
textB3: LightenDarken(textB0, -12),
|
||||||
|
textB4: LightenDarken(textB0, -15),
|
||||||
|
textB5: LightenDarken(textB0, -18),
|
||||||
|
},
|
||||||
|
fontSizes: {
|
||||||
|
fontS: '16px',
|
||||||
|
fontM: '24px',
|
||||||
|
fontL: '32px',
|
||||||
|
fontXL: '64px',
|
||||||
|
},
|
||||||
|
mediaQueries: {
|
||||||
|
"below768": "only screen and (max-width: 768px)",
|
||||||
|
"above768": "only screen and (min-width: 768px)"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default theme
|
||||||
131
src/utils.js
Normal file
131
src/utils.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
// redux
|
||||||
|
import { fetchProducts } from './redux/slices/groceryList/productsSlice';
|
||||||
|
import { fetchItems } from './redux/slices/groceryList/itemsSlice';
|
||||||
|
import { fetchLists } from './redux/slices/groceryList/listsSlice';
|
||||||
|
import store from './redux/store';
|
||||||
|
import { fetchRecipes } from './redux/slices/recipesSlice';
|
||||||
|
|
||||||
|
|
||||||
|
const updateProducts = async () => {
|
||||||
|
let APIProductIds = ''
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/productNames')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then((data) => { APIProductIds = JSON.stringify(data) })
|
||||||
|
let productIds = JSON.stringify(store.getState().products.entities.slice().map((product) => product._id))
|
||||||
|
return APIProductIds !== productIds
|
||||||
|
}
|
||||||
|
const updateItems = async () => {
|
||||||
|
let APIItemIds = ''
|
||||||
|
await fetch('https://ohomeapi.familymoyaers.com/groceryNames')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then((data) => { APIItemIds = JSON.stringify(data) })
|
||||||
|
let ItemIds = JSON.stringify(store.getState().items.entities.slice().map((item) => item._id))
|
||||||
|
return APIItemIds !== ItemIds
|
||||||
|
}
|
||||||
|
// const updateLists = async () => {
|
||||||
|
// let APILists = ''
|
||||||
|
// await fetch('https://ohomeapi.familymoyaers.com/groceryLists')
|
||||||
|
// .then(res => res.json())
|
||||||
|
// .then((data) => { APILists = JSON.stringify(data) })
|
||||||
|
// let lists = JSON.stringify(store.getState().lists.entities)
|
||||||
|
// return APILists !== lists
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
export const fetchAll = () => {
|
||||||
|
store.dispatch(fetchProducts())
|
||||||
|
store.dispatch(fetchItems())
|
||||||
|
store.dispatch(fetchLists())
|
||||||
|
store.dispatch(fetchRecipes())
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
updateProducts()
|
||||||
|
.then((result) => result && store.dispatch(fetchProducts()))
|
||||||
|
updateItems()
|
||||||
|
.then((result) => result && store.dispatch(fetchItems()))
|
||||||
|
// updateLists()
|
||||||
|
// .then((result) => result && store.dispatch(fetchLists()))
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// let ipfs = undefined;
|
||||||
|
// let orbitdb = undefined;
|
||||||
|
// export let db = undefined;
|
||||||
|
|
||||||
|
|
||||||
|
// var ipfsConfig = {
|
||||||
|
// preload: { enabled: false },
|
||||||
|
// config: {
|
||||||
|
// Addresses: {
|
||||||
|
// Swarm: [
|
||||||
|
// // Use IPFS dev signal server
|
||||||
|
// // Websocket:
|
||||||
|
// // '/dns4/ws-star-signal-1.servep2p.com/tcp/443/wss/p2p-websocket-star',
|
||||||
|
// // '/dns4/ws-star-signal-2.servep2p.com/tcp/443/wss/p2p-websocket-star',
|
||||||
|
// // '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star',
|
||||||
|
// // WebRTC:
|
||||||
|
// // '/dns4/star-signal.cloud.ipfs.team/wss/p2p-webrtc-star',
|
||||||
|
// // '/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star/',
|
||||||
|
// // '/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star/',
|
||||||
|
// // '/dns4/webrtc-star.discovery.libp2p.io/tcp/443/wss/p2p-webrtc-star/',
|
||||||
|
// // Use local signal server
|
||||||
|
// // '/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star',
|
||||||
|
// '/dns4/wrtc-star.familymoyaers.com/tcp/443/wss/p2p-webrtc-star/',
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// // Discovery: {
|
||||||
|
// // MDNS: {
|
||||||
|
// // Interval: 1
|
||||||
|
// // }
|
||||||
|
// // },
|
||||||
|
// EXPERIMENTAL: {
|
||||||
|
// pubsub: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export async function main(store) {
|
||||||
|
// // Create IPFS instance
|
||||||
|
// try {
|
||||||
|
// ipfs = await IPFS.create(ipfsConfig)
|
||||||
|
|
||||||
|
// orbitdb = await OrbitDB.createInstance(ipfs)
|
||||||
|
// const options = {
|
||||||
|
// // Give write access to ourselves
|
||||||
|
// accessController: {
|
||||||
|
// // type: 'orbitdb', //OrbitDBAccessController
|
||||||
|
// write: ['*']
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// db = await orbitdb.keyvalue('37th-database', options)
|
||||||
|
// console.log('db.load ')
|
||||||
|
// await db.load()
|
||||||
|
// await fetchAllValues(store)
|
||||||
|
// db.events.on('replicated', () => {
|
||||||
|
// fetchAllValues(store)
|
||||||
|
// })
|
||||||
|
// db.events.on('peer', () => {
|
||||||
|
// store.dispatch(peersChanged(1))
|
||||||
|
// })
|
||||||
|
// let maxTotal = 0, loaded = 0
|
||||||
|
// db.events.on('load.progress', (address, hash, entry, progress, total) => {
|
||||||
|
// maxTotal = Math.max.apply(null, [maxTotal, progress, 0])
|
||||||
|
// total = Math.max.apply(null, [progress, maxTotal, total, entry.clock.time, 0])
|
||||||
|
// console.log(maxTotal / total)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// }
|
||||||
|
// catch (err) {
|
||||||
|
// throw Error(err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const fetchAllValues = async (store) => {
|
||||||
|
// console.log('Loaded...')
|
||||||
|
// const dbValues = await Object.values(db.all)
|
||||||
|
// const products = dbValues.filter(value => value.type === 'product')
|
||||||
|
// const items = dbValues.filter(value => value.type === 'item')
|
||||||
|
// console.log('Fetched...')
|
||||||
|
// }
|
||||||
Reference in New Issue
Block a user