Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/aluxey/E-Commerce/llms.txt

Use this file to discover all available pages before exploring further.

The Customer Photo Manager allows you to curate photos displayed on your storefront’s photo wall.

Photo Structure

Each photo contains:
  • image_url - Public URL to the uploaded image
  • position - Display order (integer)
  • is_visible - Visibility toggle (boolean)
  • created_at - Upload timestamp
Database Table: customer_photos Storage Bucket: customer-photos

Uploading Photos

Two upload methods are available:

1. Drag and Drop

1

Drag Files

Drag image files over the upload zoneZone highlights when files are dragged over it
2

Drop to Upload

Release to start upload process
// CustomerPhotoManager.jsx:102
const handleDropZoneDrop = (e) => {
  e.preventDefault();
  setIsDragOver(false);
  handleFiles(e.dataTransfer.files);
};
3

Multiple Files

Multiple images can be dropped simultaneouslyAll valid image files will be uploaded

2. File Browser

1

Click Upload Zone

Click anywhere in the upload zoneOpens file picker dialog
2

Select Images

Choose one or multiple image filesAccepted: All image/* MIME types
3

Upload

Files upload automatically after selection

Upload Process

// CustomerPhotoManager.jsx:50
const handleFiles = useCallback(
  async (files) => {
    if (!files || files.length === 0) return;

    setUploading(true);
    const currentMax = photos.length > 0
      ? Math.max(...photos.map((p) => p.position))
      : -1;

    let successCount = 0;
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      if (!file.type.startsWith("image/")) continue;

      const { error: upErr } = await uploadPhoto(file, currentMax + 1 + i);
      if (upErr) {
        pushToast(t("admin.customerPhotos.error.upload"), "error");
      } else {
        successCount++;
      }
    }

    if (successCount > 0) {
      pushToast(t("admin.customerPhotos.success.upload"), "success");
      await fetchPhotos();
    }
    setUploading(false);
  },
  [photos, t, fetchPhotos]
);
Position Calculation:
  • New photos appended to end
  • Position = current max position + 1
  • Sequential positions for multiple uploads
Only image files are processed. Non-image files in a multi-file selection are skipped automatically.

Photo Grid

Photos display in a responsive grid with controls:

Photo Card Components

Drag Handle (top-left):
  • Six-dot icon for reordering
  • Indicates draggable functionality
Position Badge:
  • Shows current position number (#1, #2, etc.)
  • Updates when reordering
Action Buttons:
  • Eye Icon: Toggle visibility
  • Trash Icon: Delete photo
Implementation: CustomerPhotoManager.jsx:263

Visibility Control

Toggle whether photos appear on the public storefront:
// CustomerPhotoManager.jsx:125
const handleToggleVisibility = useCallback(
  async (photo) => {
    const newState = !photo.is_visible;
    const { error: togErr } = await toggleVisibility(photo.id, newState);
    if (togErr) {
      pushToast(t("admin.customerPhotos.error.toggle"), "error");
    } else {
      pushToast(t("admin.customerPhotos.success.visibility"), "success");
      setPhotos((prev) =>
        prev.map((p) => (p.id === photo.id ? { ...p, is_visible: newState } : p))
      );
    }
  },
  [t]
);
1

Click Eye Icon

Eye Open: Photo is visible → click to hideEye Closed: Photo is hidden → click to show
2

Instant Update

Visibility updates immediately in databaseCard opacity changes to indicate hidden state
Visual Indicator:
// CustomerPhotoManager.jsx:266
className={`photo-manager__card ${!photo.is_visible ? "is-hidden" : ""}`}
Hidden photos display with reduced opacity.

Reordering Photos

Drag-and-drop reordering changes photo display sequence:

Drag Operations

1

Start Drag

Click and hold on drag handle or photo card
// CustomerPhotoManager.jsx:142
const handleDragStart = useCallback((e, index) => {
  setDraggedIndex(index);
  e.dataTransfer.effectAllowed = "move";
  e.dataTransfer.setData("text/plain", index.toString());
  e.currentTarget.classList.add("is-dragging");
}, []);
2

Drag Over Target

Drag over desired positionTarget position highlights
3

Drop to Reorder

Release to move photo
// CustomerPhotoManager.jsx:171
const handleDrop = useCallback(
  async (e, toIndex) => {
    e.preventDefault();
    setDragOverIndex(null);

    if (draggedIndex === null || draggedIndex === toIndex) {
      setDraggedIndex(null);
      return;
    }

    // Reorder locally
    const reordered = [...photos];
    const [moved] = reordered.splice(draggedIndex, 1);
    reordered.splice(toIndex, 0, moved);

    // Update positions
    const updates = reordered.map((photo, idx) => ({
      id: photo.id,
      position: idx,
    }));

    setPhotos(reordered);
    setDraggedIndex(null);

    // Persist to database
    const { error: reorderErr } = await reorderPhotos(updates);
    if (reorderErr) {
      pushToast(t("admin.customerPhotos.error.reorder"), "error");
      await fetchPhotos();
    } else {
      pushToast(t("admin.customerPhotos.success.reorder"), "success");
    }
  },
  [draggedIndex, photos, t, fetchPhotos]
);
4

Position Update

All affected photos update their positionsChanges persist to database
Reordering updates all photo positions in a single batch operation for efficiency.

Visual Feedback

Dragging State:
  • Dragged photo: .is-dragging class
  • Drop target: .drag-over class
  • Upload zone: .is-dragging class when files hover

Deleting Photos

Deleting a photo removes it from storage and the database permanently. This action cannot be undone.
1

Click Trash Icon

Click delete button on photo card
2

Confirm Deletion

Confirm in browser dialog
// CustomerPhotoManager.jsx:111
if (!confirm(t("admin.customerPhotos.confirmDelete"))) return;
3

Delete from Storage

Photo removed from Supabase Storage bucketDatabase record deleted
const { error: delErr } = await deletePhoto(photo.id, photo.image_url);
if (delErr) {
  pushToast(t("admin.customerPhotos.error.delete"), "error");
} else {
  pushToast(t("admin.customerPhotos.success.delete"), "success");
  setPhotos((prev) => prev.filter((p) => p.id !== photo.id));
}

Reorder Hint

When multiple photos exist, a hint displays:
// CustomerPhotoManager.jsx:248
{photos.length > 1 && (
  <p className="photo-manager__reorder-hint">
    {t("admin.customerPhotos.reorderHint")}
  </p>
)}
Prompts users to drag photos to change order.

Empty State

When no photos exist:
// CustomerPhotoManager.jsx:256
<div className="photo-wall__empty">
  <Upload size={48} strokeWidth={1} />
  <h3>{t("admin.customerPhotos.empty.title")}</h3>
  <p>{t("admin.customerPhotos.empty.description")}</p>
</div>
Shows upload icon and prompt to add first photo.

Loading States

Upload in Progress:
// CustomerPhotoManager.jsx:228
{uploading ? (
  <p>{t("admin.common.loading")}</p>
) : (
  // Upload zone content
)}
Data Loading:
  • LoadingMessage component during initial fetch
  • ErrorMessage with retry if fetch fails

Image Optimization

Photos use lazy loading:
// CustomerPhotoManager.jsx:290
<img
  src={photo.image_url}
  alt={t("admin.customerPhotos.photoAlt", { position: index + 1 })}
  className="photo-manager__card-image"
  loading="lazy"
/>
Improves page performance with many photos.

Accessibility

Keyboard Navigation:
// CustomerPhotoManager.jsx:221
onKeyDown={(e) => {
  if (e.key === "Enter" || e.key === " ") {
    e.preventDefault();
    handleUploadClick();
  }
}}
Upload zone accessible via keyboard. ARIA Labels:
  • Action buttons have descriptive labels
  • Alt text includes position numbers

Photo Display Order

Photos are fetched ordered by position:
// services/adminCustomerPhotos.js (referenced)
// SELECT * FROM customer_photos ORDER BY position ASC
Lowest position number displays first in grid.

Build docs developers (and LLMs) love