mirror of
https://github.com/hanzo-apps/university.git
synced 2026-10-30 15:05:53 +00:00
263 lines
8.1 KiB
TypeScript
263 lines
8.1 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import { Box, Text, XStack, YStack } from '@/components/ui'
|
|
import { Action, Chip } from '@hanzo/ui/marketing'
|
|
import { Tag, Check, X, ArrowRight } from 'lucide-react'
|
|
import { courseCheckoutUrl } from '@/lib/pay'
|
|
import { validateCoupon, type CouponResult } from './courses-data'
|
|
|
|
interface CouponInputProps {
|
|
originalPrice: number
|
|
planId: string
|
|
courseSlug: string
|
|
courseCode: string
|
|
rebateCredits: number
|
|
onCouponChange?: (result: CouponResult) => void
|
|
returnPath?: string
|
|
}
|
|
|
|
export function CouponInput({
|
|
originalPrice,
|
|
planId,
|
|
courseSlug,
|
|
courseCode,
|
|
rebateCredits,
|
|
onCouponChange,
|
|
returnPath,
|
|
}: CouponInputProps) {
|
|
const [code, setCode] = useState('')
|
|
const [applied, setApplied] = useState<CouponResult | null>(null)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const activePrice = applied?.valid ? applied.finalPrice : originalPrice
|
|
const activeRebate = applied?.valid ? applied.rebateCredits : rebateCredits
|
|
|
|
const checkoutHref = applied?.valid
|
|
? courseCheckoutUrl(courseSlug, applied.code)
|
|
: courseCheckoutUrl(courseSlug)
|
|
|
|
|
|
const handleApply = (e?: React.FormEvent) => {
|
|
if (e) e.preventDefault()
|
|
if (!code.trim()) {
|
|
setError('Please enter a coupon code.')
|
|
return
|
|
}
|
|
|
|
const res = validateCoupon(code, originalPrice)
|
|
if (res.valid) {
|
|
setApplied(res)
|
|
setError(null)
|
|
onCouponChange?.(res)
|
|
} else {
|
|
setError(res.message)
|
|
setApplied(null)
|
|
}
|
|
}
|
|
|
|
const handleRemove = () => {
|
|
setCode('')
|
|
setApplied(null)
|
|
setError(null)
|
|
onCouponChange?.({
|
|
valid: false,
|
|
code: '',
|
|
discountAmount: 0,
|
|
originalPrice,
|
|
finalPrice: originalPrice,
|
|
rebateCredits,
|
|
message: '',
|
|
})
|
|
}
|
|
|
|
return (
|
|
<YStack gap="$3" width="100%">
|
|
{/* Price Display with Strikethrough if discounted */}
|
|
<YStack
|
|
p="$4"
|
|
rounded="var(--radius-lg)"
|
|
bg="var(--pure-black)"
|
|
borderWidth={1}
|
|
borderColor={applied?.valid ? 'var(--emerald-800)' : 'var(--border)'}
|
|
gap="$3"
|
|
>
|
|
<XStack items="baseline" justify="space-between" flexWrap="wrap" gap="$2">
|
|
<YStack>
|
|
<Text fontSize="$1" color="var(--muted-foreground)" fontFamily="$mono">
|
|
TUITION DUE TODAY
|
|
</Text>
|
|
<XStack items="baseline" gap="$2" mt="$1">
|
|
{applied?.valid && (
|
|
<Text
|
|
fontSize="$4"
|
|
fontWeight="600"
|
|
color="var(--muted-foreground)"
|
|
$platform-web={{ textDecoration: 'line-through' }}
|
|
>
|
|
${originalPrice}
|
|
</Text>
|
|
)}
|
|
<Text fontSize="$6" fontWeight="800" color="var(--white)">
|
|
${activePrice}
|
|
</Text>
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
USD
|
|
</Text>
|
|
{applied?.valid && applied.discountPercent && (
|
|
<Chip px={8} py={2} fontSize="$1" fontFamily="$mono" color="var(--emerald-400)">
|
|
{applied.discountPercent}% OFF
|
|
</Chip>
|
|
)}
|
|
</XStack>
|
|
</YStack>
|
|
|
|
<YStack items="flex-end">
|
|
<Text fontSize="$1" color="var(--muted-foreground)" fontFamily="$mono">
|
|
COMPUTE REBATE (25%)
|
|
</Text>
|
|
<Chip px={10} py={3} fontSize="$1" fontFamily="$mono" color="var(--white)" mt="$1">
|
|
+${activeRebate} Credits
|
|
</Chip>
|
|
</YStack>
|
|
</XStack>
|
|
|
|
{/* Applied Coupon Banner */}
|
|
{applied?.valid && (
|
|
<XStack
|
|
items="center"
|
|
justify="space-between"
|
|
p="$2"
|
|
px="$3"
|
|
rounded="var(--radius-md)"
|
|
bg="$panel"
|
|
$platform-web={{ backgroundColor: 'color-mix(in srgb, var(--emerald-500) 12%, transparent)' }}
|
|
borderWidth={1}
|
|
borderColor="var(--emerald-800)"
|
|
>
|
|
<XStack items="center" gap="$2">
|
|
<Check size={14} color="var(--emerald-400)" />
|
|
<Text fontSize="$1" color="var(--emerald-300)" fontWeight="600">
|
|
{applied.message}
|
|
</Text>
|
|
</XStack>
|
|
<Box
|
|
render="button"
|
|
onClick={handleRemove}
|
|
p="$1"
|
|
rounded="var(--radius-sm)"
|
|
$platform-web={{
|
|
cursor: 'pointer',
|
|
background: 'transparent',
|
|
border: 'none',
|
|
color: 'var(--muted-foreground)',
|
|
}}
|
|
>
|
|
<X size={14} />
|
|
</Box>
|
|
</XStack>
|
|
)}
|
|
|
|
{/* Coupon Input Form */}
|
|
{!applied?.valid ? (
|
|
<XStack render={<form onSubmit={handleApply} />} gap="$2" width="100%" mt="$1">
|
|
<Box flex={1} position="relative">
|
|
<input
|
|
type="text"
|
|
placeholder="Coupon code (e.g. STUDENT50, HANZO20)"
|
|
value={code}
|
|
onChange={(e) => {
|
|
setCode(e.target.value.toUpperCase())
|
|
if (error) setError(null)
|
|
}}
|
|
style={{
|
|
width: '100%',
|
|
padding: '9px 12px 9px 34px',
|
|
borderRadius: 'var(--radius-md)',
|
|
background: 'var(--card)',
|
|
color: 'var(--white)',
|
|
border: error ? '1px solid var(--red-600)' : '1px solid var(--border)',
|
|
fontSize: '13px',
|
|
fontFamily: 'monospace',
|
|
outline: 'none',
|
|
}}
|
|
/>
|
|
<Box position="absolute" l={10} t={10} pointerEvents="none" opacity={0.6}>
|
|
<Tag size={14} color="var(--white)" />
|
|
</Box>
|
|
</Box>
|
|
<Action
|
|
render="button"
|
|
type="submit"
|
|
px={14}
|
|
py={8}
|
|
$platform-web={{ fontSize: '13px' }}
|
|
>
|
|
Apply
|
|
</Action>
|
|
</XStack>
|
|
) : null}
|
|
|
|
{error && (
|
|
<Text fontSize="$1" color="var(--red-400)" mt={-4}>
|
|
{error}
|
|
</Text>
|
|
)}
|
|
|
|
{/* Suggested Quick Promos */}
|
|
{!applied?.valid && (
|
|
<XStack items="center" gap="$2" flexWrap="wrap" pt="$1">
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
Suggested codes:
|
|
</Text>
|
|
{['STUDENT50', 'HANZO20', 'EARLYBIRD'].map((sample) => (
|
|
<Box
|
|
key={sample}
|
|
render="button"
|
|
onClick={() => {
|
|
setCode(sample)
|
|
const res = validateCoupon(sample, originalPrice)
|
|
if (res.valid) {
|
|
setApplied(res)
|
|
setError(null)
|
|
onCouponChange?.(res)
|
|
}
|
|
}}
|
|
px={8}
|
|
py={2}
|
|
rounded="var(--radius-sm)"
|
|
bg="var(--card)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
$platform-web={{
|
|
cursor: 'pointer',
|
|
fontSize: '11px',
|
|
fontFamily: 'monospace',
|
|
color: 'var(--muted-foreground)',
|
|
}}
|
|
hoverStyle={{ borderColor: 'var(--white)' }}
|
|
>
|
|
{sample}
|
|
</Box>
|
|
))}
|
|
</XStack>
|
|
)}
|
|
|
|
{/* Primary Checkout Action Button */}
|
|
<YStack gap="$2" mt="$2">
|
|
<Action href={checkoutHref} fill>
|
|
Enroll in {courseCode} — ${activePrice} USD →
|
|
</Action>
|
|
<Text
|
|
fontSize="$1"
|
|
color="var(--muted-foreground)"
|
|
$platform-web={{ textAlign: 'center', display: 'block' }}
|
|
>
|
|
Includes {activeRebate} compute credit rebate, Hanzo Visor sandbox pod & HACE Credential
|
|
</Text>
|
|
</YStack>
|
|
</YStack>
|
|
</YStack>
|
|
)
|
|
}
|