From ffb0c12c08bab6ec9ae1a3eb5e28cab8e28e417b Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Fri, 1 Oct 2021 15:13:48 +0530 Subject: [PATCH] feat: introduce updated mutation system and team.vue rewrite --- .../hoppscotch-app/components/teams/Team.vue | 118 +++++++++++------- .../helpers/backend/GQLClient.ts | 48 ++++++- .../helpers/backend/mutations/Team.ts | 33 +++++ 3 files changed, 152 insertions(+), 47 deletions(-) create mode 100644 packages/hoppscotch-app/helpers/backend/mutations/Team.ts diff --git a/packages/hoppscotch-app/components/teams/Team.vue b/packages/hoppscotch-app/components/teams/Team.vue index 029a9f1b1..2f6fe89ca 100644 --- a/packages/hoppscotch-app/components/teams/Team.vue +++ b/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 @@ +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 +} diff --git a/packages/hoppscotch-app/helpers/backend/GQLClient.ts b/packages/hoppscotch-app/helpers/backend/GQLClient.ts index 006465542..cf8fd2335 100644 --- a/packages/hoppscotch-app/helpers/backend/GQLClient.ts +++ b/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 = } | { type: "gql_error" - err: T + error: T } const DEFAULT_QUERY_OPTIONS = { @@ -122,7 +123,7 @@ export function useGQLQuery< (gqlErr) => >{ 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, + variables?: MutationVariables +): TE.TaskEither, NonNullable> => + pipe( + TE.tryCatch( + () => client.mutation(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) => + >{ + type: "gql_error", + error: gqlErr as MutationFailType, + }, + // The right case (it was a GraphQL Error) + (networkErr) => + >{ + type: "network_error", + error: networkErr, + } + ) + ) + ) + ) + ) + ) diff --git a/packages/hoppscotch-app/helpers/backend/mutations/Team.ts b/packages/hoppscotch-app/helpers/backend/mutations/Team.ts new file mode 100644 index 000000000..b75f5f1ed --- /dev/null +++ b/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( + gql` + mutation DeleteTeam($teamID: String!) { + deleteTeam(teamID: $teamID) + } + `, + { + teamID, + } + ) + +export const leaveTeam = (teamID: string) => + runMutation( + gql` + mutation ExitTeam($teamID: String!) { + leaveTeam(teamID: $teamID) + } + `, + { + teamID, + } + )