🎉 chore(init): project initialization

This commit is contained in:
web@ppanel
2024-11-14 01:22:43 +07:00
commit 829edfa824
479 changed files with 61413 additions and 0 deletions
@@ -0,0 +1,70 @@
'use client';
import { Display } from '@/components/display';
import { Separator } from '@shadcn/ui/separator';
import { useTranslations } from 'next-intl';
interface SubscribeBillingProps {
order?: {
type?: number;
subscribe_id?: number;
quantity?: number;
price?: number;
reduction?: number;
coupon?: number;
fee_amount?: number;
amount?: number;
unit_price?: number;
};
}
export function SubscribeBilling({ order }: SubscribeBillingProps) {
const t = useTranslations('subscribe.billing');
return (
<>
<div className='font-semibold'>{t('billingTitle')}</div>
<ul className='grid grid-cols-2 gap-3 *:flex *:items-center *:justify-between lg:grid-cols-1'>
{order?.type && [1, 2].includes(order?.type) && (
<li>
<span className='text-muted-foreground'>{t('duration')}</span>
<span>
{order?.quantity || 1} {t('months')}
</span>
</li>
)}
<li>
<span className='text-muted-foreground'>{t('price')}</span>
<span>
<Display type='currency' value={order?.price || order?.unit_price} />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('productDiscount')}</span>
<span>
<Display type='currency' value={order?.reduction} />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('couponDiscount')}</span>
<span>
<Display type='currency' value={order?.coupon} />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('fee')}</span>
<span>
<Display type='currency' value={order?.fee_amount} />
</span>
</li>
</ul>
<Separator />
<div className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('total')}</span>
<span>
<Display type='currency' value={order?.amount} />
</span>
</div>
</>
);
}
@@ -0,0 +1,53 @@
'use client';
import { Display } from '@/components/display';
import { useTranslations } from 'next-intl';
interface SubscribeDetailProps {
subscribe?: {
traffic?: number | null;
speed_limit?: number | null;
device_limit?: number | null;
name?: string | null;
quantity?: number | null;
unit_price?: number | null;
};
}
export function SubscribeDetail({ subscribe }: SubscribeDetailProps) {
const t = useTranslations('subscribe.detail');
return (
<>
<div className='font-semibold'>{t('productDetail')}</div>
<ul className='grid grid-cols-2 gap-3 *:flex *:items-center *:justify-between lg:grid-cols-1'>
{subscribe?.name && (
<li className='flex items-center justify-between'>
<span className='text-muted-foreground line-clamp-2 flex-1'>{subscribe?.name}</span>
<span>
x <span>{subscribe?.quantity || 1}</span>
</span>
</li>
)}
<li>
<span className='text-muted-foreground'>{t('availableTraffic')}</span>
<span>
<Display type='traffic' value={subscribe?.traffic} unlimited />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('connectionSpeed')}</span>
<span>
<Display type='traffic' value={subscribe?.speed_limit} unlimited />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('connectedDevices')}</span>
<span>
<Display value={subscribe?.device_limit} type='number' unlimited />
</span>
</li>
</ul>
</>
);
}
@@ -0,0 +1,141 @@
'use client';
import { Display } from '@/components/display';
import { querySubscribeGroupList, querySubscribeList } from '@/services/user/subscribe';
import { Icon } from '@iconify/react';
import { Button } from '@shadcn/ui/button';
import { Card, CardContent, CardFooter, CardHeader } from '@shadcn/ui/card';
import { cn } from '@shadcn/ui/lib/utils';
import { Separator } from '@shadcn/ui/separator';
import { Tabs, TabsList, TabsTrigger } from '@shadcn/ui/tabs';
import { useQuery } from '@tanstack/react-query';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import Purchase from '../order/purchase';
import { SubscribeDetail } from './detail';
export default function Page() {
const t = useTranslations('subscribe');
const [subscribe, setSubscribe] = useState<API.SubscribeDetails>();
const [group, setGroup] = useState<string>('');
const { data: groups } = useQuery({
queryKey: ['querySubscribeGroupList'],
queryFn: async () => {
const { data } = await querySubscribeGroupList();
return data.data?.list || [];
},
});
const { data } = useQuery({
queryKey: ['querySubscribeList'],
queryFn: async () => {
const { data } = await querySubscribeList();
return data.data?.list || [];
},
});
return (
<>
<Tabs value={group} onValueChange={setGroup} className='space-y-4'>
{groups?.length && (
<>
<h1 className='text-muted-foreground w-full'>{t('category')}</h1>
<TabsList>
<TabsTrigger value=''>{t('all')}</TabsTrigger>
{groups.map((group) => (
<TabsTrigger key={group.id} value={String(group.id)}>
{group.name}
</TabsTrigger>
))}
</TabsList>
<h2 className='text-muted-foreground w-full'>{t('products')}</h2>
</>
)}
<div className='grid gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
{data
?.filter((item) => (group ? item.group_id === Number(group) : true))
?.map((item) => (
<Card className='flex flex-col' key={item.id}>
<CardHeader className='bg-muted/50 text-xl font-medium'>{item.name}</CardHeader>
<CardContent className='flex flex-grow flex-col gap-3 p-6 *:!text-sm'>
{/* <div className='font-semibold'>{t('productDescription')}</div> */}
<ul className='flex flex-grow flex-col gap-3'>
{(() => {
let parsedDescription;
try {
parsedDescription = JSON.parse(item.description);
} catch {
parsedDescription = { description: '', features: [] };
}
const { description, features } = parsedDescription;
return (
<>
{description && <li className='text-muted-foreground'>{description}</li>}
{features.map(
(
feature: {
icon: string;
label: string;
type: 'default' | 'success' | 'destructive';
support: boolean;
},
index: number,
) => (
<li
className={cn('flex items-center gap-1', {
'text-muted-foreground line-through':
feature.type === 'destructive',
})}
key={index}
>
{feature.icon && (
<Icon
icon={feature.icon}
className={cn('text-primary size-5', {
'text-green-500': feature.type === 'success',
'text-destructive': feature.type === 'destructive',
})}
/>
)}
{feature.label}
</li>
),
)}
</>
);
})()}
</ul>
<SubscribeDetail
subscribe={{
...item,
name: null,
}}
/>
</CardContent>
<Separator />
<CardFooter className='relative mt-2 flex flex-col gap-2'>
<h2 className='pb-5 text-2xl font-semibold sm:text-3xl'>
<Display type='currency' value={item.unit_price} />
<span className='text-base font-medium'>/{t('perMonth')}</span>
</h2>
<Button
className='absolute bottom-0 w-full rounded-b-xl rounded-t-none'
onClick={() => {
setSubscribe(item);
}}
>
{t('buy')}
</Button>
</CardFooter>
</Card>
))}
</div>
</Tabs>
<Purchase subscribe={subscribe} setSubscribe={setSubscribe} />
</>
);
}