From dae559a42bd48280a5c24c19a20bb060eda2acaf Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 11 Jun 2026 10:26:20 +0000 Subject: [PATCH] S.1.2.2 --- .../src/components/MadeDreamsCartDrawer.tsx | 185 ++++++ .../src/components/MadeDreamsStoreHeader.tsx | 66 ++ frontend/src/helpers/madeDreamsCart.ts | 240 +++++++ frontend/src/hooks/useMadeDreamsCart.ts | 70 +++ frontend/src/pages/cart.tsx | 167 +++++ frontend/src/pages/index.tsx | 593 +++++++++++++++--- 6 files changed, 1233 insertions(+), 88 deletions(-) create mode 100644 frontend/src/components/MadeDreamsCartDrawer.tsx create mode 100644 frontend/src/components/MadeDreamsStoreHeader.tsx create mode 100644 frontend/src/helpers/madeDreamsCart.ts create mode 100644 frontend/src/hooks/useMadeDreamsCart.ts create mode 100644 frontend/src/pages/cart.tsx diff --git a/frontend/src/components/MadeDreamsCartDrawer.tsx b/frontend/src/components/MadeDreamsCartDrawer.tsx new file mode 100644 index 0000000..ba02346 --- /dev/null +++ b/frontend/src/components/MadeDreamsCartDrawer.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import Link from 'next/link'; +import { + formatCurrency, + MadeDreamsCartItem, + MadeDreamsCartSummary, +} from '../helpers/madeDreamsCart'; + +type Props = { + open: boolean; + items: MadeDreamsCartItem[]; + totals: MadeDreamsCartSummary; + onClose: () => void; + onQuantityChange: (itemId: string, quantity: number) => void; + onRemove: (itemId: string) => void; + onClear: () => void; +}; + +export default function MadeDreamsCartDrawer({ + open, + items, + totals, + onClose, + onQuantityChange, + onRemove, + onClear, +}: Props) { + return ( +
+ +
+ +
+ {items.length === 0 ? ( +
+

Your cart is empty.

+

+ Add a best seller or create a custom paint by numbers kit to start your order. +

+ + Shop products + +
+ ) : ( +
+ {items.map((item) => ( +
+
+
+
+ {Array.from({ length: 9 }).map((_, index) => ( + + {index + 1} + + ))} +
+
+
+
+
+

+ {item.category} +

+

{item.name}

+
+

{formatCurrency(item.price)}

+
+ {item.options && ( +

+ {item.options.uploadedFileName && `Photo: ${item.options.uploadedFileName} · `} + {item.options.size && `${item.options.size} · `} + {item.options.difficulty && `${item.options.difficulty} difficulty`} +

+ )} +
+
+ +
+
+ + {item.quantity} + +
+ +
+
+ ))} +
+ )} +
+ +
+
+
+ Subtotal + {formatCurrency(totals.subtotal)} +
+
+ Shipping + + {totals.shipping === 0 ? 'Free' : formatCurrency(totals.shipping)} + +
+
+ Total + {formatCurrency(totals.total)} +
+
+ +
+ + View cart + + {items.length > 0 && ( + + )} +
+
+ + + ); +} diff --git a/frontend/src/components/MadeDreamsStoreHeader.tsx b/frontend/src/components/MadeDreamsStoreHeader.tsx new file mode 100644 index 0000000..8731583 --- /dev/null +++ b/frontend/src/components/MadeDreamsStoreHeader.tsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; +import Link from 'next/link'; +import MadeDreamsCartDrawer from './MadeDreamsCartDrawer'; +import useMadeDreamsCart from '../hooks/useMadeDreamsCart'; + +export default function MadeDreamsStoreHeader() { + const [isCartOpen, setIsCartOpen] = useState(false); + const { items, totals, updateQuantity, removeItem, clearCart } = useMadeDreamsCart(); + + return ( +
+
+ + + MD + + + Made Dreams + Painted personally + + + + + +
+ + + Admin login + +
+
+ + setIsCartOpen(false)} + onQuantityChange={updateQuantity} + onRemove={removeItem} + onClear={clearCart} + /> +
+ ); +} diff --git a/frontend/src/helpers/madeDreamsCart.ts b/frontend/src/helpers/madeDreamsCart.ts new file mode 100644 index 0000000..a1ea15f --- /dev/null +++ b/frontend/src/helpers/madeDreamsCart.ts @@ -0,0 +1,240 @@ +export const MADE_DREAMS_CART_STORAGE_KEY = 'madeDreamsCart'; +export const MADE_DREAMS_CART_EVENT = 'made-dreams-cart-updated'; + +export type MadeDreamsProduct = { + id: string; + name: string; + category: string; + price: number; + badge: string; + palette: string[]; + imageClass: string; + description: string; + reviews: number; + inventory: number; +}; + +export type MadeDreamsCartItem = { + id: string; + productId: string; + type: 'product' | 'custom'; + name: string; + category: string; + price: number; + quantity: number; + badge: string; + palette: string[]; + imageClass: string; + description: string; + maxInventory?: number; + options?: { + size?: string; + difficulty?: string; + uploadedFileName?: string; + }; +}; + +export type MadeDreamsCartSummary = { + itemCount: number; + subtotal: number; + shipping: number; + total: number; +}; + +export const madeDreamsProducts: MadeDreamsProduct[] = [ + { + id: 'golden-hour-atelier-kit', + name: 'Golden Hour Atelier Kit', + category: 'Best seller', + price: 68, + badge: 'Ships in 3 days', + palette: ['#1f2937', '#b45309', '#f59e0b', '#fde68a', '#fff7ed'], + imageClass: 'from-[#19130f] via-[#9a5b22] to-[#f6d287]', + description: 'A warm architectural canvas with 28 rich acrylic colors and three fine-detail brushes.', + reviews: 184, + inventory: 18, + }, + { + id: 'moonlit-anime-portrait', + name: 'Moonlit Anime Portrait', + category: 'New arrival', + price: 74, + badge: 'Limited drop', + palette: ['#111827', '#4c1d95', '#7c3aed', '#c4b5fd', '#f5f3ff'], + imageClass: 'from-[#111827] via-[#6d28d9] to-[#ddd6fe]', + description: 'A dramatic anime-inspired design with crisp numbered shapes and luminous violet hues.', + reviews: 92, + inventory: 12, + }, + { + id: 'forever-family-custom-kit', + name: 'Forever Family Custom Kit', + category: 'Personalized', + price: 89, + badge: 'Upload-ready', + palette: ['#292524', '#7f1d1d', '#fb7185', '#fed7aa', '#fff7ed'], + imageClass: 'from-[#292524] via-[#be123c] to-[#fed7aa]', + description: 'Our premium custom kit for treasured photos, hand-balanced for a painterly finish.', + reviews: 267, + inventory: 25, + }, +]; + +export const formatCurrency = (amount: number) => + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount); + +const isBrowser = () => typeof window !== 'undefined'; + +const clampQuantity = (quantity: number, maxInventory = 99) => { + const nextQuantity = Number.isFinite(quantity) ? Math.floor(quantity) : 1; + return Math.max(1, Math.min(nextQuantity, maxInventory)); +}; + +const isCartItem = (item: MadeDreamsCartItem) => + Boolean(item?.id && item?.name && item?.price >= 0 && item?.quantity > 0); + +export const readMadeDreamsCart = (): MadeDreamsCartItem[] => { + if (!isBrowser()) return []; + + const rawCart = window.localStorage.getItem(MADE_DREAMS_CART_STORAGE_KEY); + if (!rawCart) return []; + + try { + const parsedCart = JSON.parse(rawCart); + if (!Array.isArray(parsedCart)) return []; + + return parsedCart.filter(isCartItem).map((item) => ({ + ...item, + quantity: clampQuantity(item.quantity, item.maxInventory), + })); + } catch (error) { + console.error('Failed to read Made Dreams cart from localStorage:', error); + return []; + } +}; + +const emitCartUpdated = () => { + if (!isBrowser()) return; + window.dispatchEvent(new Event(MADE_DREAMS_CART_EVENT)); +}; + +export const writeMadeDreamsCart = (items: MadeDreamsCartItem[]) => { + if (!isBrowser()) return items; + + window.localStorage.setItem(MADE_DREAMS_CART_STORAGE_KEY, JSON.stringify(items)); + emitCartUpdated(); + return items; +}; + +export const getMadeDreamsCartSummary = (items: MadeDreamsCartItem[]): MadeDreamsCartSummary => { + const itemCount = items.reduce((sum, item) => sum + item.quantity, 0); + const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0); + const shipping = subtotal === 0 || subtotal >= 100 ? 0 : 9; + + return { + itemCount, + subtotal, + shipping, + total: subtotal + shipping, + }; +}; + +export const addMadeDreamsProductToCart = (product: MadeDreamsProduct, quantity = 1) => { + const items = readMadeDreamsCart(); + const existingItem = items.find((item) => item.productId === product.id && item.type === 'product'); + + if (existingItem) { + const updatedItems = items.map((item) => + item.id === existingItem.id + ? { + ...item, + quantity: clampQuantity(item.quantity + quantity, product.inventory), + } + : item, + ); + + return writeMadeDreamsCart(updatedItems); + } + + return writeMadeDreamsCart([ + ...items, + { + id: product.id, + productId: product.id, + type: 'product', + name: product.name, + category: product.category, + price: product.price, + quantity: clampQuantity(quantity, product.inventory), + badge: product.badge, + palette: product.palette, + imageClass: product.imageClass, + description: product.description, + maxInventory: product.inventory, + }, + ]); +}; + +export const addMadeDreamsCustomKitToCart = ({ + uploadedFileName, + size, + difficulty, + price, +}: { + uploadedFileName: string; + size: string; + difficulty: string; + price: number; +}) => { + const items = readMadeDreamsCart(); + const cartLineId = `custom-kit-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + + return writeMadeDreamsCart([ + ...items, + { + id: cartLineId, + productId: 'custom-paint-by-numbers-kit', + type: 'custom', + name: 'Custom Paint By Numbers Kit', + category: 'Custom generator', + price, + quantity: 1, + badge: 'Personalized', + palette: ['#201713', '#a0672a', '#d7a25d', '#f0dfca', '#fff8ef'], + imageClass: 'from-[#201713] via-[#a0672a] to-[#f5d29a]', + description: 'Photo-to-canvas custom kit with numbered pattern, curated paints, and premium brushes.', + options: { + uploadedFileName, + size, + difficulty, + }, + }, + ]); +}; + +export const updateMadeDreamsCartQuantity = (itemId: string, quantity: number) => { + const items = readMadeDreamsCart(); + + if (quantity < 1) { + return writeMadeDreamsCart(items.filter((item) => item.id !== itemId)); + } + + return writeMadeDreamsCart( + items.map((item) => + item.id === itemId + ? { + ...item, + quantity: clampQuantity(quantity, item.maxInventory), + } + : item, + ), + ); +}; + +export const removeMadeDreamsCartItem = (itemId: string) => + writeMadeDreamsCart(readMadeDreamsCart().filter((item) => item.id !== itemId)); + +export const clearMadeDreamsCart = () => writeMadeDreamsCart([]); diff --git a/frontend/src/hooks/useMadeDreamsCart.ts b/frontend/src/hooks/useMadeDreamsCart.ts new file mode 100644 index 0000000..4ffb3b8 --- /dev/null +++ b/frontend/src/hooks/useMadeDreamsCart.ts @@ -0,0 +1,70 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + addMadeDreamsCustomKitToCart, + addMadeDreamsProductToCart, + clearMadeDreamsCart, + getMadeDreamsCartSummary, + MADE_DREAMS_CART_EVENT, + MadeDreamsCartItem, + MadeDreamsProduct, + readMadeDreamsCart, + removeMadeDreamsCartItem, + updateMadeDreamsCartQuantity, +} from '../helpers/madeDreamsCart'; + +export default function useMadeDreamsCart() { + const [items, setItems] = useState([]); + + const refreshCart = useCallback(() => { + setItems(readMadeDreamsCart()); + }, []); + + useEffect(() => { + refreshCart(); + + const handleCartUpdate = () => refreshCart(); + + window.addEventListener('storage', handleCartUpdate); + window.addEventListener(MADE_DREAMS_CART_EVENT, handleCartUpdate); + + return () => { + window.removeEventListener('storage', handleCartUpdate); + window.removeEventListener(MADE_DREAMS_CART_EVENT, handleCartUpdate); + }; + }, [refreshCart]); + + const addProduct = useCallback((product: MadeDreamsProduct, quantity = 1) => { + setItems(addMadeDreamsProductToCart(product, quantity)); + }, []); + + const addCustomKit = useCallback( + (customKit: { uploadedFileName: string; size: string; difficulty: string; price: number }) => { + setItems(addMadeDreamsCustomKitToCart(customKit)); + }, + [], + ); + + const updateQuantity = useCallback((itemId: string, quantity: number) => { + setItems(updateMadeDreamsCartQuantity(itemId, quantity)); + }, []); + + const removeItem = useCallback((itemId: string) => { + setItems(removeMadeDreamsCartItem(itemId)); + }, []); + + const clearCart = useCallback(() => { + setItems(clearMadeDreamsCart()); + }, []); + + const totals = useMemo(() => getMadeDreamsCartSummary(items), [items]); + + return { + items, + totals, + addProduct, + addCustomKit, + updateQuantity, + removeItem, + clearCart, + }; +} diff --git a/frontend/src/pages/cart.tsx b/frontend/src/pages/cart.tsx new file mode 100644 index 0000000..a29bfd2 --- /dev/null +++ b/frontend/src/pages/cart.tsx @@ -0,0 +1,167 @@ +import React, { ReactElement } from 'react'; +import Head from 'next/head'; +import Link from 'next/link'; +import MadeDreamsStoreHeader from '../components/MadeDreamsStoreHeader'; +import LayoutGuest from '../layouts/Guest'; +import { getPageTitle } from '../config'; +import useMadeDreamsCart from '../hooks/useMadeDreamsCart'; +import { formatCurrency } from '../helpers/madeDreamsCart'; + +export default function MadeDreamsCartPage() { + const { items, totals, updateQuantity, removeItem, clearCart } = useMadeDreamsCart(); + + return ( +
+ + {getPageTitle('Made Dreams Cart')} + + + + + +
+
+
+

Shopping cart

+

Review your art kits.

+
+ + Continue shopping + +
+ + {items.length === 0 ? ( +
+

Your cart is empty.

+

+ Choose a ready-made canvas or upload a photo to create a personalized paint by numbers kit. +

+
+ + Shop products + + + Create custom kit + +
+
+ ) : ( +
+
+ {items.map((item) => ( +
+
+
+
+ {Array.from({ length: 16 }).map((_, index) => ( + + {index + 1} + + ))} +
+
+ +
+

{item.category}

+

{item.name}

+

{item.description}

+ {item.options && ( +
+ {item.options.uploadedFileName &&

Photo: {item.options.uploadedFileName}

} + {item.options.size &&

Canvas size: {item.options.size}

} + {item.options.difficulty &&

Difficulty: {item.options.difficulty}

} +
+ )} +
+ +
+

{formatCurrency(item.price)}

+

each

+
+ + {item.quantity} + +
+ +
+
+
+ ))} +
+ + +
+ )} +
+
+ ); +} + +MadeDreamsCartPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 01a0222..3434aa9 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,45 +1,39 @@ import React, { ReactElement, useEffect, useMemo, useState } from 'react'; import Head from 'next/head'; import Link from 'next/link'; +import MadeDreamsStoreHeader from '../components/MadeDreamsStoreHeader'; import LayoutGuest from '../layouts/Guest'; import { getPageTitle } from '../config'; - -type Product = { - name: string; - category: string; - price: string; - badge: string; - palette: string[]; - imageClass: string; - description: string; - reviews: number; -}; +import useMadeDreamsCart from '../hooks/useMadeDreamsCart'; +import { formatCurrency, madeDreamsProducts, MadeDreamsProduct } from '../helpers/madeDreamsCart'; type WorkflowStep = 'upload' | 'configure' | 'checkout'; const categories = [ - { title: 'Adult Paint By Numbers', copy: 'Museum-inspired canvases with layered color maps for mindful evenings.', accent: 'from-stone-200 to-amber-100' }, - { title: 'Anime Collection', copy: 'Cinematic linework, expressive palettes, and premium numbered detail.', accent: 'from-violet-200 to-pink-100' }, - { title: 'Celebrity Collection', copy: 'Iconic portraits translated into statement art kits for grown-up spaces.', accent: 'from-zinc-200 to-neutral-100' }, - { title: 'Family Memories Collection', copy: 'Turn weddings, vacations, and heirloom photos into keepsake canvases.', accent: 'from-rose-100 to-orange-100' }, - { title: 'Custom Paint By Numbers Generator', copy: 'Upload your photo, preview the conversion, then choose your kit finish.', accent: 'from-emerald-100 to-cyan-100' }, -]; - -const products: Product[] = [ { - name: 'Golden Hour Atelier Kit', category: 'Best seller', price: '$68', badge: 'Ships in 3 days', - palette: ['#1f2937', '#b45309', '#f59e0b', '#fde68a', '#fff7ed'], imageClass: 'from-[#19130f] via-[#9a5b22] to-[#f6d287]', - description: 'A warm architectural canvas with 28 rich acrylic colors and three fine-detail brushes.', reviews: 184, + title: 'Adult Paint By Numbers', + copy: 'Museum-inspired canvases with layered color maps for mindful evenings.', + accent: 'from-stone-200 to-amber-100', }, { - name: 'Moonlit Anime Portrait', category: 'New arrival', price: '$74', badge: 'Limited drop', - palette: ['#111827', '#4c1d95', '#7c3aed', '#c4b5fd', '#f5f3ff'], imageClass: 'from-[#111827] via-[#6d28d9] to-[#ddd6fe]', - description: 'A dramatic anime-inspired design with crisp numbered shapes and luminous violet hues.', reviews: 92, + title: 'Anime Collection', + copy: 'Cinematic linework, expressive palettes, and premium numbered detail.', + accent: 'from-violet-200 to-pink-100', }, { - name: 'Forever Family Custom Kit', category: 'Personalized', price: '$89', badge: 'Upload-ready', - palette: ['#292524', '#7f1d1d', '#fb7185', '#fed7aa', '#fff7ed'], imageClass: 'from-[#292524] via-[#be123c] to-[#fed7aa]', - description: 'Our premium custom kit for treasured photos, hand-balanced for a painterly finish.', reviews: 267, + title: 'Celebrity Collection', + copy: 'Iconic portraits translated into statement art kits for grown-up spaces.', + accent: 'from-zinc-200 to-neutral-100', + }, + { + title: 'Family Memories Collection', + copy: 'Turn weddings, vacations, and heirloom photos into keepsake canvases.', + accent: 'from-rose-100 to-orange-100', + }, + { + title: 'Custom Paint By Numbers Generator', + copy: 'Upload your photo, preview the conversion, then choose your kit finish.', + accent: 'from-emerald-100 to-cyan-100', }, ]; @@ -50,29 +44,43 @@ const reviews = [ ]; const faqs = [ - { question: 'How does the custom generator work?', answer: 'Upload a clear photo, choose your canvas size and difficulty, and Made Dreams prepares a numbered canvas kit with acrylic paints and brushes.' }, - { question: 'What makes these kits premium?', answer: 'Each kit is designed for adults with elevated palettes, gallery-style packaging, pre-numbered canvas, curated paints, and detail brushes.' }, - { question: 'Can I use a family or pet photo?', answer: 'Yes. Family portraits, travel memories, pets, and celebration photos are ideal for the custom workflow.' }, + { + question: 'How does the custom generator work?', + answer: + 'Upload a clear photo, choose your canvas size and difficulty, and Made Dreams prepares a numbered canvas kit with acrylic paints and brushes.', + }, + { + question: 'What makes these kits premium?', + answer: + 'Each kit is designed for adults with elevated palettes, gallery-style packaging, pre-numbered canvas, curated paints, and detail brushes.', + }, + { + question: 'Can I use a family or pet photo?', + answer: + 'Yes. Family portraits, travel memories, pets, and celebration photos are ideal for the custom workflow.', + }, ]; export default function MadeDreamsHome() { const [step, setStep] = useState('upload'); - const [selectedProduct, setSelectedProduct] = useState(products[0]); + const [selectedProduct, setSelectedProduct] = useState(madeDreamsProducts[0]); const [uploadedFile, setUploadedFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(''); const [size, setSize] = useState('16 × 20 in'); const [difficulty, setDifficulty] = useState('Balanced'); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); - const [cartCount, setCartCount] = useState(0); + const { addProduct, addCustomKit } = useMadeDreamsCart(); useEffect(() => { if (!uploadedFile) { setPreviewUrl(''); return undefined; } + const objectUrl = URL.createObjectURL(uploadedFile); setPreviewUrl(objectUrl); + return () => URL.revokeObjectURL(objectUrl); }, [uploadedFile]); @@ -85,117 +93,526 @@ export default function MadeDreamsHome() { const handleUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; setSuccess(''); + if (!file) return; + if (!file.type.startsWith('image/')) { setError('Please upload a JPG, PNG, or WEBP image so we can create your custom canvas preview.'); setUploadedFile(null); setStep('upload'); return; } + setError(''); setUploadedFile(file); setStep('configure'); }; - const handleCheckout = () => { + const handleAddProductToCart = (product: MadeDreamsProduct) => { + try { + setSelectedProduct(product); + addProduct(product); + setError(''); + setSuccess(`${product.name} added to your Made Dreams cart.`); + } catch (addError) { + console.error('Failed to add Made Dreams product to cart:', addError); + setError('We could not save this product to your cart. Please try again.'); + throw addError; + } + }; + + const handleAddCustomKitToCart = () => { if (!uploadedFile) { setError('Upload a photo first to create your custom Made Dreams kit.'); setStep('upload'); return; } - setError(''); - setSuccess(`Custom kit request added: ${uploadedFile.name} · ${size} · ${difficulty} difficulty · $${kitPrice}`); - setCartCount((current) => current + 1); - setStep('checkout'); - }; - const addProductToCart = (product: Product) => { - setSelectedProduct(product); - setCartCount((current) => current + 1); - setSuccess(`${product.name} added to your Made Dreams cart.`); + try { + addCustomKit({ + uploadedFileName: uploadedFile.name, + size, + difficulty, + price: kitPrice, + }); + setError(''); + setSuccess( + `Custom kit added: ${uploadedFile.name} · ${size} · ${difficulty} difficulty · ${formatCurrency( + kitPrice, + )}`, + ); + setStep('checkout'); + } catch (addError) { + console.error('Failed to add Made Dreams custom kit to cart:', addError); + setError('We could not save your custom kit to the cart. Please try again.'); + throw addError; + } }; return (
{getPageTitle('Made Dreams Premium Paint By Numbers')} - + -
-
- - MD - Made DreamsPainted personally - - -
- Cart {cartCount} - Admin login + + + {success && ( +
+
+

{success}

+
+ + View cart + + +
-
+ )}
-

Premium custom canvas kits

-

Turn your most meaningful photos into frame-worthy paint by numbers art.

-

Made Dreams sells adult paint by numbers kits for collectors, gift-givers, and creative nights in. Upload a photo, preview the numbered artwork, choose your finish, and start painting something personal.

+

+ Premium custom canvas kits +

+

+ Turn your most meaningful photos into frame-worthy paint by numbers art. +

+

+ Made Dreams sells adult paint by numbers kits for collectors, gift-givers, and creative nights in. Upload a + photo, preview the numbered artwork, choose your finish, and start painting something personal. +

+
-
{Array.from({ length: 24 }).map((_, index) => {index + 1})}
-

Custom preview

AI numbered canvas

-

Kit includes

Numbered canvas · Acrylic paints · 3 brushes

+
+
+
+ {Array.from({ length: 24 }).map((_, index) => ( + + {index + 1} + + ))} +
+
+
+
+
+
+
+

Custom preview

+

AI-numbered canvas artwork

+
+
+
+
+

Kit includes

+

+ Numbered canvas, acrylic paints, brush set, reference guide, and gift-ready packaging. +

+
-

Shop by mood

Product categories

A high-end art-store experience for adults who want beautiful kits, personal stories, and polished gifts.

-
{categories.map((category) => +
+
+

Shop by mood

+

Curated collections for adult artists.

+
+

+ Browse ready-made canvases or start from a personal photo. Every kit is designed to feel premium from + checkout to final brushstroke. +

+
+
+ {categories.map((category) => ( + + +
+ +
+
+
+
+

Best sellers & new arrivals

+

Premium kits ready for your cart.

+
+

+ Every product now has a working add-to-cart button with saved quantities, inventory limits, and a cart drawer. +

+
+ +
+ {madeDreamsProducts.map((product) => ( +
+ +
+
+

{product.name}

+ {formatCurrency(product.price)} +
+

{product.description}

+
+ + +
+
+
+ ))} +
+
-
-

Custom Paint By Numbers Generator

Upload → AI pattern → choose your kit → checkout.

This first Made Dreams workflow gives customers a complete guided request: image upload, conversion preview, size and difficulty selection, and a checkout-ready confirmation.

{(['upload', 'configure', 'checkout'] as WorkflowStep[]).map((item, index) =>
Step {index + 1}

{item}

)}
-
-
{previewUrl ?
Uploaded custom kit preview

AI pattern preview

Generated from {uploadedFile?.name}

:

Upload your photo

JPG, PNG, or WEBP images work best. Choose bright, high-resolution photos.

}
{error ?

{error}

: null}{success ?

{success}

: null}
-
-

2. Select size

{['12 × 16 in', '16 × 20 in', '24 × 36 in'].map((option) => )}
-

3. Choose difficulty

- +
+
+

Custom Paint By Numbers Generator

+

+ Upload image → AI pattern → choose size → add to cart. +

+

+ This workflow now creates a saved cart item for the customer's personalized kit. The next phase will + convert it into a paid backend order after Stripe or PayPal confirms payment. +

+
+ {['Upload photo', 'Select finish', 'Review cart'].map((label, index) => ( +
+ Step {index + 1} +

{label}

+
+ ))}
-
+
+ +
+ + + {error &&

{error}

} + +
+ + +
+ +
+
+
+

Canvas preview

+

{uploadedFile ? uploadedFile.name : 'Waiting for upload'}

+

+ {size} · {difficulty} difficulty · numbered pattern kit +

+
+

{formatCurrency(kitPrice)}

+
+ +
+
-
-

Best sellers & new arrivals

Premium product pages

Select a kit to view its gallery-style detail panel with canvas preview, paints, brushes, reviews, and add-to-cart.

-
{products.map((product) => )}
-
{Array.from({ length: 35 }).map((_, index) => {index + 1})}
{[1, 2, 3].map((item) =>
)}
-
{selectedProduct.badge}

{selectedProduct.name}

{selectedProduct.description}

Paint colors

{selectedProduct.palette.map((color) => )}

Brush information

Includes wide, medium, and micro-detail nylon brushes.

Reviews

★★★★★ {selectedProduct.reviews} verified painters

-
+
+
+
+
+
+
+ {Array.from({ length: 36 }).map((_, index) => ( + + {index + 1} + + ))} +
+
+
+ {[1, 2, 3].map((item) => ( +
+ ))} +
+
+ +
+ + {selectedProduct.badge} + +

{selectedProduct.name}

+

{selectedProduct.description}

+
+

Paint colors

+
+ {selectedProduct.palette.map((color) => ( + + ))} +
+
+
+
+

Brush information

+

Includes wide, medium, and micro-detail nylon brushes.

+
+
+

Reviews

+

★★★★★ {selectedProduct.reviews} verified painters

+
+
+ +
+
-

Customer reviews

Made to become a memory.

{reviews.map((review) =>

“{review}”

Verified customer
)}
+
+
+
+

Customer reviews

+

Made to become a memory.

+
+
+ {reviews.map((review) => ( +
+

“{review}”

+
Verified customer
+
+ ))} +
+
+
-

FAQ

Questions before you paint?

Join the studio list

Get new arrivals, custom kit tips, and launch offers.

event.preventDefault()}>
{faqs.map((faq) =>
{faq.question}

{faq.answer}

)}
+
+
+

FAQ

+

Questions before you paint?

+
+

Join the studio list

+

Get new arrivals, custom kit tips, and launch offers.

+
event.preventDefault()}> + + +
+
+
+
+ {faqs.map((faq) => ( +
+ {faq.question} +

{faq.answer}

+
+ ))} +
+
- +
); }