The Web Ratings

React / Next.js

React hooks and component integration

React Component (Functional)

Complete component with hooks:

import { useEffect, useCallback } from 'react';

// Declare global type
declare global {
  interface Window {
    TheWebRatings: any;
  }
}

interface RatingButtonProps {
  userEmail: string;
  userName?: string;
  onSuccess?: (data: any) => void;
  onError?: (error: any) => void;
}

export function RatingButton({ 
  userEmail, 
  userName, 
  onSuccess, 
  onError 
}: RatingButtonProps) {
  
  // Load widget script
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://cdn.thewebratings.com/twr.min.js';
    script.async = true;
    document.head.appendChild(script);
    
    return () => {
      document.head.removeChild(script);
    };
  }, []);
  
  // Setup event listeners
  useEffect(() => {
    if (typeof window === 'undefined' || !window.TheWebRatings) return;
    
    const handleSuccess = (data: { ratingId: string; rating: number }) => {
      console.log('Rating submitted:', data.ratingId, data.rating);
      onSuccess?.(data);
    };
    
    window.TheWebRatings.on('rating:submitted', handleSuccess);
    
    return () => {
      window.TheWebRatings.off('rating:submitted', handleSuccess);
    };
  }, [onSuccess]);
  
  // Open widget
  const handleClick = useCallback(() => {
    if (typeof window === 'undefined' || !window.TheWebRatings) {
      console.error('TheWebRatings not loaded');
      return;
    }
    
    window.TheWebRatings.open(userEmail, userName);
  }, [userEmail, userName]);
  
  return (
    <button 
      onClick={handleClick}
      className="rating-button"
    >
      ⭐ Rate This App
    </button>
  );
}

Usage in Your App

import { RatingButton } from './components/RatingButton';
import { useAuth } from './hooks/useAuth'; // Your auth hook

export function MyApp() {
  const { user } = useAuth();
  
  const handleRatingSuccess = (data: any) => {
    // Grant reward
    if (data.rating >= 4) {
      grantDiscount(user.id, '10OFF');
    }
    
    // Track analytics
    analytics.track('rating_submitted', {
      rating: data.rating
    });
  };
  
  const handleRatingError = (error: any) => {
    // Show error toast
    toast.error(error.userMessage || 'Failed to submit rating');
  };
  
  return (
    <div>
      <h1>My App</h1>
      
      {user && (
        <RatingButton
          userEmail={user.email}
          userName={user.name}
          onSuccess={handleRatingSuccess}
          onError={handleRatingError}
        />
      )}
    </div>
  );
}

Next.js App Router

For Next.js 13+ with App Router:

'use client';

import { useEffect, useState } from 'react';

export function RatingWidget() {
  const [loaded, setLoaded] = useState(false);
  
  useEffect(() => {
    // Load script only on client
    const script = document.createElement('script');
    script.src = 'https://cdn.thewebratings.com/twr.min.js';
    script.async = true;
    script.onload = () => setLoaded(true);
    document.head.appendChild(script);
    
    return () => {
      document.head.removeChild(script);
    };
  }, []);
  
  const openWidget = () => {
    if (!loaded || !window.TheWebRatings) return;
    
    // Get user from your auth (e.g., Clerk, Auth0, NextAuth)
    const user = getCurrentUser(); // Your function
    
    window.TheWebRatings.open(user.email, user.name);
  };
  
  if (!loaded) {
    return <button disabled>Loading...</button>;
  }
  
  return (
    <button onClick={openWidget} className="btn-primary">
      Rate This App
    </button>
  );
}

Custom Hook

Create a reusable hook:

import { useEffect, useCallback, useState } from 'react';

export function useRatingWidget(options?: {
  onSuccess?: (data: any) => void;
  onError?: (error: any) => void;
}) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  
  // Load script
  useEffect(() => {
    if (typeof window === 'undefined') return;
    
    const script = document.createElement('script');
    script.src = 'https://cdn.thewebratings.com/twr.min.js';
    script.async = true;
    script.onload = () => setIsLoaded(true);
    document.head.appendChild(script);
    
    return () => {
      document.head.removeChild(script);
    };
  }, []);
  
  // Setup event listeners
  useEffect(() => {
    if (!window.TheWebRatings) return;
    
    const handleSuccess = (data: { ratingId: string; rating: number }) => {
      setIsOpen(false);
      options?.onSuccess?.(data);
    };
    
    const handleOpened = () => setIsOpen(true);
    const handleClosed = () => setIsOpen(false);
    
    window.TheWebRatings.on('rating:submitted', handleSuccess);
    window.TheWebRatings.on('modal:opened', handleOpened);
    window.TheWebRatings.on('widget:closed', handleClosed);
    
    return () => {
      window.TheWebRatings.off('rating:submitted', handleSuccess);
      window.TheWebRatings.off('modal:opened', handleOpened);
      window.TheWebRatings.off('widget:closed', handleClosed);
    };
  }, [options]);
  
  const openWidget = useCallback((userEmail: string, userName?: string) => {
    if (!isLoaded || !window.TheWebRatings) {
      console.error('TheWebRatings not loaded yet');
      return;
    }
    
    window.TheWebRatings.open(userEmail, userName);
  }, [isLoaded]);
  
  return {
    isLoaded,
    isOpen,
    openWidget
  };
}

Usage:

function MyComponent() {
  const { user } = useAuth();
  const { isLoaded, openWidget } = useRatingWidget({
    onSuccess: (data) => {
      toast.success(`Thank you for your ${data.rating}-star rating!`);
    }
  });
  
  return (
    <button 
      onClick={() => openWidget(user.email, user.name)}
      disabled={!isLoaded}
    >
      Rate App
    </button>
  );
}

TypeScript Types

// types/thewebratings.d.ts

// rating:submitted payload
interface TWRRatingSubmittedData {
  ratingId: string;
  rating: number;
  timestamp?: number;
  version?: string;
}

interface TheWebRatings {
  open(userEmail: string, userName?: string): void;
  on(event: 'rating:submitted', handler: (data: TWRRatingSubmittedData) => void): void;
  on(event: 'modal:opened' | 'widget:closed', handler: (data: any) => void): void;
  off(event: string, handler: Function): void;
}

declare global {
  interface Window {
    TheWebRatings: TheWebRatings;
  }
}

export {};

Smart Rating Prompt

Show rating prompt after user completes certain actions:

import { useEffect, useState } from 'react';
import { useRatingWidget } from './useRatingWidget';

export function useSmartRatingPrompt(options: {
  triggerAfterActions: number;
  cooldownDays?: number;
}) {
  const { openWidget } = useRatingWidget();
  const [actionsCount, setActionsCount] = useState(0);
  
  const trackAction = useCallback(() => {
    const newCount = actionsCount + 1;
    setActionsCount(newCount);
    localStorage.setItem('actions_count', newCount.toString());
    
    // Check if we should prompt
    if (newCount === options.triggerAfterActions) {
      const lastPrompt = localStorage.getItem('last_rating_prompt');
      const cooldown = options.cooldownDays || 30;
      
      if (!lastPrompt || isDaysAgo(lastPrompt, cooldown)) {
        setTimeout(() => {
          promptForRating();
        }, 2000);
      }
    }
  }, [actionsCount, options]);
  
  const promptForRating = () => {
    const user = getCurrentUser();
    if (!user) return;
    
    if (confirm('Enjoying the app? Leave us a rating!')) {
      openWidget(user.email, user.name);
      localStorage.setItem('last_rating_prompt', new Date().toISOString());
    }
  };
  
  return { trackAction };
}

function isDaysAgo(dateStr: string, days: number) {
  const date = new Date(dateStr);
  const now = new Date();
  const diffDays = (now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24);
  return diffDays >= days;
}

Usage:

function MyApp() {
  const { trackAction } = useSmartRatingPrompt({
    triggerAfterActions: 3,
    cooldownDays: 30
  });
  
  const handleTaskComplete = () => {
    // Your task completion logic
    completeTask();
    
    // Track for rating prompt
    trackAction();
  };
  
  return <TaskList onComplete={handleTaskComplete} />;
}

Server Components (Next.js 13+)

For Next.js App Router with Server Components:

// app/rating-widget.tsx
'use client';

import { useEffect, useState } from 'react';
import Script from 'next/script';

export function RatingWidget({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    // Fetch user data client-side
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  if (!user) return null;
  
  return (
    <>
      <Script 
        src="https://cdn.thewebratings.com/twr.min.js"
        strategy="lazyOnload"
      />
      
      <button onClick={() => {
        if (window.TheWebRatings) {
          window.TheWebRatings.open(user.email, user.name);
        }
      }}>
        Rate App
      </button>
    </>
  );
}

Next Steps