mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
feat(ui): improve mobile responsive layouts
This commit is contained in:
@@ -9,9 +9,7 @@ import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import {
|
||||
@@ -23,6 +21,7 @@ import {
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TitledCard } from '@/components/ui/titled-card'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -48,6 +47,7 @@ import type { PaymentMethod, TopupInfo } from '../types'
|
||||
|
||||
interface SubscriptionPlansCardProps {
|
||||
topupInfo: TopupInfo | null
|
||||
onAvailabilityChange?: (available: boolean) => void
|
||||
}
|
||||
|
||||
function getEpayMethods(payMethods: PaymentMethod[] = []): PaymentMethod[] {
|
||||
@@ -56,7 +56,10 @@ function getEpayMethods(payMethods: PaymentMethod[] = []): PaymentMethod[] {
|
||||
)
|
||||
}
|
||||
|
||||
export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
export function SubscriptionPlansCard({
|
||||
topupInfo,
|
||||
onAvailabilityChange,
|
||||
}: SubscriptionPlansCardProps) {
|
||||
const { t } = useTranslation()
|
||||
const { status } = useStatus()
|
||||
|
||||
@@ -76,11 +79,11 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
const [selectedPlan, setSelectedPlan] = useState<PlanRecord | null>(null)
|
||||
|
||||
const enableStripe = !!status?.enable_stripe_topup
|
||||
const enableCreem = !!props.topupInfo?.enable_creem_topup
|
||||
const enableCreem = !!topupInfo?.enable_creem_topup
|
||||
const enableOnlineTopUp = !!status?.enable_online_topup
|
||||
const epayMethods = useMemo(
|
||||
() => getEpayMethods(props.topupInfo?.pay_methods),
|
||||
[props.topupInfo?.pay_methods]
|
||||
() => getEpayMethods(topupInfo?.pay_methods),
|
||||
[topupInfo?.pay_methods]
|
||||
)
|
||||
|
||||
const fetchPlans = useCallback(async () => {
|
||||
@@ -148,6 +151,7 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
|
||||
const hasActive = activeSubscriptions.length > 0
|
||||
const hasAny = allSubscriptions.length > 0
|
||||
const isAvailable = loading || plans.length > 0 || hasAny
|
||||
const disablePref = !hasActive
|
||||
const isSubPref =
|
||||
billingPreference === 'subscription_first' ||
|
||||
@@ -165,6 +169,10 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
return map
|
||||
}, [allSubscriptions])
|
||||
|
||||
useEffect(() => {
|
||||
onAvailabilityChange?.(isAvailable)
|
||||
}, [isAvailable, onAvailabilityChange])
|
||||
|
||||
const planTitleMap = useMemo(() => {
|
||||
const map = new Map<number, string>()
|
||||
for (const p of plans) {
|
||||
@@ -191,11 +199,11 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card className='overflow-hidden'>
|
||||
<CardHeader className='border-b'>
|
||||
<Card className='gap-0 overflow-hidden py-0'>
|
||||
<CardHeader className='border-b p-3 !pb-3 sm:p-5 sm:!pb-5'>
|
||||
<Skeleton className='h-6 w-32' />
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4 pt-6'>
|
||||
<CardContent className='space-y-4 p-3 sm:p-5'>
|
||||
<Skeleton className='h-20 w-full' />
|
||||
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
@@ -213,27 +221,16 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className='overflow-hidden'>
|
||||
<CardHeader className='border-b'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='bg-muted flex h-9 w-9 shrink-0 items-center justify-center rounded-lg'>
|
||||
<Crown className='h-4 w-4' />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
<CardTitle className='text-xl tracking-tight'>
|
||||
{t('Subscription Plans')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('Purchase a plan to enjoy model benefits')}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-5 pt-6'>
|
||||
<TitledCard
|
||||
title={t('Subscription Plans')}
|
||||
description={t('Purchase a plan to enjoy model benefits')}
|
||||
icon={<Crown className='h-4 w-4' />}
|
||||
contentClassName='space-y-4 sm:space-y-5'
|
||||
>
|
||||
{/* My subscriptions & billing preference */}
|
||||
<div className='rounded-xl border p-4'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='rounded-xl border p-3 sm:p-4'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-2.5 sm:gap-3'>
|
||||
<div className='flex min-w-0 flex-wrap items-center gap-2'>
|
||||
<span className='text-sm font-medium'>
|
||||
{t('My Subscriptions')}
|
||||
</span>
|
||||
@@ -265,12 +262,12 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='flex w-full items-center gap-2 sm:w-auto'>
|
||||
<Select
|
||||
value={displayPref}
|
||||
onValueChange={handlePreferenceChange}
|
||||
>
|
||||
<SelectTrigger className='h-8 w-[140px] text-xs'>
|
||||
<SelectTrigger className='h-8 flex-1 text-xs sm:w-[140px] sm:flex-none'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -452,7 +449,7 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
|
||||
{/* Available plans grid */}
|
||||
{plans.length > 0 ? (
|
||||
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
|
||||
<div className='grid grid-cols-1 gap-3 2xl:grid-cols-2 2xl:gap-4'>
|
||||
{plans.map((p, index) => {
|
||||
const plan = p?.plan
|
||||
if (!plan) return null
|
||||
@@ -485,7 +482,7 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
isPopular && 'border-primary/70 shadow-sm'
|
||||
)}
|
||||
>
|
||||
<CardContent className='flex h-full flex-col p-4'>
|
||||
<CardContent className='flex h-full flex-col p-3.5 sm:p-4'>
|
||||
<div className='mb-2 flex items-start justify-between gap-3'>
|
||||
<div className='min-w-0'>
|
||||
<h4 className='truncate font-semibold'>
|
||||
@@ -568,8 +565,7 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) {
|
||||
{t('No plans available')}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TitledCard>
|
||||
|
||||
<SubscriptionPurchaseDialog
|
||||
open={purchaseOpen}
|
||||
|
||||
Reference in New Issue
Block a user