> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Antony-Figueroa/my-evershop-app/llms.txt
> Use this file to discover all available pages before exploring further.

# Product Reviews Extension

> Customer review and rating system for products

## Overview

The Product Reviews extension adds a complete customer review system to your EverShop store. Customers can rate products with stars, write detailed reviews, and see reviews from other customers.

<CardGroup cols={2}>
  <Card title="Features" icon="star">
    * 5-star rating system
    * Review submission form
    * Average rating calculation
    * Responsive design
  </Card>

  <Card title="Benefits" icon="heart">
    * Build customer trust
    * Increase conversions
    * Gather product feedback
    * Social proof
  </Card>
</CardGroup>

## Product Reviews Component

The main component is located at `extensions/productReviews/src/pages/frontStore/productView/ProductReviews.tsx`:

```tsx ProductReviews.tsx theme={null}
import React, { useState } from 'react';

interface Review {
  id: string;
  author: string;
  rating: number;
  comment: string;
  createdAt: string;
}

type ProductReviewsProps = {
  product: {
    productId: string;
    name: string;
  };
  reviews?: Review[];
  action?: string;
};

function StarRating({ rating, interactive = false, onChange }: { 
  rating: number; 
  interactive?: boolean; 
  onChange?: (rating: number) => void 
}) {
  return (
    <div className="flex gap-1">
      {[1, 2, 3, 4, 5].map((star) => (
        <button
          key={star}
          type="button"
          disabled={!interactive}
          onClick={() => interactive && onChange?.(star)}
          className={`text-2xl ${interactive ? 'cursor-pointer' : 'cursor-default'} ${
            star <= rating ? 'text-yellow-400' : 'text-gray-300'
          }`}
        >
          ★
        </button>
      ))}
    </div>
  );
}

export default function ProductReviews({ product, reviews = [], action }: ProductReviewsProps) {
  const [showForm, setShowForm] = useState(false);
  const [newReview, setNewReview] = useState({ 
    author: '', 
    rating: 5, 
    comment: '' 
  });

  const averageRating = reviews.length > 0
    ? reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length
    : 0;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    // Here you would normally submit to an API
    console.log('Submitting review:', newReview);
    setShowForm(false);
    setNewReview({ author: '', rating: 5, comment: '' });
  };

  return (
    <div className="bg-[#F8FAF9] border border-[#E8F5E9] rounded-lg p-6 mt-6">
      <div className="flex items-center justify-between mb-6">
        <h3 className="text-xl font-bold text-[#2D5A3D]">
          Reseñas de Clientes
        </h3>
        <button
          onClick={() => setShowForm(!showForm)}
          className="bg-[#2D5A3D] text-white px-4 py-2 rounded-lg hover:bg-[#1E3D2A] transition-colors text-sm"
        >
          {showForm ? 'Cancelar' : 'Escribir Reseña'}
        </button>
      </div>

      {reviews.length > 0 && (
        <div className="mb-6">
          <div className="flex items-center gap-4 mb-4">
            <StarRating rating={Math.round(averageRating)} />
            <span className="text-[#4A5568]">
              {averageRating.toFixed(1)} de 5 ({reviews.length} reseñas)
            </span>
          </div>
        </div>
      )}

      {showForm && (
        <form onSubmit={handleSubmit} className="bg-white border border-[#E8F5E9] rounded-lg p-4 mb-6">
          <h4 className="font-semibold text-[#2D5A3D] mb-4">Nueva Reseña</h4>
          <div className="mb-4">
            <label className="block text-sm font-medium text-[#4A5568] mb-2">
              Tu Nombre
            </label>
            <input
              type="text"
              value={newReview.author}
              onChange={(e) => setNewReview({ ...newReview, author: e.target.value })}
              required
              className="w-full px-4 py-2 border border-[#E8F5E9] rounded-lg focus:outline-none focus:border-[#2D5A3D]"
            />
          </div>
          <div className="mb-4">
            <label className="block text-sm font-medium text-[#4A5568] mb-2">
              Calificación
            </label>
            <StarRating
              rating={newReview.rating}
              interactive
              onChange={(rating) => setNewReview({ ...newReview, rating })}
            />
          </div>
          <div className="mb-4">
            <label className="block text-sm font-medium text-[#4A5568] mb-2">
              Tu Reseña
            </label>
            <textarea
              value={newReview.comment}
              onChange={(e) => setNewReview({ ...newReview, comment: e.target.value })}
              required
              rows={4}
              className="w-full px-4 py-2 border border-[#E8F5E9] rounded-lg focus:outline-none focus:border-[#2D5A3D]"
              placeholder="Comparte tu experiencia con este producto..."
            />
          </div>
          <button
            type="submit"
            className="bg-[#2D5A3D] text-white px-6 py-2 rounded-lg hover:bg-[#1E3D2A] transition-colors"
          >
            Enviar Reseña
          </button>
        </form>
      )}

      {reviews.length === 0 ? (
        <p className="text-[#4A5568] text-center py-4">
          Sé el primero en reseñar este producto.
        </p>
      ) : (
        <div className="space-y-4">
          {reviews.map((review) => (
            <div key={review.id} className="border-b border-[#E8F5E9] pb-4 last:border-0">
              <div className="flex items-center gap-2 mb-2">
                <StarRating rating={review.rating} />
                <span className="font-medium text-[#2D5A3D]">{review.author}</span>
              </div>
              <p className="text-[#4A5568] text-sm">{review.comment}</p>
              <p className="text-[#4A5568] text-xs mt-2">
                {new Date(review.createdAt).toLocaleDateString('es-ES')}
              </p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export const layout = {
  areaId: 'productPageBottom',
  sortOrder: 10
};

export const query = `
  query Query {
    product(id: getContextValue("productId")) {
      productId
      name
      reviews {
        id
        author
        rating
        comment
        createdAt
      }
    }
    action: url(routeId: "productReviews")
  }
`;
```

## Component Features

### Star Rating Component

The `StarRating` component can be used in two modes:

<Tabs>
  <Tab title="Display Mode">
    ```tsx theme={null}
    <StarRating rating={4} />
    ```

    Shows stars as read-only display
  </Tab>

  <Tab title="Interactive Mode">
    ```tsx theme={null}
    <StarRating 
      rating={rating} 
      interactive 
      onChange={(newRating) => setRating(newRating)}
    />
    ```

    Allows users to click stars to select a rating
  </Tab>
</Tabs>

### Average Rating Calculation

Automatically calculates and displays the average rating:

```typescript theme={null}
const averageRating = reviews.length > 0
  ? reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length
  : 0;
```

Displayed as: **4.5 de 5 (12 reseñas)**

### Review Submission Form

Collapsible form with three fields:

<Steps>
  <Step title="Customer Name">
    Required text input for reviewer's name
  </Step>

  <Step title="Star Rating">
    Interactive star selector (1-5 stars)
  </Step>

  <Step title="Review Text">
    Multi-line textarea for detailed feedback
  </Step>
</Steps>

### Review Display

Each review shows:

* ⭐ Star rating visualization
* 👤 Author name
* 💬 Review comment
* 📅 Submission date (formatted in Spanish)

## Data Structure

```typescript theme={null}
interface Review {
  id: string;           // Unique review identifier
  author: string;       // Customer name
  rating: number;       // 1-5 star rating
  comment: string;      // Review text
  createdAt: string;    // ISO date string
}
```

## GraphQL Integration

The component uses GraphQL to fetch product reviews:

```graphql theme={null}
query Query {
  product(id: getContextValue("productId")) {
    productId
    name
    reviews {
      id
      author
      rating
      comment
      createdAt
    }
  }
  action: url(routeId: "productReviews")
}
```

<Note>
  The `action` field provides the URL for submitting new reviews via POST request.
</Note>

## Layout Configuration

The component is placed at the bottom of product pages:

```typescript theme={null}
export const layout = {
  areaId: 'productPageBottom',
  sortOrder: 10
};
```

This ensures reviews appear after product details and supplement information.

## User Interface

### Empty State

When no reviews exist:

```
┌─────────────────────────────────────┐
│ Reseñas de Clientes  [Escribir...] │
├─────────────────────────────────────┤
│                                     │
│  Sé el primero en reseñar este     │
│  producto.                          │
│                                     │
└─────────────────────────────────────┘
```

### With Reviews

```
┌─────────────────────────────────────┐
│ Reseñas de Clientes  [Escribir...] │
├─────────────────────────────────────┤
│ ★★★★★ 4.5 de 5 (12 reseñas)        │
├─────────────────────────────────────┤
│ ★★★★★ Juan Pérez                   │
│ Excelente producto, muy efectivo    │
│ 15 ene 2026                         │
├─────────────────────────────────────┤
│ ★★★★☆ María García                 │
│ Buen suplemento, resultados visibles│
│ 10 ene 2026                         │
└─────────────────────────────────────┘
```

## Styling

Uses Ana's Suplements brand colors:

| Element        | Color     | Purpose             |
| -------------- | --------- | ------------------- |
| Container      | `#F8FAF9` | Main background     |
| Border         | `#E8F5E9` | Card borders        |
| Heading        | `#2D5A3D` | Section title       |
| Button         | `#2D5A3D` | Primary actions     |
| Button Hover   | `#1E3D2A` | Hover state         |
| Text           | `#4A5568` | Body text           |
| Stars (filled) | `#FCD34D` | Yellow rating stars |
| Stars (empty)  | `#D1D5DB` | Gray empty stars    |

## Extension Structure

```
extensions/productReviews/
├── src/
│   └── pages/
│       └── frontStore/
│           └── productView/
│               └── ProductReviews.tsx
├── package.json
└── tsconfig.json
```

## Configuration

Enabled in `config/default.json`:

```json theme={null}
{
  "system": {
    "extensions": [
      {
        "name": "productReviews",
        "resolve": "extensions/productReviews",
        "enabled": true
      }
    ]
  }
}
```

## State Management

The component uses React hooks for state management:

```typescript theme={null}
const [showForm, setShowForm] = useState(false);  // Form visibility
const [newReview, setNewReview] = useState({      // Form data
  author: '', 
  rating: 5, 
  comment: '' 
});
```

## Form Handling

<Steps>
  <Step title="User clicks 'Escribir Reseña'">
    Form appears with default 5-star rating
  </Step>

  <Step title="User fills in name, adjusts rating, writes comment">
    State updates with each field change
  </Step>

  <Step title="User clicks 'Enviar Reseña'">
    Form submission handler processes the review
  </Step>

  <Step title="Form resets">
    Form hides and clears all fields
  </Step>
</Steps>

## Best Practices

<Tip>
  **Moderation**: Consider adding admin review moderation before publishing reviews publicly.
</Tip>

<Warning>
  **Verification**: Implement purchase verification to ensure only real customers can review products.
</Warning>

<Note>
  **Incentives**: Consider offering loyalty points or discounts for leaving reviews to increase participation.
</Note>

## Internationalization

All text is currently in Spanish:

* "Reseñas de Clientes" (Customer Reviews)
* "Escribir Reseña" (Write Review)
* "Nueva Reseña" (New Review)
* "Tu Nombre" (Your Name)
* "Calificación" (Rating)
* "Tu Reseña" (Your Review)
* "Sé el primero en reseñar este producto" (Be the first to review)

To support other languages, extract strings to translation files.

## Next Steps

<CardGroup cols={2}>
  <Card title="Offline Payments" icon="money-bill" href="/extensions/offline-payments">
    Learn about payment methods
  </Card>

  <Card title="Page Components" icon="browser" href="/guides/page-components">
    Create custom product page components
  </Card>
</CardGroup>
