diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json index 21360a7..1aa66a9 100644 --- a/dashboard/public/locales/en.json +++ b/dashboard/public/locales/en.json @@ -109,6 +109,8 @@ "determined_expire": "Determined", "expire_method": "Expiration method", "onhold_expire": "On Hold", + "reset_usage": "Reset Usage", + "revoke_subscription": "Revoke Subscription", "on_hold_expire_duration": "On Hold", "on_hold_timeout": "On Hold Timeout", "settings": { @@ -181,6 +183,20 @@ "title": "{{name}} is successfuly updated", "desc": "" } + }, + "user_revoke": { + "error": "Failed to revoke {{name}} subscription", + "success": { + "title": "{{name}} is successfuly created", + "desc": "" + } + }, + "user_reset": { + "error": "Failed to reset {{name}} data usage", + "success": { + "title": "{{name}} data usage is successfuly reseted", + "desc": "" + } } }, "delete-confirmation": { diff --git a/dashboard/src/features/users/dialogs/settings/index.tsx b/dashboard/src/features/users/dialogs/settings/index.tsx index ce7ac2c..9603bda 100644 --- a/dashboard/src/features/users/dialogs/settings/index.tsx +++ b/dashboard/src/features/users/dialogs/settings/index.tsx @@ -21,7 +21,6 @@ interface UsersSettingsDialogProps { } export const UsersSettingsDialog: FC = ({ onOpenChange, open, entity }) => { - const { t } = useTranslation(); if (entity) { diff --git a/dashboard/src/features/users/dialogs/settings/user-info/qrcode.tsx b/dashboard/src/features/users/dialogs/settings/user-info/qrcode.tsx index 058f814..f4e4e07 100644 --- a/dashboard/src/features/users/dialogs/settings/user-info/qrcode.tsx +++ b/dashboard/src/features/users/dialogs/settings/user-info/qrcode.tsx @@ -1,9 +1,18 @@ -import { UserType } from '@marzneshin/features/users'; +import { + useUserSubscriptionRevokeCmd, + UserType +} from '@marzneshin/features/users'; import QRCode from "react-qr-code"; import { QrCodeIcon } from "lucide-react"; import { FC } from 'react' -import { Card, CardContent, CardHeader, CardTitle } from '@marzneshin/components'; +import { + Button, + Card, + CardContent, + CardHeader, + CardTitle +} from '@marzneshin/components'; import { useTranslation } from 'react-i18next'; import { SubscriptionLinkButton } from './subcription-link-button'; import { getSubscriptionLink } from '@marzneshin/utils'; @@ -16,14 +25,18 @@ export const QRCodeSection: FC = ( { entity } ) => { const subscribeQrLink = getSubscriptionLink(entity.subscription_url); + const { mutate: revokeSubscription } = useUserSubscriptionRevokeCmd() const { t } = useTranslation() return ( - + {t('subscription')} +
diff --git a/dashboard/src/features/users/dialogs/settings/user-info/table.tsx b/dashboard/src/features/users/dialogs/settings/user-info/table.tsx index c425b24..a11efe7 100644 --- a/dashboard/src/features/users/dialogs/settings/user-info/table.tsx +++ b/dashboard/src/features/users/dialogs/settings/user-info/table.tsx @@ -1,6 +1,11 @@ -import { Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableRow } from "@marzneshin/components"; +import { Button, Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableRow } from "@marzneshin/components"; import { FC } from 'react'; -import { UserType, UsersStatus, UsersStatusBadge } from '@marzneshin/features/users'; +import { + useUserUsageResetCmd, + UserType, + UsersStatus, + UsersStatusBadge +} from '@marzneshin/features/users'; import { CircularProgress } from "@nextui-org/progress"; import { useTranslation } from "react-i18next"; import { format, isDate, isValid } from "date-fns"; @@ -35,12 +40,15 @@ const CircularProgressBarRow: FC<{ label: string; value: number; limit: number } export const UserInfoTable: FC = ({ entity }) => { const { t } = useTranslation(); - + const { mutate: resetUsage } = useUserUsageResetCmd() const expireDate = entity.expire ? (isDate(entity.expire) ? entity.expire : new Date(entity.expire)) : null; return ( - + {t('user_info')} + diff --git a/dashboard/src/features/users/services/index.ts b/dashboard/src/features/users/services/index.ts index 9098541..7f180cc 100644 --- a/dashboard/src/features/users/services/index.ts +++ b/dashboard/src/features/users/services/index.ts @@ -2,3 +2,5 @@ export * from './users.query' export * from './update-user.mutate' export * from './create-user.mutate' export * from './delete-user.mutate' +export * from './user-usage-reset.command' +export * from './user-subscription-revoke.command' diff --git a/dashboard/src/features/users/services/user-subscription-revoke.command.ts b/dashboard/src/features/users/services/user-subscription-revoke.command.ts new file mode 100644 index 0000000..265bb8d --- /dev/null +++ b/dashboard/src/features/users/services/user-subscription-revoke.command.ts @@ -0,0 +1,42 @@ +import { useMutation } from "@tanstack/react-query"; +import { fetch, queryClient } from "@marzneshin/utils"; +import { toast } from "sonner"; +import i18n from "@marzneshin/features/i18n"; +import { + UserMutationType +} from "@marzneshin/features/users"; + +export async function userSubscriptionRvoke(user: UserMutationType): Promise { + return fetch(`/users/${user.username}/revoke_sub`, { method: 'post', body: user }).then((user) => { + return user; + }); +} + +const handleError = (error: Error, value: UserMutationType) => { + toast.error( + i18n.t('events.user_revoke.error', { name: value.username }), + { + description: error.message + }) +} + +const handleSuccess = (value: UserMutationType) => { + toast.success( + i18n.t('events.user_revoke.success.title', { name: value.username }), + { + description: i18n.t('events.user_revoke.success.desc') + }) + queryClient.invalidateQueries({ queryKey: [UsersSubscriptionFetchKey] }) +} + + +const UsersSubscriptionFetchKey = "users"; + +export const useUserSubscriptionRevokeCmd = () => { + return useMutation({ + mutationKey: [UsersSubscriptionFetchKey, "revoke_sub"], + mutationFn: userSubscriptionRvoke, + onError: handleError, + onSuccess: handleSuccess, + }) +} diff --git a/dashboard/src/features/users/services/user-usage-reset.command.ts b/dashboard/src/features/users/services/user-usage-reset.command.ts new file mode 100644 index 0000000..f6c4baa --- /dev/null +++ b/dashboard/src/features/users/services/user-usage-reset.command.ts @@ -0,0 +1,43 @@ +import { useMutation } from "@tanstack/react-query"; +import { fetch, queryClient } from "@marzneshin/utils"; +import { toast } from "sonner"; +import i18n from "@marzneshin/features/i18n"; +import { + UsersQueryFetchKey, + UserMutationType +} from "@marzneshin/features/users"; + +export async function userUsageReset(user: UserMutationType): Promise { + return fetch(`/users/${user.username}/reset`, { method: 'post', body: user }).then((user) => { + return user; + }); +} + +const handleError = (error: Error, value: UserMutationType) => { + toast.error( + i18n.t('events.user_reset.error', { name: value.username }), + { + description: error.message + }) +} + +const handleSuccess = (value: UserMutationType) => { + toast.success( + i18n.t('events.user_reset.success.title', { name: value.username }), + { + description: i18n.t('events.user_reset.success.desc') + }) + queryClient.invalidateQueries({ queryKey: [UsersQueryFetchKey] }) +} + + +const UsersUpdateFetchKey = "users"; + +export const useUserUsageResetCmd = () => { + return useMutation({ + mutationKey: [UsersUpdateFetchKey, "reset"], + mutationFn: userUsageReset, + onError: handleError, + onSuccess: handleSuccess, + }) +}