Referral Page
v1.0.0Added in 1.8.0
A refer-a-friend page: drawn gift cards, a copyable invite link with share buttons, email invites, reward milestones and referrals.
The hero rises in once on mount beside two drawn Acme gift cards in 3D (the friend's $20 card in front, your $20 credit behind) that tilt into place over a soft glow and straighten a little on hover, with the latest subscribed friend and the next milestone floating around them on wide screens. Copy link copies the personal invite link and turns into a check; Email, X, LinkedIn and WhatsApp open the real share URLs with a prefilled message, Copy message copies that message, and More opens the system share sheet where the browser has one. The checkout code copies on click. In Your rewards the earned amount rolls up when the panel scrolls into view and the milestone track (1 friend $20, 3 a month of Pro, 5 the hoodie, 10 a year of Business) fills up to your count, across on wide screens and down on phones, with reached, next and locked states. Invite by email takes a list of addresses as chips: Enter, comma, semicolon or space adds one, a pasted list splits into chips, Backspace edits the last one, and an invalid, duplicate, already invited or own address is refused with a message. The personal note and the To line update a drawn preview of the email your friend gets. Send invites spins while onInvite runs (or 900 ms without it), then the friends appear at the top of Your referrals as Invited, a toast confirms and a success line shows; a rejection shows an error and keeps the chips. Your referrals filters by status with pill tabs that show counts (arrow keys move between them), shows a table on wide screens and stacked rows on phones, and Resend on an invited friend spins, then reads Sent. An empty list shows a first-run state with a link to the form. How it works, this month's leaderboard with your own rank, the terms in short, the FAQ (one answer open at a time) and the closing band follow. Reduced motion keeps everything still.
Built with
The sections this page is made of. In the full-screen preview, hold Alt to outline each one on the page.
Add to a project
npx wingo-ui@latest add referral-pagePro item: the CLI needs a Pro account. See plans
Usage
// app/(app)/refer/page.tsx
"use client"
import Link from "next/link"
import { ReferralPage } from "@/components/templates/referral-page"
export default function ReferRoute() {
return (
<ReferralPage
linkComponent={Link}
user={{ name: "Ryan Cooper", email: "ryan@brightlane.co", link: "https://acme.com/r/ryan-cooper", code: "RYAN20" }}
ctaTone="brand"
onInvite={async (emails, note) => {
const response = await fetch("/api/referrals/invite", { method: "POST", body: JSON.stringify({ emails, note }) })
if (!response.ok) throw new Error("Could not send the invites")
}}
/>
)
}Props
designdefault"classic""classic" | "bold" | "minimal"The whole page's look. classic follows the layout props: a split hero with the gift cards on the right over a dotted glow; bold: a pill header, a giant stage title on a gradient mesh, rounder panels, step cards, FAQ cards, an image band and a wordmark footer; minimal: a transparent header, a centered stacked hero, no glow or floating chips, outlined flat panels, no gray bands and a simple footer. The *Props overrides still win.
userdefault"REFERRAL_USER"ReferralUserThe signed-in person: { name, email, link, code }. The link fills the copy field and every share URL, the code shows under it and on the cards.
referralsdefault"REFERRAL_REFERRALS"Referral[]The starting list: { id, name?, email, status: "invited" | "signed-up" | "subscribed", date (ISO) }. Invites sent from the page are added on top. Counts drive the earned total and the milestones.
milestonesReferralMilestone[]Rewards by subscribed friends: { id, friends, reward, description, icon? }. Defaults to getReferralMilestones(amount), so the first one names your reward.
leadersdefault"REFERRAL_LEADERS"ReferralLeader[]This month's top referrers: { id, name, company, friends, earned (USD) }.
rankdefault23numberYour place on the leaderboard; past the listed leaders it shows as your own row under them.
stepsStepsSectionItem[]How it works: { icon?, title, description, meta? }. Defaults to getReferralSteps(friendAmount, amount).
termsstring[]The short version of the program terms. Defaults to getReferralTerms(amount).
faqFaqItem[]{ question, answer } pairs about referrals. Defaults to getReferralFaq(friendAmount, amount).
labelsdefault"DEFAULT_REFERRAL_PAGE_LABELS"Partial<ReferralPageLabels>Every visible string; the ones with amounts or counts in them are functions.
hrefsdefault"DEFAULT_REFERRAL_PAGE_HREFS"Partial<ReferralPageHrefs>{ workspace, rewards, invite, terms }: the header button, the in-page anchors and the full terms.
rewarddefault20numberWhat you earn per subscribed friend, in USD: the title, the cards, the earned total and the reward column.
friendRewarddefault20numberWhat your friend gets off their first month, in USD: the title, the friend's card and the email.
maxInvitesdefault20numberEmails per send; at the limit the chips field stops taking more.
defaultFilterdefault"all""all" | "invited" | "signed-up" | "subscribed"The referrals filter on load.
defaultNotedefault""stringThe personal note on load; empty shows the placeholder in the preview.
showSharedefaulttruebooleanThe share buttons under the invite link.
showRewardsdefaulttruebooleanYour rewards: the earned total and the milestone track.
showInvitedefaulttruebooleanInvite by email with the email preview.
showReferralsdefaulttruebooleanYour referrals list.
showStepsdefaulttruebooleanHow it works.
showLeaderboarddefaulttruebooleanThis month's top referrers.
showTermsdefaulttruebooleanThe short terms card.
showFaqdefaulttruebooleanThe FAQ.
showCtadefaulttruebooleanThe closing call to action.
stepsLayoutdefault"horizontal""horizontal" | "cards" | "vertical"How it works as a numbered row, numbered tiles or a timeline rail. Applies to the classic design.
ctaVariantdefault"inverted""inverted" | "soft" | "split"The closing band: a solid band, a gray band, or copy beside the buttons on a raised panel. Applies to the classic design.
alternatedefaulttruebooleanGray bands on every other visible section. Applies to the classic and bold designs.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Accents across the page: the hero glow, the cards' sheen, the milestone fills and nodes, leaderboard bars, terms checks and step markers.
ctaTonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Copy link, Send invites, the phone menu's button and the closing band; brand is the marketing orange.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"The corners of the emails and note fields.
headerdefaulttruebooleanThe marketing header with Open Acme and your avatar; off inside a site layout.
footerdefaulttruebooleanThe marketing footer.
headerPropsPartial<MarketingHeaderProps>Passed to MarketingHeader.
footerPropsPartial<MarketingFooterProps>Passed to MarketingFooter.
stepsPropsPartial<StepsSectionProps>Overrides for the StepsSection.
faqPropsPartial<FaqSectionProps>Overrides for the FaqSection.
ctaPropsPartial<CtaSectionProps>Overrides for the CtaSection.
onInvite(emails: string[], note: string) => (void | Promise<unknown>)Send invites. A promise keeps the button loading and a rejection shows an error with the chips kept. Without it the form waits 900 ms and toasts. Either way the friends then appear in Your referrals as Invited.
onResend(referral: Referral) => (void | Promise<unknown>)Resend on an invited friend; a promise keeps it loading. Without it: 900 ms and a toast.
onShare(channel: "email" | "x" | "linkedin" | "whatsapp" | "native") => voidA share button was pressed; the share URL still opens.
onCopyLink(link: string) => voidThe invite link was copied.
toastOptionsdefault"{}"ToastOptions | falseOptions for the pretend send and resend toasts ({ toasterId, duration... }); false sends none. A Toaster must be mounted to see them.
animateIndefaulttruebooleanThe hero rises in once, the cards tilt into place and the earned total rolls up on view; false keeps the page still (thumbnails). Reduced motion always does.
localedefault"en-US"stringIntl locale for the USD amounts, counters and dates.
linkComponentdefault"<a>"(props: LinkComponentProps) => ReactNodeThe router link for every in-site link on the page (next/link in an app). Share links stay plain anchors.
classNamestringClasses for the root.
classNamesPartial<Record<ReferralPageSlot, string>>Classes for header, hero, title, link, share, cards, rewards, milestones, invite, form, preview, referrals, list, steps, leaderboard, terms, faq, cta and footer.
Parts
REFERRAL_PAGE_SAMPLE
constantAll the sample content in one object: user, referrals, milestones, leaders, steps, terms and faq.
REFERRAL_USER
constantRyan Cooper with the link acme.com/r/ryan-cooper and the code RYAN20.
REFERRAL_REFERRALS
constantEight friends: three subscribed, two signed up and three invited.
REFERRAL_MILESTONES
constant1 friend $20 credit, 3 a month of Pro, 5 the Acme hoodie, 10 a year of Business.
REFERRAL_LEADERS
constantThis month's five top referrers.
REFERRAL_STEPS
constantShare your link, your friend gets $20, you get $20.
REFERRAL_TERMS
constantFive lines of program terms.
REFERRAL_FAQ
constantSix questions about timing, the friend's reward, limits, teammates, missing rewards and credit.
getReferralMilestones
function(amount = "$20") => ReferralMilestone[]: the sample milestones with your formatted reward in the first one.
getReferralSteps
function(friendAmount = "$20", amount = "$20") => StepsSectionItem[]: the three steps with both amounts.
getReferralTerms
function(amount = "$20") => string[]: the short terms.
getReferralFaq
function(friendAmount = "$20", amount = "$20") => FaqItem[]: the sample questions with both amounts.
DEFAULT_REFERRAL_PAGE_LABELS
constantThe English strings.
DEFAULT_REFERRAL_PAGE_HREFS
constantThe default link targets.
REFERRAL_PAGE_DESIGNS
constantThe design names, classic first: ["classic", "bold", "minimal"].
ReferralPageProps
typeProps of ReferralPage.
ReferralPageDesign
type"classic" | "bold" | "minimal".
ReferralFilter
type"all" | "invited" | "signed-up" | "subscribed".
Referral
typeOne invited friend.
ReferralStatus
type"invited" | "signed-up" | "subscribed".
ReferralUser
typeThe signed-in person sharing their link.
ReferralMilestone
typeA reward unlocked by a number of subscribed friends.
ReferralLeader
typeOne leaderboard row.
ReferralPageLabels
typeThe strings labels can override.
ReferralPageHrefs
typeThe link targets hrefs can override.
Examples
Bold design
design="bold": a giant stage title on a gradient mesh, rounder panels, step cards, FAQ cards and a wordmark footer.
Minimal design
design="minimal": a transparent header, a centered hero, outlined panels, no gray bands and a simple footer.
Give $50, get $25 in brand orange
friendReward 50 and reward 25 with tone and ctaTone brand: the title, cards, email and totals follow the amounts.
First visit, nobody invited yet
An empty referrals list: no earned total, an empty milestone track and a first-run state that points to the form.
Mail service down
An onInvite that rejects: add a few emails and send to see the error while the chips stay in place.
Dependencies
Files
- components/templates/referral-page.tsx
- components/templates/referral-page-data.ts