Pārlūkot izejas kodu

feat: introduce updated mutation system and team.vue rewrite

Andrew Bastin 4 gadi atpakaļ
vecāks
revīzija
5cfc6c2949

+ 74 - 44
packages/hoppscotch-app/components/teams/Team.vue

@@ -14,7 +14,7 @@
             :key="`member-${index}`"
             v-tippy="{ theme: 'tooltip' }"
             :title="member.user.displayName"
-            :src="member.user.photoURL"
+            :src="member.user.photoURL || undefined"
             :alt="member.user.displayName"
             class="rounded-full h-5 ring-primary ring-2 w-5 inline-block"
           />
@@ -33,7 +33,7 @@
         <SmartItem
           v-if="team.myRole === 'OWNER'"
           svg="edit"
-          :label="$t('action.edit')"
+          :label="$t('action.edit').toString()"
           @click.native="
             () => {
               $emit('edit-team')
@@ -45,7 +45,7 @@
           v-if="team.myRole === 'OWNER'"
           svg="trash-2"
           color="red"
-          :label="$t('action.delete')"
+          :label="$t('action.delete').toString()"
           @click.native="
             () => {
               deleteTeam()
@@ -56,7 +56,7 @@
         <SmartItem
           v-if="!(team.myRole === 'OWNER' && team.ownersCount == 1)"
           svg="trash"
-          :label="$t('team.exit')"
+          :label="$t('team.exit').toString()"
           @click.native="
             () => {
               exitTeam()
@@ -69,49 +69,79 @@
   </div>
 </template>
 
-<script>
-import { defineComponent } from "@nuxtjs/composition-api"
-import * as teamUtils from "~/helpers/teams/utils"
+<script setup lang="ts">
+import { useContext } from "@nuxtjs/composition-api"
+import { pipe } from "fp-ts/function"
+import * as TE from "fp-ts/TaskEither"
+import {
+  deleteTeam as backendDeleteTeam,
+  leaveTeam,
+} from "~/helpers/backend/mutations/Team"
+import { TeamMemberRole } from "~/helpers/backend/types/TeamMemberRole"
 
-export default defineComponent({
-  props: {
-    team: { type: Object, default: () => {} },
-    teamID: { type: String, default: null },
-  },
-  methods: {
-    deleteTeam() {
-      if (!confirm(this.$t("confirm.remove_team"))) return
-      // Call to the graphql mutation
-      teamUtils
-        .deleteTeam(this.$apollo, this.teamID)
-        .then(() => {
-          this.$toast.success(this.$t("team.deleted"), {
-            icon: "done",
-          })
+const props = defineProps<{
+  team: {
+    name: string
+    myRole: TeamMemberRole
+    ownersCount: number
+    members: Array<{
+      user: {
+        displayName: string
+        photoURL: string | null
+      }
+    }>
+  }
+  teamID: string
+}>()
+
+const {
+  app: { i18n },
+  $toast,
+} = useContext()
+
+const $t = i18n.t.bind(i18n)
+
+const deleteTeam = () => {
+  if (!confirm($t("confirm.remove_team").toString())) return
+
+  pipe(
+    backendDeleteTeam(props.teamID),
+    TE.match(
+      (err) => {
+        // TODO: Better errors ? We know the possible errors now
+        $toast.error($t("error.something_went_wrong").toString(), {
+          icon: "error_outline",
         })
-        .catch((e) => {
-          this.$toast.error(this.$t("error.something_went_wrong"), {
-            icon: "error_outline",
-          })
-          console.error(e)
+        console.error(err)
+      },
+      () => {
+        $toast.success($t("team.deleted").toString(), {
+          icon: "done",
         })
-    },
-    exitTeam() {
-      if (!confirm("Are you sure you want to exit this team?")) return
-      teamUtils
-        .exitTeam(this.$apollo, this.teamID)
-        .then(() => {
-          this.$toast.success(this.$t("team.left"), {
-            icon: "done",
-          })
+      }
+    )
+  )() // Tasks (and TEs) are lazy, so call the function returned
+}
+
+const exitTeam = () => {
+  if (!confirm("Are you sure you want to exit this team?")) return
+
+  pipe(
+    leaveTeam(props.teamID),
+    TE.match(
+      (err) => {
+        // TODO: Better errors ?
+        $toast.error($t("error.something_went_wrong").toString(), {
+          icon: "error_outline",
         })
-        .catch((e) => {
-          this.$toast.error(this.$t("error.something_went_wrong"), {
-            icon: "error_outline",
-          })
-          console.error(e)
+        console.error(err)
+      },
+      () => {
+        $toast.success($t("team.left").toString(), {
+          icon: "done",
         })
-    },
-  },
-})
+      }
+    )
+  )() // Tasks (and TEs) are lazy, so call the function returned
+}
 </script>

+ 45 - 3
packages/hoppscotch-app/helpers/backend/GQLClient.ts

@@ -15,7 +15,8 @@ import {
 } from "@urql/core"
 import { devtoolsExchange } from "@urql/devtools"
 import * as E from "fp-ts/Either"
-import { pipe } from "fp-ts/function"
+import * as TE from "fp-ts/TaskEither"
+import { pipe, constVoid } from "fp-ts/function"
 import { subscribe } from "wonka"
 import clone from "lodash/clone"
 import { getAuthIDToken } from "~/helpers/fb/auth"
@@ -49,7 +50,7 @@ export type GQLError<T extends string> =
     }
   | {
       type: "gql_error"
-      err: T
+      error: T
     }
 
 const DEFAULT_QUERY_OPTIONS = {
@@ -122,7 +123,7 @@ export function useGQLQuery<
               (gqlErr) =>
                 <GQLError<QueryFailType>>{
                   type: "gql_error",
-                  err: gqlErr as QueryFailType,
+                  error: gqlErr as QueryFailType,
                 },
               // The right case (it was a GraphQL Error)
               (networkErr) =>
@@ -163,3 +164,44 @@ export function useGQLQuery<
       }
     | { loading: true }
 }
+
+export const runMutation = <
+  MutationReturnType = any,
+  MutationFailType extends string = "",
+  MutationVariables extends {} = {}
+>(
+  mutation: string | DocumentNode | TypedDocumentNode<any, MutationVariables>,
+  variables?: MutationVariables
+): TE.TaskEither<GQLError<MutationFailType>, NonNullable<MutationReturnType>> =>
+  pipe(
+    TE.tryCatch(
+      () => client.mutation<MutationReturnType>(mutation, variables).toPromise(),
+      () => constVoid() as never // The mutation function can never fail, so this will never be called ;)
+    ),
+    TE.chainEitherK((result) =>
+      pipe(
+        result.data as MutationReturnType, // If we have the result, then okay
+        E.fromNullable(
+          // Result is null
+          pipe(
+            result.error?.networkError, // Check for network error
+            E.fromNullable(result.error?.name), // If it is null, then it is a GQL error
+            E.match(
+              // The left case (network error was null)
+              (gqlErr) =>
+                <GQLError<MutationFailType>>{
+                  type: "gql_error",
+                  error: gqlErr as MutationFailType,
+                },
+              // The right case (it was a GraphQL Error)
+              (networkErr) =>
+                <GQLError<MutationFailType>>{
+                  type: "network_error",
+                  error: networkErr,
+                }
+            )
+          )
+        )
+      )
+    )
+  )

+ 33 - 0
packages/hoppscotch-app/helpers/backend/mutations/Team.ts

@@ -0,0 +1,33 @@
+import gql from "graphql-tag"
+import { runMutation } from "../GQLClient"
+
+type DeleteTeamErrors =
+  | "team/not_required_role"
+  | "team/invalid_id"
+  | "team/member_not_found"
+
+type ExitTeamErrors = "team/invalid_id" | "team/member_not_found"
+
+export const deleteTeam = (teamID: string) =>
+  runMutation<void, DeleteTeamErrors>(
+    gql`
+      mutation DeleteTeam($teamID: String!) {
+        deleteTeam(teamID: $teamID)
+      }
+    `,
+    {
+      teamID,
+    }
+  )
+
+export const leaveTeam = (teamID: string) =>
+  runMutation<void, ExitTeamErrors>(
+    gql`
+      mutation ExitTeam($teamID: String!) {
+        leaveTeam(teamID: $teamID)
+      }
+    `,
+    {
+      teamID,
+    }
+  )