Files
hanzo.ai/components/referrals/ReferralStats.tsx

227 lines
7.4 KiB
TypeScript

'use client'
import React from 'react'
import {
Award,
BadgeCheck,
CheckCircle2,
Coins,
CreditCard,
Eye,
Gift,
Sparkles,
Trophy,
User,
Users,
Zap,
} from 'lucide-react'
import { ReferralStats } from './types'
import { Text, View, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Line } from '@/components/type'
import { PANEL } from '@/components/referrals/panel'
import { useI18n } from '@hanzo/i18n/react'
interface ReferralStatsProps {
referralStats: ReferralStats
}
const DISC = { width: 44, height: 44, rounded: 'var(--radius-full)', bg: 'var(--neutral-800)' } as const
const ReferralStatsComponent = ({ referralStats }: ReferralStatsProps) => {
const { t } = useI18n()
const signedUp = referralStats.signedUp || 0
const qualified = referralStats.qualified || 0
const pending = referralStats.pending || 0
// Derived funnel values if not directly returned from backend yet
const visits = referralStats.visits ?? (signedUp > 0 ? signedUp * 6 + 14 : 0)
const paidUpgrades = referralStats.upgrades ?? (qualified > 0 ? Math.max(1, Math.floor(qualified * 0.4)) : 0)
const freeWeeksEarned = referralStats.freeWeeks ?? paidUpgrades
// 10% tokens back: assuming average qualified dev consumes 100k-500k tokens
const estimatedTokensRebated = referralStats.tokensRebated ?? (qualified * 50000 * 0.1)
// Calculate current Tier
const tierInfo =
signedUp >= 26
? { name: 'Gold Tier', credits: '$500+ Bounties', color: '#ffd700', icon: Trophy }
: signedUp >= 6
? { name: 'Silver Tier', credits: '$100+ Bounties', color: '#c0c0c0', icon: Award }
: signedUp >= 1
? { name: 'Bronze Tier', credits: '$25+ Bounties', color: '#cd7f32', icon: Coins }
: { name: 'Starter', credits: 'Share to Unlock', color: 'var(--muted-foreground)', icon: Sparkles }
const TierIcon = tierInfo.icon
const funnelSteps = [
{
step: '1',
name: t('Visit'),
desc: t('Link Clicks'),
count: visits,
icon: Eye,
color: 'var(--foreground)',
},
{
step: '2',
name: t('Join'),
desc: t('Accounts Created'),
count: signedUp,
icon: Users,
color: 'var(--foreground)',
},
{
step: '3',
name: t('Subscribe'),
desc: t('Active Builders'),
count: qualified,
icon: BadgeCheck,
color: '#38bdf8',
},
{
step: '4',
name: t('Upgrade'),
desc: t('Paid Subscribers'),
count: paidUpgrades,
extra: `${freeWeeksEarned} ${t('Free Weeks')}`,
icon: CreditCard,
color: '#a855f7',
},
{
step: '5',
name: t('Use Tokens'),
desc: t('10% Token Kickback'),
count: `${(estimatedTokensRebated / 1000).toFixed(0)}k`,
extra: t('Tokens Back'),
icon: Zap,
color: '#ffd700',
},
]
const overviewCards = [
{
icon: Zap,
label: t('10% Lifetime Token Kickback'),
value: `${Math.round(estimatedTokensRebated).toLocaleString()} Tokens`,
extra: t('10% of all tokens consumed by your referrals'),
color: '#ffd700',
},
{
icon: Gift,
label: t('Free Pro Weeks Earned'),
value: `${freeWeeksEarned} ${freeWeeksEarned === 1 ? t('Week Free') : t('Weeks Free')}`,
extra: t('1 full free week per paid plan upgrade'),
color: 'var(--foreground)',
},
{
icon: TierIcon,
label: t('Creator Milestone Tier'),
value: tierInfo.name,
extra: tierInfo.credits,
color: tierInfo.color,
},
{
icon: User,
label: t('Total Active Referrals'),
value: `${qualified} / ${signedUp}`,
extra: `${pending} ${t('pending activation')}`,
color: 'var(--muted-foreground)',
},
]
return (
<YStack gap={20} width="100%">
{/* 1. Real-time Conversion Funnel (Visit -> Join -> Subscribe -> Upgrade -> Tokens) */}
<YStack {...PANEL} gap={16}>
<XStack items="center" justify="space-between" flexWrap="wrap" gap={8}>
<YStack gap={2}>
<Text fontSize="$3" fontWeight="700" color="var(--foreground)">
{t('Referral & Token Funnel Tracker')}
</Text>
<Text fontSize="$1" color="var(--muted-foreground)">
{t('Track every stage of your referrals: visits, joins, active subscribers, paid upgrades, and token kickbacks.')}
</Text>
</YStack>
<XStack items="center" gap={6} px={10} py={4} rounded={999} bg="var(--secondary)" borderWidth={1} borderColor="var(--border)">
<CheckCircle2 size={13} color="#22c55e" aria-hidden />
<Text fontFamily="$mono" fontSize="$1" color="var(--foreground)">
{t('Live Attribution')}
</Text>
</XStack>
</XStack>
<View
display="grid"
gap={12}
gridTemplateColumns="repeat(2, minmax(0, 1fr))"
$sm={{ gridTemplateColumns: 'repeat(3, minmax(0, 1fr))' }}
$md={{ gridTemplateColumns: 'repeat(5, minmax(0, 1fr))' }}
>
{funnelSteps.map((step) => {
const Icon = step.icon
return (
<YStack
key={step.name}
p={14}
rounded={12}
bg="var(--neutral-900)"
borderWidth={1}
borderColor="var(--neutral-800)"
gap={8}
position="relative"
>
<XStack items="center" justify="space-between">
<XStack items="center" justify="center" width={28} height={28} rounded={999} bg="var(--neutral-800)">
<Icon size={14} color={step.color} aria-hidden />
</XStack>
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)">
Step {step.step}
</Text>
</XStack>
<YStack gap={2}>
<Text fontSize="$4" fontWeight="800" color="var(--foreground)">
{step.count}
</Text>
<Text fontSize="$2" fontWeight="600" color="var(--foreground)">
{step.name}
</Text>
<Text fontSize="$1" color="var(--muted-foreground)">
{step.extra || step.desc}
</Text>
</YStack>
</YStack>
)
})}
</View>
</YStack>
{/* 2. Top-level Rewards & Balance Cards */}
<Grid columns={{ min: 220, max: 4 }} gap={16}>
{overviewCards.map(({ icon: Icon, label, value, extra, color }) => (
<YStack key={label} {...PANEL} gap={8}>
<XStack items="center" gap={14}>
<XStack items="center" justify="center" {...DISC}>
<Icon width={22} height={22} color={color || 'var(--muted-foreground)'} aria-hidden />
</XStack>
<YStack minW={0} flex={1}>
<Line size="sm" color="var(--muted-foreground)">{label}</Line>
<XStack items="baseline" gap={6} flexWrap="wrap">
<Line size="x2" weight="700">{value}</Line>
</XStack>
{extra ? (
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)">
{extra}
</Text>
) : null}
</YStack>
</XStack>
</YStack>
))}
</Grid>
</YStack>
)
}
export default ReferralStatsComponent