Reddot UI Library
Docs
Currency Provider
Currency Provider
Multi-currency support with automatic detection and real-time exchange rates
Installation
$npx shadcn@latest add https://reddot.dottools.xyz/r/currency-provider.json
Features
- 🌍 Automatic Currency Detection - Detects user's currency based on their country
- 💱 Real-time Exchange Rates - Fetches live rates from multiple API sources
- 🔄 Fallback Support - Multiple API fallbacks for reliability
- 💰 Commission Handling - Configurable commission rate for non-EUR currencies
- 🎨 Flexible Display - Multiple formatting options for prices
- ⚡ Server-Side Ready - Works with Next.js SSR
Usage
Basic Setup
Wrap your application with the CurrencyProvider:
import { CurrencyProvider } from '@/components/currency-provider';
import type { PropsWithChildren } from 'react';
export default function Layout({ children }: PropsWithChildren) {
return (
<CurrencyProvider
countryCode="US" // Usually from headers or geolocation
commissionRate={1.03} // 3% commission
>
{children}
</CurrencyProvider>
);
}Display Prices
Use the PriceDisplay component to show formatted prices:
import { PriceDisplay } from '@/components/currency-provider';
export function ProductCard({ price }: { price: number }) {
return (
<div>
<PriceDisplay
price={29.99}
showCombined={true} // Shows "31.19 USD (29.99 €)"
showPerMonth={true}
size="xl"
/>
</div>
);
}Use Currency Hook
Access currency data programmatically:
import { useCurrency } from '@/components/currency-provider';
export function Checkout() {
const { userCurrency, exchangeRate, formatPrice } = useCurrency();
const totalPrice = 99.99;
const formattedTotal = formatPrice({
initialPrice: totalPrice,
showCombined: true,
});
return <div>Total: {formattedTotal}</div>;
}Price Range Display
Show price ranges with automatic currency conversion:
import { PriceRangeDisplay } from '@/components/currency-provider';
export function PricingTiers() {
return <PriceRangeDisplay minPrice={9.99} maxPrice={99.99} separator=" to " />;
}Server-Side Usage
Geolocation Setup
The component requires a countryCode prop for currency detection. You'll need to set up geolocation based on your hosting provider:
Vercel:
First, set up geolocation in your middleware:
import { geolocation } from '@vercel/functions';
import createMiddleware from 'next-intl/middleware';
import type { NextRequest } from 'next/server';
export default async function middleware(request: NextRequest) {
const geo = geolocation(request);
// Your existing middleware logic...
const response = NextResponse.next();
// Set country header for use in components
response.headers.set('x-country', geo?.country || 'US');
return response;
}Then use it in your layout:
import { headers } from 'next/headers';
import { CurrencyProvider } from '@/components/currency-provider';
export default function Layout({ children }: PropsWithChildren) {
const headersList = await headers();
// Country code set by middleware
const countryCode = headersList.get('x-country') || 'US';
return <CurrencyProvider countryCode={countryCode}>{children}</CurrencyProvider>;
}Other providers:
- Netlify: Use
x-countryheader with Netlify Edge Functions - Cloudflare: Use
cf-ipcountryheader with Cloudflare Workers - Custom: Implement your own geolocation service or use libraries like
geoip-lite
// Example with custom geolocation service
export default function Layout({ children }: PropsWithChildren) {
const headersList = await headers();
const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip');
const countryCode = (await getCountryFromIP(ip)) || 'US';
return <CurrencyProvider countryCode={countryCode}>{children}</CurrencyProvider>;
}API Integration
Built-in API Client
The component uses a built-in API client that directly calls external APIs:
import { fetchCountryCurrency, fetchExchangeRate } from '@/components/currency-provider';
// Get currency for a country (uses RestCountries API)
const { currency } = await fetchCountryCurrency('FR'); // Returns 'EUR'
// Get exchange rate (uses Frankfurter + UniRate APIs)
const { rate } = await fetchExchangeRate('USD'); // Returns ~1.08The APIs used are:
- RestCountries API:
https://restcountries.com/v3.1/alpha/{country}?fields=currencies - Frankfurter API:
https://api.frankfurter.app/latest?from=EUR&symbols={currency} - UniRate API (fallback):
https://api.unirateapi.com/api/rates(requires API key)
Environment Variables
# Optional: For UniRate API fallback
UNI_RATE_API_KEY=your_api_key_hereProps Reference
CurrencyProvider
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Child components |
defaultCurrency | string | 'EUR' | Default currency code |
commissionRate | number | 1.03 | Commission multiplier (1.03 = 3%) |
countryCode | string | undefined | ISO country code for detection |
PriceDisplay
| Prop | Type | Default | Description |
|---|---|---|---|
price | number | required | Price in base currency (EUR) |
fallbackPrice | string | undefined | Fallback price string if needed |
showCombined | boolean | false | Show local + EUR price |
showPerMonth | boolean | false | Show "per month" text |
perMonthText | string | 'per month' | Custom text for period |
size | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Display size |
className | string | '' | Additional CSS classes |
PriceRangeDisplay
| Prop | Type | Default | Description |
|---|---|---|---|
minPrice | number | required | Minimum price in base currency |
maxPrice | number | required | Maximum price in base currency |
separator | string | ' - ' | Text between prices |
className | string | '' | Additional CSS classes |
API Sources
The component uses these free APIs:
- RestCountries - For country to currency mapping
- Frankfurter API - Primary exchange rate source (free)
- UniRate API - Fallback source (requires API key)
Examples
Custom Commission Rate
<CurrencyProvider
countryCode="JP"
commissionRate={1.05} // 5% commission for Japanese Yen
>
<PriceDisplay price={1000} />
</CurrencyProvider>EUR-Only Mode
import { useCurrency } from '@/components/currency-provider';
export function AlwaysEurPrice() {
const { formatPrice } = useCurrency();
return (
<div>
{formatPrice({
initialPrice: 49.99,
showEurOnly: true, // Always show EUR
})}
</div>
);
}Loading State
The component handles loading states automatically:
export function PriceWithLoader() {
const { isLoading } = useCurrency();
if (isLoading) {
return <div>Loading prices...</div>;
}
return <PriceDisplay price={99.99} />;
}