chore: transfer repo

This commit is contained in:
Danijel
2026-01-19 20:21:14 +01:00
commit 7d2fb0c737
213 changed files with 18085 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
'use client';
import { Button } from "@/components/ui/Button";
import { Heading, Text } from "@/components/ui/Typography";
import { useAppDispatch, useAppSelector } from "@/lib/redux/hooks";
import { BoxItem, removeFromBox, selectBoxItems, selectBoxTotal, updateQuantity } from "@/lib/redux/slices/boxSlice";
import { Minus, Plus, Trash2 } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { AddBoxToCartClient } from "./AddBoxToCartClient";
export function BuildBoxSidebar() {
const dispatch = useAppDispatch();
const items = useAppSelector(selectBoxItems);
const boxTotal = useAppSelector(selectBoxTotal);
const isEmpty = items.length === 0;
const pathname = usePathname();
// Separate box containers from products
const boxContainers = items.filter(item => item.variantId === 'box-container');
const products = items.filter(item => item.variantId !== 'box-container');
// Check if we have products for the first step
const hasProducts = products.length > 0;
// Check if we have a selected box for the second step
const hasBoxSelected = boxContainers.length > 0;
// Determine which page we're on to show appropriate button text
const isCustomizePage = pathname.includes('/customize');
const nextStepUrl = "/build-box/customize";
const nextStepText = "Idući korak";
// Determine if the next step button should be disabled
const isNextStepDisabled = isEmpty || (!isCustomizePage && !hasProducts);
const handleUpdateQuantity = (id: string, color: string | undefined, newQuantity: number) => {
if (newQuantity < 1) return;
dispatch(updateQuantity({ id, color, quantity: newQuantity }));
};
const handleRemoveItem = (id: string, color: string | undefined) => {
dispatch(removeFromBox({ id, color }));
};
return (
<div className="h-full flex flex-col">
{/* Fixed header - adjusted padding to align with main page title */}
<div className="p-4 pt-8 pb-4 border-b">
<Heading level={3} className="text-center">Your box</Heading>
</div>
{/* Scrollable products area */}
<div className="flex-1 overflow-y-auto p-4">
{isEmpty ? (
<div className="text-center py-8 text-gray-500">
<Text>Your box is empty</Text>
<Text className="text-sm mt-2">Add products to create your box</Text>
</div>
) : (
<div className="space-y-6">
{/* Show box container if any */}
{boxContainers.length > 0 && (
<div className="border-b pb-4 mb-4">
<Text className="font-medium mb-2">Box Design</Text>
{boxContainers.map((box: BoxItem) => (
<div key={box.compositeKey || box.id} className="flex items-start space-x-3">
<div className="w-20 h-24 relative flex-shrink-0">
<Image
src={box.image}
alt={box.name}
fill
className="object-cover rounded-md"
/>
</div>
<div className="flex-grow">
<div className="flex justify-between">
<div>
<div className="text-base font-medium">{box.name}</div>
<div className="text-sm text-gray-500">${box.price}</div>
</div>
<button
onClick={() => handleRemoveItem(box.id, box.color)}
className="text-gray-400 hover:text-red-500"
aria-label="Remove box"
>
<Trash2 size={18} />
</button>
</div>
</div>
</div>
))}
</div>
)}
{/* Product items */}
{products.length > 0 && (
<>
{products.map((item: BoxItem) => (
<div key={item.compositeKey || item.id} className="flex items-start space-x-3">
<div className="w-20 h-24 relative flex-shrink-0">
<Image
src={item.image}
alt={item.name}
fill
className="object-cover rounded-md"
/>
</div>
<div className="flex-grow flex flex-col justify-between min-w-0">
<div className="flex justify-between">
<div className="max-w-[75%]">
<Text className="text-base font-medium break-words">{item.name}</Text>
<div className="flex items-center mt-1">
<Text className="text-sm text-gray-500">${item.price}</Text>
{item.color && (
<div
className="w-3 h-3 rounded-full ml-2"
style={{ backgroundColor: item.color }}
aria-label="Color"
/>
)}
</div>
</div>
<button
onClick={() => handleRemoveItem(item.id, item.color)}
className="text-gray-400 hover:text-red-500 ml-2"
aria-label="Remove item"
>
<Trash2 size={18} />
</button>
</div>
<div className="flex items-center mt-2">
<div className="flex border border-gray-300 rounded-md">
<button
onClick={() => handleUpdateQuantity(item.id, item.color, item.quantity - 1)}
className="px-2 py-1 border-r border-gray-300 text-gray-500 hover:bg-gray-100 disabled:opacity-50"
disabled={item.quantity <= 1}
aria-label="Decrease quantity"
>
<Minus size={14} />
</button>
<span className="px-3 py-1 flex items-center justify-center w-8 text-center">
{item.quantity}
</span>
<button
onClick={() => handleUpdateQuantity(item.id, item.color, item.quantity + 1)}
className="px-2 py-1 border-l border-gray-300 text-gray-500 hover:bg-gray-100"
aria-label="Increase quantity"
>
<Plus size={14} />
</button>
</div>
</div>
</div>
</div>
))}
</>
)}
</div>
)}
</div>
{/* Fixed price and button at bottom */}
<div className="border-t p-4">
<div className="flex justify-between mb-4">
<Text className="font-medium">Cijena boxa</Text>
<Text className="font-medium">${boxTotal.toFixed(2)}</Text>
</div>
{isCustomizePage ? (
<AddBoxToCartClient />
) : (
<Link
href={isNextStepDisabled ? "#" : nextStepUrl}
className={isNextStepDisabled ? "pointer-events-none" : ""}
>
<Button
variant="primary"
className="w-full py-3"
disabled={isNextStepDisabled}
>
{nextStepText}
</Button>
</Link>
)}
{!isCustomizePage && !hasProducts && !isEmpty && (
<Text className="text-xs text-center text-red-500 mt-2">
Add at least one product to proceed
</Text>
)}
{isCustomizePage && !hasBoxSelected && !isEmpty && (
<Text className="text-xs text-center text-red-500 mt-2">
Select a box design to continue
</Text>
)}
</div>
</div>
);
}