들어가기
Front에서 subscription구현.
1. client가 confirm Order하면, owner가 실시간 order받음.
2. owner가 order status변경하면, 실시간으로 client가 확인 가능
3. client가 confirm Order하면, delivery에게 push감
모든 사용자가 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>
)
}
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 →
</Link>
<Link
className="mt-14 text-white hover:bg-green-600 bg-green-300 rounded-md py-3 px-10"
to={``}
>
Buy Promotion →
</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부분만 남았다