feat: 修改样式
This commit is contained in:
@@ -286,7 +286,7 @@ export default function Affiliate() {
|
||||
className='h-6 border-0 p-0 text-center text-sm focus-visible:ring-0 sm:h-8'
|
||||
style={{ width: `${Math.max(3, String(count).length + 1)}ch` }}
|
||||
/>
|
||||
<span className='text-sm'>{t('users')}</span>
|
||||
<span className='text-sm'>users</span>
|
||||
</div>
|
||||
<Button
|
||||
variant='secondary'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import SvgIcon from '@/components/SvgIcon';
|
||||
import { locales } from '@/config/constants';
|
||||
import { setLocale } from '@/utils/common';
|
||||
import {
|
||||
@@ -52,10 +53,10 @@ export default function LanguageSwitch() {
|
||||
|
||||
return (
|
||||
<Select defaultValue={locale} onValueChange={handleLanguageChange}>
|
||||
<SelectTrigger className='hover:bg-accent hover:text-accent-foreground w-auto border-none bg-transparent p-2 shadow-none focus:ring-0 [&>svg]:hidden'>
|
||||
<SelectTrigger className='hover:bg-accent hover:text-accent-foreground w-auto rounded-full border-none bg-transparent p-1 shadow-none focus:ring-0 [&>svg]:hidden'>
|
||||
<SelectValue>
|
||||
<div className='flex items-center'>
|
||||
<Icon icon={`flagpack:${country?.alpha2.toLowerCase()}`} className='!size-7' />
|
||||
<SvgIcon name={'language'} />
|
||||
<span className='sr-only'>{languages[locale as keyof typeof languages]}</span>
|
||||
</div>
|
||||
</SelectValue>
|
||||
|
||||
@@ -123,7 +123,7 @@ const StarRating = ({ rating, maxRating = 5 }: { rating: number; maxRating?: num
|
||||
const FeatureList = ({ plan }: { plan: ProcessedPlanData }) => {
|
||||
const t = useTranslations('subscribe.detail');
|
||||
const tOffer = useTranslations('components.offerDialog');
|
||||
const features = [{ label: tOffer('availableNodes'), value: plan.features?.nodes || '11' }];
|
||||
const features = [{ label: tOffer('availableNodes'), value: plan?.server_count }];
|
||||
|
||||
return (
|
||||
<div className='mt-6 space-y-0 sm:mt-6'>
|
||||
@@ -168,7 +168,7 @@ const FeatureList = ({ plan }: { plan: ProcessedPlanData }) => {
|
||||
<span className='text-xs font-light leading-[1.8461538461538463em] text-black sm:text-[13px]'>
|
||||
{tOffer('networkStabilityIndex')}
|
||||
</span>
|
||||
<StarRating rating={plan.features?.stability || 4} />
|
||||
<StarRating rating={5} />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
||||
@@ -47,3 +48,85 @@ export function SubscribeBilling({ order }: { order: API.Order }) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { Display } from '@/components/display';
|
||||
import { Separator } from '@workspace/airo-ui/components/separator';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface SubscribeBillingProps {
|
||||
order?: Partial<
|
||||
API.OrderDetail & {
|
||||
unit_price: number;
|
||||
unit_time: number;
|
||||
subscribe_discount: number;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
export function SubscribeBilling({ order }: Readonly<SubscribeBillingProps>) {
|
||||
const t = useTranslations('subscribe');
|
||||
const t_c = useTranslations('components.billing');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='mb-1 font-semibold text-[#225BA9]'>{t('billing.billingTitle')}</div>
|
||||
<ul className='grid grid-cols-1 gap-1 text-[15px] font-light text-[#666] *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||
<li>
|
||||
<span className=''>{t('billing.duration')}</span>
|
||||
<span>
|
||||
{order?.quantity === 1 ? t_c('30days') : ''}
|
||||
{order?.quantity === 12 ? t_c('365days') : ''}
|
||||
</span>
|
||||
</li>
|
||||
{order?.type && [1, 2].includes(order?.type) && (
|
||||
<li>
|
||||
<span className=''>{t('billing.duration')}</span>
|
||||
<span>
|
||||
{order?.quantity || 1} {t(order?.unit_time || 'Month')}
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<span className=''>{t('billing.price')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.price || order?.unit_price} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className=''>{t('billing.productDiscount')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.discount} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className=''>{t('billing.couponDiscount')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.coupon_discount} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('billing.fee')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.fee_amount} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('billing.gift')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.gift_amount} />
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Separator className={'mb-3 mt-4 bg-[#225BA9]'} />
|
||||
<div className='flex items-center justify-between font-semibold text-[#666]'>
|
||||
<span className=''>{t('billing.total')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.amount} />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { preCreateOrder, purchase } from '@/services/user/order';
|
||||
import { purchaseCheckout } from '@/services/user/portal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/airo-ui/components/button';
|
||||
import {
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { SubscribeBilling } from './billing';
|
||||
import { SubscribeDetail } from './detail';
|
||||
|
||||
@@ -74,6 +76,7 @@ const Purchase = forwardRef<PurchaseDialogRef, PurchaseProps>((props, ref) => {
|
||||
queryKey: ['preCreateOrder', subscribe?.id, params.quantity],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
console.log('123123', subscribe);
|
||||
const { data } = await preCreateOrder({
|
||||
...params,
|
||||
subscribe_id: subscribe?.id as number,
|
||||
@@ -107,7 +110,16 @@ const Purchase = forwardRef<PurchaseDialogRef, PurchaseProps>((props, ref) => {
|
||||
const orderNo = response.data.data?.order_no;
|
||||
if (orderNo) {
|
||||
await getUserInfo();
|
||||
router.push(`/dashboard`);
|
||||
const data = await purchaseCheckout({
|
||||
orderNo: orderNo,
|
||||
returnUrl: window.location.href,
|
||||
});
|
||||
if (data.data?.type === 'url' && data.data.checkout_url) {
|
||||
window.open(data.data.checkout_url, '_blank');
|
||||
} else {
|
||||
toast.success(t('paymentSuccess'));
|
||||
router.push(`/dashboard`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
@@ -144,12 +156,6 @@ const Purchase = forwardRef<PurchaseDialogRef, PurchaseProps>((props, ref) => {
|
||||
<span className='absolute -top-8 left-16 z-10 rounded-md bg-[#E22C2E] px-2 py-0.5 text-[10px] font-bold leading-none text-white shadow sm:text-xs'>
|
||||
{t('discount20')}
|
||||
{/* 小三角箭头 */}
|
||||
{/* <span className="
|
||||
absolute right-0 top-full
|
||||
block h-0 w-0
|
||||
border-l-[6px] border-r-[6px] border-t-[16px]
|
||||
border-l-transparent border-r-transparent border-t-[#E22C2E]
|
||||
" />*/}
|
||||
<span
|
||||
className='absolute right-0 top-[80%] h-10 w-2 bg-[#E22C2E]'
|
||||
style={{ clipPath: 'polygon(100% 0, 100% 100%, 0 0)' }}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { recharge } from '@/services/user/order';
|
||||
import { AiroButton } from '@workspace/airo-ui/components/AiroButton';
|
||||
import { Button, ButtonProps } from '@workspace/airo-ui/components/button';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -36,7 +37,9 @@ export default function Recharge(props: Readonly<ButtonProps>) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button {...props}>{t('walletRecharge')}</Button>
|
||||
<AiroButton variant={'primary'} {...props}>
|
||||
{t('walletRecharge')}
|
||||
</AiroButton>
|
||||
</DialogTrigger>
|
||||
<DialogContent className='flex h-full flex-col overflow-hidden md:h-auto'>
|
||||
<DialogHeader>
|
||||
|
||||
@@ -28,18 +28,18 @@ export function UserNav({ from = '' }: { from?: string }) {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
{from === 'profile' ? (
|
||||
<div className='mb-3 flex cursor-pointer items-center gap-2 rounded-full bg-[#EAEAEA] p-1 pr-6'>
|
||||
<Avatar className='h-[34px] w-[34px] md:h-[52px] md:w-[52px]'>
|
||||
<div className='mb-3 mt-4 flex cursor-pointer items-center gap-2 rounded-full bg-[#EAEAEA] p-[3px] pr-6'>
|
||||
<Avatar className='h-[34px] w-[34px]'>
|
||||
<AvatarImage
|
||||
alt={user?.avatar ?? ''}
|
||||
src={user?.avatar ?? ''}
|
||||
className='object-cover'
|
||||
/>
|
||||
<AvatarFallback className='to-primary text-background bg-[#0F2C53] bg-gradient-to-br text-[28px] font-bold md:text-[40px]'>
|
||||
<AvatarFallback className='to-primary text-background bg-[#0F2C53] bg-gradient-to-br text-[28px] font-bold md:text-[27px]'>
|
||||
{user?.auth_methods?.[0]?.auth_identifier.toUpperCase().charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className='flex flex-1 items-center justify-between text-xs md:text-base'>
|
||||
<div className='flex flex-1 items-center justify-between text-xs md:text-sm'>
|
||||
{user?.auth_methods?.[0]?.auth_identifier.split('@')[0]}
|
||||
</div>
|
||||
<Icon icon='lucide:ellipsis' className='text-muted-foreground !size-6' />
|
||||
@@ -61,13 +61,13 @@ export function UserNav({ from = '' }: { from?: string }) {
|
||||
forceMount
|
||||
align='end'
|
||||
side={from === 'profile' ? (isMobile ? 'top' : 'right') : undefined}
|
||||
className='flex w-[var(--sidebar-width)] flex-col gap-1 rounded-[42px] border-0 bg-transparent pl-[26px] pr-4 shadow-none md:w-64 md:gap-3 md:border md:bg-white md:p-3 md:shadow-md'
|
||||
className='mt-[1px] flex w-[var(--sidebar-width)] flex-col gap-1 rounded-[28px] border-0 bg-transparent pl-[26px] pr-4 shadow-none md:w-48 md:gap-3 md:border md:bg-white md:p-3'
|
||||
style={{
|
||||
'--sidebar-width': '14rem',
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-start gap-2 rounded-full bg-white p-1 md:bg-[#EAEAEA]'>
|
||||
<Avatar className='h-[34px] w-[34px] md:h-[52px] md:w-[52px]'>
|
||||
<div className='flex items-center justify-start gap-2 rounded-full bg-white p-[3px] md:bg-[#EAEAEA]'>
|
||||
<Avatar className='h-[34px] w-[34px]'>
|
||||
<AvatarImage
|
||||
alt={user?.avatar ?? ''}
|
||||
src={user?.avatar ?? ''}
|
||||
@@ -77,11 +77,11 @@ export function UserNav({ from = '' }: { from?: string }) {
|
||||
{user?.auth_methods?.[0]?.auth_identifier.toUpperCase().charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className='flex flex-col space-y-0.5'>
|
||||
<p className='text-xs font-medium leading-none md:text-xl'>
|
||||
<div className='flex flex-col'>
|
||||
<p className='text-xs font-medium leading-none md:text-sm'>
|
||||
{user?.auth_methods?.[0]?.auth_identifier.split('@')[0]}
|
||||
</p>
|
||||
<p className='text-muted-foreground text-[10px] md:text-xs'>
|
||||
<p className='text-muted-foreground text-[10px]'>
|
||||
{user?.auth_methods?.[0]?.auth_identifier}
|
||||
</p>
|
||||
</div>
|
||||
@@ -101,9 +101,9 @@ export function UserNav({ from = '' }: { from?: string }) {
|
||||
toggleSidebar();
|
||||
router.push(`${item.url}`);
|
||||
}}
|
||||
className='flex cursor-pointer items-center gap-3 rounded-full bg-white px-5 py-2 text-base font-medium focus:bg-[#0F2C53] focus:text-white data-[active=true]:bg-[#0F2C53] data-[active=true]:text-white md:text-xl'
|
||||
className='flex cursor-pointer items-center gap-3 rounded-full bg-white px-5 py-2.5 text-base font-medium focus:bg-[#0F2C53] focus:text-white data-[active=true]:bg-[#0F2C53] data-[active=true]:text-white md:text-sm'
|
||||
>
|
||||
<SvgIcon className='!size-4 flex-none md:!size-6' name={item.icon} />
|
||||
<SvgIcon className='!size-4 flex-none' name={item.icon} />
|
||||
<span className='flex-grow truncate'>{t(item.title)}</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
@@ -112,14 +112,9 @@ export function UserNav({ from = '' }: { from?: string }) {
|
||||
Logout();
|
||||
setUser();
|
||||
}}
|
||||
className='flex cursor-pointer items-center gap-3 rounded-full bg-[#E22C2E] px-5 py-2 text-base font-medium text-white focus:bg-[#E22C2E] focus:text-white md:bg-white md:text-xl md:text-[#0F2C53]'
|
||||
className='flex cursor-pointer items-center gap-3 rounded-full bg-[#E22C2E] px-5 py-2 text-base font-medium text-white focus:bg-[#E22C2E] focus:text-white md:bg-white md:text-sm md:text-[#0F2C53]'
|
||||
>
|
||||
<SvgIcon
|
||||
className='!size-4 flex-none md:!size-6'
|
||||
width='99'
|
||||
height='100'
|
||||
name={'exit'}
|
||||
/>
|
||||
<SvgIcon className='!size-4 flex-none' width='99' height='100' name={'exit'} />
|
||||
<span className='flex-grow'>{t('logout')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
Reference in New Issue
Block a user