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):