feat: initial commit — storefront, convex backend, and shared packages
Completes the first milestone of The Pet Loft ecommerce platform: - apps/storefront: full customer-facing Next.js app with HeroUI (cart, checkout, orders, wishlist, product detail, shop, search, auth) - convex/: serverless backend with schema, queries, mutations, actions, HTTP routes, Stripe/Shippo integrations, and co-located tests - packages/types, packages/utils, packages/convex: shared workspace packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
99
apps/storefront/src/components/wishlist/WishlistPageView.tsx
Normal file
99
apps/storefront/src/components/wishlist/WishlistPageView.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { useConvexAuth } from "convex/react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { toast } from "@heroui/react";
|
||||
import { useWishlist } from "@/lib/wishlist/useWishlist";
|
||||
import { useWishlistMutations } from "@/lib/wishlist/useWishlistMutations";
|
||||
import type { WishlistItem } from "@/lib/wishlist/types";
|
||||
import { WishlistItemGrid } from "./WishlistItemGrid";
|
||||
import { RemoveFromWishlistDialog } from "./RemoveFromWishlistDialog";
|
||||
import { WishlistSkeleton } from "./state/WishlistSkeleton";
|
||||
import { WishlistEmptyState } from "./state/WishlistEmptyState";
|
||||
import { WishlistSignInPrompt } from "./state/WishlistSignInPrompt";
|
||||
|
||||
export function WishlistPageView() {
|
||||
const { isAuthenticated, isLoading: authLoading } = useConvexAuth();
|
||||
const { items, isLoading, isEmpty } = useWishlist();
|
||||
const { removeItem, isRemoving, addToCart, isAddingToCart } =
|
||||
useWishlistMutations();
|
||||
|
||||
const [removeTarget, setRemoveTarget] = useState<WishlistItem | null>(null);
|
||||
const [removingId, setRemovingId] = useState<string | null>(null);
|
||||
const [addingToCartId, setAddingToCartId] = useState<string | null>(null);
|
||||
|
||||
const handleRemove = useCallback(
|
||||
(id: string) => {
|
||||
const target = items.find((i) => i._id === id);
|
||||
setRemoveTarget(target ?? null);
|
||||
},
|
||||
[items],
|
||||
);
|
||||
|
||||
const handleConfirmRemove = useCallback(async () => {
|
||||
if (!removeTarget) return;
|
||||
const name = removeTarget.product?.name ?? "Item";
|
||||
setRemovingId(removeTarget._id);
|
||||
try {
|
||||
await removeItem(removeTarget._id);
|
||||
toast.success(`${name} removed from wishlist`);
|
||||
} catch {
|
||||
toast.danger("Failed to remove item");
|
||||
} finally {
|
||||
setRemovingId(null);
|
||||
setRemoveTarget(null);
|
||||
}
|
||||
}, [removeTarget, removeItem]);
|
||||
|
||||
const handleAddToCart = useCallback(
|
||||
async (variantId: string) => {
|
||||
const item = items.find(
|
||||
(i) =>
|
||||
i.variant?._id === variantId ||
|
||||
i.product?.variants[0]?._id === variantId,
|
||||
);
|
||||
setAddingToCartId(item?._id ?? null);
|
||||
try {
|
||||
await addToCart(variantId, 1);
|
||||
toast.success("Added to cart");
|
||||
} catch {
|
||||
toast.danger("This item is currently out of stock");
|
||||
} finally {
|
||||
setAddingToCartId(null);
|
||||
}
|
||||
},
|
||||
[items, addToCart],
|
||||
);
|
||||
|
||||
if (!authLoading && !isAuthenticated) {
|
||||
return <WishlistSignInPrompt />;
|
||||
}
|
||||
|
||||
if (authLoading || isLoading) {
|
||||
return <WishlistSkeleton />;
|
||||
}
|
||||
|
||||
if (isEmpty) {
|
||||
return <WishlistEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WishlistItemGrid
|
||||
items={items}
|
||||
onRemove={handleRemove}
|
||||
onAddToCart={handleAddToCart}
|
||||
removingId={removingId}
|
||||
addingToCartId={addingToCartId}
|
||||
/>
|
||||
|
||||
<RemoveFromWishlistDialog
|
||||
isOpen={!!removeTarget}
|
||||
onClose={() => setRemoveTarget(null)}
|
||||
onConfirm={handleConfirmRemove}
|
||||
isRemoving={isRemoving}
|
||||
productName={removeTarget?.product?.name ?? "this item"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user