RealTime Order

김종민·2022년 10월 6일
0

Nuber-Client

목록 보기
17/21

들어가기
Front에서 subscription구현.
1. client가 confirm Order하면, owner가 실시간 order받음.
2. owner가 order status변경하면, 실시간으로 client가 확인 가능
3. client가 confirm Order하면, delivery에게 push감


1. src/pages/user/order.tsx

모든 사용자가 order 볼 수 있게

import { gql, useMutation, useQuery, useSubscription } from '@apollo/client'
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { FULL_ORDER_FRAGMENT } from '../../fragments'
import {
  EditOrderMutation,
  EditOrderMutationVariables,
  GetOrderQuery,
  GetOrderQueryVariables,
  OrderStatus,
  OrderUpdatesSubscription,
  UserRole,
} from '../../graphql/__generated__'
import { useMe } from '../../hooks/useMe'

const GET_ORDER = gql`
  query getOrder($input: GetOrderInput!) {
    getOrder(input: $input) {
      ok
      error
      order {
        ...FullOrderParts
      }
    }
  }
  ${FULL_ORDER_FRAGMENT}
`
///order Page에서 https://localhost:30000/order/${id}
///에서 order에 대한 정보를 받는 query
///order id를 받아서, order page에 order에 대한 정보를 뿌려줌.

const ORDER_SUBSCRIPTION = gql`
  subscription orderUpdates($input: OrderUpdatesInput!) {
    orderUpdates(input: $input) {
      ...FullOrderParts
    }
  }
  ${FULL_ORDER_FRAGMENT}
`
///order의 status를 pending->cooking->cooked->pickedUp
///등으로 status를 바꿀떄, 바뀐 상태를 user에게
///realTilme으로 subscription할 수 있게 하는 subscription.

const EDIT_ORDER = gql`
  mutation editOrder($input: EditOrderInput!) {
    editOrder(input: $input) {
      ok
      error
    }
  }
`
///Order의 status를 바꿀 수 있는 mutation.

interface IParams {
  id: number
}
///order의 id를 받아오는 params.

export const Order = () => {
  const { id } = useParams() as unknown as IParams
  ///path에서 order의 id를 받아옴.
  
  const { data: userData } = useMe()
  ///loggedInUser의 role을 확인하기 위해 사용한 hook.
  
  const [editOrderMutation] = useMutation<
    EditOrderMutation,
    EditOrderMutationVariables
  >(EDIT_ORDER)
  ///order의 status를 변경하기 위한 mutation.
  
  const { data, subscribeToMore } = useQuery<
    GetOrderQuery,
    GetOrderQueryVariables
  >(GET_ORDER, {
    variables: {
      input: {
        id: +id,
      },
    },
  })
  ///subscribeToMore는 query와 subscribe를 같이 작동하게 하는
  ///함수로써, 일단, query를 불러들이고, subscribe(status 변함)
  ///되면, query가 update되는 구조.
  ///query와 subscribe를 같이 쓰게 되어서 편하게 되는 구조.
  ///사용 문법을 잘 본다. 
  ///반드시 아래의 useEffect와 같이 사용되어야한다.
  
  ///useEffect의 subscribeToMore는 3가지 argument를 받는데,
  ///document, variables, updateQuery 3개를 받음.
  ///사용되는 문법을 집중적으로 볼것.
  ///특히, updateQuery부분은 문법이 상당히 어려움.
  useEffect(() => {
    if (data?.getOrder.ok) {
      subscribeToMore({
        document: ORDER_SUBSCRIPTION, ///사용할 subscription
        variables: {
          input: {
            id: +id,
          },
        },
        updateQuery: (
          prev,
          {
            subscriptionData: { data },
          }: { subscriptionData: { data: OrderUpdatesSubscription } }
          ///subscriptionData:{data}의 type을 설정해줌.
          ///prev랑 subscriptionData를 받아서 query update함.
        ) => {
          if (!data) return prev
          ///data가 없으면 바로 return시킴.
          return {
            getOrder: {
              ...prev.getOrder,
              order: {
                ...data.orderUpdates,
              },
            },
            ///getOrder query에 prev.getOrder를 넣고,
            ///order에는 subscription인 orderUpdates를 넣어줌
          }
        },
      })
    }
  }, [data])
  
  -->여기서 부터는 query와 subscription을 따로 사용할 경우의
  -->로직임.
  // const { data: subscriptionData } = useSubscription<
  //   OrderUpdatesSubscription,
  //   OrderUpdatesSubscriptionVariables
  // >(ORDER_SUBSCRIPTION, {
  //   variables: {
  //     input: {
  //       id: +id,
  //     },
  //   },
  // })
  // console.log(subscriptionData)
  
  
  const onButtonClick = (newStatus: OrderStatus) => {
    editOrderMutation({
      variables: {
        input: {
          id: +id,
          status: newStatus,
        },
      },
    })
  }
  ///버튼 클릭시, OrderStatus의 newStats(cooking, cooked등등)
  ///을 받아서 editOrderMutation응 실행시켜줌.
  
  return (
    <div className="mt-32 max-w-screen-xl flex justify-center mb-32">
      <div className="border border-gray-800 w-full max-w-screen-sm flex flex-col justify-center">
        <h4 className="bg-gray-800 w-full py-5 text-white text-center text-xl">
          Order #{id}
        </h4>
        <h5 className="p-5 pt-10 text-3xl text-center">
          ${data?.getOrder.order?.total}
        </h5>
        <div className="p-5 text-xl grid gap-6">
          <div className="border-t pt-5 border-gray-700">
            Prepared By:{''}
            <span className="font-medium">
              {data?.getOrder.order?.restaurant?.name}
            </span>
          </div>
          <div className="border-t pt-5 border-gray-700">
            Deliver To: {''}
            <span className="font-medium">
              {data?.getOrder.order?.customer?.email}
            </span>
          </div>
          <div className="border-t pt-5 border-gray-700">
            Driver : {''}
            <span className="font-medium">
              {data?.getOrder.order?.driver?.email}
            </span>
          </div>

          {userData?.me.role === 'Client' && (
            <span className="border-t border-gray-700 text-center mt-3 py-6 mb-3 text-2xl text-green-600">
              Status :{data?.getOrder.order?.status}
            </span>
          )}
          ///userRole이 Client일 경우, status만 볼 수 있게함.
          
          {userData?.me.role === UserRole.Owner && (
            <>
              {data?.getOrder.order?.status === OrderStatus.Pending && (
                <button
                  onClick={() => onButtonClick(OrderStatus.Cooking)}
                  className="btn"
                >
                  Accept Order
                </button>
              )}
              ///Owner이고, status가 pending일때, 클릭시,
              ///order status를 Cooking으로 바꿔서
              ///order를 newOrder로 update시킴.
              
              {data?.getOrder.order?.status === 'Cooking' && (
                <button
                  onClick={() => onButtonClick(OrderStatus.Cooked)}
                  className="btn"
                >
                  Order Cooked
                </button>
              )}
              ///Owner이고, status가 Cooking일떄, 클릭시,
              ///order status를 Cooked으로 바꿔서
              ///order를 newOrder로 update시킴.
              
              {data?.getOrder.order?.status !== OrderStatus.Cooking &&
                data?.getOrder.order?.status !== OrderStatus.Pending && (
                  <span className="border-t border-gray-700 text-center mt-3 py-6 mb-3 text-2xl text-green-600">
                    Status :{data?.getOrder.order?.status}
                  </span>
                )}
                ///order상태가 cooking, pending일떄, 
                ///order status를 보여주게 설정함.
            </>
          )}
          {userData?.me.role === UserRole.Delivery && (
            <>
              {data?.getOrder.order?.status === OrderStatus.Cooked && (
                <button
                  onClick={() => onButtonClick(OrderStatus.PickedUp)}
                  className="btn"
                >
                  Picked Up
                </button>
              )}
              ///role이 delivery이고, status가 Cooked일떄,
              ///status를 PickedUp으로 변경시키는 버튼.
              
              {data?.getOrder.order?.status === 'PickedUp' && (
                <button
                  onClick={() => onButtonClick(OrderStatus.Deliverd)}
                  className="btn"
                >
                  Order Deliverd
                </button>
              )}
              ///role이 delivery이고, status가 PickedUp일떄,
              ///status를 Deliverd로 변경시키는 버튼.
            </>
          )}
          {data?.getOrder.order?.status === OrderStatus.Deliverd && (
            <span className="border-t font-bold border-gray-700 text-center mt-3 py-6 mb-3 text-2xl text-green-600">
              Thank you for your service.
            </span>
          )}
          ///배달이 마무리 되었을떄, Thank you를 날려줌.
        </div>
        
      </div>
    </div>
  )
}

2. src/pages/owner/my-restaurant.tsx

my restaurant page에서 push받을 수 있게..
Client가 order때리면, restaurant Owner가 실시간으로
push를 받을 수 있게.
subscription부분만 다뤄본다.

import { gql, useQuery, useSubscription } from '@apollo/client'
import React, { useEffect } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { Dish } from '../../components/dish'
import {
  DISH_FRAGMENT,
  FULL_ORDER_FRAGMENT,
  RESTAURANT_FRAGMENT,
} from '../../fragments'
import {
  FindOneMyRestaurantQuery,
  FindOneMyRestaurantQueryVariables,
  PendingOrdersSubscription,
  PendingOrdersSubscriptionVariables,
} from '../../graphql/__generated__'

export const MY_RESTAURANT_QUERY = gql`
  query findOneMyRestaurant($input: MyRestaurantInput!) {
    findOneMyRestaurant(input: $input) {
      ok
      error
      restaurant {
        ...RestaurantParts
        menu {
          ...DishParts
        }
      }
    }
  }
  ${RESTAURANT_FRAGMENT}
  ${DISH_FRAGMENT}
`
const PENDING_ORDERS_SUBSCRIPTION = gql`
  subscription pendingOrders {
    pendingOrders {
      ...FullOrderParts
    }
  }
  ${FULL_ORDER_FRAGMENT}
`

type IParams = {
  id: string
}
///Client가 Order떄리면, Owner가 실시간으로 주문을
///push받는 subscription임.
///fragment부분은 나중에 한꺼번에 다 다뤄본다.

const MyRestaurant = () => {
  const { id } = useParams() as unknown as IParams
  const { data } = useQuery<
    FindOneMyRestaurantQuery,
    FindOneMyRestaurantQueryVariables
  >(MY_RESTAURANT_QUERY, {
    variables: {
      input: {
        id: +id,
      },
    },
  })
  console.log(id)
  console.log(data)

  const { data: subscriptionData } = useSubscription<
    PendingOrdersSubscription,
    PendingOrdersSubscriptionVariables
  >(PENDING_ORDERS_SUBSCRIPTION)
  ///위에서 만든 subscription을 사용하는
  ///useSubscription.

  const navigate = useNavigate()

  useEffect(() => {
    if (subscriptionData?.pendingOrders.id) {
      navigate(`/orders/${subscriptionData.pendingOrders.id}`)
    }
  }, [subscriptionData])
  ///Owner가 restaurant Detail Page에서 Order를 받았을 때,
  ///order page로 바로 redirect되게 함.

  return (
    <div>
      <div
        className=" py-28 bg-cover"
        style={{
          backgroundImage: `url(${data?.findOneMyRestaurant.restaurant?.coverImg})`,
        }}
      >
        <div className="bg-white w-1/2 py-8 opacity-60 ">
          <h4 className="text-4xl mb-3">
            {data?.findOneMyRestaurant.restaurant?.name}
          </h4>
          <h5 className="test-sm font-light mb-2">
            {data?.findOneMyRestaurant.restaurant?.category?.name}
          </h5>
          <h6 className="test-sm font-light">
            {data?.findOneMyRestaurant.restaurant?.address}
          </h6>
        </div>
      </div>
      <div className="mt-10 ml-4">
        <Link
          className="mr-14 hover:bg-gray-600 text-white rounded-md bg-gray-300 py-3 px-10"
          to={`/restaurants/${id}/add-dish`}
        >
          Add Dish &rarr;
        </Link>
        <Link
          className="mt-14 text-white hover:bg-green-600 bg-green-300 rounded-md py-3 px-10"
          to={``}
        >
          Buy Promotion &rarr;
        </Link>
      </div>
      <div className="mt-10">
        {data?.findOneMyRestaurant.restaurant?.menu.length === 0 ? (
          <h3>You have no dish</h3>
        ) : (
          <div className="grid mt-16 md:grid-cols-3 gap-x-5 gap-y-10 mx-5 mb-5">
            {data?.findOneMyRestaurant.restaurant?.menu.map((dish, index) => (
              <Dish
                key={index}
                name={dish.name}
                description={dish.description}
                price={dish.price}
              />
            ))}
          </div>
        )}
      </div>
    </div>
  )
}

export default MyRestaurant

NOTICE!!!!
subscription부분은 무지하게 중요, 중요, 중요.
subscribeToMore부분의 queryUpdate부분은 문법이 매우 난해하기 떄문에, 여러번 집중해서 봐 볼 필요가 있다..
이제 Delivery Dashboard부분만 남았다

profile
코딩하는초딩쌤

0개의 댓글