Skip to main content

Overview

DADDO’s catalog system allows you to share your products publicly through customizable links and QR codes. Catalogs can be filtered by category, include business information, and provide an optimized viewing experience for customers.

Public Catalog Pages

Fetching Catalog Data

The catalog action (src/Redux/actions/Products/catalog_actions.js) retrieves products for a specific user:
export const fetchCatalogByUser = (userId, category = null) => async (dispatch) => {
  try {
    dispatch({ type: "CATALOG_REQUEST" });
    const url = category
      ? `/products/catalogs/${userId}?category=${category}`
      : `/products/catalogs/${userId}`;
    const { data } = await api.get(url);

    dispatch({
      type: "CATALOG_SUCCESS",
      payload: data,
    });
  } catch (error) {
    dispatch({
      type: "CATALOG_FAILURE",
      payload: error.response?.data?.message || error.message,
    });
  }
};
Catalogs are publicly accessible without authentication, making them perfect for sharing with customers.

Catalog URL Structure

Catalog URLs follow this pattern:
/catalog/{userId}?category={categoryId}&showBusiness=1&showPhone=1

Base URL

/catalog/{userId} - Shows all products for the user

Category Filter

?category={categoryId} - Filter by specific category

Business Info

?showBusiness=1 - Display business name in header

Contact Info

?showPhone=1 - Show phone number in header

Catalog Page Component

The CatalogPage component (src/Views/CatalogPage.jsx) renders the public catalog view:
const CatalogPage = () => {
  const dispatch = useDispatch();
  const { userId } = useParams();
  const [searchParams] = useSearchParams();

  const category = searchParams.get("category");
  const showBusiness = searchParams.get("showBusiness") === "1";
  const showPhone = searchParams.get("showPhone") === "1";

  const { catalog, loading, error, businessName, phone } = useSelector(
    (state) => state.catalog
  );

  useEffect(() => {
    if (userId) dispatch(fetchCatalogByUser(userId, category));
  }, [userId, category]);

  // Render catalog...
};

Normalizing Image Data

The catalog handles multiple image formats from different sources:
const normalizeImages = (images) => {
  if (!Array.isArray(images)) return [];
  return images
    .map((it) => {
      if (typeof it === "string") return { url: it };
      if (typeof it === "object")
        return { url: it.url || it.secure_url || it.src || null };
      return null;
    })
    .filter((i) => i?.url);
};

ImageCarousel Component

Products with multiple images display an interactive carousel:
const ImageCarousel = ({ images = [], fixedHeight = 200, onImageClick }) => {
  const [currentIndex, setCurrentIndex] = useState(0);
  const imgs = normalizeImages(images);

  const prev = () =>
    setCurrentIndex((i) => (i === 0 ? imgs.length - 1 : i - 1));
  const next = () =>
    setCurrentIndex((i) => (i === imgs.length - 1 ? 0 : i + 1));

  return (
    <div className="w-full">
      <div
        className="relative rounded-xl overflow-hidden bg-gray-900 bg-opacity-25"
        style={{ height: fixedHeight }}
      >
        <img
          src={imgs[currentIndex].url}
          alt=""
          onClick={() => onImageClick?.(imgs[currentIndex].url)}
          className="w-full h-full object-contain cursor-pointer"
        />
      </div>

      {imgs.length > 1 && (
        <div className="flex justify-center items-center gap-2 mt-1">
          <button onClick={prev} className="p-1 rounded-full bg-gray-900 hover:bg-gray-300">
            <ChevronLeft size={16} />
          </button>
          <span className="text-xs text-gray-300">
            {currentIndex + 1}/{imgs.length}
          </span>
          <button onClick={next} className="p-1 rounded-full bg-gray-900 hover:bg-gray-300">
            <ChevronRight size={16} />
          </button>
        </div>
      )}
    </div>
  );
};

Product Card Component

CatalogProductCard

Each product is displayed in a card with variant support:
const CatalogProductCard = ({ product, openModal }) => {
  const [showVariant, setShowVariant] = useState(false);
  const [selectedVariant, setSelectedVariant] = useState(null);

  const productImages = normalizeImages(product?.images);
  const variants = product?.variants || [];

  const active = showVariant ? selectedVariant : product;

  const handleVariantClick = (variant) => {
    const vImages = normalizeImages(variant?.images);
    setSelectedVariant({
      ...variant,
      _images: vImages.length ? vImages : productImages,
    });
    setShowVariant(true);
  };

  return (
    <div className="bg-gray-900 bg-opacity-25 rounded-xl shadow-sm hover:shadow-lg transition p-2">
      <ImageCarousel
        images={active?._images ?? productImages}
        fixedHeight={160}
        onImageClick={openModal}
      />

      <div className="mt-2 space-y-1">
        <h2 className="font-sants text-gray-300 text-sm line-clamp-1">
          {active?.name}
        </h2>
        <p className="text-l font-sants text-indigo-400">
          ${active?.price}
        </p>
        <p className="text-xs text-gray-500">
          Stock: {active?.stock}
        </p>
      </div>

      {/* Variant buttons */}
      {!showVariant && variants.length > 0 && (
        <div className="flex flex-wrap gap-1 mt-2">
          {variants.map((v) => (
            <button
              key={v.id}
              onClick={() => handleVariantClick(v)}
              className="text-xs px-2 py-1 border rounded-full hover:bg-indigo-50"
            >
              {v.color || v.size || "Variante"}
            </button>
          ))}
        </div>
      )}

      {showVariant && (
        <button
          onClick={() => setShowVariant(false)}
          className="text-xs text-indigo-600 mt-2"
        >
          ← Volver
        </button>
      )}
    </div>
  );
};
When a variant is selected, the card displays the variant’s price, stock, and images instead of the main product details.

Image Modal

Clicking on any product image opens a fullscreen modal:
{modalImage && (
  <div
    onClick={() => setModalImage(null)}
    className="fixed inset-0 bg-black/80 flex items-center justify-center p-4"
  >
    <img
      src={modalImage}
      className="max-h-[90vh] rounded-xl"
    />
  </div>
)}

QR Code Generation

The MyCatalogLink component (src/components/MyCatalogLink.jsx) generates shareable catalog links and QR codes:
import { QRCodeCanvas } from "qrcode.react";

const MyCatalogLink = ({ onClose }) => {
  const user = useSelector((state) => state.auth.user);
  const { categories } = useSelector((state) => state.categories || {});

  const [selectedCategory, setSelectedCategory] = useState("");
  const [showBusinessOnLink, setShowBusinessOnLink] = useState(false);
  const [showPhoneOnLink, setShowPhoneOnLink] = useState(false);

  const baseUrl = `${window.location.origin}/catalog/${user.id}`;
  const linkParams = new URLSearchParams();
  if (selectedCategory) linkParams.append("category", selectedCategory);
  if (showBusinessOnLink) linkParams.append("showBusiness", "1");
  if (showPhoneOnLink) linkParams.append("showPhone", "1");
  const catalogUrl = linkParams.toString() ? `${baseUrl}?${linkParams}` : baseUrl;

  return (
    <div className="flex flex-col items-center gap-4">
      <QRCodeCanvas value={catalogUrl} size={140} />
      <a href={catalogUrl} target="_blank" rel="noreferrer">
        {catalogUrl}
      </a>
    </div>
  );
};

QR Code Features

Dynamic URLs

QR codes automatically update based on selected filters

High Quality

140x140px canvas for clear scanning

Instant Share

Copy link or scan QR code to share

Category Filter

Generate QR codes for specific categories

Query Parameters

Catalog URLs support multiple query parameters for customization:
Filter the catalog to show only products from a specific category.
/catalog/123?category=456
Display the business name in the catalog header.
/catalog/123?showBusiness=1
Display the contact phone number in the catalog header.
/catalog/123?showPhone=1

Parameter Handling

const [searchParams] = useSearchParams();

const category = searchParams.get("category");
const showBusiness = searchParams.get("showBusiness") === "1";
const showPhone = searchParams.get("showPhone") === "1";

// Display business info conditionally
{showBusiness && (
  <h1 className="text-2xl font-bold text-gray-800">
    {businessName}
  </h1>
)}

{showPhone && phone && (
  <p className="text-gray-500">{phone}</p>
)}

Copy to Clipboard

The catalog link can be copied with one click:
const [copied, setCopied] = useState(false);

const handleCopy = () => {
  navigator.clipboard.writeText(catalogUrl);
  setCopied(true);
  setTimeout(() => setCopied(false), 2000);
};

return (
  <button onClick={handleCopy}>
    {copied ? <Check size={13} /> : <Copy size={13} />}
    {copied ? 'Copiado' : 'Copiar enlace'}
  </button>
);

PDF Export

Users can download their catalog as a PDF:
import { exportCatalogPDF } from "../Redux/actions/Products/export_pdf";

const [includeBusinessName, setIncludeBusinessName] = useState(true);
const [includeOwnerName, setIncludeOwnerName] = useState(true);
const [includePhone, setIncludePhone] = useState(true);

const handleDownloadPDF = () => {
  dispatch(exportCatalogPDF(user.id, {
    includeBusinessName, 
    includeOwnerName, 
    includePhone,
    selectedCategories: selectedCategory ? [selectedCategory] : [],
  }));
};
PDF export is an async operation that may take a few seconds depending on the number of products and images in the catalog.

Responsive Catalog Grid

The catalog uses a responsive grid layout:
<div className="
  grid
  grid-cols-2
  sm:grid-cols-2
  md:grid-cols-3
  lg:grid-cols-4
  gap-3
">
  {catalog?.map((product) => (
    <CatalogProductCard
      key={product.id}
      product={product}
      openModal={(url) => setModalImage(url)}
    />
  ))}
</div>

Catalog List View

The CatalogList component shows all public catalogs from different users:
import { fetchAllCatalogs } from "../Redux/actions/Products/allcatalogs_actions";

// View available at /allcatalogs
This feature allows users to discover other businesses using DADDO and browse their product catalogs.

Best Practices

  • Use high-quality product images
  • Keep product names concise
  • Update stock levels regularly
  • Use category filters for large catalogs
  • Include business name for branding
  • Generate QR codes for physical locations
  • Share links on social media
  • Include catalog link in email signatures
  • Print QR codes on business cards
  • Use category-specific links for promotions
  • Enable business name for brand recognition
  • Show phone number for easy contact
  • Keep catalogs organized with categories
  • Update product images regularly
  • Ensure accurate stock information

Build docs developers (and LLMs) love