mirror of
https://github.com/perfect-panel/ppanel-web.git
synced 2026-02-06 11:40:28 -05:00
🐛 fix(user): Update user subscribe display
This commit is contained in:
parent
7023875548
commit
3bb714d15c
21
CHANGELOG.md
21
CHANGELOG.md
@ -1,24 +1,23 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.34](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.33...v1.0.0-beta.34) (2025-04-02)
|
||||
|
||||
|
||||
### ✨ Features
|
||||
|
||||
* **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
|
||||
* **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
|
||||
* **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
|
||||
|
||||
- **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
|
||||
- **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
|
||||
- **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
|
||||
* **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
|
||||
* **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
|
||||
* **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
|
||||
* **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
|
||||
* **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
|
||||
- **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
|
||||
- **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
|
||||
- **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
|
||||
- **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
|
||||
- **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
|
||||
- **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
|
||||
1
apps/admin/services/admin/typings.d.ts
vendored
1
apps/admin/services/admin/typings.d.ts
vendored
@ -1748,6 +1748,7 @@ declare namespace API {
|
||||
subscribe: Subscribe;
|
||||
start_time: number;
|
||||
expire_time: number;
|
||||
finished_at: number;
|
||||
reset_time: number;
|
||||
traffic: number;
|
||||
download: number;
|
||||
|
||||
@ -25,6 +25,24 @@ export async function getApplication(options?: { [key: string]: any }) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Check verification code POST /v1/common/check_verification_code */
|
||||
export async function checkVerificationCode(
|
||||
body: API.CheckVerificationCodeRequest,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.Response & { data?: API.CheckVerificationCodeRespone }>(
|
||||
'/v1/common/check_verification_code',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** Get verification code POST /v1/common/send_code */
|
||||
export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) {
|
||||
return request<API.Response & { data?: API.SendCodeResponse }>('/v1/common/send_code', {
|
||||
|
||||
12
apps/admin/services/common/typings.d.ts
vendored
12
apps/admin/services/common/typings.d.ts
vendored
@ -121,6 +121,17 @@ declare namespace API {
|
||||
telephone: string;
|
||||
};
|
||||
|
||||
type CheckVerificationCodeRequest = {
|
||||
method: 'email' | 'mobile';
|
||||
account: string;
|
||||
code: string;
|
||||
type: number;
|
||||
};
|
||||
|
||||
type CheckVerificationCodeRespone = {
|
||||
status: boolean;
|
||||
};
|
||||
|
||||
type CloseOrderRequest = {
|
||||
orderNo: string;
|
||||
};
|
||||
@ -910,6 +921,7 @@ declare namespace API {
|
||||
subscribe: Subscribe;
|
||||
start_time: number;
|
||||
expire_time: number;
|
||||
finished_at: number;
|
||||
reset_time: number;
|
||||
traffic: number;
|
||||
download: number;
|
||||
|
||||
@ -32,6 +32,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/componen
|
||||
import { Separator } from '@workspace/ui/components/separator';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import { differenceInDays, formatDate, isBrowser } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
@ -88,6 +89,12 @@ export default function Content() {
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const statusWatermarks = {
|
||||
2: t('finished'),
|
||||
3: t('expired'),
|
||||
4: t('deducted'),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{userSubscribe.length ? (
|
||||
@ -159,209 +166,259 @@ export default function Content() {
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
{userSubscribe.map((item) => (
|
||||
<Card key={item.id}>
|
||||
<CardHeader className='flex flex-row flex-wrap items-center justify-between gap-2 space-y-0'>
|
||||
<CardTitle className='font-medium'>
|
||||
{item.subscribe.name}
|
||||
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
|
||||
</CardTitle>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button size='sm' variant='destructive'>
|
||||
{t('resetSubscription')}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t('prompt')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('confirmResetSubscription')}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await resetUserSubscribeToken({
|
||||
user_subscribe_id: item.id,
|
||||
});
|
||||
await refetch();
|
||||
toast.success(t('resetSuccess'));
|
||||
}}
|
||||
>
|
||||
{t('confirm')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<ResetTraffic id={item.id} replacement={item.subscribe.replacement} />
|
||||
<Renewal id={item.id} subscribe={item.subscribe} />
|
||||
<Unsubscribe id={item.id} allowDeduction={item.subscribe.allow_deduction} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col *:justify-between lg:grid-cols-4'>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('used')}</span>
|
||||
<span className='text-2xl font-bold'>
|
||||
<Display
|
||||
type='traffic'
|
||||
value={item.upload + item.download}
|
||||
unlimited={!item.traffic}
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('totalTraffic')}</span>
|
||||
<span className='text-2xl font-bold'>
|
||||
<Display type='traffic' value={item.traffic} unlimited={!item.traffic} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('nextResetDays')}</span>
|
||||
<span className='text-2xl font-semibold'>
|
||||
{item.reset_time
|
||||
? differenceInDays(new Date(item.reset_time), new Date())
|
||||
: t('noReset')}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('expirationDays')}</span>
|
||||
<span className='text-2xl font-semibold'>
|
||||
{}
|
||||
{item.expire_time
|
||||
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
|
||||
: t('noLimit')}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Separator className='mt-4' />
|
||||
<Accordion type='single' collapsible defaultValue='0' className='w-full'>
|
||||
{getUserSubscribe(item.token, protocol)?.map((url, index) => (
|
||||
<AccordionItem key={url} value={String(index)}>
|
||||
<AccordionTrigger className='hover:no-underline'>
|
||||
<div className='flex w-full flex-row items-center justify-between'>
|
||||
<CardTitle className='text-sm font-medium'>
|
||||
{t('subscriptionUrl')} {index + 1}
|
||||
</CardTitle>
|
||||
{userSubscribe.map((item) => {
|
||||
return (
|
||||
<Card
|
||||
key={item.id}
|
||||
className={cn('relative', {
|
||||
'relative opacity-80 grayscale': item.status === 3,
|
||||
'relative hidden opacity-60 blur-[0.3px] grayscale': item.status === 4,
|
||||
})}
|
||||
>
|
||||
{item.status >= 2 && (
|
||||
<div
|
||||
className={cn(
|
||||
'pointer-events-none absolute left-0 top-0 z-10 h-full w-full overflow-hidden mix-blend-difference',
|
||||
{
|
||||
'text-destructive': item.status === 2,
|
||||
'text-white': item.status === 3 || item.status === 4,
|
||||
},
|
||||
)}
|
||||
style={{
|
||||
filter: 'contrast(200%) brightness(150%) invert(0.2)',
|
||||
}}
|
||||
>
|
||||
<div className='absolute inset-0'>
|
||||
{Array.from({ length: 16 }).map((_, i) => {
|
||||
const row = Math.floor(i / 4);
|
||||
const col = i % 4;
|
||||
// 计算位置百分比
|
||||
const top = 10 + row * 25 + (col % 2 === 0 ? 5 : -5);
|
||||
const left = 5 + col * 30 + (row % 2 === 0 ? 0 : 10);
|
||||
|
||||
<CopyToClipboard
|
||||
text={url}
|
||||
onCopy={(text, result) => {
|
||||
if (result) {
|
||||
toast.success(t('copySuccess'));
|
||||
}
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
className='absolute rotate-[-30deg] whitespace-nowrap text-lg font-black opacity-40'
|
||||
style={{
|
||||
top: `${top}%`,
|
||||
left: `${left}%`,
|
||||
textShadow: '0px 0px 1px rgba(255,255,255,0.5)',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className='text-primary hover:bg-accent mr-4 flex cursor-pointer rounded p-2 text-sm'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
{statusWatermarks[item.status as keyof typeof statusWatermarks]}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<CardHeader className='flex flex-row flex-wrap items-center justify-between gap-2 space-y-0'>
|
||||
<CardTitle className='font-medium'>
|
||||
{item.subscribe.name}
|
||||
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
|
||||
</CardTitle>
|
||||
{item.status !== 4 && (
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button size='sm' variant='destructive'>
|
||||
{t('resetSubscription')}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t('prompt')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('confirmResetSubscription')}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await resetUserSubscribeToken({
|
||||
user_subscribe_id: item.id,
|
||||
});
|
||||
await refetch();
|
||||
toast.success(t('resetSuccess'));
|
||||
}}
|
||||
>
|
||||
<Icon icon='uil:copy' className='mr-2 size-5' />
|
||||
{t('copy')}
|
||||
</span>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className='grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'>
|
||||
{applications
|
||||
?.filter((application) => {
|
||||
const platformApps = application.platform?.[platform];
|
||||
return platformApps && platformApps.length > 0;
|
||||
})
|
||||
.map((application) => {
|
||||
const platformApps = application.platform?.[platform];
|
||||
const app =
|
||||
platformApps?.find((item) => item.is_default) || platformApps?.[0];
|
||||
if (!app) return null;
|
||||
{t('confirm')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<ResetTraffic id={item.id} replacement={item.subscribe.replacement} />
|
||||
<Renewal id={item.id} subscribe={item.subscribe} />
|
||||
|
||||
const handleCopy = (text: string, result: boolean) => {
|
||||
<Unsubscribe id={item.id} allowDeduction={item.subscribe.allow_deduction} />
|
||||
</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col *:justify-between lg:grid-cols-4'>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('used')}</span>
|
||||
<span className='text-2xl font-bold'>
|
||||
<Display
|
||||
type='traffic'
|
||||
value={item.upload + item.download}
|
||||
unlimited={!item.traffic}
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('totalTraffic')}</span>
|
||||
<span className='text-2xl font-bold'>
|
||||
<Display type='traffic' value={item.traffic} unlimited={!item.traffic} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('nextResetDays')}</span>
|
||||
<span className='text-2xl font-semibold'>
|
||||
{item.reset_time
|
||||
? differenceInDays(new Date(item.reset_time), new Date())
|
||||
: t('noReset')}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('expirationDays')}</span>
|
||||
<span className='text-2xl font-semibold'>
|
||||
{}
|
||||
{item.expire_time
|
||||
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
|
||||
: t('noLimit')}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Separator className='mt-4' />
|
||||
<Accordion type='single' collapsible defaultValue='0' className='w-full'>
|
||||
{getUserSubscribe(item.token, protocol)?.map((url, index) => (
|
||||
<AccordionItem key={url} value={String(index)}>
|
||||
<AccordionTrigger className='hover:no-underline'>
|
||||
<div className='flex w-full flex-row items-center justify-between'>
|
||||
<CardTitle className='text-sm font-medium'>
|
||||
{t('subscriptionUrl')} {index + 1}
|
||||
</CardTitle>
|
||||
|
||||
<CopyToClipboard
|
||||
text={url}
|
||||
onCopy={(text, result) => {
|
||||
if (result) {
|
||||
const href = getAppSubLink(application.subscribe_type, url);
|
||||
const showSuccessMessage = () => {
|
||||
toast.success(
|
||||
<>
|
||||
<p>{t('copySuccess')}</p>
|
||||
<br />
|
||||
<p>{t('manualImportMessage')}</p>
|
||||
</>,
|
||||
);
|
||||
};
|
||||
|
||||
if (isBrowser() && href) {
|
||||
window.location.href = href;
|
||||
const checkRedirect = setTimeout(() => {
|
||||
if (window.location.href !== href) {
|
||||
showSuccessMessage();
|
||||
}
|
||||
clearTimeout(checkRedirect);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessMessage();
|
||||
toast.success(t('copySuccess'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={application.name}
|
||||
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs'
|
||||
>
|
||||
<span>{application.name}</span>
|
||||
|
||||
{application.icon && (
|
||||
<Image
|
||||
src={application.icon}
|
||||
alt={application.name}
|
||||
width={64}
|
||||
height={64}
|
||||
className='p-1'
|
||||
/>
|
||||
)}
|
||||
<div className='flex'>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='secondary'
|
||||
className='rounded-r-none px-1.5'
|
||||
asChild
|
||||
>
|
||||
<Link href={app.url}>{t('download')}</Link>
|
||||
</Button>
|
||||
|
||||
<CopyToClipboard
|
||||
text={getAppSubLink(application.subscribe_type, url) || url}
|
||||
onCopy={handleCopy}
|
||||
>
|
||||
<Button size='sm' className='rounded-l-none p-2'>
|
||||
{t('import')}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className='text-muted-foreground hidden size-full flex-col items-center justify-between gap-2 text-sm lg:flex'>
|
||||
<span>{t('qrCode')}</span>
|
||||
<QRCodeCanvas
|
||||
value={url}
|
||||
size={80}
|
||||
bgColor='transparent'
|
||||
fgColor='rgb(59, 130, 246)'
|
||||
/>
|
||||
<span className='text-center'>{t('scanToSubscribe')}</span>
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className='text-primary hover:bg-accent mr-4 flex cursor-pointer rounded p-2 text-sm'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Icon icon='uil:copy' className='mr-2 size-5' />
|
||||
{t('copy')}
|
||||
</span>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className='grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'>
|
||||
{applications
|
||||
?.filter((application) => {
|
||||
const platformApps = application.platform?.[platform];
|
||||
return platformApps && platformApps.length > 0;
|
||||
})
|
||||
.map((application) => {
|
||||
const platformApps = application.platform?.[platform];
|
||||
const app =
|
||||
platformApps?.find((item) => item.is_default) ||
|
||||
platformApps?.[0];
|
||||
if (!app) return null;
|
||||
|
||||
const handleCopy = (text: string, result: boolean) => {
|
||||
if (result) {
|
||||
const href = getAppSubLink(application.subscribe_type, url);
|
||||
const showSuccessMessage = () => {
|
||||
toast.success(
|
||||
<>
|
||||
<p>{t('copySuccess')}</p>
|
||||
<br />
|
||||
<p>{t('manualImportMessage')}</p>
|
||||
</>,
|
||||
);
|
||||
};
|
||||
|
||||
if (isBrowser() && href) {
|
||||
window.location.href = href;
|
||||
const checkRedirect = setTimeout(() => {
|
||||
if (window.location.href !== href) {
|
||||
showSuccessMessage();
|
||||
}
|
||||
clearTimeout(checkRedirect);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessMessage();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={application.name}
|
||||
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs'
|
||||
>
|
||||
<span>{application.name}</span>
|
||||
|
||||
{application.icon && (
|
||||
<Image
|
||||
src={application.icon}
|
||||
alt={application.name}
|
||||
width={64}
|
||||
height={64}
|
||||
className='p-1'
|
||||
/>
|
||||
)}
|
||||
<div className='flex'>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='secondary'
|
||||
className='rounded-r-none px-1.5'
|
||||
asChild
|
||||
>
|
||||
<Link href={app.url}>{t('download')}</Link>
|
||||
</Button>
|
||||
|
||||
<CopyToClipboard
|
||||
text={getAppSubLink(application.subscribe_type, url) || url}
|
||||
onCopy={handleCopy}
|
||||
>
|
||||
<Button size='sm' className='rounded-l-none p-2'>
|
||||
{t('import')}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className='text-muted-foreground hidden size-full flex-col items-center justify-between gap-2 text-sm lg:flex'>
|
||||
<span>{t('qrCode')}</span>
|
||||
<QRCodeCanvas
|
||||
value={url}
|
||||
size={80}
|
||||
bgColor='transparent'
|
||||
fgColor='rgb(59, 130, 246)'
|
||||
/>
|
||||
<span className='text-center'>{t('scanToSubscribe')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "kopírovat",
|
||||
"copyFailure": "Kopírování selhalo, prosím zkopírujte ručně",
|
||||
"copySuccess": "Kopírování úspěšné",
|
||||
"deducted": "Zrušeno",
|
||||
"download": "stáhnout",
|
||||
"expirationDays": "Doba platnosti/dny",
|
||||
"expired": "Vypršelo",
|
||||
"finished": "Provoz vyčerpán",
|
||||
"import": "Importovat",
|
||||
"latestAnnouncement": "Nejnovější oznámení",
|
||||
"manualImportMessage": "Tato aplikace momentálně nepodporuje spuštění, prosím importujte ručně, adresa předplatného byla automaticky zkopírována",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Kopieren",
|
||||
"copyFailure": "Kopieren fehlgeschlagen, bitte manuell kopieren",
|
||||
"copySuccess": "Kopieren erfolgreich",
|
||||
"deducted": "Storniert",
|
||||
"download": "Herunterladen",
|
||||
"expirationDays": "Ablaufzeit/Tage",
|
||||
"expired": "Abgelaufen",
|
||||
"finished": "Verkehr erschöpft",
|
||||
"import": "Importieren",
|
||||
"latestAnnouncement": "Neueste Ankündigung",
|
||||
"manualImportMessage": "Diese App unterstützt derzeit keine Aktivierung. Bitte manuell importieren, die Abonnementadresse wurde automatisch kopiert.",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Copy",
|
||||
"copyFailure": "Copy failed, please copy manually",
|
||||
"copySuccess": "Copy successful",
|
||||
"deducted": "Canceled",
|
||||
"download": "Download",
|
||||
"expirationDays": "Expiration Days",
|
||||
"expired": "Expired",
|
||||
"finished": "Traffic exhausted",
|
||||
"import": "Import",
|
||||
"latestAnnouncement": "Latest Announcement",
|
||||
"manualImportMessage": "This app does not support activation. Please import manually. The subscription address has been copied.",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Copiar",
|
||||
"copyFailure": "Error al copiar, por favor copia manualmente",
|
||||
"copySuccess": "Copia exitosa",
|
||||
"deducted": "Cancelado",
|
||||
"download": "descargar",
|
||||
"expirationDays": "Días de vencimiento",
|
||||
"expired": "Caducado",
|
||||
"finished": "Tráfico agotado",
|
||||
"import": "importar",
|
||||
"latestAnnouncement": "Último anuncio",
|
||||
"manualImportMessage": "Esta aplicación no admite la activación por el momento. Por favor, importe manualmente. La dirección de suscripción ha sido copiada automáticamente.",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "copiar",
|
||||
"copyFailure": "Error al copiar, por favor copia manualmente",
|
||||
"copySuccess": "Copia exitosa",
|
||||
"deducted": "Cancelado",
|
||||
"download": "descargar",
|
||||
"expirationDays": "Días de vencimiento",
|
||||
"expired": "Expirado",
|
||||
"finished": "Tráfico agotado",
|
||||
"import": "Importar",
|
||||
"latestAnnouncement": "Último anuncio",
|
||||
"manualImportMessage": "Esta aplicación no admite la activación por el momento, por favor importe manualmente, la dirección de suscripción se ha copiado automáticamente",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "کپی",
|
||||
"copyFailure": "کپی ناموفق بود، لطفاً به صورت دستی کپی کنید",
|
||||
"copySuccess": "کپی با موفقیت انجام شد",
|
||||
"deducted": "لغو شده",
|
||||
"download": "دانلود",
|
||||
"expirationDays": "روزهای انقضا",
|
||||
"expired": "منقضی شده",
|
||||
"finished": "ترافیک تمام شده",
|
||||
"import": "وارد کردن",
|
||||
"latestAnnouncement": "آخرین اعلامیه",
|
||||
"manualImportMessage": "این برنامه از فعالسازی پشتیبانی نمیکند. لطفاً به صورت دستی وارد کنید. آدرس اشتراک کپی شده است.",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "kopioi",
|
||||
"copyFailure": "Kopiointi epäonnistui, kopioi manuaalisesti",
|
||||
"copySuccess": "Kopiointi onnistui",
|
||||
"deducted": "Peruutettu",
|
||||
"download": "lataa",
|
||||
"expirationDays": "Vanhentumispäivät",
|
||||
"expired": "Vanhentunut",
|
||||
"finished": "Liikenne loppunut",
|
||||
"import": "Tuo",
|
||||
"latestAnnouncement": "Viimeisin ilmoitus",
|
||||
"manualImportMessage": "Tämä sovellus ei tue herätystä tällä hetkellä, tuo manuaalisesti, tilausosoite on kopioitu automaattisesti",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Copier",
|
||||
"copyFailure": "Échec de la copie, veuillez copier manuellement",
|
||||
"copySuccess": "Copie réussie",
|
||||
"deducted": "Annulé",
|
||||
"download": "télécharger",
|
||||
"expirationDays": "Date d'expiration/jours",
|
||||
"expired": "Expiré",
|
||||
"finished": "Trafic épuisé",
|
||||
"import": "Importer",
|
||||
"latestAnnouncement": "Dernière annonce",
|
||||
"manualImportMessage": "Cette application ne prend pas encore en charge l'activation, veuillez importer manuellement, l'adresse d'abonnement a été copiée automatiquement",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "प्रतिलिपि",
|
||||
"copyFailure": "प्रतिलिपि बनाने में विफल, कृपया मैन्युअल रूप से प्रतिलिपि बनाएँ",
|
||||
"copySuccess": "प्रतिलिपि सफल",
|
||||
"deducted": "रद्द किया गया",
|
||||
"download": "डाउनलोड",
|
||||
"expirationDays": "समाप्ति समय/दिन",
|
||||
"expired": "समाप्त",
|
||||
"finished": "ट्रैफ़िक समाप्त",
|
||||
"import": "आयात",
|
||||
"latestAnnouncement": "नवीनतम घोषणा",
|
||||
"manualImportMessage": "यह ऐप फिलहाल जागृत करने का समर्थन नहीं करता है, कृपया मैन्युअल रूप से आयात करें, सदस्यता पता स्वचालित रूप से कॉपी कर लिया गया है।",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Másolás",
|
||||
"copyFailure": "Másolás sikertelen, kérjük, másolja kézzel",
|
||||
"copySuccess": "Sikeres másolás",
|
||||
"deducted": "Törölve",
|
||||
"download": "letöltés",
|
||||
"expirationDays": "Lejárati idő/nap",
|
||||
"expired": "Lejárt",
|
||||
"finished": "Forgalom kimerült",
|
||||
"import": "importálás",
|
||||
"latestAnnouncement": "Legújabb bejelentés",
|
||||
"manualImportMessage": "Ez az alkalmazás jelenleg nem támogatja az ébresztést, kérjük, importálja manuálisan, az előfizetési cím automatikusan másolva lett",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "コピー",
|
||||
"copyFailure": "コピーに失敗しました。手動でコピーしてください",
|
||||
"copySuccess": "コピー成功",
|
||||
"deducted": "キャンセルされた",
|
||||
"download": "ダウンロード",
|
||||
"expirationDays": "有効期限/日",
|
||||
"expired": "期限切れ",
|
||||
"finished": "トラフィックが使い切られました",
|
||||
"import": "インポート",
|
||||
"latestAnnouncement": "最新のお知らせ",
|
||||
"manualImportMessage": "このアプリは現在起動をサポートしていません。手動でインポートしてください。サブスクリプションアドレスは自動的にコピーされました。",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "복사",
|
||||
"copyFailure": "복사 실패, 수동으로 복사하세요",
|
||||
"copySuccess": "복사 성공",
|
||||
"deducted": "취소됨",
|
||||
"download": "다운로드",
|
||||
"expirationDays": "만료일/일",
|
||||
"expired": "만료됨",
|
||||
"finished": "트래픽 소진됨",
|
||||
"import": "가져오기",
|
||||
"latestAnnouncement": "최신 공지",
|
||||
"manualImportMessage": "이 앱은 현재 호출을 지원하지 않습니다. 수동으로 가져오세요. 구독 주소가 자동으로 복사되었습니다.",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "kopier",
|
||||
"copyFailure": "Kopiering mislyktes, vennligst kopier manuelt",
|
||||
"copySuccess": "Kopiering vellykket",
|
||||
"deducted": "Avbrutt",
|
||||
"download": "last ned",
|
||||
"expirationDays": "Utløpsdato/dager",
|
||||
"expired": "Utløpt",
|
||||
"finished": "Trafikk brukt opp",
|
||||
"import": "Importer",
|
||||
"latestAnnouncement": "Siste kunngjøring",
|
||||
"manualImportMessage": "Denne appen støtter foreløpig ikke oppstart, vennligst importer manuelt, abonnementsadressen er automatisk kopiert",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "kopiuj",
|
||||
"copyFailure": "Kopiowanie nie powiodło się, proszę skopiować ręcznie",
|
||||
"copySuccess": "Skopiowano pomyślnie",
|
||||
"deducted": "Anulowane",
|
||||
"download": "pobierz",
|
||||
"expirationDays": "Czas wygaśnięcia/dni",
|
||||
"expired": "Wygasło",
|
||||
"finished": "Wykończony ruch",
|
||||
"import": "Importuj",
|
||||
"latestAnnouncement": "Najnowsze ogłoszenie",
|
||||
"manualImportMessage": "Ta aplikacja tymczasowo nie obsługuje wywoływania, proszę zaimportować ręcznie, adres subskrypcji został automatycznie skopiowany",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Copiar",
|
||||
"copyFailure": "Falha ao copiar, por favor copie manualmente",
|
||||
"copySuccess": "Cópia bem-sucedida",
|
||||
"deducted": "Cancelado",
|
||||
"download": "baixar",
|
||||
"expirationDays": "Data de expiração/dias",
|
||||
"expired": "Expirado",
|
||||
"finished": "Tráfego esgotado",
|
||||
"import": "Importar",
|
||||
"latestAnnouncement": "Último Anúncio",
|
||||
"manualImportMessage": "Este aplicativo não suporta ativação no momento, por favor, importe manualmente. O endereço de assinatura foi copiado automaticamente.",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Copiază",
|
||||
"copyFailure": "Copierea a eșuat, vă rugăm să copiați manual",
|
||||
"copySuccess": "Copiere reușită",
|
||||
"deducted": "Anulat",
|
||||
"download": "descărcare",
|
||||
"expirationDays": "Zile până la expirare",
|
||||
"expired": "Expirat",
|
||||
"finished": "Trafic epuizat",
|
||||
"import": "Import",
|
||||
"latestAnnouncement": "Ultimul anunț",
|
||||
"manualImportMessage": "Această aplicație nu suportă momentan activarea, vă rugăm să importați manual, adresa de abonament a fost copiată automat",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Копировать",
|
||||
"copyFailure": "Не удалось скопировать, пожалуйста, скопируйте вручную",
|
||||
"copySuccess": "Копирование успешно",
|
||||
"deducted": "Отменено",
|
||||
"download": "скачать",
|
||||
"expirationDays": "Срок действия/дни",
|
||||
"expired": "Истекло",
|
||||
"finished": "Трафик исчерпан",
|
||||
"import": "Импорт",
|
||||
"latestAnnouncement": "Последнее объявление",
|
||||
"manualImportMessage": "Это приложение временно не поддерживает вызов, пожалуйста, импортируйте вручную, адрес подписки уже скопирован",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "คัดลอก",
|
||||
"copyFailure": "คัดลอกไม่สำเร็จ กรุณาคัดลอกด้วยตนเอง",
|
||||
"copySuccess": "คัดลอกสำเร็จ",
|
||||
"deducted": "ยกเลิก",
|
||||
"download": "ดาวน์โหลด",
|
||||
"expirationDays": "วันหมดอายุ/วัน",
|
||||
"expired": "หมดอายุ",
|
||||
"finished": "การใช้งานหมด",
|
||||
"import": "นำเข้า",
|
||||
"latestAnnouncement": "ประกาศล่าสุด",
|
||||
"manualImportMessage": "แอปนี้ยังไม่รองรับการเปิดใช้งาน กรุณานำเข้าด้วยตนเอง ที่อยู่การสมัครสมาชิกได้ถูกคัดลอกอัตโนมัติแล้ว",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "kopyala",
|
||||
"copyFailure": "Kopyalama başarısız oldu, lütfen elle kopyalayın",
|
||||
"copySuccess": "Kopyalama başarılı",
|
||||
"deducted": "İptal edildi",
|
||||
"download": "indir",
|
||||
"expirationDays": "Son Kullanma Süresi/Gün",
|
||||
"expired": "Süresi dolmuş",
|
||||
"finished": "Trafik tükendi",
|
||||
"import": "İçe Aktar",
|
||||
"latestAnnouncement": "Son Duyuru",
|
||||
"manualImportMessage": "Bu uygulama şu anda uyandırmayı desteklemiyor, lütfen elle içe aktarın, abone adresi otomatik olarak kopyalandı",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Копіювати",
|
||||
"copyFailure": "Не вдалося скопіювати, будь ласка, скопіюйте вручну",
|
||||
"copySuccess": "Скопійовано успішно",
|
||||
"deducted": "Скасовано",
|
||||
"download": "завантажити",
|
||||
"expirationDays": "Термін дії/дні",
|
||||
"expired": "Термін закінчився",
|
||||
"finished": "Трафік вичерпано",
|
||||
"import": "Імпорт",
|
||||
"latestAnnouncement": "Останнє оголошення",
|
||||
"manualImportMessage": "Цей додаток тимчасово не підтримує виклик, будь ласка, імпортуйте вручну, адресу підписки вже скопійовано",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "Sao chép",
|
||||
"copyFailure": "Sao chép thất bại, vui lòng sao chép thủ công",
|
||||
"copySuccess": "Sao chép thành công",
|
||||
"deducted": "Đã hủy",
|
||||
"download": "tải xuống",
|
||||
"expirationDays": "Thời gian hết hạn/ngày",
|
||||
"expired": "Hết hạn",
|
||||
"finished": "Lưu lượng đã sử dụng",
|
||||
"import": "Nhập khẩu",
|
||||
"latestAnnouncement": "Thông báo mới nhất",
|
||||
"manualImportMessage": "Ứng dụng này hiện không hỗ trợ kích hoạt, vui lòng nhập thủ công, địa chỉ đăng ký đã được sao chép tự động",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "复制",
|
||||
"copyFailure": "复制失败,请手动复制",
|
||||
"copySuccess": "复制成功",
|
||||
"deducted": "已取消",
|
||||
"download": "下载",
|
||||
"expirationDays": "到期时间/天",
|
||||
"expired": "已过期",
|
||||
"finished": "流量已用尽",
|
||||
"import": "导入",
|
||||
"latestAnnouncement": "最新公告",
|
||||
"manualImportMessage": "该应用暂不支持唤起,请手动导入,已自动复制订阅地址",
|
||||
|
||||
@ -5,8 +5,11 @@
|
||||
"copy": "複製",
|
||||
"copyFailure": "複製失敗,請手動複製",
|
||||
"copySuccess": "複製成功",
|
||||
"deducted": "已取消",
|
||||
"download": "下載",
|
||||
"expirationDays": "到期時間/天",
|
||||
"expired": "已過期",
|
||||
"finished": "流量已用盡",
|
||||
"import": "匯入",
|
||||
"latestAnnouncement": "最新公告",
|
||||
"manualImportMessage": "該應用暫不支援喚起,請手動導入,已自動複製訂閱地址",
|
||||
|
||||
@ -25,6 +25,24 @@ export async function getApplication(options?: { [key: string]: any }) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Check verification code POST /v1/common/check_verification_code */
|
||||
export async function checkVerificationCode(
|
||||
body: API.CheckVerificationCodeRequest,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<API.Response & { data?: API.CheckVerificationCodeRespone }>(
|
||||
'/v1/common/check_verification_code',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** Get verification code POST /v1/common/send_code */
|
||||
export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) {
|
||||
return request<API.Response & { data?: API.SendCodeResponse }>('/v1/common/send_code', {
|
||||
|
||||
12
apps/user/services/common/typings.d.ts
vendored
12
apps/user/services/common/typings.d.ts
vendored
@ -121,6 +121,17 @@ declare namespace API {
|
||||
telephone: string;
|
||||
};
|
||||
|
||||
type CheckVerificationCodeRequest = {
|
||||
method: 'email' | 'mobile';
|
||||
account: string;
|
||||
code: string;
|
||||
type: number;
|
||||
};
|
||||
|
||||
type CheckVerificationCodeRespone = {
|
||||
status: boolean;
|
||||
};
|
||||
|
||||
type CloseOrderRequest = {
|
||||
orderNo: string;
|
||||
};
|
||||
@ -910,6 +921,7 @@ declare namespace API {
|
||||
subscribe: Subscribe;
|
||||
start_time: number;
|
||||
expire_time: number;
|
||||
finished_at: number;
|
||||
reset_time: number;
|
||||
traffic: number;
|
||||
download: number;
|
||||
|
||||
1
apps/user/services/user/typings.d.ts
vendored
1
apps/user/services/user/typings.d.ts
vendored
@ -1010,6 +1010,7 @@ declare namespace API {
|
||||
subscribe: Subscribe;
|
||||
start_time: number;
|
||||
expire_time: number;
|
||||
finished_at: number;
|
||||
reset_time: number;
|
||||
traffic: number;
|
||||
download: number;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user