fix: title-case VIES data, animate company info, no price flash

VIES returns ALL CAPS — now title-cased for display. Company info
slides in with framer-motion. Price breakdown stays visible during
VAT verification instead of flashing to a loading spinner.
This commit is contained in:
Usman Baig
2026-03-27 12:42:07 +01:00
parent 9656225b60
commit cb7e4c7c98

View File

@@ -2,7 +2,7 @@
import { useState, useEffect, useCallback } from 'react' import { useState, useEffect, useCallback } from 'react'
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { motion } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { Select } from '@ciphera-net/ui' import { Select } from '@ciphera-net/ui'
import { TRAFFIC_TIERS, PLAN_PRICES } from '@/lib/plans' import { TRAFFIC_TIERS, PLAN_PRICES } from '@/lib/plans'
import { COUNTRY_OPTIONS } from '@/lib/countries' import { COUNTRY_OPTIONS } from '@/lib/countries'
@@ -21,6 +21,11 @@ interface PlanSummaryProps {
const inputClass = const inputClass =
'w-full rounded-lg border border-neutral-700 bg-neutral-800/50 px-3 py-2.5 text-sm text-white placeholder:text-neutral-500 focus:outline-none focus:ring-1 focus:ring-brand-orange focus:border-brand-orange transition-colors' 'w-full rounded-lg border border-neutral-700 bg-neutral-800/50 px-3 py-2.5 text-sm text-white placeholder:text-neutral-500 focus:outline-none focus:ring-1 focus:ring-brand-orange focus:border-brand-orange transition-colors'
/** Convert VIES ALL-CAPS text to title case (e.g. "SA SODIMAS" → "Sa Sodimas") */
function toTitleCase(s: string) {
return s.replace(/\S+/g, (w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
}
export default function PlanSummary({ plan, interval, limit, country, vatId, onCountryChange, onVatIdChange }: PlanSummaryProps) { export default function PlanSummary({ plan, interval, limit, country, vatId, onCountryChange, onVatIdChange }: PlanSummaryProps) {
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
@@ -148,17 +153,35 @@ export default function PlanSummary({ plan, interval, limit, country, vatId, onC
</button> </button>
</div> </div>
{/* Verified company info */} {/* Verified company info */}
{isVatValid && vatResult?.company_name && ( <AnimatePresence>
<div className="mt-2 rounded-lg bg-green-500/5 border border-green-500/20 px-3 py-2 text-xs text-neutral-400"> {isVatValid && vatResult?.company_name && (
<p className="font-medium text-green-400">{vatResult.company_name}</p> <motion.div
{vatResult.company_address && ( initial={{ opacity: 0, height: 0 }}
<p className="mt-0.5 whitespace-pre-line">{vatResult.company_address}</p> animate={{ opacity: 1, height: 'auto' }}
)} exit={{ opacity: 0, height: 0 }}
</div> transition={{ duration: 0.25, ease: 'easeOut' }}
)} className="overflow-hidden"
{isVatChecked && vatResult && !vatResult.vat_exempt && ( >
<p className="mt-1.5 text-xs text-yellow-400">VAT ID could not be verified. 21% VAT will apply.</p> <div className="mt-2 rounded-lg bg-green-500/5 border border-green-500/20 px-3 py-2 text-xs text-neutral-400">
)} <p className="font-medium text-green-400">{toTitleCase(vatResult.company_name)}</p>
{vatResult.company_address && (
<p className="mt-0.5 whitespace-pre-line">{toTitleCase(vatResult.company_address)}</p>
)}
</div>
</motion.div>
)}
{isVatChecked && vatResult && !vatResult.vat_exempt && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="mt-1.5 text-xs text-yellow-400"
>
VAT ID could not be verified. 21% VAT will apply.
</motion.p>
)}
</AnimatePresence>
</div> </div>
{/* Price breakdown */} {/* Price breakdown */}
@@ -176,10 +199,10 @@ export default function PlanSummary({ plan, interval, limit, country, vatId, onC
</span> </span>
)} )}
</div> </div>
) : vatLoading ? ( ) : vatLoading && !vatResult ? (
<div className="flex items-center gap-2 text-sm text-neutral-400"> <div className="flex items-center gap-2 text-sm text-neutral-400">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-neutral-600 border-t-white" /> <div className="h-4 w-4 animate-spin rounded-full border-2 border-neutral-600 border-t-white" />
{vatId ? 'Verifying VAT ID...' : 'Calculating VAT...'} Calculating VAT...
</div> </div>
) : vatResult ? ( ) : vatResult ? (
<div className="space-y-1.5 text-sm"> <div className="space-y-1.5 text-sm">