npm package: @reviewshield/react-widget
Description: ReviewShield – embeddable Google reviews React component
Latest version: 1.1.2

# @reviewshield/react-widget

> **Embeddable Google Reviews widget for React** — powered by the ReviewShield proxy API. Display authentic Google Business reviews on any website without exposing your Google API key.

[![npm version](https://img.shields.io/npm/v/@reviewshield/react-widget.svg)](https://www.npmjs.com/package/@reviewshield/react-widget)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](../../LICENSE)
[![React](https://img.shields.io/badge/React-%3E%3D18-blue.svg)](https://react.dev)

---

## Table of Contents

1. [Features](#features)
2. [How It Works](#how-it-works)
3. [Prerequisites](#prerequisites)
4. [Installation](#installation)
5. [Step-by-Step Setup Guide](#step-by-step-setup-guide)
6. [Usage](#usage)
7. [Props Reference](#props-reference)
8. [Customisation](#customisation)
9. [Individual Exports](#individual-exports)
10. [TypeScript Types](#typescript-types)
11. [Accessibility & Compliance](#accessibility--compliance)
12. [FAQ](#faq)
13. [Contact & Technical Support](#contact--technical-support)

---

## Features

| Feature                        | Details                                                            |
| ------------------------------ | ------------------------------------------------------------------ |
| 🚀 **Drop-in component**       | Single `<ReviewShield />` component renders everything             |
| 🔒 **Secure by design**        | Your Google API key stays server-side; never exposed to browsers   |
| 🌐 **Origin validation**       | Each API key is locked to your whitelisted domain(s)               |
| ⚡ **Cache-first**             | Reviews are cached for 24 hours — minimal API calls, maximum speed |
| 🎨 **Grid & Carousel layouts** | Responsive grid or horizontal scroll carousel, out of the box      |
| 💀 **Skeleton loading**        | Animated placeholder cards while reviews load                      |
| ❌ **Error & empty states**    | Friendly fallback UI with a one-click retry button                 |
| 🔍 **JSON-LD Schema Markup**   | Auto-injected `AggregateRating` + `Review` structured data for SEO |
| ♿ **Accessible**              | Full ARIA labels, semantic HTML, and `schema.org` microdata        |
| ✅ **ACCC compliant**          | Mandatory "View all reviews on Google" link included automatically |
| 🎯 **Minimum rating filter**   | Show only reviews above a star threshold                           |
| 🪝 **Headless hook**           | `useReviews()` hook for fully custom UI                            |
| 📦 **Tree-shakeable**          | ESM + CJS dual build via `tsup`                                    |

---

## How It Works

```
Your Website                ReviewShield Proxy API          Google Places API
──────────────              ─────────────────────────       ─────────────────
<ReviewShield               POST /api/reviews                Fetches live
  apiKey="rs_live_…"  ───▶  • Validates API key        ───▶ Google Business
  proxyUrl="https://…"      • Checks domain whitelist        reviews
/>                          • Enforces rate limit
                            • Returns cached reviews   ◀───
                    ◀─────  (24h TTL)
```

The widget **never** contacts Google directly. All requests go through your ReviewShield proxy, which holds the Google Places API key securely on the server.

---

## Prerequisites

| Requirement                       | Details                                                                                                 |
| --------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **React**                         | ≥ 18                                                                                                    |
| **Node.js**                       | ≥ 18                                                                                                    |
| **Tailwind CSS**                  | [v3+](https://tailwindcss.com/docs/installation) — required for the widget's styles to render correctly |
| **A ReviewShield API key**        | Obtained from the ReviewShield dashboard (see [Step 2](#step-2--create-your-api-key))                   |
| **A deployed ReviewShield proxy** | The proxy API handles all Google API calls (see [Step 3](#step-3--configure-the-proxy-url))             |

---

## Installation

```bash
# npm
npm install @reviewshield/react-widget

# yarn
yarn add @reviewshield/react-widget

# pnpm
pnpm add @reviewshield/react-widget
```

> **Peer dependencies** — `react` and `react-dom` (≥ 18) must already be installed in your project. The package bundles `tailwind-merge` automatically; no extra setup is needed.

---

## Step-by-Step Setup Guide

### Step 1 — Install the package

```bash
npm install @reviewshield/react-widget
```

### Step 2 — Create your API key

1. Sign up or log in to the **ReviewShield Dashboard** at the URL provided to you by your account manager.

   > **Note:** The exact dashboard URL will be confirmed by the ReviewShield team. Contact [support@reviewshield.app](mailto:support@reviewshield.app) if you have not received it.

2. Navigate to **API Keys** → **New Key**.
3. Enter a name for the key (e.g. `My Website Production`).
4. Add your website's domain to the **Allowed Domains** whitelist (e.g. `mywebsite.com`). Requests from any other origin will be rejected.
5. Copy the generated key — it looks like `rs_live_xxxxxxxxxxxxxxxx`.

> ⚠️ **Keep this key private.** Do not commit it to source control. Use environment variables (see Step 4).

### Step 3 — Configure the proxy URL

The `proxyUrl` is the base URL of the deployed ReviewShield proxy API that sits between your widget and Google's Places API.

- If you are using the **hosted ReviewShield service**, your proxy URL will be provided in the dashboard.
- If you are **self-hosting** the proxy (`apps/proxy-api` in this monorepo), deploy it to Vercel (or any Node.js host) and use that deployment URL.

```bash
# Example proxy URL
https://your-proxy.vercel.app
```

### Step 4 — Store your API key safely

Store the API key in an environment variable so it is never hard-coded:

```bash
# .env.local  (Next.js / Vite / Create React App)
NEXT_PUBLIC_REVIEWSHIELD_API_KEY=rs_live_xxxxxxxxxxxxxxxx
NEXT_PUBLIC_REVIEWSHIELD_PROXY_URL=https://your-proxy.vercel.app
```

> For **Next.js**, prefix with `NEXT_PUBLIC_` to expose the variable client-side.  
> For **Vite**, prefix with `VITE_`.  
> For **Create React App**, prefix with `REACT_APP_`.

### Step 5 — Add the widget to your page

```tsx
import { ReviewShield } from "@reviewshield/react-widget";

export default function ReviewsSection() {
  return (
    <ReviewShield
      apiKey={process.env.NEXT_PUBLIC_REVIEWSHIELD_API_KEY!}
      proxyUrl={process.env.NEXT_PUBLIC_REVIEWSHIELD_PROXY_URL}
      googleMapsUrl="https://g.page/r/YOUR_GOOGLE_PLACE_ID/review"
      layout="grid"
      themeColor="#16A34A"
      minRating={4}
    />
  );
}
```

That's it! The widget will automatically:

- Fetch reviews from your proxy
- Show skeleton cards while loading
- Render a responsive grid of review cards
- Include a "View all reviews on Google" link
- Inject JSON-LD structured data for SEO

---

## Usage

### Basic usage

```tsx
import { ReviewShield } from "@reviewshield/react-widget";

<ReviewShield
  apiKey="rs_live_abc123"
  proxyUrl="https://your-proxy.vercel.app"
/>;
```

### Full example with all props

```tsx
import { ReviewShield } from "@reviewshield/react-widget";

<ReviewShield
  apiKey="rs_live_abc123"
  proxyUrl="https://your-proxy.vercel.app"
  layout="carousel" // "grid" | "carousel"
  minRating={4} // Only show 4 and 5 star reviews
  themeColor="#16A34A" // Brand colour for stars and avatar backgrounds
  googleMapsUrl="https://g.page/r/YOUR_PLACE_ID/review"
  className="my-8 px-4" // Extra CSS classes on the outer <section>
  reviewCardClassName="border-2" // Extra CSS classes on each review card
/>;
```

### Carousel layout

```tsx
<ReviewShield
  apiKey="rs_live_abc123"
  proxyUrl="https://your-proxy.vercel.app"
  layout="carousel"
  themeColor="#2563EB"
  googleMapsUrl="https://g.page/r/YOUR_PLACE_ID/review"
/>
```

### Headless usage with `useReviews` hook

Build a completely custom UI using the underlying data hook:

```tsx
import { useReviews, StarRating } from "@reviewshield/react-widget";

function MyCustomReviews() {
  const { reviews, isLoading, error, refetch } = useReviews({
    apiKey: "rs_live_abc123",
    proxyUrl: "https://your-proxy.vercel.app",
    minRating: 3,
  });

  if (isLoading) return <p>Loading reviews…</p>;
  if (error) return <button onClick={refetch}>Retry</button>;

  return (
    <ul>
      {reviews.map((review) => (
        <li key={review.id}>
          <strong>{review.authorName}</strong>
          <StarRating rating={review.rating} themeColor="#16A34A" />
          <p>{review.text}</p>
        </li>
      ))}
    </ul>
  );
}
```

---

## Props Reference

### `<ReviewShield />`

| Prop                  | Type                   | Default                | Required | Description                                                                 |
| --------------------- | ---------------------- | ---------------------- | -------- | --------------------------------------------------------------------------- |
| `apiKey`              | `string`               | —                      | ✅       | Your ReviewShield API key (`rs_live_…`)                                     |
| `proxyUrl`            | `string`               | Current origin         | ❌       | Base URL of the ReviewShield proxy API                                      |
| `layout`              | `"grid" \| "carousel"` | `"grid"`               | ❌       | Display layout for the review cards                                         |
| `minRating`           | `number` (1–5)         | `1`                    | ❌       | Hide reviews with a rating below this value                                 |
| `themeColor`          | `string`               | `"#4F46E5"`            | ❌       | Brand colour applied to stars and avatar backgrounds (any valid CSS colour) |
| `googleMapsUrl`       | `string`               | Google search fallback | ❌       | Your Google Business URL for the "View all reviews" link                    |
| `className`           | `string`               | —                      | ❌       | Additional CSS class(es) applied to the outer `<section>` element           |
| `reviewCardClassName` | `string`               | —                      | ❌       | Additional CSS class(es) applied to each individual review card             |
| `googleLinkClassName` | `string`               | —                      | ❌       | Additional CSS class(es) applied to the "View all reviews on Google" link   |

---

## Customisation

### Changing the colour theme

Pass any valid CSS colour string to `themeColor`:

```tsx
// Green brand
<ReviewShield apiKey="…" themeColor="#16A34A" />

// Blue brand
<ReviewShield apiKey="…" themeColor="#2563EB" />

// Custom hex, RGB, or HSL
<ReviewShield apiKey="…" themeColor="hsl(262, 83%, 58%)" />
```

### Styling with CSS classes

Use `className`, `reviewCardClassName`, and `googleLinkClassName` to layer your own Tailwind or custom classes:

```tsx
<ReviewShield
  apiKey="…"
  className="max-w-5xl mx-auto py-12" // outer <section>
  reviewCardClassName="bg-gray-50 border-none" // each review card
  googleLinkClassName="bg-blue-100 text-blue-700 border-blue-200 hover:bg-blue-200" // 'View all reviews on Google' link
/>
```

**Tip:**

- Use `className` to style the outermost widget container (`<section>`)
- Use `reviewCardClassName` for each review card
- Use `googleLinkClassName` to override the "View all reviews on Google" link styling only

### Filtering reviews by star rating

Show only your best reviews by setting `minRating`:

```tsx
// Only 4 and 5 star reviews
<ReviewShield apiKey="…" minRating={4} />

// Only 5 star reviews
<ReviewShield apiKey="…" minRating={5} />
```

### Adding structured data for SEO

The `<SchemaMarkup />` component is included automatically inside `<ReviewShield />`. It injects a `<script type="application/ld+json">` tag with `AggregateRating` and `Review` structured data. No extra configuration is required.

To use it independently in a custom layout:

```tsx
import { SchemaMarkup } from "@reviewshield/react-widget";

// Render once per page alongside your custom review UI
<SchemaMarkup
  reviews={reviews}
  businessName="My Business"
  placeUrl="https://g.page/r/YOUR_PLACE_ID"
/>;
```

---

## Individual Exports

All public exports are available as named imports:

```tsx
import {
  ReviewShield, // Full drop-in widget
  ReviewCard, // Single review card
  StarRating, // Star rating display (1–5 stars)
  SkeletonCard, // Animated loading placeholder card
  SchemaMarkup, // JSON-LD structured data injector
  useReviews, // Data-fetching hook (headless usage)
} from "@reviewshield/react-widget";
```

### `<ReviewCard />`

Renders a single review card. Useful when building a custom layout.

```tsx
import { ReviewCard } from "@reviewshield/react-widget";

<ReviewCard
  review={{
    authorName: "Jane Smith",
    rating: 5,
    text: "Absolutely fantastic service!",
    time: 1700000000,
    relativeTime: "2 months ago",
    profilePhotoUrl: "https://…/photo.jpg",
  }}
  themeColor="#16A34A"
  className="my-custom-class"
/>;
```

### `<StarRating />`

Renders filled/empty stars for a given rating.

```tsx
import { StarRating } from "@reviewshield/react-widget";

<StarRating rating={4} themeColor="#F59E0B" />;
```

### `<SkeletonCard />`

Animated loading placeholder that matches the `ReviewCard` dimensions.

```tsx
import { SkeletonCard } from "@reviewshield/react-widget";

<SkeletonCard />;
```

### `<SchemaMarkup />`

Injects JSON-LD structured data (`AggregateRating` + `Review`) for SEO.

```tsx
import { SchemaMarkup } from "@reviewshield/react-widget";

<SchemaMarkup
  reviews={reviews}
  businessName="My Coffee Shop"
  placeUrl="https://g.page/r/YOUR_PLACE_ID"
/>;
```

### `useReviews(options)`

Custom hook that fetches and caches reviews from the proxy API.

```tsx
import { useReviews } from "@reviewshield/react-widget";

const { reviews, isLoading, error, refetch } = useReviews({
  apiKey: "rs_live_abc123",
  proxyUrl: "https://your-proxy.vercel.app",
  minRating: 1, // optional, default: 1
});
```

**Returns:**

| Property    | Type             | Description                                           |
| ----------- | ---------------- | ----------------------------------------------------- |
| `reviews`   | `Review[]`       | Array of review objects (filtered by `minRating`)     |
| `isLoading` | `boolean`        | `true` while the request is in-flight                 |
| `error`     | `string \| null` | Error message if the request failed, otherwise `null` |
| `refetch`   | `() => void`     | Re-fetches reviews on demand                          |

---

## TypeScript Types

All public types are exported from the package:

```tsx
import type {
  Review,
  ReviewShieldConfig,
  UseReviewsResult,
} from "@reviewshield/react-widget";
```

### `Review`

```ts
interface Review {
  id?: string;
  businessId?: string;
  authorName: string;
  authorUrl?: string;
  profilePhotoUrl?: string;
  rating: number; // 1–5
  text: string;
  time: number; // Unix timestamp (seconds)
  relativeTime?: string; // e.g. "2 months ago"
  fetchedAt?: string;
}
```

### `ReviewShieldConfig`

```ts
interface ReviewShieldConfig {
  apiKey: string;
  proxyUrl?: string;
  layout?: "grid" | "carousel";
  minRating?: number;
  themeColor?: string;
  className?: string;
  googleMapsUrl?: string;
}
```

### `UseReviewsResult`

```ts
interface UseReviewsResult {
  reviews: Review[];
  isLoading: boolean;
  error: string | null;
  refetch: () => void;
}
```

---

## Accessibility & Compliance

- All interactive elements have proper `aria-label` attributes.
- Loading state sets `aria-busy="true"` and `aria-label="Loading reviews…"`.
- Error state uses `role="alert"` for screen-reader announcements.
- Review cards use semantic `<article>` elements with `schema.org` microdata.
- The "View all reviews on Google" link is **always rendered** when reviews are displayed, as required by the [Australian Competition and Consumer Commission (ACCC)](https://www.accc.gov.au) guidelines on online reviews.

---

## FAQ

**Q: Do I need Tailwind CSS in my project?**  
A: Yes. The widget renders Tailwind CSS utility classes for all its styles. You must have Tailwind CSS installed and configured in your project for the widget to display correctly. Follow the [Tailwind CSS installation guide](https://tailwindcss.com/docs/installation) if you have not already set it up. The `tailwind-merge` dependency bundled with the widget only resolves class-name conflicts — it does not provide Tailwind's CSS.

**Q: Where is my Google API key?**  
A: Your Google Places API key is stored securely in the ReviewShield proxy API environment variables. It is **never** sent to or stored in the browser.

**Q: What happens if the proxy is unavailable?**  
A: The widget renders a friendly error card with a "Retry" button. No reviews are shown and the page remains fully functional.

**Q: Can I use the widget without the ReviewShield proxy?**  
A: The widget is designed to work with the ReviewShield proxy API. You can self-host the proxy by deploying `apps/proxy-api` from this monorepo. See the [root README](../../README.md) for instructions.

**Q: How do I find my Google Maps URL?**  
A: Go to your Google Business Profile, click "Share", and copy the short link. It will look like `https://g.page/r/XXXXXXXXXXXXXXXXXX/review`.

**Q: How often are reviews refreshed?**  
A: Reviews are cached by the proxy for 24 hours. The `refetch()` function from `useReviews` bypasses the in-component state but is still subject to the server-side cache TTL.

---

## Contact & Technical Support

For technical assistance with the `@reviewshield/react-widget` package:

| Channel                 | Details                                                                                                              |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
| 📧 **Email**            | [support@reviewshield.app](mailto:support@reviewshield.app)                                                          |
| 🐛 **Bug reports**      | [Open an issue](https://github.com/AAgecy/atechgooglereviews/issues/new?labels=bug&template=bug_report.md) on GitHub |
| 💡 **Feature requests** | [Open a discussion](https://github.com/AAgecy/atechgooglereviews/discussions) on GitHub                              |
| 📖 **Documentation**    | [Root README](../../README.md) for full monorepo setup and proxy API docs                                            |

> When reporting a bug, please include your React version, package version, browser/Node.js version, and a minimal reproduction.

---

## License

MIT © ReviewShield