Files
web/src/components/composites/purchase/shared/side-panel.tsx

206 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import {use, useEffect, useRef, useState} from 'react'
import Link from 'next/link'
import {useFormContext, useWatch} from 'react-hook-form'
import {Card} from '@/components/ui/card'
import {buttonVariants} from '@/components/ui/button'
import {useProfileStore} from '@/components/stores/profile'
import Pay from '@/components/composites/purchase/pay'
import {FieldPayment} from './field-payment'
import {buildPurchaseResource, PurchaseKind, PurchaseSelection} from './resource'
import {getPrice, getPriceHome} from '@/actions/resource'
import {ExtraResp} from '@/lib/api'
import {formatPurchaseLiveLabel} from './sku'
import {User} from '@/lib/models'
import {PurchaseFormValues} from './form-values'
import {IdCard} from 'lucide-react'
const emptyPrice: ExtraResp<typeof getPrice> = {
price: '0.00',
actual: '0.00',
discounted: '0.00',
}
export type PurchaseSidePanelProps = {
kind: PurchaseKind
}
export function PurchaseSidePanel(props: PurchaseSidePanelProps) {
const {control} = useFormContext<PurchaseFormValues>()
const method = useWatch<PurchaseFormValues>({control, name: 'pay_type'}) as PurchaseFormValues['pay_type']
const mode = useWatch<PurchaseFormValues>({control, name: 'type'}) as PurchaseFormValues['type']
const live = useWatch<PurchaseFormValues>({control, name: 'live'}) as PurchaseFormValues['live']
const quota = useWatch<PurchaseFormValues>({control, name: 'quota'}) as PurchaseFormValues['quota']
const expire = useWatch<PurchaseFormValues>({control, name: 'expire'}) as PurchaseFormValues['expire']
const dailyLimit = useWatch<PurchaseFormValues>({control, name: 'daily_limit'}) as PurchaseFormValues['daily_limit']
const profile = use(useProfileStore(store => store.profile))
const selection: PurchaseSelection = {
kind: props.kind,
mode,
live,
quota,
expire,
dailyLimit,
}
const priceData = usePurchasePrice(profile, selection)
const {price, actual: discountedPrice = '0.00'} = priceData
const totalDiscount = getTotalDiscount(price, discountedPrice)
const hasDiscount = Number(totalDiscount) > 0
const liveLabel = formatPurchaseLiveLabel(live, props.kind)
const resource = buildPurchaseResource(selection)
return (
<Card className="flex-none basis-90 p-6 flex flex-col gap-6 relative">
<h3></h3>
<ul className="flex flex-col gap-3">
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">{mode === '2' ? '包量套餐' : '包时套餐'}</span>
</li>
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500">IP </span>
<span className="text-sm">{liveLabel}</span>
</li>
{mode === '2' ? (
<>
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"> IP </span>
<span className="text-sm">{quota} </span>
</li>
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">{price}</span>
</li>
{hasDiscount && (
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">-{totalDiscount}</span>
</li>
)}
</>
) : (
<>
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">{expire} </span>
</li>
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">{dailyLimit} </span>
</li>
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">{price}</span>
</li>
{hasDiscount && (
<li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span>
<span className="text-sm">-{totalDiscount}</span>
</li>
)}
</>
)}
</ul>
<div className="border-b border-gray-200"></div>
<p className="flex justify-between items-center">
<span></span>
<span className="text-xl text-orange-500">{discountedPrice}</span>
</p>
{profile ? (
profile.id_type !== 0 ? (
<>
<FieldPayment balance={profile.balance}/>
<Pay
method={method}
balance={profile.balance}
amount={discountedPrice}
resource={resource}
/>
</>
) : (
<div className="flex flex-col gap-3">
<p className="text-sm text-gray-500">
</p>
<Link
href="/admin/identify"
className={buttonVariants()}
>
<IdCard size={16} className="mr-1"/>
</Link>
</div>
)
) : (
<Link href="/login" className={buttonVariants()}>
</Link>
)}
</Card>
)
}
function usePurchasePrice(profile: User | null, selection: PurchaseSelection) {
const [priceData, setPriceData] = useState<ExtraResp<typeof getPrice>>(emptyPrice)
const requestIdRef = useRef(0)
const {kind, mode, live, quota, expire, dailyLimit} = selection
useEffect(() => {
const requestId = ++requestIdRef.current
const loadPrice = async () => {
try {
const resource = buildPurchaseResource({
kind,
mode,
live,
quota,
expire,
dailyLimit,
})
const response = profile
? await getPrice(resource)
: await getPriceHome(resource)
if (requestId !== requestIdRef.current) {
return
}
if (!response.success) {
throw new Error(response.message || '获取价格失败')
}
setPriceData({
price: response.data.price,
actual: response.data.actual ?? response.data.price ?? '0.00',
discounted: response.data.discounted ?? '0.00',
})
}
catch (error) {
if (requestId !== requestIdRef.current) {
return
}
console.error('获取价格失败:', error)
setPriceData(emptyPrice)
}
}
loadPrice()
}, [dailyLimit, expire, kind, live, mode, profile, quota])
return priceData
}
function getTotalDiscount(price: string, discountedPrice: string) {
const originalPrice = Number.parseFloat(price)
const actualPrice = Number.parseFloat(discountedPrice)
if (Number.isNaN(originalPrice) || Number.isNaN(actualPrice)) {
return '0.00'
}
return (originalPrice - actualPrice).toFixed(2)
}