@@ -10,10 +10,12 @@ import { Switch } from '@/components/ui/switch'
1010import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from '@/components/ui/select'
1111import { Separator } from '@/components/ui/separator'
1212import { 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'
1414import { useSettingsContext } from './_dashboard.settings'
1515import { ConfigFormat } from '@/service/api'
1616import { toast } from 'sonner'
17+ import { Popover , PopoverContent , PopoverTrigger } from '@/components/ui/popover'
18+ import { useClipboard } from '@/hooks/use-clipboard'
1719import { DndContext , closestCenter , KeyboardSensor , PointerSensor , useSensor , useSensors , DragEndEvent } from '@dnd-kit/core'
1820import { SortableContext , sortableKeyboardCoordinates , useSortable , rectSortingStrategy } from '@dnd-kit/sortable'
1921import { 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