Skip to content

Commit 41f9fd5

Browse files
committed
fix(subscription): add info popover for subscriptions settings
1 parent 8b6d6cc commit 41f9fd5

File tree

5 files changed

+163
-5
lines changed

5 files changed

+163
-5
lines changed

dashboard/public/statics/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,7 @@
901901
"jalali_expire_date": "Expiry date of the user in solar calendar",
902902
"time_left": "Remaining time of the user",
903903
"status_emoji": "User status as an emoji (✅,⌛️,🪫,❌,🔌)",
904+
"usage_percentage": "Data usage percentage",
904905
"protocol": "Proxy protocol (e.g. VMess)",
905906
"transport": "Proxy transport method (e.g. ws)",
906907
"admin_username": "Admin username"

dashboard/public/statics/locales/fa.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,7 @@
780780
"jalali_expire_date": "تاریخ انقضا به تقویم خورشیدی",
781781
"time_left": "زمان باقی‌مانده",
782782
"status_emoji": "وضعیت کاربر به صورت ایموجی (✅,⌛️,🪫,❌,🔌)",
783+
"usage_percentage": "درصد استفاده از داده",
783784
"protocol": "پروتکل پروکسی (مثال: VMess)",
784785
"transport": "روش انتقال پروکسی (مثال: ws)",
785786
"admin_username": "نام کاربری ادمین"

dashboard/public/statics/locales/ru.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,7 @@
10621062
"jalali_expire_date": "Дата истечения срока по солнечному календарю",
10631063
"time_left": "Оставшееся время",
10641064
"status_emoji": "Статус пользователя в виде эмодзи (✅,⌛️,🪫,❌,🔌)",
1065+
"usage_percentage": "Процент использования данных",
10651066
"protocol": "Протокол прокси (например, VMess)",
10661067
"transport": "Метод транспорта прокси (например, ws)",
10671068
"admin_username": "Имя пользователя администратора"

dashboard/public/statics/locales/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,7 @@
11051105
"jalali_expire_date": "阳历到期日期",
11061106
"time_left": "剩余时间",
11071107
"status_emoji": "用户状态表情符号 (✅,⌛️,🪫,❌,🔌)",
1108+
"usage_percentage": "数据使用百分比",
11081109
"protocol": "代理协议(如 VMess)",
11091110
"transport": "代理传输方式(如 ws)",
11101111
"admin_username": "管理员用户名"

dashboard/src/pages/_dashboard.settings.subscriptions.tsx

Lines changed: 159 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import { Switch } from '@/components/ui/switch'
1010
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
1111
import { Separator } from '@/components/ui/separator'
1212
import { Badge } from '@/components/ui/badge'
13-
import { Plus, Trash2, Filter, FileText, Link, Clock, HelpCircle, User, Settings, Code, FileCode2, Sword, Shield, Lock, GripVertical, RotateCcw } from 'lucide-react'
13+
import { Plus, Trash2, Filter, FileText, Link, Clock, HelpCircle, User, Settings, Code, FileCode2, Sword, Shield, Lock, GripVertical, RotateCcw, Info } from 'lucide-react'
1414
import { useSettingsContext } from './_dashboard.settings'
1515
import { ConfigFormat } from '@/service/api'
1616
import { toast } from 'sonner'
17+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
18+
import { useClipboard } from '@/hooks/use-clipboard'
1719
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core'
1820
import { SortableContext, sortableKeyboardCoordinates, useSortable, rectSortingStrategy } from '@dnd-kit/sortable'
1921
import { CSS } from '@dnd-kit/utilities'
@@ -394,6 +396,12 @@ export default function SubscriptionSettings() {
394396
const { t } = useTranslation()
395397
const dir = useDirDetection()
396398
const { settings, isLoading, error, updateSettings, isSaving } = useSettingsContext()
399+
const { copy } = useClipboard()
400+
401+
const handleCopy = (text: string) => {
402+
copy(text)
403+
toast.success(t('usersTable.copied'))
404+
}
397405
const [isAddAppOpen, setIsAddAppOpen] = useState(false)
398406
const [newAppName, setNewAppName] = useState('')
399407
const [newAppPlatform, setNewAppPlatform] = useState<'android' | 'ios' | 'windows' | 'macos' | 'linux' | 'appletv' | 'androidtv'>('android')
@@ -870,10 +878,156 @@ export default function SubscriptionSettings() {
870878
name="profile_title"
871879
render={({ field }) => (
872880
<FormItem className="space-y-2">
873-
<FormLabel className="flex items-center gap-2 text-sm font-medium">
874-
<User className="h-4 w-4" />
875-
{t('settings.subscriptions.general.profileTitle')}
876-
</FormLabel>
881+
<div className="flex items-center gap-2">
882+
<FormLabel className="flex items-center gap-2 text-sm font-medium">
883+
<User className="h-4 w-4" />
884+
{t('settings.subscriptions.general.profileTitle')}
885+
</FormLabel>
886+
<Popover>
887+
<PopoverTrigger asChild>
888+
<Button type="button" variant="ghost" size="icon" className="h-4 w-4 p-0 hover:bg-transparent">
889+
<Info className="h-4 w-4 text-muted-foreground" />
890+
</Button>
891+
</PopoverTrigger>
892+
<PopoverContent className="w-[320px] p-3" side="right" align="start">
893+
<div className="space-y-1.5">
894+
<h4 className="mb-2 text-[12px] font-medium">{t('hostsDialog.variables.title')}</h4>
895+
<div className="space-y-1">
896+
<div className="flex items-center gap-1.5">
897+
<code
898+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
899+
onClick={() => handleCopy('{SERVER_IP}')}
900+
title={t('copy')}
901+
>
902+
{'{SERVER_IP}'}
903+
</code>
904+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.server_ip')}</span>
905+
</div>
906+
<div className="flex items-center gap-1.5">
907+
<code
908+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
909+
onClick={() => handleCopy('{SERVER_IPV6}')}
910+
title={t('copy')}
911+
>
912+
{'{SERVER_IPV6}'}
913+
</code>
914+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.server_ipv6')}</span>
915+
</div>
916+
<div className="flex items-center gap-1.5">
917+
<code
918+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
919+
onClick={() => handleCopy('{USERNAME}')}
920+
title={t('copy')}
921+
>
922+
{'{USERNAME}'}
923+
</code>
924+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.username')}</span>
925+
</div>
926+
<div className="flex items-center gap-1.5">
927+
<code
928+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
929+
onClick={() => handleCopy('{DATA_USAGE}')}
930+
title={t('copy')}
931+
>
932+
{'{DATA_USAGE}'}
933+
</code>
934+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.data_usage')}</span>
935+
</div>
936+
<div className="flex items-center gap-1.5">
937+
<code
938+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
939+
onClick={() => handleCopy('{DATA_LEFT}')}
940+
title={t('copy')}
941+
>
942+
{'{DATA_LEFT}'}
943+
</code>
944+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.data_left')}</span>
945+
</div>
946+
<div className="flex items-center gap-1.5">
947+
<code
948+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
949+
onClick={() => handleCopy('{DATA_LIMIT}')}
950+
title={t('copy')}
951+
>
952+
{'{DATA_LIMIT}'}
953+
</code>
954+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.data_limit')}</span>
955+
</div>
956+
<div className="flex items-center gap-1.5">
957+
<code
958+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
959+
onClick={() => handleCopy('{DAYS_LEFT}')}
960+
title={t('copy')}
961+
>
962+
{'{DAYS_LEFT}'}
963+
</code>
964+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.days_left')}</span>
965+
</div>
966+
<div className="flex items-center gap-1.5">
967+
<code
968+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
969+
onClick={() => handleCopy('{EXPIRE_DATE}')}
970+
title={t('copy')}
971+
>
972+
{'{EXPIRE_DATE}'}
973+
</code>
974+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.expire_date')}</span>
975+
</div>
976+
<div className="flex items-center gap-1.5">
977+
<code
978+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
979+
onClick={() => handleCopy('{JALALI_EXPIRE_DATE}')}
980+
title={t('copy')}
981+
>
982+
{'{JALALI_EXPIRE_DATE}'}
983+
</code>
984+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.jalali_expire_date')}</span>
985+
</div>
986+
<div className="flex items-center gap-1.5">
987+
<code
988+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
989+
onClick={() => handleCopy('{TIME_LEFT}')}
990+
title={t('copy')}
991+
>
992+
{'{TIME_LEFT}'}
993+
</code>
994+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.time_left')}</span>
995+
</div>
996+
<div className="flex items-center gap-1.5">
997+
<code
998+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
999+
onClick={() => handleCopy('{STATUS_EMOJI}')}
1000+
title={t('copy')}
1001+
>
1002+
{'{STATUS_EMOJI}'}
1003+
</code>
1004+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.status_emoji')}</span>
1005+
</div>
1006+
<div className="flex items-center gap-1.5">
1007+
<code
1008+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
1009+
onClick={() => handleCopy('{USAGE_PERCENTAGE}')}
1010+
title={t('copy')}
1011+
>
1012+
{'{USAGE_PERCENTAGE}'}
1013+
</code>
1014+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.usage_percentage')}</span>
1015+
</div>
1016+
<div className="flex items-center gap-1.5">
1017+
<code
1018+
className="cursor-pointer rounded-sm bg-muted/50 px-1.5 py-0.5 text-[11px] transition-colors hover:bg-muted"
1019+
onClick={() => handleCopy('{ADMIN_USERNAME}')}
1020+
title={t('copy')}
1021+
>
1022+
{'{ADMIN_USERNAME}'}
1023+
</code>
1024+
<span className="text-[11px] text-muted-foreground">{t('hostsDialog.variables.admin_username')}</span>
1025+
</div>
1026+
</div>
1027+
</div>
1028+
</PopoverContent>
1029+
</Popover>
1030+
</div>
8771031
<FormControl>
8781032
<Input placeholder={t('settings.subscriptions.general.profileTitlePlaceholder')} {...field} />
8791033
</FormControl>

0 commit comments

Comments
 (0)