🐛 fix(api): Update subscription_protocol to subscribe_type for consistency across services

This commit is contained in:
web@ppanel 2025-01-18 19:36:39 +07:00
parent 9d8b814212
commit b6da51b618
12 changed files with 106 additions and 67 deletions

View File

@ -42,7 +42,7 @@ import { z } from 'zod';
const platforms = ['windows', 'macos', 'linux', 'android', 'ios', 'harmony'];
const defaultValues = {
subscription_protocol: 'Clash',
subscribe_type: 'Clash',
name: '',
icon: '',
url: '',
@ -58,7 +58,7 @@ const versionSchema = z.object({
const formSchema = z.object({
icon: z.string(),
name: z.string(),
subscription_protocol: z.string(),
subscribe_type: z.string(),
platform: z.object({
windows: z.array(versionSchema).optional(),
macos: z.array(versionSchema).optional(),
@ -172,7 +172,7 @@ export default function SubscribeAppForm<
/>
<FormField
control={form.control}
name='subscription_protocol'
name='subscribe_type'
render={({ field }) => (
<FormItem>
<FormLabel>{t('subscriptionProtocol')}</FormLabel>

View File

@ -76,9 +76,9 @@ export default function SubscribeApp() {
header: t('appName'),
},
{
accessorKey: 'subscription_protocol',
accessorKey: 'subscribe_type',
header: t('subscriptionProtocol'),
cell: ({ row }) => row.getValue('subscription_protocol'),
cell: ({ row }) => row.getValue('subscribe_type'),
},
]}
actions={{

View File

@ -50,7 +50,7 @@ declare namespace API {
name: string;
icon: string;
description: string;
subscription_protocol: string;
subscribe_type: string;
platform: ApplicationPlatform;
};

View File

@ -42,7 +42,7 @@ declare namespace API {
name: string;
icon: string;
description: string;
subscription_protocol: string;
subscribe_type: string;
platform: ApplicationPlatform;
};

View File

@ -43,6 +43,15 @@ import CopyToClipboard from 'react-copy-to-clipboard';
import { toast } from 'sonner';
import Subscribe from '../subscribe/page';
const platforms: (keyof API.ApplicationPlatform)[] = [
'windows',
'mac',
'linux',
'ios',
'android',
'harmony',
];
export default function Content() {
const t = useTranslations('dashboard');
const { getUserSubscribe, getAppSubLink } = useGlobalStore();
@ -56,14 +65,14 @@ export default function Content() {
return data.data?.list || [];
},
});
const { data: application } = useQuery({
const { data: applications } = useQuery({
queryKey: ['queryApplicationConfig'],
queryFn: async () => {
const { data } = await queryApplicationConfig();
return data.data as API.ApplicationResponse;
return data.data?.applications || [];
},
});
const [platform, setPlatform] = useState<keyof API.ApplicationResponse>(getPlatform());
const [platform, setPlatform] = useState<keyof API.ApplicationPlatform>(getPlatform());
const { data } = useQuery({
queryKey: ['getStat'],
@ -87,16 +96,27 @@ export default function Content() {
<div className='flex flex-wrap justify-between gap-4'>
<Tabs
value={platform}
onValueChange={(value) => setPlatform(value as keyof API.ApplicationResponse)}
onValueChange={(value) => setPlatform(value as keyof API.ApplicationPlatform)}
className='w-full max-w-full md:w-auto'
>
<TabsList className='flex *:flex-auto'>
{application &&
Object.keys(application)?.map((item) => (
<TabsTrigger value={item} key={item} className='px-1 uppercase lg:px-3'>
{item}
</TabsTrigger>
))}
{platforms.map((item) => (
<TabsTrigger value={item} key={item} className='px-1 lg:px-3'>
<Icon
icon={`${
{
windows: 'simple-icons:windows',
mac: 'simple-icons:apple',
linux: 'simple-icons:linux',
ios: 'simple-icons:ios',
android: 'simple-icons:android',
harmony: 'simple-icons:harmonyos',
}[item]
}`}
className='size-6'
/>
</TabsTrigger>
))}
</TabsList>
</Tabs>
{data?.protocol && data?.protocol.length > 1 && (
@ -215,54 +235,69 @@ export default function Content() {
</AccordionTrigger>
<AccordionContent>
<div className='grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'>
{application?.[platform]?.map((app) => (
<div
key={app.name}
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs'
>
<span>{app.name}</span>
{app.icon && (
<Image
src={app.icon}
alt={app.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>
{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) => {
const href = getAppSubLink(application.subscribe_type, url);
<CopyToClipboard
text={url}
onCopy={(text, result) => {
const href = getAppSubLink(app.subscribe_type, url);
if (isBrowser() && href) {
window.location.href = href;
} else if (result) {
toast.success(
<>
<p>{t('copySuccess')}</p>
<p>{t('manualImportMessage')}</p>
</>,
);
}
}}
if (isBrowser() && href) {
window.location.href = href;
return;
}
if (result) {
toast.success(
<>
<p>{t('copySuccess')}</p>
<p>{t('manualImportMessage')}</p>
</>,
);
}
};
return (
<div
key={application.name}
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs'
>
<Button size='sm' className='rounded-l-none p-2'>
{t('import')}
</Button>
</CopyToClipboard>
</div>
</div>
))}
<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={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

View File

@ -42,7 +42,7 @@ declare namespace API {
name: string;
icon: string;
description: string;
subscription_protocol: string;
subscribe_type: string;
platform: ApplicationPlatform;
};

View File

@ -42,7 +42,7 @@ declare namespace API {
name: string;
icon: string;
description: string;
subscription_protocol: string;
subscribe_type: string;
platform: ApplicationPlatform;
};

View File

@ -51,7 +51,7 @@ export function Logout() {
}
}
export function getPlatform(): 'windows' | 'mac' | 'linux' | 'android' | 'ios' {
export function getPlatform(): 'windows' | 'mac' | 'linux' | 'android' | 'ios' | 'harmony' {
const parser = new UAParser();
const os = parser.getOS();
const osName = os.name?.toLowerCase() || '';
@ -70,6 +70,7 @@ export function getPlatform(): 'windows' | 'mac' | 'linux' | 'android' | 'ios' {
return 'linux';
if (osName.includes('android')) return 'android';
if (osName.includes('ios')) return 'ios';
if (osName.includes('harmony')) return 'harmony';
return 'windows';
}

BIN
bun.lockb

Binary file not shown.

View File

@ -23,6 +23,7 @@
"@hookform/resolvers": "^3.10.0",
"@iconify-json/flagpack": "^1.2.2",
"@iconify-json/mdi": "^1.2.2",
"@iconify-json/simple-icons": "^1.2.20",
"@iconify-json/uil": "^1.2.3",
"@iconify/react": "^5.2.0",
"@monaco-editor/react": "^4.6.0",

View File

@ -1,5 +1,5 @@
import { Button } from '@workspace/ui/components/button';
import { Label } from '@workspace/ui/components/label.js';
import { Label } from '@workspace/ui/components/label';
import { Switch } from '@workspace/ui/components/switch';
import { Combobox } from '@workspace/ui/custom-components/combobox';
import { EnhancedInput, EnhancedInputProps } from '@workspace/ui/custom-components/enhanced-input';

View File

@ -2,6 +2,7 @@
import { icons as FlagPack } from '@iconify-json/flagpack';
import { icons as Mdi } from '@iconify-json/mdi';
import { icons as Simple } from '@iconify-json/simple-icons';
import { icons as Uil } from '@iconify-json/uil';
import { addCollection, Icon as Iconify, IconProps } from '@iconify/react';
@ -9,6 +10,7 @@ import { addCollection, Icon as Iconify, IconProps } from '@iconify/react';
addCollection(FlagPack);
addCollection(Mdi);
addCollection(Uil);
addCollection(Simple);
export function Icon(props: IconProps) {
return <Iconify {...props} />;