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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@ declare namespace API {
name: string; name: string;
icon: string; icon: string;
description: string; description: string;
subscription_protocol: string; subscribe_type: string;
platform: ApplicationPlatform; 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 parser = new UAParser();
const os = parser.getOS(); const os = parser.getOS();
const osName = os.name?.toLowerCase() || ''; const osName = os.name?.toLowerCase() || '';
@ -70,6 +70,7 @@ export function getPlatform(): 'windows' | 'mac' | 'linux' | 'android' | 'ios' {
return 'linux'; return 'linux';
if (osName.includes('android')) return 'android'; if (osName.includes('android')) return 'android';
if (osName.includes('ios')) return 'ios'; if (osName.includes('ios')) return 'ios';
if (osName.includes('harmony')) return 'harmony';
return 'windows'; return 'windows';
} }

BIN
bun.lockb

Binary file not shown.

View File

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

View File

@ -1,5 +1,5 @@
import { Button } from '@workspace/ui/components/button'; 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 { Switch } from '@workspace/ui/components/switch';
import { Combobox } from '@workspace/ui/custom-components/combobox'; import { Combobox } from '@workspace/ui/custom-components/combobox';
import { EnhancedInput, EnhancedInputProps } from '@workspace/ui/custom-components/enhanced-input'; 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 FlagPack } from '@iconify-json/flagpack';
import { icons as Mdi } from '@iconify-json/mdi'; 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 { icons as Uil } from '@iconify-json/uil';
import { addCollection, Icon as Iconify, IconProps } from '@iconify/react'; 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(FlagPack);
addCollection(Mdi); addCollection(Mdi);
addCollection(Uil); addCollection(Uil);
addCollection(Simple);
export function Icon(props: IconProps) { export function Icon(props: IconProps) {
return <Iconify {...props} />; return <Iconify {...props} />;