123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- <template>
- <SmartModal v-if="show" :title="t('team.edit')" @close="hideModal">
- <template #body>
- <div class="flex flex-col px-2">
- <div class="relative flex">
- <input
- id="selectLabelTeamEdit"
- v-model="name"
- v-focus
- class="input floating-input"
- placeholder=" "
- type="text"
- autocomplete="off"
- @keyup.enter="saveTeam"
- />
- <label for="selectLabelTeamEdit">
- {{ t("action.label") }}
- </label>
- </div>
- <div class="flex items-center justify-between flex-1 pt-4">
- <label for="memberList" class="p-4">
- {{ t("team.members") }}
- </label>
- <div class="flex">
- <ButtonSecondary
- svg="user-plus"
- :label="t('team.invite')"
- filled
- @click.native="
- () => {
- $emit('invite-team')
- }
- "
- />
- </div>
- </div>
- <div
- v-if="teamDetails.loading"
- class="flex flex-col items-center justify-center"
- >
- <SmartSpinner class="mb-4" />
- <span class="text-secondaryLight">{{ t("state.loading") }}</span>
- </div>
- <div
- v-if="
- !teamDetails.loading &&
- E.isRight(teamDetails.data) &&
- teamDetails.data.right.team.teamMembers
- "
- class="divide-dividerLight border-divider border divide-y rounded"
- >
- <div
- v-if="teamDetails.data.right.team.teamMembers === 0"
- class="text-secondaryLight flex flex-col items-center justify-center p-4"
- >
- <img
- :src="`/images/states/${$colorMode.value}/add_group.svg`"
- loading="lazy"
- class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
- :alt="`${t('empty.members')}`"
- />
- <span class="pb-4 text-center">
- {{ t("empty.members") }}
- </span>
- <ButtonSecondary
- svg="user-plus"
- :label="t('team.invite')"
- @click.native="
- () => {
- emit('invite-team')
- }
- "
- />
- </div>
- <div v-else>
- <div
- v-for="(member, index) in membersList"
- :key="`member-${index}`"
- class="divide-dividerLight flex divide-x"
- >
- <input
- class="flex flex-1 px-4 py-2 bg-transparent"
- :placeholder="`${t('team.email')}`"
- :name="'param' + index"
- :value="member.email"
- readonly
- />
- <span>
- <tippy
- ref="memberOptions"
- interactive
- trigger="click"
- theme="popover"
- arrow
- >
- <template #trigger>
- <span class="select-wrapper">
- <input
- class="flex flex-1 px-4 py-2 bg-transparent cursor-pointer"
- :placeholder="`${t('team.permissions')}`"
- :name="'value' + index"
- :value="
- typeof member.role === 'string'
- ? member.role
- : JSON.stringify(member.role)
- "
- readonly
- />
- </span>
- </template>
- <SmartItem
- label="OWNER"
- @click.native="
- () => {
- updateMemberRole(member.userID, 'OWNER')
- memberOptions[index].tippy().hide()
- }
- "
- />
- <SmartItem
- label="EDITOR"
- @click.native="
- () => {
- updateMemberRole(member.userID, 'EDITOR')
- memberOptions[index].tippy().hide()
- }
- "
- />
- <SmartItem
- label="VIEWER"
- @click.native="
- () => {
- updateMemberRole(member.userID, 'VIEWER')
- memberOptions[index].tippy().hide()
- }
- "
- />
- </tippy>
- </span>
- <div class="flex">
- <ButtonSecondary
- id="member"
- v-tippy="{ theme: 'tooltip' }"
- :title="t('action.remove')"
- svg="trash"
- color="red"
- @click.native="removeExistingTeamMember(member.userID)"
- />
- </div>
- </div>
- </div>
- </div>
- <div
- v-if="!teamDetails.loading && E.isLeft(teamDetails.data)"
- class="flex flex-col items-center"
- >
- <i class="material-icons mb-4">help_outline</i>
- {{ t("error.something_went_wrong") }}
- </div>
- </div>
- </template>
- <template #footer>
- <span>
- <ButtonPrimary :label="t('action.save')" @click.native="saveTeam" />
- <ButtonSecondary
- :label="t('action.cancel')"
- @click.native="hideModal"
- />
- </span>
- </template>
- </SmartModal>
- </template>
- <script setup lang="ts">
- import { computed, ref, toRef, watch } from "@nuxtjs/composition-api"
- import * as E from "fp-ts/Either"
- import {
- GetTeamDocument,
- GetTeamQuery,
- GetTeamQueryVariables,
- TeamMemberAddedDocument,
- TeamMemberRemovedDocument,
- TeamMemberRole,
- TeamMemberUpdatedDocument,
- } from "~/helpers/backend/graphql"
- import {
- removeTeamMember,
- renameTeam,
- updateTeamMemberRole,
- } from "~/helpers/backend/mutations/Team"
- import { TeamNameCodec } from "~/helpers/backend/types/TeamName"
- import { useGQLQuery } from "~/helpers/backend/GQLClient"
- import { useI18n, useToast } from "~/helpers/utils/composables"
- const t = useI18n()
- const emit = defineEmits<{
- (e: "hide-modal"): void
- }>()
- const memberOptions = ref<any | null>(null)
- const props = defineProps<{
- show: boolean
- editingTeam: {
- name: string
- }
- editingTeamID: string
- }>()
- const toast = useToast()
- const name = toRef(props.editingTeam, "name")
- watch(
- () => props.editingTeam.name,
- (newName: string) => {
- name.value = newName
- }
- )
- watch(
- () => props.editingTeamID,
- (teamID: string) => {
- teamDetails.execute({ teamID })
- }
- )
- const teamDetails = useGQLQuery<GetTeamQuery, GetTeamQueryVariables, "">({
- query: GetTeamDocument,
- variables: {
- teamID: props.editingTeamID,
- },
- defer: true,
- updateSubs: computed(() => {
- if (props.editingTeamID) {
- return [
- {
- key: 1,
- query: TeamMemberAddedDocument,
- variables: {
- teamID: props.editingTeamID,
- },
- },
- {
- key: 2,
- query: TeamMemberUpdatedDocument,
- variables: {
- teamID: props.editingTeamID,
- },
- },
- {
- key: 3,
- query: TeamMemberRemovedDocument,
- variables: {
- teamID: props.editingTeamID,
- },
- },
- ]
- } else return []
- }),
- })
- const roleUpdates = ref<
- {
- userID: string
- role: TeamMemberRole
- }[]
- >([])
- watch(
- () => teamDetails,
- () => {
- if (teamDetails.loading) return
- const data = teamDetails.data
- if (E.isRight(data)) {
- const members = data.right.team?.teamMembers ?? []
- // Remove deleted members
- roleUpdates.value = roleUpdates.value.filter(
- (update) =>
- members.findIndex((y) => y.user.uid === update.userID) !== -1
- )
- }
- }
- )
- const updateMemberRole = (userID: string, role: TeamMemberRole) => {
- const updateIndex = roleUpdates.value.findIndex(
- (item) => item.userID === userID
- )
- if (updateIndex !== -1) {
- // Role Update exists
- roleUpdates.value[updateIndex].role = role
- } else {
- // Role Update does not exist
- roleUpdates.value.push({
- userID,
- role,
- })
- }
- }
- const membersList = computed(() => {
- if (teamDetails.loading) return []
- const data = teamDetails.data
- if (E.isLeft(data)) return []
- if (E.isRight(data)) {
- const members = (data.right.team?.teamMembers ?? []).map((member) => {
- const updatedRole = roleUpdates.value.find(
- (update) => update.userID === member.user.uid
- )
- return {
- userID: member.user.uid,
- email: member.user.email!,
- role: updatedRole?.role ?? member.role,
- }
- })
- return members
- }
- return []
- })
- const removeExistingTeamMember = async (userID: string) => {
- const removeTeamMemberResult = await removeTeamMember(
- userID,
- props.editingTeamID
- )()
- if (E.isLeft(removeTeamMemberResult)) {
- toast.error(`${t("error.something_went_wrong")}`)
- } else {
- toast.success(`${t("team.member_removed")}`)
- }
- }
- const saveTeam = async () => {
- if (name.value !== "") {
- if (TeamNameCodec.is(name.value)) {
- const updateTeamNameResult = await renameTeam(
- props.editingTeamID,
- name.value
- )()
- if (E.isLeft(updateTeamNameResult)) {
- toast.error(`${t("error.something_went_wrong")}`)
- } else {
- roleUpdates.value.forEach(async (update) => {
- const updateMemberRoleResult = await updateTeamMemberRole(
- update.userID,
- props.editingTeamID,
- update.role
- )()
- if (E.isLeft(updateMemberRoleResult)) {
- toast.error(`${t("error.something_went_wrong")}`)
- console.error(updateMemberRoleResult.left.error)
- }
- })
- }
- hideModal()
- toast.success(`${t("team.saved")}`)
- } else {
- return toast.error(`${t("team.name_length_insufficient")}`)
- }
- } else {
- return toast.error(`${t("empty.team_name")}`)
- }
- }
- const hideModal = () => {
- emit("hide-modal")
- }
- </script>
|