diff --git a/backend/src/main/java/com/matchmycoffee/controller/controlleradvice/OrderValidationErrorHandler.java b/backend/src/main/java/com/matchmycoffee/controller/controlleradvice/OrderValidationErrorHandler.java index aa6803d..c0c54cc 100644 --- a/backend/src/main/java/com/matchmycoffee/controller/controlleradvice/OrderValidationErrorHandler.java +++ b/backend/src/main/java/com/matchmycoffee/controller/controlleradvice/OrderValidationErrorHandler.java @@ -10,10 +10,12 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import java.time.Instant; @@ -57,6 +59,25 @@ public final ErrorResponse handleInsufficientStockException(InsufficientStockExc return buildErrorResponse(HttpStatus.CONFLICT, e.getMessage()); } + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public final ErrorResponse handleTypeMismatch(MethodArgumentTypeMismatchException e) { + log.debug("MethodArgumentTypeMismatchException occurred", e); + String message = String.format("Invalid value '%s' for parameter '%s'.", e.getValue(), e.getName()); + return buildErrorResponse(HttpStatus.BAD_REQUEST, message); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public final ErrorResponse handleValidationExceptions(MethodArgumentNotValidException e) { + log.debug("MethodArgumentNotValidException occurred", e); + String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + return buildErrorResponse(HttpStatus.BAD_REQUEST, message); + } + + @Data @Getter @Setter diff --git a/backend/src/main/java/com/matchmycoffee/dto/response/product/ProductSummaryResponse.java b/backend/src/main/java/com/matchmycoffee/dto/response/product/ProductSummaryResponse.java index 2395bd7..fa02874 100644 --- a/backend/src/main/java/com/matchmycoffee/dto/response/product/ProductSummaryResponse.java +++ b/backend/src/main/java/com/matchmycoffee/dto/response/product/ProductSummaryResponse.java @@ -20,4 +20,6 @@ public class ProductSummaryResponse { private Integer roastLevel; private Double averageRating; private Long reviewCount; + private Integer stock; + private Boolean isActive; } diff --git a/backend/src/main/java/com/matchmycoffee/service/impl/OrderServiceImpl.java b/backend/src/main/java/com/matchmycoffee/service/impl/OrderServiceImpl.java index ff4d7b8..a0a6566 100644 --- a/backend/src/main/java/com/matchmycoffee/service/impl/OrderServiceImpl.java +++ b/backend/src/main/java/com/matchmycoffee/service/impl/OrderServiceImpl.java @@ -135,11 +135,6 @@ public Order addItemToOrder(Long orderId, OrderItemRequest itemRequest) throw new IllegalOrderArgumentException("Order status is not pending!"); } - if (!product.getIsActive()) { - log.error("Product is not active: {}", product.getName()); - throw new IllegalOrderArgumentException("Product is not active!"); - } - // Create OrderItem with price from product OrderItem orderItem = new OrderItem(); orderItem.setOrder(savedOrder); diff --git a/frontend/README.md b/frontend/README.md index 524f668..7576501 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -205,10 +205,6 @@ Both contexts persist data to `localStorage` for a seamless user experience acro See `package.json` for the complete list of dependencies. -## đźš§ Development Status - -Currently, the frontend is using **mock data** from `src/utils/DummyProducts.ts`. The API integration layer in `src/services/main.api.ts` is prepared for backend integration but returns dummy data for now. - ### Next Steps - Connect to actual Spring Boot backend API diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index d5345aa..36c7428 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -41,7 +41,8 @@ "quantity": "Quantity", "writeReviewPlaceholder": "Write your review here...", "reviewerName": "Your Name", - "reviewerNamePlaceholder": "Enter your name" + "reviewerNamePlaceholder": "Enter your name", + "outOfStock": "Out of Stock" }, "title": "Match My Coffee", "description": "Find your perfect coffee match based on your preferences and taste!", diff --git a/frontend/src/components/cart/CheckoutModal.tsx b/frontend/src/components/cart/CheckoutModal.tsx index 2994727..cefc5a3 100644 --- a/frontend/src/components/cart/CheckoutModal.tsx +++ b/frontend/src/components/cart/CheckoutModal.tsx @@ -1,3 +1,4 @@ +import axios from 'axios'; import { useState } from 'react'; import { Alert, Modal, Spinner } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; @@ -25,7 +26,9 @@ function CheckoutModal(props: CheckoutModalProps) { const [error, setError] = useState(null); const handleConfirm = async () => { - if (!order) return; + if (!order) { + return; + } setIsSubmitting(true); setError(null); @@ -41,9 +44,21 @@ function CheckoutModal(props: CheckoutModalProps) { }, 5000); onHide(); } catch (err) { - console.error('Failed to submit order:', err); - const errorMessage = (err as { response?: { data?: { error?: string } } })?.response?.data?.error; - setError(errorMessage ?? t('checkout.orderSubmitError') ?? 'Failed to submit order. Please try again.'); + if (axios.isAxiosError(err)) { + const responseData = err.response?.data as + | { data: { message: string } } + | { message: string } + | undefined; + + const backendMessage = + (responseData && 'data' in responseData && responseData.data?.message) ?? + (responseData && 'message' in responseData && responseData.message) ?? + err.message; + + setError(backendMessage || 'An unexpected error occurred'); + } else { + setError('An unexpected error occurred'); + } } finally { setIsSubmitting(false); } diff --git a/frontend/src/components/common/ProductCard.tsx b/frontend/src/components/common/ProductCard.tsx index 9138322..b570725 100644 --- a/frontend/src/components/common/ProductCard.tsx +++ b/frontend/src/components/common/ProductCard.tsx @@ -1,13 +1,12 @@ -import { Button, ButtonGroup, Card, Dropdown, DropdownButton } from 'react-bootstrap'; +import { Button, Card } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { IoCloseSharp } from 'react-icons/io5'; import { Link } from 'react-router-dom'; import { useProductActions } from '../../hooks/useProductActions.ts'; import type { ProductSummary } from '../../types/ProductsType.ts'; -import AddToCartButton from '../common/AddToCartButton.tsx'; -import AddToFavoritesButton from '../common/AddToFavoritesButton.tsx'; import StarRating from '../common/StarRating.tsx'; +import CustomButtonGroup from '../product/CustomButtonGroup.tsx'; import style from './ProductCard.module.css'; interface ProductTypeProps { @@ -53,27 +52,18 @@ function ProductCard(props: ProductTypeProps) { - {inCart ? ( - <> - - {Array.from({ length: 10 }, (_, i) => i + 1).map((count) => ( - - {count} - - ))} - - + {data.isActive || data.stock > 0 ? ( +
+ +
) : ( - +
{t('product.outOfStock')}
)} - ); diff --git a/frontend/src/components/product/CustomButtonGroup.tsx b/frontend/src/components/product/CustomButtonGroup.tsx new file mode 100644 index 0000000..1b82cf8 --- /dev/null +++ b/frontend/src/components/product/CustomButtonGroup.tsx @@ -0,0 +1,43 @@ +import { ButtonGroup, Dropdown, DropdownButton } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import type { ProductSummary } from '../../types/ProductsType.ts'; +import AddToCartButton from '../common/AddToCartButton.tsx'; +import AddToFavoritesButton from '../common/AddToFavoritesButton.tsx'; + +interface CustomButtonGroupProps { + inCart?: boolean; + data: ProductSummary; + currentQuantity: number; + handleQuantitySelect: (eventKey: string | null) => void; +} + +function CustomButtonGroup({ inCart, data, currentQuantity, handleQuantitySelect }: CustomButtonGroupProps) { + const { t } = useTranslation(); + + return ( + <> + {inCart ? ( + + {Array.from({ length: Math.min(10, data.stock) }, (_, i) => i + 1).map((count) => ( + + {count} + + ))} + + ) : ( + + )} + + + ); +} + +export default CustomButtonGroup; diff --git a/frontend/src/pages/CartCheckoutPage.tsx b/frontend/src/pages/CartCheckoutPage.tsx index ccb9c9a..e030b99 100644 --- a/frontend/src/pages/CartCheckoutPage.tsx +++ b/frontend/src/pages/CartCheckoutPage.tsx @@ -1,3 +1,4 @@ +import axios from 'axios'; import React, { useContext, useState } from 'react'; import { Alert, Dropdown, DropdownButton, Form } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; @@ -105,9 +106,19 @@ function CartCheckoutPage() { setCreatedOrder(order); setModalShow(true); } catch (err) { - console.error('Failed to create order:', err); - const errorMessage = (err as { response?: { data?: { error?: string } } })?.response?.data?.error; - setError(errorMessage ?? t('checkout.orderCreationError') ?? 'Failed to create order. Please try again.'); + if (axios.isAxiosError(err)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const responseData = err.response?.data; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const backendMessage = responseData?.data?.message ?? responseData?.message ?? err.message; + + setError( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + backendMessage ?? t('checkout.orderCreationError') ?? 'Failed to create order. Please try again.', + ); + } else { + setError(t('checkout.orderCreationError') ?? 'Failed to create order. Please try again.'); + } } finally { setIsSubmitting(false); } diff --git a/frontend/src/pages/ProductPage.tsx b/frontend/src/pages/ProductPage.tsx index d4d29c1..fa29f80 100644 --- a/frontend/src/pages/ProductPage.tsx +++ b/frontend/src/pages/ProductPage.tsx @@ -51,10 +51,14 @@ function ProductPage() {

{product.price} $

-
- - -
+ {product.isActive && product.stock > 0 ? ( +
+ + +
+ ) : ( +

{t('product.outOfStock')}

+ )}
@@ -121,23 +125,25 @@ function ProductPage() { -
-
-

{t('product.reviews')}

- - {Array.isArray(reviews) && reviews.length !== 0 - ? reviews.map((review, index) => ( -
-
-

{review.authorName}

- + {product.isActive && product.stock > 0 ? ( +
+
+

{t('product.reviews')}

+ + {Array.isArray(reviews) && reviews.length !== 0 + ? reviews.map((review, index) => ( +
+
+

{review.authorName}

+ +
+

{review.comment}

-

{review.comment}

-
- )) - : null} -
-
+ )) + : null} + + + ) : null} ); diff --git a/frontend/src/types/ProductsType.ts b/frontend/src/types/ProductsType.ts index 51658be..8fb6ff4 100644 --- a/frontend/src/types/ProductsType.ts +++ b/frontend/src/types/ProductsType.ts @@ -6,6 +6,8 @@ export interface ProductSummary { price: number; imageUrl: string; isBlend: boolean; + stock: number; + isActive: boolean; specifications: { roastLevel: number; @@ -17,8 +19,6 @@ export interface ProductSummary { export interface ProductDetail extends ProductSummary { description: string; - stock: number; - isActive: boolean; specifications: { roastLevel: number; diff --git a/frontend/src/utils/DummyProducts.ts b/frontend/src/utils/DummyProducts.ts deleted file mode 100644 index 59c9992..0000000 --- a/frontend/src/utils/DummyProducts.ts +++ /dev/null @@ -1,1675 +0,0 @@ -import type { BlogPostDetailedType, BlogPostType } from '../types/BlogPostType.ts'; -import type { - ProductBrewingMethod, - ProductDetail, - ProductOrigin, - ProductSummary, - ProductTaste, -} from '../types/ProductsType.ts'; -import type { ReviewType } from '../types/ReviewType.ts'; - -const RAW_PRODUCTS = [ - { - name: 'Ethiopian Yirgacheffe', - id: 1, - description: 'Bright and floral with notes of citrus and berries', - price: 16.99, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-01-15T10:00:00Z', - updated_at: '2024-01-15T10:00:00Z', - }, - { - name: 'Colombian Supremo', - id: 2, - description: 'Rich and balanced with chocolate undertones', - price: 14.99, - stock: 75, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-01-15T10:00:00Z', - updated_at: '2024-01-15T10:00:00Z', - }, - { - name: 'House Blend', - id: 3, - description: 'A smooth everyday blend perfect for any occasion', - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - price: 12.99, - stock: 100, - is_active: true, - is_blend: true, - roast_level: 3, - created_at: '2024-01-15T10:00:00Z', - updated_at: '2024-01-15T10:00:00Z', - }, - { - name: 'Sumatra Mandheling', - id: 4, - description: 'Full-bodied with earthy and herbal notes', - price: 15.99, - stock: 60, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 2, - created_at: '2024-01-16T10:00:00Z', - updated_at: '2024-01-16T10:00:00Z', - }, - { - name: 'Kenya AA', - id: 5, - description: 'Bold and wine-like with blackcurrant notes', - price: 17.99, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-01-16T10:00:00Z', - updated_at: '2024-01-16T10:00:00Z', - }, - { - name: 'Guatemala Antigua', - id: 6, - description: 'Smooth with cocoa and spice flavors', - price: 15.49, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-01-17T10:00:00Z', - updated_at: '2024-01-17T10:00:00Z', - }, - { - name: 'Costa Rica Tarrazu', - id: 7, - description: 'Clean and crisp with bright acidity', - price: 16.49, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-01-17T10:00:00Z', - updated_at: '2024-01-17T10:00:00Z', - }, - { - name: 'Brazilian Santos', - id: 8, - description: 'Mild and nutty with low acidity', - price: 13.99, - stock: 80, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-01-18T10:00:00Z', - updated_at: '2024-01-18T10:00:00Z', - }, - { - name: 'French Roast', - id: 9, - description: 'Bold and smoky with intense flavor', - price: 14.49, - stock: 70, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 5, - acidity_score: 1, - created_at: '2024-01-18T10:00:00Z', - updated_at: '2024-01-18T10:00:00Z', - }, - { - name: 'Italian Roast', - id: 10, - description: 'Dark and robust with a bittersweet finish', - price: 14.49, - stock: 65, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 5, - acidity_score: 1, - created_at: '2024-01-19T10:00:00Z', - updated_at: '2024-01-19T10:00:00Z', - }, - { - name: 'Mexican Chiapas', - id: 11, - description: 'Light-bodied with delicate chocolate notes', - price: 14.99, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-01-19T10:00:00Z', - updated_at: '2024-01-19T10:00:00Z', - }, - { - name: 'Papua New Guinea', - id: 12, - description: 'Sweet and complex with fruity undertones', - price: 16.99, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 4, - created_at: '2024-01-20T10:00:00Z', - updated_at: '2024-01-20T10:00:00Z', - }, - { - name: 'Jamaican Blue Mountain', - id: 13, - description: 'Exceptionally smooth with mild flavor', - price: 39.99, - stock: 20, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 2, - created_at: '2024-01-20T10:00:00Z', - updated_at: '2024-01-20T10:00:00Z', - }, - { - name: 'Hawaiian Kona', - id: 14, - description: 'Rich and aromatic with a smooth finish', - price: 34.99, - stock: 25, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-01-21T10:00:00Z', - updated_at: '2024-01-21T10:00:00Z', - }, - { - name: 'Breakfast Blend', - id: 15, - description: 'Light and lively to start your day', - price: 12.49, - stock: 90, - is_active: true, - is_blend: true, - roast_level: 2, - acidity_score: 4, - created_at: '2024-01-21T10:00:00Z', - updated_at: '2024-01-21T10:00:00Z', - }, - { - name: 'Espresso Blend', - id: 16, - description: 'Bold and intense for perfect espresso shots', - price: 15.99, - stock: 75, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: true, - roast_level: 4, - acidity_score: 2, - created_at: '2024-01-22T10:00:00Z', - updated_at: '2024-01-22T10:00:00Z', - }, - { - name: 'Decaf Colombian', - id: 17, - description: 'All the flavor without the caffeine', - price: 14.99, - stock: 60, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-01-22T10:00:00Z', - updated_at: '2024-01-22T10:00:00Z', - }, - { - name: 'Yemen Mocha', - id: 18, - description: 'Exotic and winey with chocolate notes', - price: 24.99, - stock: 30, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 4, - created_at: '2024-01-23T10:00:00Z', - updated_at: '2024-01-23T10:00:00Z', - }, - { - name: 'Rwanda Bourbon', - id: 19, - description: 'Sweet and fruity with floral hints', - price: 17.49, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-01-23T10:00:00Z', - updated_at: '2024-01-23T10:00:00Z', - }, - { - name: 'Tanzanian Peaberry', - id: 20, - description: 'Bright and clean with wine-like acidity', - price: 18.99, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-01-24T10:00:00Z', - updated_at: '2024-01-24T10:00:00Z', - }, - { - name: 'Indian Monsoon Malabar', - id: 21, - description: 'Mellow and spicy with low acidity', - price: 16.49, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 1, - created_at: '2024-01-24T10:00:00Z', - updated_at: '2024-01-24T10:00:00Z', - }, - { - name: 'Nicaragua Jinotega', - id: 22, - description: 'Balanced with chocolate and caramel notes', - price: 15.49, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-01-25T10:00:00Z', - updated_at: '2024-01-25T10:00:00Z', - }, - { - name: 'Peru Organic', - id: 23, - description: 'Smooth and mild with nutty sweetness', - price: 15.99, - stock: 60, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-01-25T10:00:00Z', - updated_at: '2024-01-25T10:00:00Z', - }, - { - name: 'Vietnamese Robusta', - id: 24, - description: 'Strong and bold with high caffeine', - price: 11.99, - stock: 70, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 1, - created_at: '2024-01-26T10:00:00Z', - updated_at: '2024-01-26T10:00:00Z', - }, - { - name: 'Honduras Marcala', - id: 25, - description: 'Sweet and fruity with caramel finish', - price: 14.99, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-01-26T10:00:00Z', - updated_at: '2024-01-26T10:00:00Z', - }, - { - name: 'El Salvador Pacamara', - id: 26, - description: 'Complex and creamy with berry notes', - price: 18.49, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-01-27T10:00:00Z', - updated_at: '2024-01-27T10:00:00Z', - }, - { - name: 'Burundi Kayanza', - id: 27, - description: 'Vibrant and juicy with citrus notes', - price: 17.99, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-01-27T10:00:00Z', - updated_at: '2024-01-27T10:00:00Z', - }, - { - name: 'Panama Geisha', - id: 28, - description: 'Exquisite floral and jasmine notes', - price: 49.99, - stock: 15, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 1, - acidity_score: 5, - created_at: '2024-01-28T10:00:00Z', - updated_at: '2024-01-28T10:00:00Z', - }, - { - name: 'Ugandan Bugisu', - id: 29, - description: 'Bold with winey and fruity character', - price: 15.49, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 4, - created_at: '2024-01-28T10:00:00Z', - updated_at: '2024-01-28T10:00:00Z', - }, - { - name: 'Thai Doi Chaang', - id: 30, - description: 'Smooth with chocolate and spice notes', - price: 16.99, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-01-29T10:00:00Z', - updated_at: '2024-01-29T10:00:00Z', - }, - { - name: 'Malawi Mzuzu', - id: 31, - description: 'Bright and sweet with floral hints', - price: 16.49, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-01-29T10:00:00Z', - updated_at: '2024-01-29T10:00:00Z', - }, - { - name: 'Zambian Northern', - id: 32, - description: 'Clean and balanced with honey sweetness', - price: 15.99, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-01-30T10:00:00Z', - updated_at: '2024-01-30T10:00:00Z', - }, - { - name: 'Bolivian Yungas', - id: 33, - description: 'Delicate with caramel and citrus notes', - price: 17.49, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-01-30T10:00:00Z', - updated_at: '2024-01-30T10:00:00Z', - }, - { - name: 'Ecuador Vilcabamba', - id: 34, - description: 'Smooth and chocolatey with low acidity', - price: 15.49, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-01-31T10:00:00Z', - updated_at: '2024-01-31T10:00:00Z', - }, - { - name: 'Dominican Barahona', - id: 35, - description: 'Rich and full-bodied with chocolate notes', - price: 14.99, - stock: 60, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-01-31T10:00:00Z', - updated_at: '2024-01-31T10:00:00Z', - }, - { - name: 'Puerto Rican Yauco', - id: 36, - description: 'Balanced with hints of spice and citrus', - price: 19.99, - stock: 30, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-02-01T10:00:00Z', - updated_at: '2024-02-01T10:00:00Z', - }, - { - name: 'Cuban Serrano', - id: 37, - description: 'Earthy and robust with tobacco notes', - price: 22.99, - stock: 25, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 2, - created_at: '2024-02-01T10:00:00Z', - updated_at: '2024-02-01T10:00:00Z', - }, - { - name: 'Haitian Blue', - id: 38, - description: 'Sweet and mellow with chocolate tones', - price: 17.99, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-02-02T10:00:00Z', - updated_at: '2024-02-02T10:00:00Z', - }, - { - name: 'Venezuelan Maracaibo', - id: 39, - description: 'Delicate with wine-like sweetness', - price: 18.99, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-02T10:00:00Z', - updated_at: '2024-02-02T10:00:00Z', - }, - { - name: 'Morning Glory Blend', - id: 40, - description: 'Bright and energizing breakfast coffee', - price: 13.49, - stock: 85, - is_active: true, - is_blend: true, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-03T10:00:00Z', - updated_at: '2024-02-03T10:00:00Z', - }, - { - name: 'Midnight Roast', - id: 41, - description: 'Deep and dark for late-night brewing', - price: 14.99, - stock: 70, - is_active: true, - is_blend: true, - roast_level: 5, - acidity_score: 1, - created_at: '2024-02-03T10:00:00Z', - updated_at: '2024-02-03T10:00:00Z', - }, - { - name: 'Alpine Blend', - id: 42, - description: 'Mountain-grown beans with crisp clarity', - price: 15.49, - stock: 65, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-04T10:00:00Z', - updated_at: '2024-02-04T10:00:00Z', - }, - { - name: 'Tropical Fusion', - id: 43, - description: 'Exotic fruits and floral aromatics', - price: 16.99, - stock: 55, - is_active: true, - is_blend: true, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-04T10:00:00Z', - updated_at: '2024-02-04T10:00:00Z', - }, - { - name: 'Smokehouse Blend', - id: 44, - description: 'Bold and smoky with intense character', - price: 15.49, - stock: 60, - is_active: true, - is_blend: true, - roast_level: 5, - acidity_score: 1, - created_at: '2024-02-05T10:00:00Z', - updated_at: '2024-02-05T10:00:00Z', - }, - { - name: 'Velvet Blend', - id: 45, - description: 'Smooth and creamy with chocolate notes', - price: 14.49, - stock: 75, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-05T10:00:00Z', - updated_at: '2024-02-05T10:00:00Z', - }, - { - name: 'Heritage Blend', - id: 46, - description: 'Traditional roasting for timeless taste', - price: 13.99, - stock: 80, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-06T10:00:00Z', - updated_at: '2024-02-06T10:00:00Z', - }, - { - name: 'Artisan Blend', - id: 47, - description: 'Carefully crafted small-batch roast', - price: 17.99, - stock: 50, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-06T10:00:00Z', - updated_at: '2024-02-06T10:00:00Z', - }, - { - name: 'Java Estate', - id: 48, - description: 'Classic Indonesian with earthy depth', - price: 16.49, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 2, - created_at: '2024-02-07T10:00:00Z', - updated_at: '2024-02-07T10:00:00Z', - }, - { - name: 'Sulawesi Toraja', - id: 49, - description: 'Rich and complex with herbal notes', - price: 18.49, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 2, - created_at: '2024-02-07T10:00:00Z', - updated_at: '2024-02-07T10:00:00Z', - }, - { - name: 'Bali Blue Moon', - id: 50, - description: 'Sweet and syrupy with chocolate undertones', - price: 17.49, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-08T10:00:00Z', - updated_at: '2024-02-08T10:00:00Z', - }, - { - name: 'Laos Bolaven Plateau', - id: 51, - description: 'Smooth with chocolate and caramel notes', - price: 16.99, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-08T10:00:00Z', - updated_at: '2024-02-08T10:00:00Z', - }, - { - name: 'Myanmar Shan', - id: 52, - description: 'Delicate and floral with tea-like quality', - price: 19.99, - stock: 30, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-09T10:00:00Z', - updated_at: '2024-02-09T10:00:00Z', - }, - { - name: 'Nepal Himalayan', - id: 53, - description: 'Bright and clean with citrus notes', - price: 18.99, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-09T10:00:00Z', - updated_at: '2024-02-09T10:00:00Z', - }, - { - name: 'Chinese Yunnan', - id: 54, - description: 'Earthy and mellow with chocolate hints', - price: 15.99, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-10T10:00:00Z', - updated_at: '2024-02-10T10:00:00Z', - }, - { - name: 'Australian Skybury', - id: 55, - description: 'Clean and bright with nutty sweetness', - price: 21.99, - stock: 25, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-02-10T10:00:00Z', - updated_at: '2024-02-10T10:00:00Z', - }, - { - name: 'Timor-Leste Organic', - id: 56, - description: 'Rich and earthy with spicy notes', - price: 17.49, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-11T10:00:00Z', - updated_at: '2024-02-11T10:00:00Z', - }, - { - name: 'Madagascar Antalaha', - id: 57, - description: 'Sweet and fruity with vanilla notes', - price: 19.49, - stock: 30, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-11T10:00:00Z', - updated_at: '2024-02-11T10:00:00Z', - }, - { - name: 'Zimbabwe AA', - id: 58, - description: 'Bright and wine-like with berry notes', - price: 17.99, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-02-12T10:00:00Z', - updated_at: '2024-02-12T10:00:00Z', - }, - { - name: 'Cameroon Boyo', - id: 59, - description: 'Full-bodied with cocoa and spice', - price: 16.49, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-12T10:00:00Z', - updated_at: '2024-02-12T10:00:00Z', - }, - { - name: 'Angola Ambriz', - id: 60, - description: 'Smooth and balanced with nutty character', - price: 15.49, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-13T10:00:00Z', - updated_at: '2024-02-13T10:00:00Z', - }, - { - name: 'Congo Kivu', - id: 61, - description: 'Complex with berry and floral notes', - price: 18.49, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-13T10:00:00Z', - updated_at: '2024-02-13T10:00:00Z', - }, - { - name: 'Sierra Leone Gola', - id: 62, - description: 'Mild and sweet with chocolate tones', - price: 16.99, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-14T10:00:00Z', - updated_at: '2024-02-14T10:00:00Z', - }, - { - name: 'Ivory Coast Daloa', - id: 63, - description: 'Bold and earthy with cocoa notes', - price: 14.99, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 2, - created_at: '2024-02-14T10:00:00Z', - updated_at: '2024-02-14T10:00:00Z', - }, - { - name: 'Togo Kpalimé', - id: 64, - description: 'Clean and bright with citrus hints', - price: 15.99, - stock: 45, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-15T10:00:00Z', - updated_at: '2024-02-15T10:00:00Z', - }, - { - name: 'Decaf House Blend', - id: 65, - description: 'Full flavor without caffeine', - price: 13.99, - stock: 70, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-15T10:00:00Z', - updated_at: '2024-02-15T10:00:00Z', - }, - { - name: 'Decaf French Roast', - id: 66, - description: 'Dark and bold without the buzz', - price: 14.49, - stock: 60, - is_active: true, - is_blend: false, - roast_level: 5, - acidity_score: 1, - created_at: '2024-02-16T10:00:00Z', - updated_at: '2024-02-16T10:00:00Z', - }, - { - name: 'Organic Fair Trade Blend', - id: 67, - description: 'Ethically sourced with balanced flavor', - price: 16.49, - stock: 65, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-16T10:00:00Z', - updated_at: '2024-02-16T10:00:00Z', - }, - { - name: 'Single Origin Sampler', - id: 68, - description: 'Variety pack of our finest origins', - price: 29.99, - stock: 40, - is_active: true, - is_blend: true, - roast_level: 3, - created_at: '2024-02-17T10:00:00Z', - updated_at: '2024-02-17T10:00:00Z', - }, - { - name: 'Dark Roast Sampler', - id: 69, - description: 'Collection of our boldest roasts', - price: 24.99, - stock: 45, - is_active: true, - is_blend: true, - roast_level: 5, - acidity_score: 1, - created_at: '2024-02-17T10:00:00Z', - updated_at: '2024-02-17T10:00:00Z', - }, - { - name: 'Light Roast Collection', - id: 70, - description: 'Bright and vibrant coffee selection', - price: 26.99, - stock: 40, - is_active: true, - is_blend: true, - roast_level: 1, - acidity_score: 5, - created_at: '2024-02-18T10:00:00Z', - updated_at: '2024-02-18T10:00:00Z', - }, - { - name: 'Cold Brew Blend', - id: 71, - description: 'Optimized for smooth cold brewing', - price: 15.99, - stock: 80, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-18T10:00:00Z', - updated_at: '2024-02-18T10:00:00Z', - }, - { - name: 'Pour Over Blend', - id: 72, - description: 'Perfect balance for manual brewing', - price: 16.49, - stock: 70, - is_active: true, - is_blend: true, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-19T10:00:00Z', - updated_at: '2024-02-19T10:00:00Z', - }, - { - name: 'Moka Pot Blend', - id: 73, - description: 'Bold flavor for stovetop espresso', - price: 14.99, - stock: 65, - is_active: true, - is_blend: true, - roast_level: 4, - acidity_score: 2, - created_at: '2024-02-19T10:00:00Z', - updated_at: '2024-02-19T10:00:00Z', - }, - { - name: 'Aeropress Blend', - id: 74, - description: 'Clean and bright for Aeropress brewing', - price: 15.49, - stock: 75, - is_active: true, - is_blend: true, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-20T10:00:00Z', - updated_at: '2024-02-20T10:00:00Z', - }, - { - name: 'Chemex Blend', - id: 75, - description: 'Clarity and complexity for filter coffee', - price: 16.99, - stock: 60, - is_active: true, - is_blend: true, - roast_level: 2, - acidity_score: 4, - created_at: '2024-02-20T10:00:00Z', - updated_at: '2024-02-20T10:00:00Z', - }, - { - name: 'Vanilla Cream Flavored', - id: 76, - description: 'Smooth vanilla and cream notes', - price: 13.99, - stock: 85, - is_active: true, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-21T10:00:00Z', - updated_at: '2024-02-21T10:00:00Z', - }, - { - name: 'Hazelnut Flavored', - id: 77, - description: 'Rich hazelnut aroma and taste', - price: 13.99, - stock: 90, - is_active: true, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-21T10:00:00Z', - updated_at: '2024-02-21T10:00:00Z', - }, - { - name: 'Caramel Macchiato Flavored', - id: 78, - description: 'Sweet caramel and espresso flavor', - price: 14.49, - stock: 75, - is_active: true, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-22T10:00:00Z', - updated_at: '2024-02-22T10:00:00Z', - }, - { - name: 'Irish Cream Flavored', - id: 79, - description: 'Creamy with hints of whiskey flavor', - price: 14.49, - stock: 70, - is_active: true, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-22T10:00:00Z', - updated_at: '2024-02-22T10:00:00Z', - }, - { - name: 'Cinnamon Roll Flavored', - id: 80, - description: 'Sweet cinnamon and vanilla pastry notes', - price: 13.99, - stock: 80, - is_active: true, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-23T10:00:00Z', - updated_at: '2024-02-23T10:00:00Z', - }, - { - name: 'Pumpkin Spice Seasonal', - id: 81, - description: 'Warm spices perfect for autumn', - price: 14.99, - stock: 60, - is_active: false, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-23T10:00:00Z', - updated_at: '2024-02-23T10:00:00Z', - }, - { - name: 'Peppermint Mocha Seasonal', - id: 82, - description: 'Festive chocolate and mint combination', - price: 14.99, - stock: 55, - is_active: false, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-24T10:00:00Z', - updated_at: '2024-02-24T10:00:00Z', - }, - { - name: 'Eggnog Seasonal', - id: 83, - description: 'Creamy holiday spice flavor', - price: 14.99, - stock: 50, - is_active: false, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-24T10:00:00Z', - updated_at: '2024-02-24T10:00:00Z', - }, - { - name: 'Gingerbread Seasonal', - id: 84, - description: 'Spicy ginger and molasses notes', - price: 14.99, - stock: 50, - is_active: false, - is_blend: false, - roast_level: 3, - acidity_score: 2, - created_at: '2024-02-25T10:00:00Z', - updated_at: '2024-02-25T10:00:00Z', - }, - { - name: 'Spring Blossom Seasonal', - id: 85, - description: 'Light and floral spring blend', - price: 15.99, - stock: 45, - is_active: false, - is_blend: true, - roast_level: 1, - acidity_score: 5, - created_at: '2024-02-25T10:00:00Z', - updated_at: '2024-02-25T10:00:00Z', - }, - { - name: 'Summer Citrus Seasonal', - id: 86, - description: 'Bright and refreshing summer blend', - price: 15.99, - stock: 50, - is_active: false, - is_blend: true, - roast_level: 2, - acidity_score: 5, - created_at: '2024-02-26T10:00:00Z', - updated_at: '2024-02-26T10:00:00Z', - }, - { - name: 'Autumn Harvest Seasonal', - id: 87, - description: 'Warm and cozy fall blend', - price: 15.99, - stock: 55, - is_active: true, - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-26T10:00:00Z', - updated_at: '2024-02-26T10:00:00Z', - }, - { - name: 'Winter Warmer Seasonal', - id: 88, - description: 'Rich and comforting winter blend', - price: 15.99, - stock: 60, - is_active: true, - is_blend: true, - roast_level: 4, - acidity_score: 2, - created_at: '2024-02-27T10:00:00Z', - updated_at: '2024-02-27T10:00:00Z', - }, - { - name: 'Organic Ethiopia Natural', - id: 89, - description: 'Berry-forward organic processing', - price: 18.99, - stock: 40, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-02-27T10:00:00Z', - updated_at: '2024-02-27T10:00:00Z', - }, - { - name: 'Organic Peru Fair Trade', - id: 90, - description: 'Smooth organic with fair trade certification', - price: 16.99, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 3, - created_at: '2024-02-28T10:00:00Z', - updated_at: '2024-02-28T10:00:00Z', - }, - { - name: 'Organic Mexico', - id: 91, - description: 'Mild organic with chocolate notes', - price: 15.99, - stock: 60, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-02-28T10:00:00Z', - updated_at: '2024-02-28T10:00:00Z', - }, - { - name: 'Organic Sumatra', - id: 92, - description: 'Full-bodied organic Indonesian', - price: 17.49, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 4, - acidity_score: 2, - created_at: '2024-03-01T10:00:00Z', - updated_at: '2024-03-01T10:00:00Z', - }, - { - name: 'Organic Guatemala', - id: 93, - description: 'Balanced organic with cocoa notes', - price: 16.49, - stock: 55, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 3, - acidity_score: 3, - created_at: '2024-03-01T10:00:00Z', - updated_at: '2024-03-01T10:00:00Z', - }, - { - name: 'Micro-lot Honduras', - id: 94, - description: 'Limited edition small farm lot', - price: 22.99, - stock: 20, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-03-02T10:00:00Z', - updated_at: '2024-03-02T10:00:00Z', - }, - { - name: 'Micro-lot Colombia', - id: 95, - description: 'Rare single-farm Colombian coffee', - price: 24.99, - stock: 18, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-03-02T10:00:00Z', - updated_at: '2024-03-02T10:00:00Z', - }, - { - name: 'Competition Grade Kenya', - id: 96, - description: 'Award-winning auction lot', - price: 29.99, - stock: 15, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-03-03T10:00:00Z', - updated_at: '2024-03-03T10:00:00Z', - }, - { - name: 'Honey Processed Costa Rica', - id: 97, - description: 'Sweet honey process method', - price: 19.99, - stock: 35, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 4, - created_at: '2024-03-03T10:00:00Z', - updated_at: '2024-03-03T10:00:00Z', - }, - { - name: 'Anaerobic Ethiopia', - id: 98, - description: 'Experimental fermentation process', - price: 26.99, - stock: 25, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-03-04T10:00:00Z', - updated_at: '2024-03-04T10:00:00Z', - }, - { - name: 'Carbonic Maceration Colombia', - id: 99, - description: 'Wine-inspired processing technique', - price: 27.99, - stock: 22, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: false, - roast_level: 2, - acidity_score: 5, - created_at: '2024-03-04T10:00:00Z', - updated_at: '2024-03-04T10:00:00Z', - }, - { - name: 'Anniversary Blend', - id: 100, - description: 'Special blend celebrating our heritage', - price: 19.99, - stock: 50, - is_active: true, - image_url: 'https://www.zozocafe.ro/userfiles/f73e7d77-ff3b-4c52-9475-e668bd626300/products/361737637_big.jpg', - is_blend: true, - roast_level: 3, - acidity_score: 3, - created_at: '2024-03-05T10:00:00Z', - updated_at: '2024-03-05T10:00:00Z', - }, -]; - -const getMockOrigins = (name: string, isBlend: boolean): ProductOrigin[] => { - if (isBlend) { - return [ - { id: 1, region: 'South America', continent: 'South America', percentage: 60 }, - { id: 2, region: 'Africa', continent: 'Africa', percentage: 40 }, - ]; - } - - // Simple logic to guess origin from name - const region = name.split(' ')[0]; // e.g., "Ethiopian" from "Ethiopian Yirgacheffe" - return [{ id: 1, region: region, continent: 'Unknown', percentage: 100 }]; -}; - -const getMockTastes = (description: string): ProductTaste[] => { - const tastes: ProductTaste[] = []; - const descLower = description.toLowerCase(); - - if (descLower.includes('chocolate') || descLower.includes('cocoa')) { - tastes.push({ name: 'Chocolate', category: { name: 'Sweet', colorCode: '#795548' } }); - } - if (descLower.includes('berry') || descLower.includes('fruit') || descLower.includes('citrus')) { - tastes.push({ name: 'Fruity', category: { name: 'Fruity', colorCode: '#FF9800' } }); - } - if (descLower.includes('floral') || descLower.includes('jasmine')) { - tastes.push({ name: 'Floral', category: { name: 'Floral', colorCode: '#E91E63' } }); - } - if (descLower.includes('spice') || descLower.includes('earthy')) { - tastes.push({ name: 'Spicy', category: { name: 'Spicy', colorCode: '#8D6E63' } }); - } - - // Default if no matches - if (tastes.length === 0) { - tastes.push({ name: 'Balanced', category: { name: 'Classic', colorCode: '#607D8B' } }); - } - return tastes; -}; - -const getMockImages = (productId: number): string => { - const images = [ - 'https://cafeo.ro/4261-large_default/kimbo-intenso-cafea-boabe-1kg.jpg', - 'https://www.cafemagia.ro/images/produse/davidoff-cafe-espresso-57-1-kg-cafea-prajita-boabe-edit.jpg', - 'https://lcdn.altex.ro/media/catalog/product/9/0/9000403895358_1_89b28c6f.jpg', - 'https://deutschermarkt.ro/wp-content/uploads/2020/07/Cafea-boabe-Jacobs-Caffe-Crema-Classico-1Kg-1.jpg', - 'https://c.cdnmp.net/947378707/p/m/5/kimbo-barista-espresso-napoli-cafea-boabe-1kg~24725.jpg', - 'https://cdn.bestvalue.eu/media/cache/sylius_shop_product_original/borbone-cafea-cafea-boabe-espresso-intenso-1000-gr-112df136f053cec320d2ff00.jpg', - 'https://imgproxy-retcat.assets.schwarz/7O4_IvbfKebQfgdGhZRdypQledrNr0v-mIroUBmb8ow/sm:1/w:1278/h:959/cz/M6Ly9wcm9kLWNhd/GFsb2ctbWVkaWEvcm8vMS9FQzZENDFDQTJCQkNGMTdBMERBQjMyMEY/2NjFDQTcxOUUwODU1QjUyMTU4NTY4MEY3ODhGRTEwRDIyQjU1NUZGLmpwZw.jpg', - 'https://gomagcdn.ro/domains/coffeepoint.ro/files/product/large/doncafe-espresso-intense-cafea-boabe-1-kg-439710.webp', - 'https://noircoffee.ro/cdn/shop/files/noir-ethiopia-1_2048x.jpg?v=1695807890', - 'https://gomagcdn.ro/domains/cafea-premium.ro/files/product/medium/dallmayr-ethiopia-cafea-boabe-500g-copie-665376.webp', - ]; - - return images[productId % images.length]; -}; - -const getMockBrewing = (roast: number): ProductBrewingMethod[] => { - const methods = []; - if (roast <= 2) { - methods.push({ - id: 1, - name: 'Pour Over', - iconUrl: null, - isOptimal: true, - description: 'Best for bright flavors', - }); - } else if (roast >= 4) { - methods.push({ id: 2, name: 'Espresso', iconUrl: null, isOptimal: true, description: 'Best for rich body' }); - } else { - methods.push({ - id: 3, - name: 'Drip Coffee', - iconUrl: null, - isOptimal: true, - description: 'Great for everyday brewing', - }); - } - return methods; -}; - -const NAMES = [ - 'Alex Johnson', - 'Sam Smith', - 'Taylor Doe', - 'Jordan Brown', - 'Casey Lee', - 'Jamie Wilson', - 'Riley Miller', - 'Morgan Davis', - 'Drew Evans', - 'Avery Clark', -]; - -const COMMENTS = [ - "Absolutely delicious! Best coffee I've had in years.", - 'A bit too bitter for my taste, but still good quality.', - 'Fast delivery and great packaging. The aroma is amazing.', - 'Perfect morning brew. Highly recommended!', - 'It was okay, but I expected more given the price.', - 'Smooth texture and rich flavor. Will buy again.', - 'Not my favorite, a bit too acidic.', - 'Five stars! The roast level is exactly as described.', - null, - 'Bought this as a gift and they loved it!', -]; - -const getRandomDate = () => { - const start = new Date(2023, 0, 1); - const end = new Date(); - const date = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); - return date.toISOString(); -}; - -export const MOCK_REVIEWS: ReviewType[] = Array.from({ length: Math.floor(Math.random() * 1000) }).map((_, index) => ({ - id: index + 1, - productId: Math.floor(Math.random() * 100) + 1, - authorName: NAMES[Math.floor(Math.random() * NAMES.length)], - rating: Math.floor(Math.random() * 5) + 1, - comment: COMMENTS[Math.floor(Math.random() * COMMENTS.length)], - isApproved: Math.random() > 0.1, - createdAt: getRandomDate(), -})); - -export const PRODUCT_DETAIL: ProductDetail[] = RAW_PRODUCTS.map((product) => { - const productReviews = MOCK_REVIEWS.filter((review) => review.productId === product.id); - - const count = productReviews.length; - - const totalStars = productReviews.reduce((sum, review) => sum + review.rating, 0); - const avg = count > 0 ? totalStars / count : 0; - - return { - id: product.id, - name: product.name, - description: product.description, - stock: product.stock, - isActive: product.is_active, - price: product.price, - imageUrl: getMockImages(product.id), - isBlend: product.is_blend, - - specifications: { - roastLevel: product.roast_level, - acidityScore: product.acidity_score ?? 3, - }, - - origins: getMockOrigins(product.name, product.is_blend), - - tastes: getMockTastes(product.description), - - averageRating: parseFloat(avg.toFixed(1)), - reviewCount: count, - - brewingMethods: getMockBrewing(product.roast_level), - }; -}); - -export const ALL_PRODUCT_SUMMARY: ProductSummary[] = PRODUCT_DETAIL.map((detail) => { - return { - id: detail.id, - name: detail.name, - price: detail.price, - imageUrl: detail.imageUrl, - isBlend: detail.isBlend, - - specifications: { - roastLevel: detail.specifications.roastLevel, - }, - - tastes: detail.tastes.map((t) => ({ name: t.name, colorCode: t.category.colorCode })), - - averageRating: detail.averageRating, - reviewCount: detail.reviewCount, - }; -}); - -export const BLOG_POSTS: BlogPostDetailedType[] = [ - { - id: 1, - title: 'The Art of the Pour-Over: How to Brew Better Coffee at Home', - authorRole: 'Head Barista', - publishedAt: '2024-03-10T09:00:00Z', - isPublished: true, - content: ` -
-

Brewing coffee is more than just a morning routine; it's a ritual. If you want to elevate your cup, the pour-over method is the best place to start.

- -

1. The Ratio Matters

-

The golden rule for pour-over coffee is the 1:16 ratio. That means for every 1 gram of coffee, you use 16 grams of water. For a standard mug, try 20g of coffee to 320g of water.

- -

2. The Bloom

-

Don't just pour all the water at once! Pour just enough water to wet the grounds (about 40g) and let it sit for 30 seconds. You will see bubbles rising—this is CO2 escaping. We call this the bloom, and it ensures an even extraction.

- Roast Levels Illustration - -

3. The Pour

-

Pour the remaining water in slow, concentric circles, avoiding the edges of the filter. Keep the water level consistent. The total brew time should be between 2:30 and 3:00 minutes.

- -
"Good coffee is an act of patience."
-
- `, - }, - { - id: 2, - title: 'Oat, Almond, or Dairy? Choosing the Right Milk for Your Latte', - authorRole: 'Marketing', - publishedAt: '2024-03-12T14:30:00Z', - isPublished: true, - content: ` -
-

Gone are the days when whole milk was your only option. Today, the milk alternative market is booming, but how do you know which one pairs best with your espresso?

- Roast Levels Illustration - -

Whole Milk (The Classic)

-

If you love traditional latte art and a rich, creamy texture, whole dairy milk is still the king. The fat content allows for silky micro-foam that holds its shape perfectly.

- -

Oat Milk (The Modern Favorite)

-

Oat milk has taken the coffee world by storm. It has a neutral flavor that doesn't overpower the coffee beans, and it steams surprisingly well. It's our top recommendation for a dairy-free cappuccino.

- -

Almond Milk (The Tricky One)

-

Almond milk can be nutty and delicious, but be careful—it often separates (curdles) in highly acidic coffee due to temperature shock. Ask for a "barista blend" for the best results.

-
- `, - }, - { - id: 3, - title: 'Why Your Grinder Is More Important Than Your Coffee Machine', - authorRole: 'Head Roaster', - publishedAt: '2024-03-15T08:00:00Z', - isPublished: true, - content: ` -
-

Most beginners spend $500 on an espresso machine and $20 on a blade grinder. This is the biggest mistake you can make.

- -

Consistency is Key

-

A cheap blade grinder chops beans unevenly, resulting in "fines" (dust) and "boulders" (chunks). This leads to coffee that tastes both bitter and sour at the same time.

- Roast Levels Illustration - -

Burr Grinders

-
    -
  • Uniformity: They crush beans to a precise size.
  • -
  • Control: You can dial in the exact setting for French Press (coarse) or Espresso (fine).
  • -
  • Flavor: You extract the actual notes of the bean, not just generic "coffee" taste.
  • -
-

If you have a budget for upgrades, upgrade your grinder first.

-
- `, - }, - { - id: 4, - title: 'Understanding Roast Levels: Light vs. Dark', - authorRole: 'Marketing', - publishedAt: '2024-03-18T10:15:00Z', - isPublished: true, - content: ` -
-

Walk into a specialty shop and you'll see bags labeled "Light," "City," or "Vienna." What does it all mean?

- -

Light Roast

-

Light brown in color with no oil on the surface. These beans retain the most caffeine and the original characteristics of the origin (fruity, floral, acidic notes).

- -

Medium Roast

-

The crowd pleaser. Medium brown color with a balanced flavor, aroma, and acidity. This is what we use for our signature House Blend.

- -

Dark Roast

-

Dark brown, often oily surface. The origin flavors are overtaken by the roasting process, resulting in smoky, chocolatey, or nutty flavors. Perfect for those who add milk and sugar.

- Roast Levels Illustration - -
- `, - }, - { - id: 5, - title: 'Understanding Roast Levels: Light vs. Dark', - authorRole: 'Marketing', - publishedAt: '2024-03-18T10:15:00Z', - isPublished: false, - content: ` -
-

Walk into a specialty shop and you'll see bags labeled "Light," "City," or "Vienna." What does it all mean?

- -

Light Roast

-

Light brown in color with no oil on the surface. These beans retain the most caffeine and the original characteristics of the origin (fruity, floral, acidic notes).

- -

Medium Roast

-

The crowd pleaser. Medium brown color with a balanced flavor, aroma, and acidity. This is what we use for our signature House Blend.

- -

Dark Roast

-

Dark brown, often oily surface. The origin flavors are overtaken by the roasting process, resulting in smoky, chocolatey, or nutty flavors. Perfect for those who add milk and sugar.

-
- `, - }, -]; - -export const ALL_BLOG_POSTS_SUMMARY: BlogPostType[] = BLOG_POSTS.map((post) => ({ - id: post.id, - title: post.title, - publishedAt: post.publishedAt, - isPublished: post.isPublished, -}));