Team.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <template>
  2. <div class="flex flex-col flex-1 border rounded border-divider">
  3. <div
  4. class="flex items-start flex-1"
  5. :class="
  6. compact
  7. ? team.myRole === 'OWNER'
  8. ? 'cursor-pointer hover:bg-primaryDark transition hover:border-dividerDark focus-visible:border-dividerDark'
  9. : 'cursor-not-allowed bg-primaryLight'
  10. : ''
  11. "
  12. @click="
  13. compact
  14. ? team.myRole === 'OWNER'
  15. ? $emit('invite-team')
  16. : noPermission()
  17. : ''
  18. "
  19. >
  20. <div class="p-4">
  21. <label
  22. class="font-semibold text-secondaryDark"
  23. :class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }"
  24. >
  25. {{ team.name || t("state.nothing_found") }}
  26. </label>
  27. <div class="flex mt-2 overflow-hidden -space-x-1">
  28. <img
  29. v-for="(member, index) in team.teamMembers"
  30. :key="`member-${index}`"
  31. v-tippy="{ theme: 'tooltip' }"
  32. :title="member.user.displayName"
  33. :src="member.user.photoURL || undefined"
  34. :alt="member.user.displayName"
  35. class="inline-block w-5 h-5 rounded-full ring-primary ring-2"
  36. loading="lazy"
  37. />
  38. </div>
  39. </div>
  40. </div>
  41. <div v-if="!compact" class="flex items-end justify-between flex-shrink-0">
  42. <span>
  43. <ButtonSecondary
  44. v-if="team.myRole === 'OWNER'"
  45. svg="edit"
  46. class="rounded-none"
  47. :label="t('action.edit')"
  48. @click.native="
  49. () => {
  50. $emit('edit-team')
  51. }
  52. "
  53. />
  54. <ButtonSecondary
  55. v-if="team.myRole === 'OWNER'"
  56. svg="user-plus"
  57. class="rounded-none"
  58. :label="t('team.invite')"
  59. @click.native="
  60. () => {
  61. emit('invite-team')
  62. }
  63. "
  64. />
  65. </span>
  66. <span>
  67. <tippy ref="options" interactive trigger="click" theme="popover" arrow>
  68. <template #trigger>
  69. <ButtonSecondary
  70. v-tippy="{ theme: 'tooltip' }"
  71. :title="t('action.more')"
  72. svg="more-vertical"
  73. />
  74. </template>
  75. <SmartItem
  76. v-if="team.myRole === 'OWNER'"
  77. svg="edit"
  78. :label="t('action.edit')"
  79. @click.native="
  80. () => {
  81. $emit('edit-team')
  82. $refs.options.tippy().hide()
  83. }
  84. "
  85. />
  86. <SmartItem
  87. v-if="team.myRole === 'OWNER'"
  88. svg="trash-2"
  89. color="red"
  90. :label="t('action.delete')"
  91. @click.native="
  92. () => {
  93. confirmRemove = true
  94. $refs.options.tippy().hide()
  95. }
  96. "
  97. />
  98. <SmartItem
  99. v-if="!(team.myRole === 'OWNER' && team.ownersCount == 1)"
  100. svg="trash"
  101. :label="t('team.exit')"
  102. @click.native="
  103. () => {
  104. confirmExit = true
  105. $refs.options.tippy().hide()
  106. }
  107. "
  108. />
  109. </tippy>
  110. </span>
  111. </div>
  112. <SmartConfirmModal
  113. :show="confirmRemove"
  114. :title="t('confirm.remove_team')"
  115. @hide-modal="confirmRemove = false"
  116. @resolve="deleteTeam()"
  117. />
  118. <SmartConfirmModal
  119. :show="confirmExit"
  120. :title="t('confirm.exit_team')"
  121. @hide-modal="confirmExit = false"
  122. @resolve="exitTeam()"
  123. />
  124. </div>
  125. </template>
  126. <script setup lang="ts">
  127. import { ref } from "@nuxtjs/composition-api"
  128. import { pipe } from "fp-ts/function"
  129. import * as TE from "fp-ts/TaskEither"
  130. import { TeamMemberRole } from "~/helpers/backend/graphql"
  131. import {
  132. deleteTeam as backendDeleteTeam,
  133. leaveTeam,
  134. } from "~/helpers/backend/mutations/Team"
  135. import { useI18n, useToast } from "~/helpers/utils/composables"
  136. const t = useI18n()
  137. const props = defineProps<{
  138. team: {
  139. name: string
  140. myRole: TeamMemberRole
  141. ownersCount: number
  142. teamMembers: Array<{
  143. user: {
  144. displayName: string
  145. photoURL: string | null
  146. }
  147. }>
  148. }
  149. teamID: string
  150. compact: boolean
  151. }>()
  152. const emit = defineEmits<{
  153. (e: "edit-team"): void
  154. }>()
  155. const toast = useToast()
  156. const confirmRemove = ref(false)
  157. const confirmExit = ref(false)
  158. const deleteTeam = () => {
  159. pipe(
  160. backendDeleteTeam(props.teamID),
  161. TE.match(
  162. (err) => {
  163. // TODO: Better errors ? We know the possible errors now
  164. toast.error(`${t("error.something_went_wrong")}`)
  165. console.error(err)
  166. },
  167. () => {
  168. toast.success(`${t("team.deleted")}`)
  169. }
  170. )
  171. )() // Tasks (and TEs) are lazy, so call the function returned
  172. }
  173. const exitTeam = () => {
  174. pipe(
  175. leaveTeam(props.teamID),
  176. TE.match(
  177. (err) => {
  178. // TODO: Better errors ?
  179. toast.error(`${t("error.something_went_wrong")}`)
  180. console.error(err)
  181. },
  182. () => {
  183. toast.success(`${t("team.left")}`)
  184. }
  185. )
  186. )() // Tasks (and TEs) are lazy, so call the function returned
  187. }
  188. const noPermission = () => {
  189. toast.error(`${t("profile.no_permission")}`)
  190. }
  191. </script>