学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Django3.2 | 21 | QRオーダーシステムの構築 | react-icons

[20 | Permission] << [ホーム] >> [22 | カテゴリー登録]

作業は「qrmenu_react」側に移ります。


react-iconsを使用できるようターミナルでインストールします。


コマンド
npm i react-icons@4.2.0 -s


「src/apis.js」ファイルに記述を追加します。


記述追加 【QRMenu/qrmenu_react/src/apis.js】83行目

import { toast } from 'react-toastify';

function request(path,  {data = null, token = null, method = "GET" }) {
  return fetch(path, {
    method,
    headers: {
      Authorization: token ? `Token ${token}` : "",
      "Content-Type": "application/json",
    },
    body: method !=="GET" && method !== "DELETE" ? JSON.stringify(data): null,
  })
    .then((response) => {

      //もし成功したら
      if (response.ok) {
        if(method === "DELETE") {
          return true;
        }
        //toast.success("ログイン成功");
        return response.json();
      }
      //失敗
      return response.json().then((json) => {
          //JSONエラー
          if (response.status === 400) {
            toast.error("氏名もしくはパスワードに間違いがあります。");
            const errors = Object.keys(json).map(
                (k) => `${(json[k].join(" "))}`
            );
            throw new Error(errors.join(" "));
          }
          throw new Error(JSON.stringify(json));
        })
        .catch((e) => {
          if (e.name === "SyntaxError") {
            throw new Error(response.statusText);
          }
          throw new Error(e);
        })
    })

    .catch((e) => {
      //全エラー
      toast(e.message, { type: "error" });
    })
}

export function signIn(username, password) {
  return request("/auth/token/login/", {
    data: {username, password},
    method: "POST",
  })
}

export function register(username, password) {
  return request("/auth/users/", {
    data: {username, password},
    method: "POST",
  })
}

export function fetchPlaces(token) {
  return request("/api/places/", {token});
}

export function addPlace(data, token) {
  return request("/api/places/", {data, token, method: "POST" });
}

export function uploadImage(image) {
  const formData = new FormData();
  formData.append("file", image);
  formData.append("upload_preset", "qrmenu_photos");

  return fetch("https://api.cloudinary.com/v1_1/dov57gocw/image/upload", {
    method: "POST",
    body: formData,
  }).then((response) => {
    return response.json();
  });
}

export function fetchPlace(id, token) {
  return request(`/api/places/${id}`, { token });
}



「qrmenu_react/src/pages」フォルダに「Place.js」ファイルを新規作成します。
新規作成した「src/pages/Place.js」ファイルを以下のように編集します。



新規作成 【Desktop/QRMenu/qrmenu_react/src/pages/Place.js】

import { IoMdArrowBack } from 'react-icons/io';
import { Row, Col, Button } from 'react-bootstrap';
import { useParams, useHistory } from 'react-router-dom';
import React, { useEffect, useState, useContext } from 'react';

import { fetchPlace } from '../apis';
import AuthContext from '../contexts/AuthContext';
import MainLayout from '../layouts/MainLayout';

const Place = () => {
    const [place, setPlace] = useState({});

    const auth = useContext(AuthContext);
    const params = useParams();
    const history = useHistory();

    const onBack = () => history.push("/places");

    const onFetchPlace = async () => {
        const json = await fetchPlace(params.id, auth.token);
        if(json) {
            setPlace(json);
        }
    };

    useEffect(() => {
        onFetchPlace();
    }, []);

    return (
        <mainLayout>
            <Row>
                <Col lg={12}>
                    <div className="mb-4">
                        <div className="d-flex align-items-center">
                            <Button variant="link" onClick={onBack}>
                                <IoMdArrowBack size={25} color="black" />
                            </Button>
                            <h3 className="mb-0 ml-2 mr-2">{place.name}</h3>
                        </div>
                    </div>
                </Col>
            </Row>
        </mainLayout>
    )
};

export default Place;



「src/router/App.js」ファイルを編集します。


記述編集 【QRMenu/qrmenu_react/src/router/App.js】

import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import React from 'react';

import { AuthProvider } from '../contexts/AuthContext';
import PrivateRoute from './PrivateRoute';

import Home from '../pages/Home';
import Login from '../pages/Login';
import Register from '../pages/Register';
import Places from '../pages/Places';
import Place from '../pages/Place';

function App() {
  return (
    <AuthProvider>
    <BrowserRouter>
      <Switch>
        <Route exact path='/'>
          <Home />
        </Route>
        <Route exact path='/login'>
          <Login />
        </Route>
        <Route exact path='/register'>
          <Register />
        </Route>
        
        <PrivateRoute exact path='/places/:id'>
          <Place />
        </PrivateRoute>                  
        <PrivateRoute exact path='/places'>
          <Places />
        </PrivateRoute>        
      </Switch>
    </BrowserRouter>
    <ToastContainer />
    </AuthProvider>
  )
}

export default App;



「src/pages/Places.js」ファイルを編集します。


記述編集 【QRMenu/qrmenu_react/src/pages/Places.js】

import { Row, Col, Modal } from 'react-bootstrap';
import { useHistory } from 'react-router-dom';
import { React, useEffect, useState, useContext } from 'react';
import styled from 'styled-components';

import { fetchPlaces } from '../apis';
import AuthContext from '../contexts/AuthContext';

import MainLayout from '../layouts/MainLayout';
import PlaceForm from '../containers/PlaceForm';

const Place = styled.div`
    margin-bottom: 20px;
    cursor: pointer;
    transition: all 0.2s; 
    :hover {
        transform: scale(1.05);
    }
    > div {
        background-size: cover;
        height: 200px;
        border-radius: 5px;


    }
    > p {
        margin-top: 5px;
        font-size: 20px;
        font-weight: bold;
    }

`;

const AddPlaceButton =  styled.div`
    border: 1px dashed gray;
    height: 200px;
    border-radius: 5px;
    display: flex;
    aligh-items: center;
    justify-content: center;
    padding-top: 5rem;
    font-size: 20px;
    cursor: pointer;
    background-color: white;
    :hover {
        background-color: #fbfbfb;
    }
`;


const Places = () => {
    const [places, setPlaces] = useState([]);
    const [show, setShow] = useState(false); 

    const auth = useContext(AuthContext);
    const history = useHistory();

    const onHide = () => setShow(false);
    const onShow = () => setShow(true);

    const onFetchPlaces = async () => {
        const json = await fetchPlaces(auth.token);
        if (json) {
            setPlaces(json);
        }
    };

    const onDone = () => {
        onFetchPlaces();
        onHide();
    }

    useEffect(() => {
        onFetchPlaces();
    }, []);
   
    return (
        <MainLayout>
            <h3>登録済み飲食店</h3>
            <br/>
            <Modal show={show} onHide={onHide} centerd>

                <Modal.Body>
                    <PlaceForm onDone={onDone} />
                </Modal.Body>

            </Modal>

            <Row>
                { places.map((place) => (
                 
                    <Col key={place.id} lg={4}>
                    <Place onClick={() => history.push(`/places/${place.id}`)}>
                        <div style={{ backgroundImage: `url(${place.image})` }}></div>
                        <p>{place.name}</p>
                    </Place>
                    </Col>
                ))}
                <Col lg={4}>
                    <AddPlaceButton onClick={onShow}>飲食店登録</AddPlaceButton>
                </Col>
            </Row>
        </MainLayout>
    )
}

export default Places;



これで、登録した飲食店をクリックすると詳細ページにジャンプするようになりました。

登録した飲食店をクリック
登録した飲食店をクリック


飲食店詳細ページ
飲食店詳細ページ



↓↓クリックして頂けると励みになります。


[20 | Permission] << [ホーム] >> [22 | カテゴリー登録]