[su_spoiler title=”CreateOrder.jsx”]
import { useState } from ‘react’;
import { Form, redirect, useActionData, useNavigation } from ‘react-router-dom’;
import { createOrder } from ‘../../services/apiRestaurant’;
import Button from ‘../../ui/Button’;
import EmptyCart from ‘../cart/EmptyCart’;
import { useDispatch, useSelector } from ‘react-redux’;
import { clearCart, getCart, getTotalCartPrice } from ‘../cart/cartSlice’;
import store from ‘../../store’;
import { formatCurrency } from ‘../../utils/helpers’;
import { fetchAddress } from ‘../user/userSlice’;
// https://uibakery.io/regex-library/phone-number
const isValidPhone = (str) =>
/^\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}$/.test(
str
);
function CreateOrder() {
const [withPriority, setWithPriority] = useState(false);
const {
username,
status: addressStatus,
position,
address,
error: errorAddress,
} = useSelector((state) => state.user);
const isLoadingAddress = addressStatus === ‘loading’;
const navigation = useNavigation();
const isSubmitting = navigation.state === ‘submitting’;
const formErrors = useActionData();
const dispatch = useDispatch();
const cart = useSelector(getCart);
const totalCartPrice = useSelector(getTotalCartPrice);
const priorityPrice = withPriority ? totalCartPrice * 0.2 : 0;
const totalPrice = totalCartPrice + priorityPrice;
if (!cart.length) return <EmptyCart />;
return (
<div className=“px-4 py-6”>
<h2 className=“mb-8 text-xl font-semibold”>Ready to order? Let’s go!</h2>
{/* <Form method=”POST” action=”/order/new”> */}
<Form method=“POST”>
<div className=“mb-5 flex flex-col gap-2 sm:flex-row sm:items-center”>
<label className=“sm:basis-40”>First Name</label>
<input
className=“input grow”
type=“text”
name=“customer”
defaultValue={username}
required
/>
</div>
<div className=“mb-5 flex flex-col gap-2 sm:flex-row sm:items-center”>
<label className=“sm:basis-40”>Phone number</label>
<div className=“grow”>
<input className=“input w-full” type=“tel” name=“phone” required />
{formErrors?.phone && (
<p className=“mt-2 rounded-md bg-red-100 p-2 text-xs text-red-700”>
{formErrors.phone}
</p>
)}
</div>
</div>
<div className=“relative mb-5 flex flex-col gap-2 sm:flex-row sm:items-center”>
<label className=“sm:basis-40”>Address</label>
<div className=“grow”>
<input
className=“input w-full”
type=“text”
name=“address”
disabled={isLoadingAddress}
defaultValue={address}
required
/>
{addressStatus === ‘error’ && (
<p className=“mt-2 rounded-md bg-red-100 p-2 text-xs text-red-700”>
{errorAddress}
</p>
)}
</div>
{!position.latitude && !position.longitude && (
<span className=“absolute right-[3px] top-[3px] z-50 md:right-[5px] md:top-[5px]”>
<Button
disabled={isLoadingAddress}
type=“small”
onClick={(e) => {
e.preventDefault();
dispatch(fetchAddress());
}}
>
Get position
</Button>
</span>
)}
</div>
<div className=“mb-12 flex items-center gap-5”>
<input
className=“h-6 w-6 accent-yellow-400 focus:outline-none focus:ring focus:ring-yellow-400 focus:ring-offset-2”
type=“checkbox”
name=“priority”
id=“priority”
value={withPriority}
onChange={(e) => setWithPriority(e.target.checked)}
/>
<label htmlFor=“priority” className=“font-medium”>
Want to yo give your order priority?
</label>
</div>
<div>
<input type=“hidden” name=“cart” value={JSON.stringify(cart)} />
<input
type=“hidden”
name=“position”
value={
position.longitude && position.latitude
? `${position.latitude},${position.longitude}`
: ”
}
/>
<Button disabled={isSubmitting || isLoadingAddress} type=“primary”>
{isSubmitting
? ‘Placing order….’
: `Order now from ${formatCurrency(totalPrice)}`}
</Button>
</div>
</Form>
</div>
);
}
export async function action({ request }) {
const formData = await request.formData();
const data = Object.fromEntries(formData);
const order = {
…data,
cart: JSON.parse(data.cart),
priority: data.priority === ‘true’,
};
console.log(order);
const errors = {};
if (!isValidPhone(order.phone))
errors.phone =
‘Please give us your correct phone number. We might need it to contact you.’;
if (Object.keys(errors).length > 0) return errors;
// If everything is okay, create new order and redirect
const newOrder = await createOrder(order);
// Do NOT overuse
store.dispatch(clearCart());
return redirect(`/order/${newOrder.id}`);
}
export default CreateOrder;
[/su_spoiler]
[su_spoiler title=”Order.jsx”]
// Test ID: IIDSAT
import { useFetcher, useLoaderData } from ‘react-router-dom’;
import OrderItem from ‘./OrderItem’;
import { getOrder } from ‘../../services/apiRestaurant’;
import {
calcMinutesLeft,
formatCurrency,
formatDate,
} from ‘../../utils/helpers’;
import { useEffect } from ‘react’;
import UpdateOrder from ‘./UpdateOrder’;
function Order() {
const order = useLoaderData();
const fetcher = useFetcher();
useEffect(
function () {
if (!fetcher.data && fetcher.state === ‘idle’) fetcher.load(‘/menu’);
},
[fetcher]
);
// Everyone can search for all orders, so for privacy reasons we’re gonna gonna exclude names or address, these are only for the restaurant staff
const {
id,
status,
priority,
priorityPrice,
orderPrice,
estimatedDelivery,
cart,
} = order;
const deliveryIn = calcMinutesLeft(estimatedDelivery);
return (
<div className=“space-y-8 px-4 py-6”>
<div className=“flex flex-wrap items-center justify-between gap-2”>
<h2 className=“text-xl font-semibold”>Order #{id} status</h2>
<div className=“space-x-2”>
{priority && (
<span className=“rounded-full bg-red-500 px-3 py-1 text-sm font-semibold uppercase tracking-wide text-red-50”>
Priority
</span>
)}
<span className=“rounded-full bg-green-500 px-3 py-1 text-sm font-semibold uppercase tracking-wide text-green-50”>
{status} order
</span>
</div>
</div>
<div className=“flex flex-wrap items-center justify-between gap-2 bg-stone-200 px-6 py-5”>
<p className=“font-medium”>
{deliveryIn >= 0
? `Only ${calcMinutesLeft(estimatedDelivery)} minutes left 😃`
: ‘Order should have arrived’}
</p>
<p className=“text-xs text-stone-500”>
(Estimated delivery: {formatDate(estimatedDelivery)})
</p>
</div>
<ul className=“dive-stone-200 divide-y border-b border-t”>
{cart.map((item) => (
<OrderItem
item={item}
key={item.pizzaId}
isLoadingIngredients={fetcher.state === ‘loading’}
ingredients={
fetcher?.data?.find((el) => el.id === item.pizzaId)
?.ingredients ?? []
}
/>
))}
</ul>
<div className=“space-y-2 bg-stone-200 px-6 py-5”>
<p className=“text-sm font-medium text-stone-600”>
Price pizza: {formatCurrency(orderPrice)}
</p>
{priority && (
<p className=“text-sm font-medium text-stone-600”>
Price priority: {formatCurrency(priorityPrice)}
</p>
)}
<p className=“font-bold”>
To pay on delivery: {formatCurrency(orderPrice + priorityPrice)}
</p>
</div>
{!priority && <UpdateOrder order={order} />}
</div>
);
}
export async function loader({ params }) {
const order = await getOrder(params.orderId);
return order;
}
export default Order;
[/su_spoiler]
[su_spoiler title=”OrderItem.jsx”]
import { formatCurrency } from ‘../../utils/helpers’;
function OrderItem({ item, isLoadingIngredients, ingredients }) {
const { quantity, name, totalPrice } = item;
return (
<li className=“space-y-1 py-3”>
<div className=“flex items-center justify-between gap-4 text-sm”>
<p>
<span className=“font-bold”>{quantity}×</span> {name}
</p>
<p className=“font-bold”>{formatCurrency(totalPrice)}</p>
</div>
<p className=“text-sm capitalize italic text-stone-500”>
{isLoadingIngredients ? ‘Loading…’ : ingredients.join(‘, ‘)}
</p>
</li>
);
}
export default OrderItem;
[/su_spoiler]
[su_spoiler title=”CreateOrder.jsx”]
import { useState } from ‘react’;
import { useNavigate } from ‘react-router-dom’;
function SearchOrder() {
const [query, setQuery] = useState(”);
const navigate = useNavigate();
function handleSubmit(e) {
e.preventDefault();
if (!query) return;
navigate(`/order/${query}`);
setQuery(”);
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder=“Search order #”
value={query}
onChange={(e) => setQuery(e.target.value)}
className=“w-28 rounded-full bg-yellow-100 px-4 py-2 text-sm transition-all duration-300 placeholder:text-stone-400 focus:outline-none focus:ring focus:ring-yellow-500 focus:ring-opacity-50 sm:w-64 sm:focus:w-72”
/>
</form>
);
}
export default SearchOrder;
[/su_spoiler]
[su_spoiler title=”UpdateOrder.jsx”]
import { useFetcher } from ‘react-router-dom’;
import Button from ‘../../ui/Button’;
import { updateOrder } from ‘../../services/apiRestaurant’;
function UpdateOrder({ order }) {
const fetcher = useFetcher();
return (
<fetcher.Form method=“PATCH” className=“text-right”>
<Button type=“primary”>Make priority</Button>
</fetcher.Form>
);
}
export default UpdateOrder;
export async function action({ request, params }) {
const data = { priority: true };
await updateOrder(params.orderId, data);
return null;
}
[/su_spoiler]