Переглянути джерело

Add redux. Create store, Recipes container and fetch for some recipes

Tati 7 роки тому
батько
коміт
66aa9c864d

+ 7 - 1
package.json

@@ -9,21 +9,27 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "@material/react-button": "^0.11.0",
     "@material/react-card": "^0.11.0",
     "@material/react-drawer": "^0.11.0",
+    "@material/react-layout-grid": "^0.11.0",
     "@types/classnames": "^2.2.7",
     "@types/dotenv": "^6.1.0",
     "@types/material-components-web": "^0.43.1",
     "@types/node": "^11.13.2",
+    "@types/react-redux": "^7.0.6",
+    "@types/redux-thunk": "^2.1.0",
     "@types/sqlite3": "^3.1.3",
     "dotenv": "^6.2.0",
     "express": "^4.16.4",
     "material-components-web": "^1.1.1",
     "nano": "^8.0.0",
     "ramda": "^0.26.1",
+    "redux-thunk": "^2.3.0",
     "sqlite3": "^4.0.6"
   },
   "devDependencies": {
-    "@types/ramda": "github:types/npm-ramda#dist"
+    "@types/ramda": "github:types/npm-ramda#dist",
+    "redux-devtools-extension": "^2.13.8"
   }
 }

+ 6 - 2
recipes/package.json

@@ -17,8 +17,11 @@
     "node-sass": "^4.11.0",
     "react": "^16.8.4",
     "react-dom": "^16.8.4",
+    "react-redux": "^7.0.1",
     "react-router-dom": "^5.0.0",
     "react-scripts": "2.1.8",
+    "redux": "^4.0.1",
+    "redux-thunk": "^2.3.0",
     "typescript": "^3.4.1"
   },
   "scripts": {
@@ -39,11 +42,12 @@
     "not op_mini all"
   ],
   "devDependencies": {
-    "@storybook/react": "^5.0.6",
+    "@babel/core": "^7.4.3",
     "@storybook/addon-actions": "^5.0.6",
     "@storybook/addon-links": "^5.0.6",
     "@storybook/addons": "^5.0.6",
-    "@babel/core": "^7.4.3",
+    "@storybook/react": "^5.0.6",
+    "@types/react-redux": "^7.0.6",
     "babel-loader": "^8.0.5"
   }
 }

BIN
recipes/recipes-design.xd


+ 33 - 15
recipes/src/App.tsx

@@ -1,9 +1,13 @@
 import { BrowserRouter as Router, Route, Link, NavLink } from "react-router-dom";
 import React, { Component, Props } from 'react';
+import { Provider } from 'react-redux';
 import CBKDrawer from './components/Drawer';
-import List, {ListItem, ListItemText} from '@material/react-list';
-
+import { Grid, Row } from '@material/react-layout-grid';
+import List, { ListItem, ListItemText } from '@material/react-list';
+import configureStore from 'store/configureStore';
+import Recipes from "containers/recipes";
 import './styles/app.scss';
+import "@material/react-layout-grid/dist/layout-grid.min.css";
 
 function Index() {
   return <h2>Home</h2>;
@@ -13,6 +17,8 @@ function Users() {
   return <h2>Users</h2>;
 }
 
+const store = configureStore();
+
 class App extends Component<{}, {}> {
   navbarItems: { title: string, path: string, active: boolean }[]
 
@@ -27,19 +33,31 @@ class App extends Component<{}, {}> {
 
   render() {
     return (
-      <Router>
-        <CBKDrawer>
-          {
-            this.navbarItems.map((item, i) => (
-              <NavLink
-                to={item.path}
-                activeClassName='active'
-                key={i}
-              >{item.title}</NavLink>
-            ))
-          }
-        </CBKDrawer>
-      </Router>
+      <Provider store={store}>
+        <Router>
+          <CBKDrawer>
+            {
+              this.navbarItems.map((item, i) => (
+                <NavLink
+                  to={item.path}
+                  activeClassName='active'
+                  key={i}
+                >{item.title}</NavLink>
+              ))
+            }
+          </CBKDrawer>
+          <Grid className="cbk-grid">
+            <Row>
+              <Route path="/recipes" component={Recipes}>
+              </Route>
+              <Route path="/planner">
+              </Route>
+              <Route path="/planner">
+              </Route>
+            </Row>
+          </Grid>
+        </Router>
+      </Provider>
     )
   }
 }

+ 25 - 10
recipes/src/components/Card/index.tsx

@@ -2,26 +2,41 @@ import React from 'react';
 import Card, {
   CardActions,
   CardActionButtons,
-  CardActionIcons
+  CardActionIcons,
+  CardPrimaryContent,
+  CardMedia,
 } from "@material/react-card";
+import Button from "@material/react-button";
 
 import '@material/react-card/dist/card.min.css';
+import '@material/react-button/dist/button.min.css';
+import './styles.scss';
+import { ReactNodeArray } from 'react';
+import sample_img from "./sample.png";
 
-function RCard(){
+const not_found = "https://www.archgard.com/assets/upload_fallbacks/image_not_found-54bf2d65c203b1e48fea1951497d4f689907afe3037d02a02dcde5775746765c.png";
+type CBKCardProps = {
+  children?: ReactNodeArray,
+  title: string,
+  img?: string,
+}
+  
+function _Card(props: CBKCardProps){
   return(
-    <Card>
-      <h1>Title</h1>
+    <Card outlined className='cbk-card'>
+      <CardMedia imageUrl={props.img || sample_img}></CardMedia>
+      <CardPrimaryContent>
+        <h6>{props.title}</h6>
+      </CardPrimaryContent>
       <CardActions>
         <CardActionButtons>
-          <button>Click Me</button>
+          <Button>Edit</Button>
+          <Button>shopping</Button>
+          <Button>delete</Button>
         </CardActionButtons>
-
-        <CardActionIcons>
-          <i>Click Me Too!</i>
-        </CardActionIcons>
       </CardActions>
     </Card>
   )
 }
 
-export default RCard;
+export default _Card;

BIN
recipes/src/components/Card/sample.png


+ 13 - 0
recipes/src/components/Card/styles.scss

@@ -0,0 +1,13 @@
+.cbk-card {
+  flex-direction: row;
+  border-radius: 0px;
+  border: 0px;
+  margin: 8px 0;
+  .mdc-card__media {
+    width: 168px;
+  }
+  .mdc-card__primary-action {
+    padding: 8px;
+    width: 100%;
+  }
+}

+ 57 - 0
recipes/src/containers/recipes.tsx

@@ -0,0 +1,57 @@
+import React, { Component } from "react";
+import {
+  fetchRecipes,
+  receiveRecipes,
+} from "store/recipes/actions";
+import { connect } from "react-redux";
+import { Grid, Row, Cell } from "@material/react-layout-grid";
+import Card from 'components/Card';
+
+class Recipes extends Component<any, any> {
+  constructor(props: any) {
+    super(props);
+    console.log('props', props);
+  }
+
+  componentDidMount() {
+    console.log('props', this.props)
+    const { dispatch } = this.props;
+    dispatch(fetchRecipes({}));
+  }
+
+  componentDidUpdate(prevProps: any) {
+    const { dispatch, recipes } = this.props;
+    dispatch(receiveRecipes(recipes));
+  }
+
+  render() {
+    console.log(this.props.data);
+
+    return(
+      <Cell columns={6}>
+        {
+          this.props.data.map((recipe: any, i: number) => {
+            console.log("card");
+            return (
+              <Row>
+                <Cell columns={10}>
+                  <Card
+                    key="i"
+                    title={recipe.name}
+                  />
+                </Cell>
+              </Row>
+            )
+          })
+        }
+      </Cell>
+    )
+  }
+}
+
+function mapStateToProps(state: any) {
+  const { recipes } = state;
+  return state.recipes;
+}
+
+export default  connect(mapStateToProps)(Recipes);

+ 5 - 2
recipes/src/index.js

@@ -1,9 +1,12 @@
 import React from 'react';
-import ReactDOM from 'react-dom';
+import { render } from 'react-dom';
 import App from './App';
 import * as serviceWorker from './serviceWorker';
 
-ReactDOM.render(<App />, document.getElementById('root'));
+render(
+  <App></App>,
+  document.getElementById('root')
+)
 
 // If you want your app to work offline and load faster, you can change
 // unregister() to register() below. Note this comes with some pitfalls.

+ 0 - 1
recipes/src/routes/Recipes.tsx

@@ -38,7 +38,6 @@ class Recipes extends Component<{}, RecipeState> {
   render() {
     return (
       <div>
-        <Card></Card>
         {/* {
           this.state.recipes.map((_recipe) => (
             <RecipeCard recipe={_recipe} key={_recipe._id}/>

+ 18 - 0
recipes/src/store/configureStore.ts

@@ -0,0 +1,18 @@
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import { composeWithDevTools } from 'redux-devtools-extension';
+import { recipesReducer } from './recipes/reducers';
+
+const composeEnhancers = composeWithDevTools({});
+
+const configureStore = () => {
+  return createStore(
+    recipesReducer,
+    undefined,
+    composeEnhancers(
+      applyMiddleware(thunk)
+    )
+  )
+}
+
+export default configureStore;

+ 31 - 0
recipes/src/store/recipes/actions.ts

@@ -0,0 +1,31 @@
+// import { Dispatch } from "react";
+import axios from 'axios';
+
+export const RECEIVE_RECIPES = 'RECEIVE_RECIPES';
+export const REQUEST_RECIPES = 'REQUEST_RECIPES';
+
+export const requestRecipes = (query: {}) => ({
+  type: REQUEST_RECIPES,
+  isFetching: true,
+});
+
+export const receiveRecipes = (json: []) => ({
+  type: RECEIVE_RECIPES,
+  isFetching: false,
+  payload: json,
+});
+
+export function fetchRecipes(query: any) {
+  return (dispatch: any) => {
+    dispatch(requestRecipes(query))
+    return axios.get('https://recipes.davidventura.com.ar/recipes/all')
+    .then(response => {
+      console.log('response', response.data);
+      return response.data
+    })
+    .then(json => dispatch(receiveRecipes(json)))
+    .catch(error => {
+      console.log('error', error);
+    })
+  }
+}

+ 34 - 0
recipes/src/store/recipes/reducers.ts

@@ -0,0 +1,34 @@
+const initialState = {
+  recipes: {
+    isFetching: false,
+    data: [],
+  }
+};
+
+export const recipesReducer = (
+  state = initialState,
+  action: any
+) => {
+  console.log("action", action)
+  switch (action.type) {
+    case 'REQUEST_RECIPES': 
+      return {
+        ...state,
+        recipes: {
+          ...state.recipes,
+          isFetching: action.isFetching,
+        }
+      }
+    case 'RECEIVE_RECIPES':
+      return {
+        ...state,
+        recipes: {
+          ...state.recipes,
+          isFetching: action.isFetching,
+          data: action.payload || state.recipes.data,
+        }
+      }
+    default:
+      return state;
+  }
+}

+ 15 - 1
recipes/src/styles/app.scss

@@ -26,6 +26,11 @@ h1 {
   font-size: $font-size-xx-large;
 }
 
+h6 {
+  font-family: $font-family-display;
+  font-weight: $font-weight-bold;
+  font-size: $font-size-x-large;
+}
 code {
   font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
     monospace;
@@ -34,4 +39,13 @@ code {
 #root {
   display: flex;
   height: 100vh;
-}
+}
+
+.cbk-grid {
+  display: flex;
+  width: 100%;
+
+  .mdc-layout-grid__inner {
+    width: 100%;
+  }
+}