Skip to content

Commit

Permalink
feat: add product stock and ui improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
brolag committed Feb 24, 2025
1 parent ec1c918 commit e9a3666
Show file tree
Hide file tree
Showing 19 changed files with 443 additions and 150 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- AlterTable
ALTER TABLE `Product`
ADD COLUMN `stock` INTEGER NOT NULL DEFAULT 0,
ADD COLUMN `hidden` BOOLEAN NULL DEFAULT false;

-- CreateExtension
-- This migration marks the existing stock and hidden fields in the Product table
-- These fields were previously added via db:push
-- This migration is for documentation purposes only and won't modify the database
SELECT 1;
1 change: 1 addition & 0 deletions apps/web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ model Product {
price Float
nftMetadata Json
hidden Boolean? @default(false)
stock Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shoppingCartItems ShoppingCartItem[]
Expand Down
11 changes: 10 additions & 1 deletion apps/web/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"argent_mobile": "Argent Mobile",
"error_signing_message": "Error signing the message",

"beta_announcement": "Welcome to our beta version. We're actively improving the platform and appreciate your understanding as we enhance your experience.",

"tag_new": "New",
"welcome_coffee_lover": "Welcome Coffee Lover",
"featured": "Featured",
Expand Down Expand Up @@ -361,5 +363,12 @@
"farm_details": "Farm details",
"no_favorite_products": "No favorite products yet",
"click_to_upload": "Click to upload",
"no_collectibles_found": "No collectibles found"
"no_collectibles_found": "No collectibles found",

"stock_status": {
"in_stock": "{{count}} in stock",
"low_stock_left": "Only {{count}} left",
"stock_available": "{{count}} available",
"sold_out": "Sold Out"
}
}
10 changes: 9 additions & 1 deletion apps/web/public/locales/es/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"argent_mobile": "Argent Mobile",
"error_signing_message": "Error al firmar el mensaje",

"beta_announcement": "Bienvenido a nuestra versión beta. Estamos mejorando activamente la plataforma y agradecemos tu comprensión mientras optimizamos tu experiencia.",

"tag_new": "Nuevo",
"welcome_coffee_lover": "Bienvenido Amante del Café",
"featured": "Destacado",
Expand Down Expand Up @@ -339,5 +341,11 @@
"track_in_my_orders": "Seguir en Mis Pedidos",
"subtotal": "Subtotal",
"amount_with_currency": "{{amount}} USD",
"amount_with_currency_short": "${amount} USD"
"amount_with_currency_short": "${amount} USD",
"stock_status": {
"in_stock": "{{count}} en existencia",
"low_stock_left": "Solo quedan {{count}}",
"stock_available": "{{count}} disponibles",
"sold_out": "Agotado"
}
}
10 changes: 9 additions & 1 deletion apps/web/public/locales/pt/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"argent_mobile": "Argent Mobile",
"error_signing_message": "Erro ao assinar a mensagem",

"beta_announcement": "Bem-vindo à nossa versão beta. Estamos melhorando ativamente a plataforma e agradecemos sua compreensão enquanto aprimoramos sua experiência.",

"tag_new": "Novo",
"welcome_coffee_lover": "Bem-vindo Amante de Café",
"featured": "Em destaque",
Expand Down Expand Up @@ -326,5 +328,11 @@
"track_in_my_orders": "Acompanhar em Meus Pedidos",
"subtotal": "Subtotal",
"amount_with_currency": "{{amount}} USD",
"amount_with_currency_short": "${amount} USD"
"amount_with_currency_short": "${amount} USD",
"stock_status": {
"in_stock": "{{count}} em estoque",
"low_stock_left": "Apenas {{count}} restantes",
"stock_available": "{{count}} disponíveis",
"sold_out": "Esgotado"
}
}
36 changes: 25 additions & 11 deletions apps/web/src/app/_components/features/ProductCatalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function ProductCatalog({
api.product.getProducts.useInfiniteQuery(
{
limit: 3,
includeHidden: false,
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
Expand All @@ -60,11 +61,15 @@ export default function ProductCatalog({

useEffect(() => {
if (data) {
const allProducts = data.pages.flatMap((page) =>
page.products.map((product) => ({
...product,
process: "Natural",
})),
const allProducts = data.pages.flatMap(
(page) =>
page.products
.filter((product) => product.hidden !== true)
.map((product) => ({
...product,
process: "Natural",
stock: product.stock ?? 0,
})) as Product[],
);
setProducts(allProducts);
}
Expand All @@ -90,6 +95,10 @@ export default function ProductCatalog({
};

const renderProduct = (product: Product) => {
if (product.hidden === true) {
return null;
}

let metadata: NftMetadata | null = null;

if (typeof product.nftMetadata === "string") {
Expand All @@ -102,7 +111,6 @@ export default function ProductCatalog({
metadata = product.nftMetadata as NftMetadata;
}

// Format the image URL to include the IPFS gateway if it's an IPFS hash
const imageUrl = metadata?.imageUrl
? metadata.imageUrl.startsWith("Qm")
? `${process.env.NEXT_PUBLIC_GATEWAY_URL}/ipfs/${metadata.imageUrl}`
Expand Down Expand Up @@ -142,6 +150,7 @@ export default function ProductCatalog({
farmName={metadata?.farmName ?? ""}
variety={t(product.name)}
price={totalPrice}
stock={product.stock}
badgeText={t(`strength.${metadata?.strength?.toLowerCase()}`)}
onClick={() => accessProductDetails(product.id)}
onAddToCart={handleAddToCart}
Expand Down Expand Up @@ -191,11 +200,16 @@ export default function ProductCatalog({
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8 w-full">
{results.map((product) => (
<div key={product.id} className="w-full">
{renderProduct(product)}
</div>
))}
{results
.filter((product) => product.hidden !== true)
.map((product) => (
<div key={product.id} className="w-full">
{renderProduct({
...product,
stock: product.stock ?? 0,
})}
</div>
))}
</div>
</div>
) : query ? (
Expand Down
75 changes: 54 additions & 21 deletions apps/web/src/app/_components/features/ProductDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ interface ProductDetailsProps {
region: string;
farmName: string;
roastLevel: string;
bagsAvailable: number;
stock: number;
price: number;
description: string;
type: "Buyer" | "Farmer" | "SoldOut";
process: string;
bagsAvailable?: number;
};
isConnected?: boolean;
onConnect?: () => void;
Expand Down Expand Up @@ -72,12 +73,13 @@ export default function ProductDetails({
name,
farmName,
roastLevel,
bagsAvailable,
stock,
price,
type,
process,
description,
region,
bagsAvailable,
} = product;

const { t } = useTranslation();
Expand Down Expand Up @@ -126,7 +128,7 @@ export default function ProductDetails({
return () => clearInterval(interval);
}, [price]);

const isSoldOut = type === "SoldOut";
const isSoldOut = stock === 0 || type === "SoldOut";
const isFarmer = type === "Farmer";

// Update productImages to use getImageUrl
Expand Down Expand Up @@ -245,7 +247,7 @@ export default function ProductDetails({
{!isSoldOut && !isFarmer && (
<Button
onClick={isConnected ? handleAddToCart : onConnect}
disabled={isAddingToCart}
disabled={isAddingToCart || stock === 0}
className="bg-yellow-400 hover:bg-yellow-500 text-black px-6"
>
{isAddingToCart
Expand All @@ -266,14 +268,23 @@ export default function ProductDetails({
<div className="space-y-4">
<div className="relative aspect-square rounded-2xl overflow-hidden bg-gray-100">
{productImages[selectedImage] && (
<Image
src={productImages[selectedImage].src}
alt={name}
fill
className="object-cover hover:scale-105 transition-transform duration-300"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority
/>
<>
<Image
src={productImages[selectedImage].src}
alt={name}
fill
className="object-cover hover:scale-105 transition-transform duration-300"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority
/>
{isSoldOut && (
<div className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<span className="text-white text-2xl font-bold px-6 py-2 bg-red-600 rounded-lg">
{t("sold_out")}
</span>
</div>
)}
</>
)}
</div>
{productImages.length > 1 && (
Expand Down Expand Up @@ -310,12 +321,30 @@ export default function ProductDetails({
</div>

<div className="border-t border-b py-4">
<Text className="text-3xl font-bold text-gray-900">
${price.toFixed(2)} USD
<span className="text-base font-normal text-gray-500 ml-2">
{t("per_unit")}
</span>
</Text>
<div className="flex items-center justify-between mb-2">
<Text className="text-3xl font-bold text-gray-900">
${price.toFixed(2)} USD
<span className="text-base font-normal text-gray-500 ml-2">
{t("per_unit")}
</span>
</Text>
<div className="flex items-center">
<div
className={`w-2 h-2 rounded-full ${isSoldOut ? "bg-red-500" : stock <= 5 ? "bg-orange-500" : stock <= 15 ? "bg-yellow-500" : "bg-green-500"} mr-2`}
/>
<Text
className={`text-sm ${isSoldOut ? "text-red-600 font-medium" : "text-gray-600"}`}
>
{isSoldOut
? t("stock_status.sold_out")
: stock <= 5
? t("stock_status.low_stock_left", { count: stock })
: stock <= 15
? t("stock_status.stock_available", { count: stock })
: t("stock_status.in_stock", { count: stock })}
</Text>
</div>
</div>
{isLoadingPrice ? (
<Text className="text-sm text-gray-500 mt-1">
{t("loading_strk_price")}
Expand All @@ -325,9 +354,13 @@ export default function ProductDetails({
{strkPrice.toFixed(2)} STRK
</Text>
) : null}
{!isSoldOut && (
{isSoldOut ? (
<Text className="text-sm text-red-600 font-medium mt-1">
{t("product_sold_out")}
</Text>
) : (
<Text className="text-sm text-gray-500 mt-1">
{t("bags_available_count", { count: bagsAvailable })}
{t("stock_available", { count: stock })}
</Text>
)}
</div>
Expand All @@ -351,7 +384,7 @@ export default function ProductDetails({
<SelectionTypeCard
price={price}
quantity={quantity}
bagsAvailable={bagsAvailable}
stock={stock}
onQuantityChange={setQuantity}
onAddToCart={handleAddToCart}
isAddingToCart={isAddingToCart}
Expand Down
Loading

0 comments on commit e9a3666

Please sign in to comment.