TotalCustomers.tsx
A social proof component in Plainform that displays customer avatars, star ratings, and total customer count from paid Stripe checkout sessions
The TotalCustomers component fetches real customer data from Stripe and displays it as social proof with avatars, star ratings, and customer count. It's commonly used in hero sections to build trust.
Props
| TotalCustomers Props | Type | Default |
|---|---|---|
text | string | - |
Usage
Basic Implementation
import { TotalCustomers } from '@/components/TotalCustomers';
export default function HomePage() {
return (
<section>
<h1>Welcome to Our Product</h1>
<TotalCustomers text="customers already using this" />
</section>
);
}In Hero Section
import { TotalCustomers } from '@/components/TotalCustomers';
import locale from '@/locales/en.json';
export function Hero() {
const heroLocale = locale?.homePage?.heroSection;
return (
<section className="flex flex-col items-center gap-8">
<h1>{heroLocale?.title}</h1>
<p>{heroLocale?.description}</p>
<TotalCustomers text={heroLocale?.customersText} />
</section>
);
}Features
- Real Data: Fetches actual customer count from paid Stripe checkout sessions
- Customer Avatars: Displays profile images from your S3
mockupUsers/folder - Star Rating: Shows 5-star rating visualization
- Responsive Design: Adapts layout for mobile and desktop
- Automatic Fallback: Returns null if no orders exist
- Image Optimization: Uses Next.js Image with fallback support
Implementation Details
Data Fetching
The component uses the getCustomers() helper to fetch data from the Stripe customers API route:
export async function getCustomers() {
const res = await fetch(`${env.SITE_URL}/api/stripe/customers`, {
cache: 'force-cache',
next: { tags: ['stripe/customers'] },
});
return res.json();
}Returns:
totalCustomersCount- Number of unique paid Stripe customersimageUrls- Array of customer avatar/mockup image URLs
Customers API Route
The API route at app/api/stripe/customers/route.ts counts unique paid customers from Stripe checkout sessions:
const paidCustomerIds = new Set<string>();
allSessions.forEach((session) => {
if (
session.payment_status === 'paid' &&
typeof session.customer === 'string'
) {
paidCustomerIds.add(session.customer);
}
});
const totalCustomersCount = paidCustomerIds.size;It also reads avatar images from S3 using the mockupUsers/ prefix. The API
requests at most 5 images from S3, then limits the returned avatar list to the
number of paid customers.
For example:
- 2 paid customers and 5 images in S3 → 2 avatars displayed
- 5 paid customers and 3 images in S3 → 3 avatars displayed
- 12 paid customers and 10 images in S3 → at most 5 avatars displayed
The count is based on unique Stripe customer IDs from paid checkout sessions, not the number of local Clerk users or raw checkout sessions.
Avatar Display
Customer avatars are displayed with overlapping effect:
<div className="flex items-center -space-x-2">
{imageUrls?.map((img, i) => (
<ImageWithFallback
src={img}
alt={`User Mockup ${i}`}
width={84}
height={84}
className="size-12 object-cover rounded-full ring-4 ring-surface"
key={`user-${i}`}
/>
))}
</div>The -space-x-2 class creates the overlapping effect.
Star Rating
The component renders 5 filled star icons:
<div className="flex items-center gap-1">
<SvgFinder icon="Star" className="fill-yellow-400 stroke-yellow-400" />
<SvgFinder icon="Star" className="fill-yellow-400 stroke-yellow-400" />
<SvgFinder icon="Star" className="fill-yellow-400 stroke-yellow-400" />
<SvgFinder icon="Star" className="fill-yellow-400 stroke-yellow-400" />
<SvgFinder icon="Star" className="fill-yellow-400 stroke-yellow-400" />
</div>Customization
Custom Text
Pass different text for various contexts:
{/* Hero section */}
<TotalCustomers text="happy customers" />
{/* Pricing page */}
<TotalCustomers text="businesses trust us" />
{/* Testimonials section */}
<TotalCustomers text="5-star reviews" />Styling Avatars
Customize avatar appearance:
<ImageWithFallback
src={img}
alt={`User ${i}`}
width={84}
height={84}
className="size-16 object-cover rounded-full ring-2 ring-primary"
/>Changing Star Color
Modify star icon colors:
<SvgFinder icon="Star" className="fill-blue-400 stroke-blue-400" />Locale Configuration
Configure the text in locales/en.json:
{
"homePage": {
"heroSection": {
"customersText": "customers already using this"
}
}
}Conditional Rendering
The component returns null if there are no paid customers:
if (!data?.totalCustomersCount) {
return null;
}This prevents displaying "0 customers" on new installations.
Performance
- Server Component: Fetches data on the server for optimal performance
- Image Optimization: Uses Next.js Image with
priorityflag - Quality Setting: Images loaded at 80% quality for faster loading
Styling
The component uses Tailwind CSS with responsive design:
- Desktop: Horizontal layout with avatars and text side-by-side
- Mobile: Vertical layout with centered content (
max-md:flex-col max-md:items-center) - Avatar rings:
ring-4 ring-surfacecreates white border effect - Text color:
text-neutral-foregroundfor secondary text
Related
- ImageWithFallback.tsx - Image component with fallback
- SvgFinder.tsx - Icon component
- Stripe Integration - Payment setup
How is this guide ?
Last updated on