From d9e5c50c7313aee43867a37591695a6526c7432e Mon Sep 17 00:00:00 2001 From: Ivan <ivntsng@gmail.com> Date: Sun, 17 Nov 2024 01:56:54 -0800 Subject: [PATCH] hide store section from non-profile owners & handle empty orders as a valid state --- frontend/src/components/pages/Orders.tsx | 16 ++- frontend/src/components/pages/Profile.tsx | 163 ++++++++++++---------- frontend/src/lib/types/api.ts | 2 + store/app/routers/orders.py | 24 +++- 4 files changed, 122 insertions(+), 83 deletions(-) diff --git a/frontend/src/components/pages/Orders.tsx b/frontend/src/components/pages/Orders.tsx index d375f991..bac7a0de 100644 --- a/frontend/src/components/pages/Orders.tsx +++ b/frontend/src/components/pages/Orders.tsx @@ -5,7 +5,9 @@ import OrderCard from "@/components/orders/OrderCard"; import Spinner from "@/components/ui/Spinner"; import { Button } from "@/components/ui/button"; import type { paths } from "@/gen/api"; +import { useAlertQueue } from "@/hooks/useAlertQueue"; import { useAuthentication } from "@/hooks/useAuth"; +import { ApiError } from "@/lib/types/api"; import ROUTES from "@/lib/types/routes"; type OrderWithProduct = @@ -16,6 +18,7 @@ const OrdersPage: React.FC = () => { const { api, currentUser, isAuthenticated, isLoading } = useAuthentication(); const [orders, setOrders] = useState<OrderWithProduct[] | null>(null); const [loadingOrders, setLoadingOrders] = useState(true); + const { addErrorAlert } = useAlertQueue(); useEffect(() => { const fetchOrders = async () => { @@ -26,12 +29,17 @@ const OrdersPage: React.FC = () => { "/orders/user-orders-with-products", ); if (error) { - console.error("Failed to fetch orders", error); + const apiError = error as ApiError; + if (apiError.status === 500) { + addErrorAlert({ + message: "Failed to fetch orders", + detail: apiError.message || "An unexpected error occurred", + }); + } + setOrders([]); } else { - setOrders(data); + setOrders(data || []); } - } catch (error) { - console.error("Error fetching orders", error); } finally { setLoadingOrders(false); } diff --git a/frontend/src/components/pages/Profile.tsx b/frontend/src/components/pages/Profile.tsx index 7804b217..1e84181c 100644 --- a/frontend/src/components/pages/Profile.tsx +++ b/frontend/src/components/pages/Profile.tsx @@ -181,6 +181,12 @@ export const RenderProfile = (props: RenderProfileProps) => { } }; + const getListingsTabLabel = () => { + return canEdit + ? "Your Robot Listings" + : `${user.first_name || user.username}'s Robot Listings`; + }; + return ( <div className="space-y-8 mb-12"> <Card className="w-full max-w-4xl mx-auto"> @@ -274,67 +280,72 @@ export const RenderProfile = (props: RenderProfileProps) => { <Card className="w-full max-w-4xl mx-auto"> <CardHeader> - <h2 className="text-2xl font-bold">Store</h2> + {canEdit && <h2 className="text-2xl font-bold">Store</h2>} </CardHeader> <CardContent> - <div className="mb-4"> - {user.stripe_connect && - !user.stripe_connect.onboarding_completed ? ( - <p className="text-gray-6 text-sm"> - Your Stripe account setup is not complete. Please resolve - outstanding requirements to begin selling robots. It may take - some time for Stripe to process your info between submissions. - </p> - ) : user.stripe_connect && - user.stripe_connect.onboarding_completed ? ( - <p className="text-gray-6 text-sm"> - You are set up to sell robots on K-Scale. - </p> - ) : ( - <p>Complete seller onboarding to sell robots on K-Scale</p> - )} - </div> - <div className="flex sm:flex-row flex-col gap-2 mb-8"> - <Tooltip content="View your orders" position="bottom"> - <Button - onClick={() => navigate(ROUTES.ORDERS.path)} - variant="outline" - > - Orders - </Button> - </Tooltip> - {!user.stripe_connect ? ( - <Tooltip content="Start selling on K-Scale" position="bottom"> - <Button - onClick={() => navigate(ROUTES.SELL.ONBOARDING.path)} - variant="outline" - > - Start Seller Onboarding - </Button> - </Tooltip> - ) : !user.stripe_connect.onboarding_completed ? ( - <Tooltip content="Continue onboarding" position="bottom"> - <Button - onClick={() => navigate(ROUTES.SELL.ONBOARDING.path)} - variant="outline" - > - Complete Seller Onboarding - </Button> - </Tooltip> - ) : ( - <Tooltip - content="Sell Robots or View Your Dashboard" - position="bottom" - > - <Button - onClick={() => navigate(ROUTES.SELL.DASHBOARD.path)} - variant="outline" - > - Seller Dashboard - </Button> - </Tooltip> - )} - </div> + {canEdit && ( + <> + <div className="mb-4"> + {user.stripe_connect && + !user.stripe_connect.onboarding_completed ? ( + <p className="text-gray-6 text-sm"> + Your Stripe account setup is not complete. Please resolve + outstanding requirements to begin selling robots. It may + take some time for Stripe to process your info between + submissions. + </p> + ) : user.stripe_connect && + user.stripe_connect.onboarding_completed ? ( + <p className="text-gray-6 text-sm"> + You are set up to sell robots on K-Scale. + </p> + ) : ( + <p>Complete seller onboarding to sell robots on K-Scale</p> + )} + </div> + <div className="flex sm:flex-row flex-col gap-2 mb-8"> + <Tooltip content="View your orders" position="bottom"> + <Button + onClick={() => navigate(ROUTES.ORDERS.path)} + variant="outline" + > + Orders + </Button> + </Tooltip> + {!user.stripe_connect ? ( + <Tooltip content="Start selling on K-Scale" position="bottom"> + <Button + onClick={() => navigate(ROUTES.SELL.ONBOARDING.path)} + variant="outline" + > + Start Seller Onboarding + </Button> + </Tooltip> + ) : !user.stripe_connect.onboarding_completed ? ( + <Tooltip content="Continue onboarding" position="bottom"> + <Button + onClick={() => navigate(ROUTES.SELL.ONBOARDING.path)} + variant="outline" + > + Complete Seller Onboarding + </Button> + </Tooltip> + ) : ( + <Tooltip + content="Sell Robots or View Your Dashboard" + position="bottom" + > + <Button + onClick={() => navigate(ROUTES.SELL.DASHBOARD.path)} + variant="outline" + > + Seller Dashboard + </Button> + </Tooltip> + )} + </div> + </> + )} <div className="flex flex-col items-center space-y-4"> <Tabs defaultValue="own" @@ -352,28 +363,32 @@ export const RenderProfile = (props: RenderProfileProps) => { value="own" className="data-[state=active]:bg-gray-3" > - Your Robot Listings + {getListingsTabLabel()} </TabsTrigger> </Button> - <Button - variant="outline" - asChild - className={`text-xs sm:text-sm px-2 sm:px-4 hover:bg-gray-11 ${value === "upvoted" ? "border text-gray-12" : ""}`} - > - <TabsTrigger - value="upvoted" - className="data-[state=active]:bg-gray-3" + {canEdit && ( + <Button + variant="outline" + asChild + className={`text-xs sm:text-sm px-2 sm:px-4 hover:bg-gray-11 ${value === "upvoted" ? "border text-gray-12" : ""}`} > - Upvoted Robots - </TabsTrigger> - </Button> + <TabsTrigger + value="upvoted" + className="data-[state=active]:bg-gray-3" + > + Upvoted Robots + </TabsTrigger> + </Button> + )} </TabsList> <TabsContent value="own"> <MyListingGrid userId={user.id} /> </TabsContent> - <TabsContent value="upvoted"> - <UpvotedGrid page={upvotedPage} setPage={setUpvotedPage} /> - </TabsContent> + {canEdit && ( + <TabsContent value="upvoted"> + <UpvotedGrid page={upvotedPage} setPage={setUpvotedPage} /> + </TabsContent> + )} </Tabs> </div> </CardContent> diff --git a/frontend/src/lib/types/api.ts b/frontend/src/lib/types/api.ts index 62688449..733beccc 100644 --- a/frontend/src/lib/types/api.ts +++ b/frontend/src/lib/types/api.ts @@ -6,6 +6,8 @@ export interface ApiValidationError { } export interface ApiError { + status: number; + message: string; detail: string | ApiValidationError[]; } diff --git a/store/app/routers/orders.py b/store/app/routers/orders.py index 817dba3e..f1c9b363 100644 --- a/store/app/routers/orders.py +++ b/store/app/routers/orders.py @@ -1,5 +1,6 @@ """Defines the router endpoints for handling Orders.""" +from logging import getLogger from typing import Annotated, Dict, List from fastapi import APIRouter, Depends, HTTPException, status @@ -14,6 +15,8 @@ router = APIRouter() +logger = getLogger(__name__) + class ProductInfo(BaseModel): id: str @@ -77,13 +80,24 @@ async def get_user_orders_with_products( orders = await crud.get_orders_by_user_id(user.id) orders_with_products = [] for order in orders: - if order.stripe_product_id is None: - continue # Skip orders without a stripe_product_id - product = await stripe.get_product(order.stripe_product_id, crud) - orders_with_products.append(OrderWithProduct(order=order, product=ProductInfo(**product))) + try: + if order.stripe_product_id is None: + continue + product = await stripe.get_product(order.stripe_product_id, crud) + orders_with_products.append(OrderWithProduct(order=order, product=ProductInfo(**product))) + except Exception as e: + logger.error( + "Error processing order", extra={"order_id": order.id, "error": str(e), "user_id": user.id} + ) + continue return orders_with_products except ItemNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No orders found for this user") + return [] + except Exception as e: + logger.error("Error fetching user orders", extra={"user_id": user.id, "error": str(e)}) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error fetching orders: {str(e)}" + ) class UpdateOrderAddressRequest(BaseModel):