Skip to content

Commit

Permalink
Edit order delivery address (#482)
Browse files Browse the repository at this point in the history
  • Loading branch information
Winston-Hsiao authored Oct 16, 2024
1 parent fc5ef79 commit 85f3f47
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 4 deletions.
174 changes: 174 additions & 0 deletions frontend/src/components/modals/EditAddressModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, { useState } from "react";

import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { paths } from "@/gen/api";
import { useAlertQueue } from "@/hooks/useAlertQueue";
import { useAuthentication } from "@/hooks/useAuth";

type Order =
paths["/orders/get_user_orders"]["get"]["responses"][200]["content"]["application/json"][0];

interface EditAddressModalProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
order: Order;
onOrderUpdate: (updatedOrder: Order) => void;
}

const EditAddressModal: React.FC<EditAddressModalProps> = ({
isOpen,
onOpenChange,
order,
onOrderUpdate,
}) => {
const { client } = useAuthentication();
const { addAlert, addErrorAlert } = useAlertQueue();
const [address, setAddress] = useState({
shipping_name: order.shipping_name || "",
shipping_address_line1: order.shipping_address_line1 || "",
shipping_address_line2: order.shipping_address_line2 || "",
shipping_city: order.shipping_city || "",
shipping_state: order.shipping_state || "",
shipping_postal_code: order.shipping_postal_code || "",
shipping_country: order.shipping_country || "",
});

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setAddress((prev) => ({ ...prev, [name]: value }));
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const { data, error } = await client.PUT(
"/orders/update_order_address/{order_id}",
{
params: { path: { order_id: order.id } },
body: address,
},
);

if (error) {
addErrorAlert("Failed to update delivery address");
console.error("Error updating address:", error);
} else {
addAlert("Delivery address updated", "success");
onOrderUpdate(data);
}

onOpenChange(false);
} catch (error) {
console.error("Error updating address:", error);
}
};

return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[425px] bg-gray-1 text-gray-12 border border-gray-3 rounded-lg shadow-lg">
<DialogHeader>
<DialogTitle>Edit Delivery Address</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="shipping_name">Name</Label>
<Input
id="shipping_name"
name="shipping_name"
value={address.shipping_name}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="shipping_address_line1">Address Line 1</Label>
<Input
id="shipping_address_line1"
name="shipping_address_line1"
value={address.shipping_address_line1}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="shipping_address_line2">Address Line 2</Label>
<Input
id="shipping_address_line2"
name="shipping_address_line2"
value={address.shipping_address_line2}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="shipping_city">City</Label>
<Input
id="shipping_city"
name="shipping_city"
value={address.shipping_city}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="shipping_state">State</Label>
<Input
id="shipping_state"
name="shipping_state"
value={address.shipping_state}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="shipping_postal_code">Postal Code</Label>
<Input
id="shipping_postal_code"
name="shipping_postal_code"
value={address.shipping_postal_code}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="shipping_country">Country</Label>
<Input
id="shipping_country"
name="shipping_country"
value={address.shipping_country}
onChange={handleInputChange}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
<Button
type="submit"
className="bg-primary-9 text-gray-1 hover:bg-gray-12"
>
Save Changes
</Button>
</div>
</form>
</DialogContent>
</Dialog>
);
};

export default EditAddressModal;
49 changes: 45 additions & 4 deletions frontend/src/components/orders/OrderCard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import React from "react";
import React, { useState } from "react";

import EditAddressModal from "@/components/modals/EditAddressModal";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { paths } from "@/gen/api";
import { formatPrice } from "@/lib/utils/formatNumber";
import { normalizeStatus } from "@/lib/utils/formatString";

type OrderWithProduct =
paths["/orders/get_user_orders_with_products"]["get"]["responses"][200]["content"]["application/json"][0];

type Order =
paths["/orders/get_user_orders"]["get"]["responses"][200]["content"]["application/json"][0];

const orderStatuses = [
"processing",
"in_development",
Expand All @@ -27,9 +37,14 @@ const activeStatuses = [
const redStatuses = ["cancelled", "refunded", "failed"];

const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
orderWithProduct,
orderWithProduct: initialOrderWithProduct,
}) => {
const [orderWithProduct, setOrderWithProduct] = useState(
initialOrderWithProduct,
);
const { order, product } = orderWithProduct;
const [isEditAddressModalOpen, setIsEditAddressModalOpen] = useState(false);

const currentStatusIndex = orderStatuses.indexOf(order.status);
const isRedStatus = redStatuses.includes(order.status);
const showStatusBar = activeStatuses.includes(order.status);
Expand All @@ -50,6 +65,10 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({

const unitPrice = order.amount / order.quantity;

const handleOrderUpdate = (updatedOrder: Order) => {
setOrderWithProduct((prev) => ({ ...prev, order: updatedOrder }));
};

return (
<div className="bg-white shadow-md rounded-lg p-4 md:p-6 mb-4 w-full">
<h2 className="text-gray-12 font-bold text-2xl mb-1">{product.name}</h2>
Expand All @@ -59,9 +78,24 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
{normalizeStatus(order.status)}
</span>
</p>
<div className="text-sm sm:text-base text-gray-9 flex flex-col gap-1 mb-4">
<div className="text-sm sm:text-base text-gray-10 flex flex-col mb-4">
<p>Order ID: {order.id}</p>
<p>Quantity: {order.quantity}</p>
<div className="mb-2">
<DropdownMenu>
<DropdownMenuTrigger className="text-primary-9 underline cursor-pointer">
Manage order
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onSelect={() => setIsEditAddressModalOpen(true)}
className="cursor-pointer hover:bg-gray-100"
>
Change delivery address
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<p>Quantity: {order.quantity}x</p>
<p>
<span className="font-medium">{formatPrice(order.amount)}</span>{" "}
<span className="font-light">
Expand Down Expand Up @@ -161,6 +195,13 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
className="w-full h-64 object-cover rounded-md mt-2"
/>
)}

<EditAddressModal
isOpen={isEditAddressModalOpen}
onOpenChange={setIsEditAddressModalOpen}
order={order}
onOrderUpdate={handleOrderUpdate}
/>
</div>
);
};
Expand Down
69 changes: 69 additions & 0 deletions frontend/src/gen/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/orders/update_order_address/{order_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/** Update Order Address */
put: operations["update_order_address_orders_update_order_address__order_id__put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
Expand Down Expand Up @@ -1443,6 +1460,23 @@ export interface components {
/** Tags */
tags?: string[] | null;
};
/** UpdateOrderAddressRequest */
UpdateOrderAddressRequest: {
/** Shipping Name */
shipping_name: string;
/** Shipping Address Line1 */
shipping_address_line1: string;
/** Shipping Address Line2 */
shipping_address_line2: string | null;
/** Shipping City */
shipping_city: string;
/** Shipping State */
shipping_state: string;
/** Shipping Postal Code */
shipping_postal_code: string;
/** Shipping Country */
shipping_country: string;
};
/** UpdateUserRequest */
UpdateUserRequest: {
/** Email */
Expand Down Expand Up @@ -3276,4 +3310,39 @@ export interface operations {
};
};
};
update_order_address_orders_update_order_address__order_id__put: {
parameters: {
query?: never;
header?: never;
path: {
order_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdateOrderAddressRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Order"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}
29 changes: 29 additions & 0 deletions store/app/crud/orders.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""This module provides CRUD operations for orders."""

from pydantic import ValidationError

from store.app.crud.base import BaseCrud, ItemNotFoundError
from store.app.model import Order

Expand All @@ -24,3 +26,30 @@ async def get_order(self, order_id: str) -> Order | None:
async def get_order_by_session_id(self, session_id: str) -> Order | None:
orders = await self._get_items_from_secondary_index("stripe_checkout_session_id", session_id, Order)
return orders[0] if orders else None

async def update_order(self, order_id: str, update_data: dict) -> Order:
order = await self.get_order(order_id)
if not order:
raise ItemNotFoundError("Order not found")

# Create a dict of current order data
current_data = order.model_dump()

# Update with new data
current_data.update(update_data)

try:
# Validate the updated data
Order(**current_data)
except ValidationError as e:
# If validation fails, raise an error
raise ValueError(f"Invalid update data: {str(e)}")

# If validation passes, update the order
await self._update_item(order_id, Order, update_data)

# Fetch and return the updated order
updated_order = await self.get_order(order_id)
if not updated_order:
raise ItemNotFoundError("Updated order not found")
return updated_order
Loading

0 comments on commit 85f3f47

Please sign in to comment.