mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-24 23:05:01 +03:00
feat(sub): redesign the subscription page around usage, tabs and app imports
The info page was a long key/value table followed by every link and two app dropdowns, and it rendered left-to-right even for Persian and Arabic. It now leads with a usage ring, the remaining quota and a stats grid, and splits the rest into Subscription / Apps / Configs tabs. - Status tells expired, data-used-up and disabled apart instead of one "Inactive", replacing the hard-coded English expiry chip. - The Apps tab keeps every Android and iOS app with its existing deep link, preselects the visitor's platform and adds Windows: Hiddify and Clash Verge Rev import directly, v2rayN copies the link. - fa-IR and ar-EG render right-to-left; URLs, IDs and sizes stay LTR. - The footer shows the support link and the client refresh interval, so subPageContext now carries subUpdates (also in ?format=info). - Status, days-left and app deep-link logic lives in subPageModel.ts, with unit tests pinning the deep links the page already shipped.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Modal, QRCode } from 'antd';
|
||||
import { CopyOutlined, DownloadOutlined, QrcodeOutlined } from '@ant-design/icons';
|
||||
|
||||
interface SubQrButtonProps {
|
||||
value: string;
|
||||
label: string;
|
||||
onCopy: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function SubQrButton({ value, label, onCopy }: SubQrButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const saveQr = () => {
|
||||
const canvas = qrRef.current?.querySelector('canvas');
|
||||
if (!canvas) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.download = `${label || 'qrcode'}.png`;
|
||||
link.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button icon={<QrcodeOutlined />} aria-label="QR" title="QR" onClick={() => setOpen(true)} />
|
||||
<Modal
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
footer={null}
|
||||
width={440}
|
||||
centered
|
||||
destroyOnHidden
|
||||
rootClassName="sub-qr-modal"
|
||||
title={t('subscription.qrTitle')}
|
||||
>
|
||||
<p className="sub-muted sub-qr-modal-hint">{t('subscription.qrHint')}</p>
|
||||
<div ref={qrRef} className="sub-qr-modal-code">
|
||||
<QRCode
|
||||
value={value}
|
||||
size={240}
|
||||
type="canvas"
|
||||
marginSize={2}
|
||||
bordered={false}
|
||||
color="#000000"
|
||||
bgColor="#ffffff"
|
||||
/>
|
||||
</div>
|
||||
<Input.TextArea
|
||||
className="sub-qr-modal-link"
|
||||
value={value}
|
||||
readOnly
|
||||
dir="ltr"
|
||||
autoSize={{ minRows: 2, maxRows: 5 }}
|
||||
/>
|
||||
<div className="sub-qr-modal-actions">
|
||||
<Button type="primary" size="large" icon={<CopyOutlined />} onClick={() => onCopy(value)}>
|
||||
{t('copy')}
|
||||
</Button>
|
||||
<Button size="large" icon={<DownloadOutlined />} onClick={saveQr}>
|
||||
{t('subscription.saveQr')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user