373 lines
11 KiB
Vue
373 lines
11 KiB
Vue
<template>
|
|
<div class="flex flex-col">
|
|
<!-- Table View for All Users -->
|
|
<div class="flex flex-col">
|
|
<h1 class="text-lg font-bold text-secondaryDark">
|
|
{{ t('users.users') }}
|
|
</h1>
|
|
<div class="flex items-center space-x-4 py-10">
|
|
<HoppButtonPrimary
|
|
:label="t('users.invite_user')"
|
|
@click="showInviteUserModal = true"
|
|
:icon="IconAddUser"
|
|
/>
|
|
|
|
<div class="flex">
|
|
<HoppButtonSecondary
|
|
outline
|
|
filled
|
|
:label="t('users.invited_users')"
|
|
:to="'/users/invited'"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<div
|
|
v-if="fetching && !error && usersList.length === 0"
|
|
class="flex justify-center"
|
|
>
|
|
<HoppSmartSpinner />
|
|
</div>
|
|
|
|
<div v-else-if="error">{{ t('users.load_list_error') }}</div>
|
|
|
|
<div v-else-if="usersList.length >= 1" class="m-5">
|
|
<HoppSmartTable
|
|
cell-styles="px-6 py-1"
|
|
:list="newUsersList"
|
|
:headings="headings"
|
|
@on-row-clicked="goToUserDetails"
|
|
>
|
|
<template #name="{ item }">
|
|
<div>
|
|
<div class="flex flex-col truncate justify-center">
|
|
<span
|
|
v-if="item.name.length"
|
|
class="my-1 truncate whitespace-normal"
|
|
>
|
|
{{ item.name }}
|
|
</span>
|
|
|
|
<span v-if="item.name.length === 0 && !isUserAdmin(item)"
|
|
>-</span
|
|
>
|
|
|
|
<span
|
|
v-if="isUserAdmin(item)"
|
|
class="text-xs font-medium px-3 py-1 my-1 rounded-full bg-green-900 text-green-300 w-min"
|
|
>
|
|
Admin
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #createdOn="{ item }">
|
|
<div class="flex flex-col truncate">
|
|
<span v-if="item" class="truncate">
|
|
{{ getCreatedDate(item.createdOn) }}
|
|
</span>
|
|
<span v-else> - </span>
|
|
|
|
<div>
|
|
<div class="text-gray-400 text-tiny">
|
|
<span>
|
|
<span>
|
|
{{ getCreatedTime(item.createdOn) }}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #action="{ item }">
|
|
<div class="relative">
|
|
<span>
|
|
<tippy interactive trigger="click" theme="popover">
|
|
<HoppButtonSecondary
|
|
v-tippy="{ theme: 'tooltip' }"
|
|
:icon="IconMoreHorizontal"
|
|
/>
|
|
<template #content="{ hide }">
|
|
<div
|
|
ref="tippyActions"
|
|
class="flex flex-col focus:outline-none"
|
|
tabindex="0"
|
|
@keyup.escape="hide()"
|
|
>
|
|
<HoppSmartItem
|
|
v-if="!isUserAdmin(item)"
|
|
:icon="IconUserCheck"
|
|
:label="t('users.make_admin')"
|
|
class="!hover:bg-emerald-600"
|
|
@click="makeUserAdmin(item)"
|
|
/>
|
|
<HoppSmartItem
|
|
v-else
|
|
:icon="IconUserMinus"
|
|
:label="t('users.remove_admin_status')"
|
|
class="!hover:bg-emerald-600"
|
|
@click="makeAdminToUser(item)"
|
|
/>
|
|
<HoppSmartItem
|
|
v-if="!isUserAdmin(item)"
|
|
:icon="IconTrash"
|
|
:label="t('users.delete_user')"
|
|
class="!hover:bg-red-600"
|
|
@click="deleteUser(item)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</tippy>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</HoppSmartTable>
|
|
</div>
|
|
|
|
<div v-else class="flex justify-center">{{ t('users.no_users') }}</div>
|
|
|
|
<div
|
|
v-if="hasNextPage && usersList.length >= usersPerPage"
|
|
class="flex justify-center my-5 px-3 py-2 cursor-pointer font-semibold rounded-3xl bg-dividerDark hover:bg-divider transition mx-auto w-38 text-secondaryDark"
|
|
@click="fetchNextUsers"
|
|
>
|
|
<span>{{ t('users.show_more') }}</span>
|
|
<icon-lucide-chevron-down class="ml-2 text-lg" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<UsersInviteModal
|
|
:show="showInviteUserModal"
|
|
@hide-modal="showInviteUserModal = false"
|
|
@send-invite="sendInvite"
|
|
/>
|
|
<HoppSmartConfirmModal
|
|
:show="confirmDeletion"
|
|
:title="t('users.confirm_user_deletion')"
|
|
@hide-modal="confirmDeletion = false"
|
|
@resolve="deleteUserMutation(deleteUserUID)"
|
|
/>
|
|
<HoppSmartConfirmModal
|
|
:show="confirmUserToAdmin"
|
|
:title="t('users.confirm_user_to_admin')"
|
|
@hide-modal="confirmUserToAdmin = false"
|
|
@resolve="makeUserAdminMutation(userToAdminUID)"
|
|
/>
|
|
<HoppSmartConfirmModal
|
|
:show="confirmAdminToUser"
|
|
:title="t('users.confirm_admin_to_user')"
|
|
@hide-modal="confirmAdminToUser = false"
|
|
@resolve="makeAdminToUserMutation(adminToUserUID)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { format } from 'date-fns';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { useMutation } from '@urql/vue';
|
|
import {
|
|
InviteNewUserDocument,
|
|
MakeUserAdminDocument,
|
|
RemoveUserByAdminDocument,
|
|
RemoveUserAsAdminDocument,
|
|
UsersListDocument,
|
|
UsersListQuery,
|
|
} from '../../helpers/backend/graphql';
|
|
import { usePagedQuery } from '~/composables/usePagedQuery';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useToast } from '~/composables/toast';
|
|
import { HoppButtonSecondary } from '@hoppscotch/ui';
|
|
import IconAddUser from '~icons/lucide/user-plus';
|
|
import IconTrash from '~icons/lucide/trash';
|
|
import IconUserMinus from '~icons/lucide/user-minus';
|
|
import IconUserCheck from '~icons/lucide/user-check';
|
|
import IconMoreHorizontal from '~icons/lucide/more-horizontal';
|
|
import { useI18n } from '~/composables/i18n';
|
|
|
|
// Get Proper Date Formats
|
|
const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy');
|
|
const getCreatedTime = (date: string) => format(new Date(date), 'hh:mm a');
|
|
|
|
const t = useI18n();
|
|
|
|
const toast = useToast();
|
|
|
|
// Get Paginated Results of all the users in the infra
|
|
const usersPerPage = 20;
|
|
const {
|
|
fetching,
|
|
error,
|
|
goToNextPage: fetchNextUsers,
|
|
list: usersList,
|
|
hasNextPage,
|
|
} = usePagedQuery(
|
|
UsersListDocument,
|
|
(x) => x.admin.allUsers,
|
|
(x) => x.uid,
|
|
usersPerPage,
|
|
{ cursor: undefined, take: usersPerPage }
|
|
);
|
|
|
|
// The new users list that is used in the table
|
|
const newUsersList = computed(() => {
|
|
return usersList.value.map((user) => {
|
|
return {
|
|
uid: user.uid,
|
|
name: user.displayName ?? '',
|
|
email: user.email ?? '',
|
|
createdOn: user.createdOn,
|
|
action: '',
|
|
};
|
|
});
|
|
});
|
|
|
|
const isUserAdmin = (
|
|
selectedUser: UsersListQuery['admin']['allUsers'][number]
|
|
) => {
|
|
return usersList.value.filter((user) => {
|
|
return user.uid === selectedUser.uid;
|
|
})[0].isAdmin;
|
|
};
|
|
|
|
// Table Headings
|
|
const headings = [
|
|
{ key: 'uid', label: t('users.id') },
|
|
{ key: 'name', label: t('users.name') },
|
|
{ key: 'email', label: t('users.email') },
|
|
{ key: 'createdOn', label: t('users.date') },
|
|
{ key: 'action', label: '', preventClick: true },
|
|
];
|
|
|
|
// Send Invitation through Email
|
|
const sendInvitation = useMutation(InviteNewUserDocument);
|
|
const showInviteUserModal = ref(false);
|
|
|
|
const sendInvite = async (email: string) => {
|
|
if (!email.trim()) {
|
|
toast.error(`${t('state.invalid_email')}`);
|
|
return;
|
|
}
|
|
const variables = { inviteeEmail: email.trim() };
|
|
await sendInvitation.executeMutation(variables).then((result) => {
|
|
if (result.error) {
|
|
toast.error(`${t('state.email_failure')}`);
|
|
} else {
|
|
toast.success(`${t('state.email_success')}`);
|
|
showInviteUserModal.value = false;
|
|
}
|
|
});
|
|
};
|
|
|
|
// Go to Individual User Details Page
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const goToUserDetails = (user: { uid: string }) =>
|
|
router.push('/users/' + user.uid);
|
|
|
|
watch(
|
|
() => route.params.id,
|
|
() => window.location.reload()
|
|
);
|
|
|
|
// User Deletion
|
|
const userDeletion = useMutation(RemoveUserByAdminDocument);
|
|
const confirmDeletion = ref(false);
|
|
const deleteUserUID = ref<string | null>(null);
|
|
|
|
const deleteUserMutation = async (id: string | null) => {
|
|
if (!id) {
|
|
confirmDeletion.value = false;
|
|
toast.error(`${t('state.delete_user_failure')}`);
|
|
return;
|
|
}
|
|
const variables = { uid: id };
|
|
await userDeletion.executeMutation(variables).then((result) => {
|
|
if (result.error) {
|
|
toast.error(`${t('state.delete_user_failure')}`);
|
|
} else {
|
|
toast.success(`${t('state.delete_user_success')}`);
|
|
usersList.value = usersList.value.filter((user) => user.uid !== id);
|
|
}
|
|
});
|
|
confirmDeletion.value = false;
|
|
deleteUserUID.value = null;
|
|
};
|
|
|
|
// Make User Admin
|
|
const userToAdmin = useMutation(MakeUserAdminDocument);
|
|
const confirmUserToAdmin = ref(false);
|
|
const userToAdminUID = ref<string | null>(null);
|
|
|
|
const makeUserAdmin = (user: { uid: string }) => {
|
|
confirmUserToAdmin.value = true;
|
|
userToAdminUID.value = user.uid;
|
|
};
|
|
|
|
const makeUserAdminMutation = async (id: string | null) => {
|
|
if (!id) {
|
|
confirmUserToAdmin.value = false;
|
|
toast.error(`${t('state.admin_failure')}`);
|
|
return;
|
|
}
|
|
const variables = { uid: id };
|
|
await userToAdmin.executeMutation(variables).then((result) => {
|
|
if (result.error) {
|
|
toast.error(`${t('state.admin_failure')}`);
|
|
} else {
|
|
toast.success(`${t('state.admin_success')}`);
|
|
usersList.value = usersList.value.map((user) => {
|
|
if (user.uid === id) {
|
|
user.isAdmin = true;
|
|
}
|
|
return user;
|
|
});
|
|
}
|
|
});
|
|
confirmUserToAdmin.value = false;
|
|
userToAdminUID.value = null;
|
|
};
|
|
|
|
// Remove Admin Status from a current Admin
|
|
const adminToUser = useMutation(RemoveUserAsAdminDocument);
|
|
const confirmAdminToUser = ref(false);
|
|
const adminToUserUID = ref<string | null>(null);
|
|
|
|
const makeAdminToUser = (user: { uid: string }) => {
|
|
confirmAdminToUser.value = true;
|
|
adminToUserUID.value = user.uid;
|
|
};
|
|
|
|
const deleteUser = (user: { uid: string }) => {
|
|
confirmDeletion.value = true;
|
|
deleteUserUID.value = user.uid;
|
|
};
|
|
|
|
const makeAdminToUserMutation = async (id: string | null) => {
|
|
if (!id) {
|
|
confirmAdminToUser.value = false;
|
|
toast.error(`${t('state.remove_admin_failure')}`);
|
|
return;
|
|
}
|
|
const variables = { uid: id };
|
|
await adminToUser.executeMutation(variables).then((result) => {
|
|
if (result.error) {
|
|
toast.error(`${t('state.remove_admin_failure')}`);
|
|
} else {
|
|
toast.success(`${t('state.remove_admin_success')}`);
|
|
usersList.value = usersList.value.map((user) => {
|
|
if (user.uid === id) {
|
|
user.isAdmin = false;
|
|
}
|
|
return user;
|
|
});
|
|
}
|
|
});
|
|
confirmAdminToUser.value = false;
|
|
adminToUserUID.value = null;
|
|
};
|
|
</script>
|