diff --git a/app/dashboard/src/components/header/github-button.tsx b/app/dashboard/src/components/header/github-button.tsx
@@ -14,7 +14,7 @@ export const GithubMenuButton = () => {
data-show-count="true"
aria-label="Star Marzneshin on GitHub"
>
- Star
+ Star
</GitHubButton>
)
}
diff --git a/app/dashboard/src/components/header/index.tsx b/app/dashboard/src/components/header/index.tsx
@@ -33,18 +33,19 @@ export const Header: FC<HeaderProps> = () => {
p="6"
borderBottom={'1px solid gray.600'}
position="sticky"
+ alignItems="center"
zIndex={999}
top={0}
>
<Text as="h1" fontWeight="bold" fontSize="2xl" m="0">
{t(activePage.name)}
</Text>
- <Box css={{ direction: 'rtl' }}>
+ <Box css={{ direction: 'ltr' }}>
<HStack alignItems="center">
- <HeaderMenu />
+ <GithubMenuButton />
<Language />
<ThemeToggle />
- <GithubMenuButton />
+ <HeaderMenu />
</HStack>
</Box>
</HStack>
diff --git a/app/dashboard/src/components/table/handle-sort.ts b/app/dashboard/src/components/table/handle-sort.ts
@@ -0,0 +1,22 @@
+
+import { FilterType } from 'types';
+
+export const handleSort = (
+ filters: FilterType,
+ column: string,
+ onFilterChange: (filters: Partial<FilterType>) => void
+) => {
+ let newSort = filters.sort;
+ if (newSort.includes(column)) {
+ if (newSort.startsWith('-')) {
+ newSort = '-created_at';
+ } else {
+ newSort = '-' + column;
+ }
+ } else {
+ newSort = column;
+ }
+ onFilterChange({
+ sort: newSort,
+ });
+};
diff --git a/app/dashboard/src/components/table/index.ts b/app/dashboard/src/components/table/index.tsx
rename from app/dashboard/src/components/table/index.ts
rename to app/dashboard/src/components/table/index.tsx
@@ -1,4 +1,5 @@
export * from './icons';
+export * from './handle-sort';
export * from './sort';
export * from './pagination';
export * from './status-sort-select';
diff --git a/app/dashboard/src/pages/nodes/table/action-buttons.tsx b/app/dashboard/src/pages/nodes/table/action-buttons.tsx
@@ -0,0 +1,51 @@
+
+import { t } from 'i18next';
+import { Cog6ToothIcon } from '@heroicons/react/24/outline';
+import { chakra, HStack, IconButton, Tooltip } from '@chakra-ui/react';
+import { FC } from 'react';
+
+const iconProps = {
+ baseStyle: {
+ w: 4,
+ h: 4,
+ },
+};
+const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps);
+type ActionButtonsProps = {};
+
+export const ActionButtons: FC<ActionButtonsProps> = () => {
+ return (
+ <HStack
+ justifyContent="flex-end"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <Tooltip
+ label={
+ t('nodesTable.coreSettings')
+ }
+ placement="top"
+ >
+ <IconButton
+ p="0 !important"
+ aria-label="core settings"
+ bg="transparent"
+ _dark={{
+ _hover: {
+ bg: 'gray.700',
+ },
+ }}
+ size={{
+ base: 'sm',
+ md: 'md',
+ }}
+ >
+ <CoreSettingsIcon />
+ </IconButton>
+ </Tooltip>
+ </HStack >
+ );
+};
+
diff --git a/app/dashboard/src/pages/nodes/table.tsx b/app/dashboard/src/pages/nodes/table/index.tsx
rename from app/dashboard/src/pages/nodes/table.tsx
rename to app/dashboard/src/pages/nodes/table/index.tsx
@@ -5,7 +5,6 @@ import {
AccordionItem,
AccordionPanel,
Box,
- chakra,
HStack,
IconButton,
Table,
@@ -21,8 +20,8 @@ import {
VStack,
} from '@chakra-ui/react';
import classNames from 'classnames';
+import { ActionButtons } from './action-buttons';
-import { t } from 'i18next';
import { FC, Fragment, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StatusBadge } from 'components/status-badge';
@@ -33,18 +32,9 @@ import {
EditIcon,
Sort,
StatusSortSelect,
+ handleSort,
} from 'components/table';
import { useNodes } from 'stores';
-import { Cog6ToothIcon } from '@heroicons/react/24/outline';
-
-const iconProps = {
- baseStyle: {
- w: 4,
- h: 4,
- },
-};
-
-const CoreSettingsIcon = chakra(Cog6ToothIcon, iconProps);
type ExpandedIndex = number | number[];
@@ -57,7 +47,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
onEditingNode,
onAddingNode,
onFilterChange,
- refetchNodes,
} = useNodes();
const total = nodes.length;
@@ -72,22 +61,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
// TODO: Find a different mechancism to to detect ftiler
// const isFiltered = users.length !== total;
- const handleSort = (column: string) => {
- let newSort = filters.sort;
- if (newSort.includes(column)) {
- if (newSort.startsWith('-')) {
- newSort = '-created_at';
- } else {
- newSort = '-' + column;
- }
- } else {
- newSort = column;
- }
- onFilterChange({
- sort: newSort,
- });
- };
-
const handleStatusFilter = (e: any) => {
onFilterChange({
status: e.target.value.length > 0 ? e.target.value : undefined,
@@ -106,7 +79,6 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
/>
);
- refetchNodes();
return (
<Box id="node-table" overflowX={{ base: 'unset', md: 'unset' }} >
<Accordion
@@ -123,7 +95,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
pl={4}
pr={4}
cursor={'pointer'}
- onClick={handleSort.bind(null, 'name')}
+ onClick={handleSort.bind(null, filters, 'name', onFilterChange)}
>
<HStack>
<span>{t('name')}</span>
@@ -163,7 +135,7 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
minW="100px"
cursor={'pointer'}
pr={0}
- onClick={handleSort.bind(null, 'xray_version')}
+ onClick={handleSort.bind(null, filters, 'xray_version', onFilterChange)}
>
<HStack>
<span>{t('nodesTable.xrayVersion')}</span>
@@ -306,9 +278,9 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
<Thead zIndex="docked" position="relative">
<Tr>
<Th
- minW="140px"
+ minW="200px"
cursor={'pointer'}
- onClick={handleSort.bind(null, 'name')}
+ onClick={handleSort.bind(null, filters, 'name', onFilterChange)}
>
<HStack>
<span>{t('name')}</span>
@@ -316,8 +288,8 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- width="400px"
- minW="150px"
+ width="100px"
+ minW="70px"
cursor={'pointer'}
>
<HStack spacing={0} position="relative">
@@ -341,10 +313,10 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- width="350px"
- minW="230px"
+ width="100px"
+ minW="70px"
cursor={'pointer'}
- onClick={handleSort.bind(null, 'xray_version')}
+ onClick={handleSort.bind(null, filters, 'xray_version', onFilterChange)}
>
<HStack>
<span>{t('nodesTable.xrayVersion')}</span>
@@ -352,10 +324,10 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- width="350px"
- minW="230px"
+ width="100px"
+ minW="70px"
cursor={'pointer'}
- onClick={handleSort.bind(null, 'xray_version')}
+ onClick={handleSort.bind(null, filters, 'address', onFilterChange)}
>
<HStack>
<span>{t('nodesTable.ipPort')}</span>
@@ -363,8 +335,8 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</HStack>
</Th>
<Th
- width="200px"
- minW="180px"
+ width="60px"
+ minW="50px"
/>
</Tr>
</Thead>
@@ -420,41 +392,3 @@ export const NodesTable: FC<NodesTableProps> = (props) => {
</Box>
);
};
-
-type ActionButtonsProps = {};
-
-const ActionButtons: FC<ActionButtonsProps> = () => {
- return (
- <HStack
- justifyContent="flex-end"
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- }}
- >
- <Tooltip
- label={
- t('nodesTable.coreSettings')
- }
- placement="top"
- >
- <IconButton
- p="0 !important"
- aria-label="core settings"
- bg="transparent"
- _dark={{
- _hover: {
- bg: 'gray.700',
- },
- }}
- size={{
- base: 'sm',
- md: 'md',
- }}
- >
- <CoreSettingsIcon />
- </IconButton>
- </Tooltip>
- </HStack >
- );
-};
diff --git a/app/dashboard/src/pages/services/table.tsx b/app/dashboard/src/pages/services/table.tsx
@@ -1,5 +1,6 @@
import {
+ Accordion,
Box,
HStack,
Table,
@@ -13,15 +14,17 @@ import {
} from '@chakra-ui/react';
import classNames from 'classnames';
-import { FC, Fragment, useEffect, useState } from 'react';
+import { FC, useState, } from 'react';
import { useTranslation } from 'react-i18next';
import {
EmptySection,
+ handleSort,
Pagination,
Sort
} from 'components/table';
import { useServices } from 'stores';
+type ExpandedIndex = number | number[];
type ServicesTableProps = {} & TableProps;
export const ServicesTable: FC<ServicesTableProps> = (props) => {
@@ -31,52 +34,104 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
onEditingService,
onCreateService,
onFilterChange,
- refetchServices,
} = useServices();
-
+ const useTable = useBreakpointValue({ base: false, md: true });
const { t } = useTranslation();
- const marginTop = useBreakpointValue({ base: 120, lg: 72 }) || 72;
- const [top, setTop] = useState(`${marginTop}px`);
-
- useEffect(() => {
- const calcTop = () => {
- const el = document.querySelectorAll('#filters')[0] as HTMLElement;
- setTop(`${el.offsetHeight}px`);
- };
- window.addEventListener('scroll', calcTop);
- () => window.removeEventListener('scroll', calcTop);
- }, []);
-
- const handleSort = (column: string) => {
- let newSort = filters.sort;
- if (newSort.includes(column)) {
- if (newSort.startsWith('-')) {
- newSort = '-created_at';
- } else {
- newSort = '-' + column;
- }
- } else {
- newSort = column;
- }
- onFilterChange({
- sort: newSort,
- });
- };
+ const [selectedRow,] = useState<ExpandedIndex | undefined>(
+ undefined
+ );
- refetchServices();
return (
<Box id="services-table" h="45vh" overflowX={{ base: 'unset', md: 'unset' }} >
+ <Accordion
+ allowMultiple
+ display={{ base: 'block', md: 'none' }}
+ index={selectedRow}
+ >
+ <Table orientation="vertical" zIndex="docked" {...props}>
+ <Thead zIndex="docked" position="relative">
+ <Tr>
+ <Tr>
+ <Th
+ position="sticky"
+ minW="200px"
+ pl={4}
+ pr={4}
+ cursor={'pointer'}
+ onClick={handleSort.bind(null, filters, 'name', onFilterChange)}
+ >
+ <HStack>
+ <span>{t('services')}</span>
+ <Sort sort={filters.sort} column="name" />
+ </HStack>
+ </Th>
+ <Th
+ position="sticky"
+ minW="50px"
+ cursor={'pointer'}
+ pr={0}
+ onClick={handleSort.bind(null, filters, 'users_number', onFilterChange)}
+ >
+ <HStack>
+ <span>{t('servicesTable.usersNumber')}</span>
+ <Sort sort={filters.sort} column="users_number" />
+ </HStack>
+ </Th>
+ <Th
+ position="sticky"
+ minW="50px"
+ cursor={'pointer'}
+ pr={0}
+ onClick={handleSort.bind(null, filters, 'inbounds_number', onFilterChange)}
+ >
+ <HStack>
+ <span>{t('servicesTable.inboundsNumber')}</span>
+ <Sort sort={filters.sort} column="inbounds_number" />
+ </HStack>
+ </Th>
+ </Tr>
+ </Tr>
+ </Thead>
+ <Tbody>
+ {!useTable &&
+ services?.map((service, i) => {
+ return (
+ <Tr
+ cursor="pointer"
+ key={service.id}
+ className={classNames('interactive', {
+ 'last-row': i === (services.length - 1),
+ })}
+ onClick={(e) => {
+ e.stopPropagation();
+ onEditingService(service);
+ }}
+ >
+ <Td borderBottom={0} minW="100px" pl={4} pr={4}>
+ {service.name}
+ </Td>
+ <Td borderBottom={0} minW="50px" pl={6} pr={6}>
+ {service.users.length}
+ </Td>
+ <Td borderBottom={0} minW="50px" pr={0}>
+ {service.inbounds.length}
+ </Td>
+ </Tr>
+ );
+ })}
+ </Tbody>
+ </Table>
+ </Accordion>
<Table orientation="vertical" zIndex="docked" {...props}>
<Thead zIndex="docked" position="relative">
<Tr>
<Th
position="sticky"
- top={top}
- minW="100px"
+ minW="200px"
pl={4}
pr={4}
cursor={'pointer'}
- onClick={handleSort.bind(null, 'name')}
+ onClick={handleSort.bind(null, filters, 'name', onFilterChange)}
>
<HStack>
<span>{t('services')}</span>
@@ -85,11 +140,10 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={top}
minW="50px"
cursor={'pointer'}
pr={0}
- onClick={handleSort.bind(null, 'users_number')}
+ onClick={handleSort.bind(null, filters, 'users_number', onFilterChange)}
>
<HStack>
<span>{t('servicesTable.usersNumber')}</span>
@@ -98,11 +152,10 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={top}
minW="50px"
cursor={'pointer'}
pr={0}
- onClick={handleSort.bind(null, 'inbounds_number')}
+ onClick={handleSort.bind(null, filters, 'inbounds_number', onFilterChange)}
>
<HStack>
<span>{t('servicesTable.inboundsNumber')}</span>
@@ -112,9 +165,9 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
</Tr>
</Thead>
<Tbody>
- {services.length !== 0 ? services.map((service, i) => {
- return (
- <Fragment key={service.name}>
+ {useTable &&
+ services?.map((service, i) => {
+ return (
<Tr
cursor="pointer"
key={service.id}
@@ -136,9 +189,9 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
{service.inbounds.length}
</Td>
</Tr>
- </Fragment>
- );
- }) :
+ );
+ })}
+ {services.length == 0 && (
<Tr>
<Td colSpan={3}>
<EmptySection
@@ -150,7 +203,7 @@ export const ServicesTable: FC<ServicesTableProps> = (props) => {
/>
</Td>
</Tr>
- }
+ )}
</Tbody>
</Table>
<Pagination total={services.length} onFilterChange={onFilterChange} filters={filters} />
diff --git a/app/dashboard/src/pages/users/table/action-buttons.tsx b/app/dashboard/src/pages/users/table/action-buttons.tsx
@@ -0,0 +1,144 @@
+
+import { t } from 'i18next';
+import CopyToClipboard from 'react-copy-to-clipboard';
+import { User } from 'types';
+import {
+ CopiedIcon,
+ CopyIcon,
+ QRIcon,
+ SubscriptionLinkIcon
+} from 'components/table';
+import {
+ HStack,
+ IconButton,
+ Tooltip,
+} from '@chakra-ui/react';
+import { FC, useEffect, useState } from 'react';
+import { useUsers } from 'stores';
+
+type ActionButtonsProps = {
+ user: User;
+};
+
+export const ActionButtons: FC<ActionButtonsProps> = ({ user }) => {
+ const { setQRCode, setSubLink } = useUsers();
+
+ const proxyLinks = user.links.join('\r\n');
+
+ const [copied, setCopied] = useState([-1, false]);
+ useEffect(() => {
+ if (copied[1]) {
+ setTimeout(() => {
+ setCopied([-1, false]);
+ }, 1000);
+ }
+ }, [copied]);
+ return (
+ <HStack
+ justifyContent="flex-end"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <CopyToClipboard
+ text={
+ user.subscription_url.startsWith('/')
+ ? window.location.origin + user.subscription_url
+ : user.subscription_url
+ }
+ onCopy={() => {
+ setCopied([0, true]);
+ }}
+ >
+ <div>
+ <Tooltip
+ label={
+ copied[0] == 0 && copied[1]
+ ? t('usersTable.copied')
+ : t('usersTable.copyLink')
+ }
+ placement="top"
+ >
+ <IconButton
+ p="0 !important"
+ aria-label="copy subscription link"
+ bg="transparent"
+ _dark={{
+ _hover: {
+ bg: 'gray.700',
+ },
+ }}
+ size={{
+ base: 'sm',
+ md: 'md',
+ }}
+ >
+ {copied[0] == 0 && copied[1] ? (
+ <CopiedIcon />
+ ) : (
+ <SubscriptionLinkIcon />
+ )}
+ </IconButton>
+ </Tooltip>
+ </div>
+ </CopyToClipboard>
+ <CopyToClipboard
+ text={proxyLinks}
+ onCopy={() => {
+ setCopied([1, true]);
+ }}
+ >
+ <div>
+ <Tooltip
+ label={
+ copied[0] == 1 && copied[1]
+ ? t('usersTable.copied')
+ : t('usersTable.copyConfigs')
+ }
+ placement="top"
+ >
+ <IconButton
+ p="0 !important"
+ aria-label="copy configs"
+ bg="transparent"
+ _dark={{
+ _hover: {
+ bg: 'gray.700',
+ },
+ }}
+ size={{
+ base: 'sm',
+ md: 'md',
+ }}
+ >
+ {copied[0] == 1 && copied[1] ? <CopiedIcon /> : <CopyIcon />}
+ </IconButton>
+ </Tooltip>
+ </div>
+ </CopyToClipboard>
+ <Tooltip label="QR Code" placement="top">
+ <IconButton
+ p="0 !important"
+ aria-label="qr code"
+ bg="transparent"
+ _dark={{
+ _hover: {
+ bg: 'gray.700',
+ },
+ }}
+ size={{
+ base: 'sm',
+ md: 'md',
+ }}
+ onClick={() => {
+ setQRCode(user.links);
+ setSubLink(user.subscription_url);
+ }}
+ >
+ <QRIcon />
+ </IconButton>
+ </Tooltip>
+ </HStack >
+ );
+};
diff --git a/app/dashboard/src/pages/users/table/index.tsx b/app/dashboard/src/pages/users/table/index.tsx
@@ -20,13 +20,9 @@ import {
VStack,
} from '@chakra-ui/react';
import classNames from 'classnames';
-
import { statusColors } from 'constants/Settings';
-import { t } from 'i18next';
-import { FC, Fragment, useEffect, useState } from 'react';
-import CopyToClipboard from 'react-copy-to-clipboard';
+import { FC, Fragment, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { User } from 'types';
import { OnlineBadge } from 'components/online-badge';
import { OnlineStatus } from 'components/online-status';
import { StatusBadge } from 'components/status-badge';
@@ -34,16 +30,14 @@ import {
Pagination,
EmptySection,
AccordionArrowIcon,
+ handleSort,
EditIcon,
- CopiedIcon,
- CopyIcon,
StatusSortSelect,
Sort,
- QRIcon,
- SubscriptionLinkIcon
} from 'components/table';
import { UsageSlider, UsageSliderCompact } from './usage-slider';
import { useUsers } from 'stores';
+import { ActionButtons } from './action-buttons';
type ExpandedIndex = number | number[];
@@ -54,7 +48,6 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
usersFilters: filters,
users: { users },
users: { total },
- refetchUsers,
onEditingUser,
onCreateUser,
onFilterChange,
@@ -64,37 +57,10 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
const [selectedRow, setSelectedRow] = useState<ExpandedIndex | undefined>(
undefined
);
- const marginTop = useBreakpointValue({ base: 120, lg: 72 }) || 72;
- const [top, setTop] = useState(`${marginTop}px`);
const useTable = useBreakpointValue({ base: false, md: true });
- useEffect(() => {
- const calcTop = () => {
- const el = document.querySelectorAll('#filters')[0] as HTMLElement;
- setTop(`${el.offsetHeight}px`);
- };
- window.addEventListener('scroll', calcTop);
- () => window.removeEventListener('scroll', calcTop);
- }, []);
-
const isFiltered = users.length !== total;
- const handleSort = (column: string) => {
- let newSort = filters.sort;
- if (newSort.includes(column)) {
- if (newSort.startsWith('-')) {
- newSort = '-created_at';
- } else {
- newSort = '-' + column;
- }
- } else {
- newSort = column;
- }
- onFilterChange({
- sort: newSort,
- });
- };
-
const handleStatusFilter = (e: any) => {
onFilterChange({
status: e.target.value.length > 0 ? e.target.value : undefined,
@@ -104,7 +70,6 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
const toggleAccordion = (index: number) => {
setSelectedRow(index === selectedRow ? undefined : index);
};
- refetchUsers();
return (
<Box id="users-table" overflowX={{ base: 'unset', md: 'unset' }}>
<Accordion
@@ -117,12 +82,11 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
<Tr>
<Th
position="sticky"
- top={top}
minW="120px"
pl={4}
pr={4}
cursor={'pointer'}
- onClick={handleSort.bind(null, 'username')}
+ onClick={handleSort.bind(null, filters, 'username', onFilterChange)}
>
<HStack>
<span>{t('users')}</span>
@@ -131,7 +95,6 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={top}
minW="50px"
pl={0}
pr={0}
@@ -160,11 +123,10 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={top}
minW="100px"
cursor={'pointer'}
pr={0}
- onClick={handleSort.bind(null, 'used_traffic')}
+ onClick={handleSort.bind(null, filters, 'used_traffic', onFilterChange)}
>
<HStack>
<span>{t('usersTable.dataUsage')}</span>
@@ -173,7 +135,6 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={top}
minW="32px"
w="32px"
p={0}
@@ -332,10 +293,9 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
<Tr>
<Th
position="sticky"
- top={{ base: 'unset', md: top }}
minW="140px"
cursor={'pointer'}
- onClick={handleSort.bind(null, 'username')}
+ onClick={handleSort.bind(null, filters, 'username', onFilterChange)}
>
<HStack>
<span>{t('username')}</span>
@@ -344,7 +304,6 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={{ base: 'unset', md: top }}
width="400px"
minW="150px"
cursor={'pointer'}
@@ -371,11 +330,10 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={{ base: 'unset', md: top }}
width="350px"
minW="230px"
cursor={'pointer'}
- onClick={handleSort.bind(null, 'used_traffic')}
+ onClick={handleSort.bind(null, filters, 'used_traffic', onFilterChange)}
>
<HStack>
<span>{t('usersTable.dataUsage')}</span>
@@ -384,7 +342,6 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Th>
<Th
position="sticky"
- top={{ base: 'unset', md: top }}
width="200px"
minW="180px"
/>
@@ -448,130 +405,3 @@ export const UsersTable: FC<UsersTableProps> = (props) => {
</Box>
);
};
-
-type ActionButtonsProps = {
- user: User;
-};
-
-const ActionButtons: FC<ActionButtonsProps> = ({ user }) => {
- const { setQRCode, setSubLink } = useUsers();
-
- const proxyLinks = user.links.join('\r\n');
-
- const [copied, setCopied] = useState([-1, false]);
- useEffect(() => {
- if (copied[1]) {
- setTimeout(() => {
- setCopied([-1, false]);
- }, 1000);
- }
- }, [copied]);
- return (
- <HStack
- justifyContent="flex-end"
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- }}
- >
- <CopyToClipboard
- text={
- user.subscription_url.startsWith('/')
- ? window.location.origin + user.subscription_url
- : user.subscription_url
- }
- onCopy={() => {
- setCopied([0, true]);
- }}
- >
- <div>
- <Tooltip
- label={
- copied[0] == 0 && copied[1]
- ? t('usersTable.copied')
- : t('usersTable.copyLink')
- }
- placement="top"
- >
- <IconButton
- p="0 !important"
- aria-label="copy subscription link"
- bg="transparent"
- _dark={{
- _hover: {
- bg: 'gray.700',
- },
- }}
- size={{
- base: 'sm',
- md: 'md',
- }}
- >
- {copied[0] == 0 && copied[1] ? (
- <CopiedIcon />
- ) : (
- <SubscriptionLinkIcon />
- )}
- </IconButton>
- </Tooltip>
- </div>
- </CopyToClipboard>
- <CopyToClipboard
- text={proxyLinks}
- onCopy={() => {
- setCopied([1, true]);
- }}
- >
- <div>
- <Tooltip
- label={
- copied[0] == 1 && copied[1]
- ? t('usersTable.copied')
- : t('usersTable.copyConfigs')
- }
- placement="top"
- >
- <IconButton
- p="0 !important"
- aria-label="copy configs"
- bg="transparent"
- _dark={{
- _hover: {
- bg: 'gray.700',
- },
- }}
- size={{
- base: 'sm',
- md: 'md',
- }}
- >
- {copied[0] == 1 && copied[1] ? <CopiedIcon /> : <CopyIcon />}
- </IconButton>
- </Tooltip>
- </div>
- </CopyToClipboard>
- <Tooltip label="QR Code" placement="top">
- <IconButton
- p="0 !important"
- aria-label="qr code"
- bg="transparent"
- _dark={{
- _hover: {
- bg: 'gray.700',
- },
- }}
- size={{
- base: 'sm',
- md: 'md',
- }}
- onClick={() => {
- setQRCode(user.links);
- setSubLink(user.subscription_url);
- }}
- >
- <QRIcon />
- </IconButton>
- </Tooltip>
- </HStack >
- );
-};