From c332808fe4250e17f8085da777e95346c8fb9153 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Fri, 1 Oct 2021 13:46:22 +0530 Subject: [PATCH 01/17] feat: initial setup of new backend comms --- .../hoppscotch-app/components/teams/index.vue | 57 ++++-- .../helpers/backend/GQLClient.ts | 165 ++++++++++++++++++ .../helpers/backend/QueryErrors.ts | 3 + .../helpers/backend/types/TeamMemberRole.ts | 1 + .../helpers/backend/types/TeamName.ts | 13 ++ packages/hoppscotch-app/helpers/fb/auth.ts | 4 + packages/hoppscotch-app/package.json | 5 + pnpm-lock.yaml | 40 ++++- 8 files changed, 273 insertions(+), 15 deletions(-) create mode 100644 packages/hoppscotch-app/helpers/backend/GQLClient.ts create mode 100644 packages/hoppscotch-app/helpers/backend/QueryErrors.ts create mode 100644 packages/hoppscotch-app/helpers/backend/types/TeamMemberRole.ts create mode 100644 packages/hoppscotch-app/helpers/backend/types/TeamName.ts diff --git a/packages/hoppscotch-app/components/teams/index.vue b/packages/hoppscotch-app/components/teams/index.vue index 64c9e501d..82636de4f 100644 --- a/packages/hoppscotch-app/components/teams/index.vue +++ b/packages/hoppscotch-app/components/teams/index.vue @@ -18,25 +18,29 @@ @click.native="displayModalAdd(true)" />
{{ $t("state.loading") }}
help_outline {{ $t("empty.teams") }}
import { gql } from "@apollo/client/core" -import { ref } from "@nuxtjs/composition-api" -import { useGQLQuery, isApolloError } from "~/helpers/apollo" +import { ref, watchEffect } from "@nuxtjs/composition-api" +import * as E from "fp-ts/Either" +import { useGQLQuery } from "~/helpers/backend/GQLClient" +import { MyTeamsQueryError } from "~/helpers/backend/QueryErrors" +import { TeamMemberRole } from "~/helpers/backend/types/TeamMemberRole" const showModalAdd = ref(false) const showModalEdit = ref(false) const editingTeam = ref({}) // TODO: Check this out const editingTeamID = ref("") -const { loading: myTeamsLoading, data: myTeams } = useGQLQuery({ - query: gql` +const myTeams = useGQLQuery< + { + myTeams: Array<{ + id: string + name: string + myRole: TeamMemberRole + ownersCount: number + members: Array<{ + user: { + photoURL: string | null + displayName: string + email: string + uid: string + } + role: TeamMemberRole + }> + }> + }, + MyTeamsQueryError +>( + gql` query GetMyTeams { myTeams { id @@ -86,8 +116,11 @@ const { loading: myTeamsLoading, data: myTeams } = useGQLQuery({ } } } - `, - pollInterval: 10000, + ` +) + +watchEffect(() => { + console.log(myTeams) }) const displayModalAdd = (shouldDisplay: boolean) => { diff --git a/packages/hoppscotch-app/helpers/backend/GQLClient.ts b/packages/hoppscotch-app/helpers/backend/GQLClient.ts new file mode 100644 index 000000000..006465542 --- /dev/null +++ b/packages/hoppscotch-app/helpers/backend/GQLClient.ts @@ -0,0 +1,165 @@ +import { + computed, + ref, + onMounted, + onBeforeUnmount, + reactive, + Ref, +} from "@nuxtjs/composition-api" +import { DocumentNode } from "graphql/language" +import { + createClient, + TypedDocumentNode, + OperationResult, + defaultExchanges, +} from "@urql/core" +import { devtoolsExchange } from "@urql/devtools" +import * as E from "fp-ts/Either" +import { pipe } from "fp-ts/function" +import { subscribe } from "wonka" +import clone from "lodash/clone" +import { getAuthIDToken } from "~/helpers/fb/auth" + +const BACKEND_GQL_URL = + process.env.CONTEXT === "production" + ? "https://api.hoppscotch.io/graphql" + : "https://api.hoppscotch.io/graphql" + +const client = createClient({ + url: BACKEND_GQL_URL, + fetchOptions: () => { + const token = getAuthIDToken() + + return { + headers: { + authorization: token ? `Bearer ${token}` : "", + }, + } + }, + exchanges: [devtoolsExchange, ...defaultExchanges], +}) + +/** + * A wrapper type for defining errors possible in a GQL operation + */ +export type GQLError = + | { + type: "network_error" + error: Error + } + | { + type: "gql_error" + err: T + } + +const DEFAULT_QUERY_OPTIONS = { + noPolling: false, + pause: undefined as Ref | undefined, +} + +type GQL_QUERY_OPTIONS = typeof DEFAULT_QUERY_OPTIONS + +type UseQueryLoading = { + loading: true +} + +type UseQueryLoaded< + QueryFailType extends string = "", + QueryReturnType = any +> = { + loading: false + data: E.Either, QueryReturnType> +} + +type UseQueryReturn = + | UseQueryLoading + | UseQueryLoaded + +export function isLoadedGQLQuery( + x: UseQueryReturn +): x is { + loading: false + data: E.Either, QueryReturnType> +} { + return !x.loading +} + +export function useGQLQuery< + QueryReturnType = any, + QueryFailType extends string = "", + QueryVariables extends object = {} +>( + query: string | DocumentNode | TypedDocumentNode, + variables?: QueryVariables, + options: Partial = DEFAULT_QUERY_OPTIONS +): + | { loading: false; data: E.Either, QueryReturnType> } + | { loading: true } { + type DataType = E.Either, QueryReturnType> + + const finalOptions = Object.assign(clone(DEFAULT_QUERY_OPTIONS), options) + + const data = ref() + + let subscription: { unsubscribe(): void } | null = null + + onMounted(() => { + const gqlQuery = client.query(query, variables) + + const processResult = (result: OperationResult) => + pipe( + // The target + result.data as QueryReturnType | undefined, + // Define what happens if data does not exist (it is an error) + E.fromNullable( + pipe( + // Take the network error value + result.error?.networkError, + // If it null, set the left to the generic error name + E.fromNullable(result.error?.name), + E.match( + // The left case (network error was null) + (gqlErr) => + >{ + type: "gql_error", + err: gqlErr as QueryFailType, + }, + // The right case (it was a GraphQL Error) + (networkErr) => + >{ + type: "network_error", + error: networkErr, + } + ) + ) + ) + ) + + if (finalOptions.noPolling) { + gqlQuery.toPromise().then((result) => { + data.value = processResult(result) + }) + } else { + subscription = pipe( + gqlQuery, + subscribe((result) => { + data.value = processResult(result) + }) + ) + } + }) + + onBeforeUnmount(() => { + subscription?.unsubscribe() + }) + + return reactive({ + loading: computed(() => !data.value), + data: data!, + }) as + | { + loading: false + data: DataType + } + | { loading: true } +} diff --git a/packages/hoppscotch-app/helpers/backend/QueryErrors.ts b/packages/hoppscotch-app/helpers/backend/QueryErrors.ts new file mode 100644 index 000000000..37d1963f8 --- /dev/null +++ b/packages/hoppscotch-app/helpers/backend/QueryErrors.ts @@ -0,0 +1,3 @@ +export type UserQueryError = "user/not_found" + +export type MyTeamsQueryError = "ea/not_invite_or_admin" diff --git a/packages/hoppscotch-app/helpers/backend/types/TeamMemberRole.ts b/packages/hoppscotch-app/helpers/backend/types/TeamMemberRole.ts new file mode 100644 index 000000000..a98db8c4c --- /dev/null +++ b/packages/hoppscotch-app/helpers/backend/types/TeamMemberRole.ts @@ -0,0 +1 @@ +export type TeamMemberRole = "OWNER" | "EDITOR" | "VIEWER" \ No newline at end of file diff --git a/packages/hoppscotch-app/helpers/backend/types/TeamName.ts b/packages/hoppscotch-app/helpers/backend/types/TeamName.ts new file mode 100644 index 000000000..ce8253a04 --- /dev/null +++ b/packages/hoppscotch-app/helpers/backend/types/TeamName.ts @@ -0,0 +1,13 @@ +import * as t from "io-ts" + +interface TeamNameBrand { + readonly TeamName: unique symbol +} + +export const TeamNameCodec = t.brand( + t.string, + (x): x is t.Branded => x.length > 6, + "TeamName" +) + +export type TeamName = t.TypeOf diff --git a/packages/hoppscotch-app/helpers/fb/auth.ts b/packages/hoppscotch-app/helpers/fb/auth.ts index eafaf2766..88767e4c2 100644 --- a/packages/hoppscotch-app/helpers/fb/auth.ts +++ b/packages/hoppscotch-app/helpers/fb/auth.ts @@ -141,6 +141,10 @@ export function initAuth() { }) } +export function getAuthIDToken(): string | null { + return authIdToken$.getValue() +} + /** * Sign user in with a popup using Google */ diff --git a/packages/hoppscotch-app/package.json b/packages/hoppscotch-app/package.json index 24d27dbe1..55dd024e5 100644 --- a/packages/hoppscotch-app/package.json +++ b/packages/hoppscotch-app/package.json @@ -35,6 +35,7 @@ "@nuxtjs/robots": "^2.5.0", "@nuxtjs/sitemap": "^2.4.0", "@nuxtjs/toast": "^3.3.1", + "@urql/core": "^2.3.3", "acorn": "^8.5.0", "acorn-walk": "^8.2.0", "axios": "^0.21.4", @@ -48,6 +49,8 @@ "graphql": "^15.6.0", "graphql-language-service-interface": "^2.8.4", "graphql-language-service-parser": "^1.9.2", + "graphql-tag": "^2.12.5", + "io-ts": "^2.2.16", "json-loader": "^0.5.7", "lodash": "^4.17.21", "mustache": "^4.2.0", @@ -66,6 +69,7 @@ "vue-textarea-autosize": "^1.1.1", "vue-tippy": "^4.11.0", "vuejs-auto-complete": "^0.9.0", + "wonka": "^4.0.15", "yargs-parser": "^20.2.9" }, "devDependencies": { @@ -90,6 +94,7 @@ "@types/esprima": "^4.0.3", "@types/lodash": "^4.14.174", "@types/splitpanes": "^2.2.1", + "@urql/devtools": "^2.0.3", "@vue/runtime-dom": "^3.2.19", "@vue/test-utils": "^1.2.2", "babel-core": "^7.0.0-bridge.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d3297026..9df89872c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,8 @@ importers: '@types/esprima': ^4.0.3 '@types/lodash': ^4.14.174 '@types/splitpanes': ^2.2.1 + '@urql/core': ^2.3.3 + '@urql/devtools': ^2.0.3 '@vue/runtime-dom': ^3.2.19 '@vue/test-utils': ^1.2.2 acorn: ^8.5.0 @@ -69,6 +71,8 @@ importers: graphql: ^15.6.0 graphql-language-service-interface: ^2.8.4 graphql-language-service-parser: ^1.9.2 + graphql-tag: ^2.12.5 + io-ts: ^2.2.16 jest: ^27.2.2 jest-serializer-vue: ^2.0.2 json-loader: ^0.5.7 @@ -102,6 +106,7 @@ importers: vue-textarea-autosize: ^1.1.1 vue-tippy: ^4.11.0 vuejs-auto-complete: ^0.9.0 + wonka: ^4.0.15 worker-loader: ^3.0.8 yargs-parser: ^20.2.9 dependencies: @@ -114,6 +119,7 @@ importers: '@nuxtjs/robots': 2.5.0 '@nuxtjs/sitemap': 2.4.0 '@nuxtjs/toast': 3.3.1 + '@urql/core': 2.3.3_graphql@15.6.0 acorn: 8.5.0 acorn-walk: 8.2.0 axios: 0.21.4 @@ -127,6 +133,8 @@ importers: graphql: 15.6.0 graphql-language-service-interface: 2.8.4_graphql@15.6.0 graphql-language-service-parser: 1.9.2_graphql@15.6.0 + graphql-tag: 2.12.5_graphql@15.6.0 + io-ts: 2.2.16_fp-ts@2.11.3 json-loader: 0.5.7 lodash: 4.17.21 mustache: 4.2.0 @@ -138,13 +146,14 @@ importers: socketio-wildcard: 2.0.0 splitpanes: 2.3.8 tern: 0.24.3 - vue-apollo: 3.0.8 + vue-apollo: 3.0.8_graphql-tag@2.12.5 vue-cli-plugin-apollo: 0.22.2_typescript@4.4.3 vue-functional-data-merge: 3.1.0 vue-github-button: 1.3.0 vue-textarea-autosize: 1.1.1 vue-tippy: 4.11.0 vuejs-auto-complete: 0.9.0 + wonka: 4.0.15 yargs-parser: 20.2.9 devDependencies: '@babel/core': 7.15.5 @@ -168,6 +177,7 @@ importers: '@types/esprima': 4.0.3 '@types/lodash': 4.14.174 '@types/splitpanes': 2.2.1 + '@urql/devtools': 2.0.3_@urql+core@2.3.3+graphql@15.6.0 '@vue/runtime-dom': 3.2.19 '@vue/test-utils': 1.2.2 babel-core: 7.0.0-bridge.0_@babel+core@7.15.5 @@ -4592,6 +4602,27 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /@urql/core/2.3.3_graphql@15.6.0: + resolution: {integrity: sha512-Bi9mafTFu0O1XZmI7/HrEk12LHZW+Fs/V1FqSJoUDgYIhARIJW6cCh3Havy1dJJ0FETxYmmQQXPf6kst+IP2qQ==} + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + dependencies: + '@graphql-typed-document-node/core': 3.1.0_graphql@15.6.0 + graphql: 15.6.0 + wonka: 4.0.15 + dev: false + + /@urql/devtools/2.0.3_@urql+core@2.3.3+graphql@15.6.0: + resolution: {integrity: sha512-TktPLiBS9LcBPHD6qcnb8wqOVcg3Bx0iCtvQ80uPpfofwwBGJmqnQTjUdEFU6kwaLOFZULQ9+Uo4831G823mQw==} + peerDependencies: + '@urql/core': '>= 1.14.0' + graphql: '>= 0.11.0' + dependencies: + '@urql/core': 2.3.3_graphql@15.6.0 + graphql: 15.6.0 + wonka: 4.0.15 + dev: true + /@vue/babel-helper-vue-jsx-merge-props/1.2.1: resolution: {integrity: sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==} dev: false @@ -10432,7 +10463,6 @@ packages: fp-ts: ^2.5.0 dependencies: fp-ts: 2.11.3 - dev: true /ip/1.1.5: resolution: {integrity: sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=} @@ -17362,12 +17392,13 @@ packages: deprecated: Sorry but vue-analytics is no longer maintained. I would suggest you switch to vue-gtag, with love, the guy who made the package. dev: true - /vue-apollo/3.0.8: + /vue-apollo/3.0.8_graphql-tag@2.12.5: resolution: {integrity: sha512-RnkC75PMoGwl1sdZdVO3R9P51wqmgOVi4QmljkBaTzlVThVlqfkJhrBcPiw2K9EohvSagvZclNqXktyOCcXbBA==} peerDependencies: graphql-tag: ^2 dependencies: chalk: 2.4.2 + graphql-tag: 2.12.5_graphql@15.6.0 serialize-javascript: 4.0.0 throttle-debounce: 2.3.0 dev: false @@ -17865,6 +17896,9 @@ packages: babel-walk: 3.0.0-canary-5 optional: true + /wonka/4.0.15: + resolution: {integrity: sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==} + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} From ffb0c12c08bab6ec9ae1a3eb5e28cab8e28e417b Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Fri, 1 Oct 2021 15:13:48 +0530 Subject: [PATCH 02/17] 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, + } + ) From 93f55f561943460f14201de9aacd0121cfdcb6d1 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Sat, 2 Oct 2021 13:22:07 +0530 Subject: [PATCH 03/17] feat: init profile page --- .../hoppscotch-app/assets/icons/verified.svg | 1 + .../hoppscotch-app/components/app/Header.vue | 6 + .../hoppscotch-app/components/teams/index.vue | 15 +- packages/hoppscotch-app/locales/en.json | 1 + packages/hoppscotch-app/pages/profile.vue | 138 ++++++++++++++++++ packages/hoppscotch-app/pages/settings.vue | 114 +-------------- 6 files changed, 149 insertions(+), 126 deletions(-) create mode 100644 packages/hoppscotch-app/assets/icons/verified.svg create mode 100644 packages/hoppscotch-app/pages/profile.vue diff --git a/packages/hoppscotch-app/assets/icons/verified.svg b/packages/hoppscotch-app/assets/icons/verified.svg new file mode 100644 index 000000000..2a2c4e926 --- /dev/null +++ b/packages/hoppscotch-app/assets/icons/verified.svg @@ -0,0 +1 @@ + diff --git a/packages/hoppscotch-app/components/app/Header.vue b/packages/hoppscotch-app/components/app/Header.vue index 283e58b48..04804d63f 100644 --- a/packages/hoppscotch-app/components/app/Header.vue +++ b/packages/hoppscotch-app/components/app/Header.vue @@ -69,6 +69,12 @@ svg="user" /> + -

- {{ $t("team.title") }} -

-
- -
-
+
+
+
+
+
+ +
+
+
+ + +
+ +

+ {{ currentUser.email || $t("state.nothing_found") }} + +

+
+
+ + +
+

+ {{ $t("settings.sync") }} +

+
+ {{ $t("settings.sync_description") }} +
+
+
+ + {{ $t("settings.sync_collections") }} + +
+
+ + {{ $t("settings.sync_environments") }} + +
+
+ + {{ $t("settings.sync_history") }} + +
+
+
+
+ + + + + +
+
+
+ +
+
+ + + diff --git a/packages/hoppscotch-app/pages/settings.vue b/packages/hoppscotch-app/pages/settings.vue index 27bad3436..3229a1ff2 100644 --- a/packages/hoppscotch-app/pages/settings.vue +++ b/packages/hoppscotch-app/pages/settings.vue @@ -1,104 +1,6 @@ - diff --git a/packages/hoppscotch-app/helpers/fb/auth.ts b/packages/hoppscotch-app/helpers/fb/auth.ts index d6f24b20c..6ccabf4f4 100644 --- a/packages/hoppscotch-app/helpers/fb/auth.ts +++ b/packages/hoppscotch-app/helpers/fb/auth.ts @@ -95,18 +95,16 @@ export function initAuth() { probableUser$.next(JSON.parse(getLocalConfig("login_state") ?? "null")) - currentUser$.subscribe((user) => { + onAuthStateChanged(auth, (user) => { + /** Whether the user was logged in before */ + const wasLoggedIn = currentUser$.value !== null + if (user) { probableUser$.next(user) } else { probableUser$.next(null) removeLocalConfig("login_state") } - }) - - onAuthStateChanged(auth, (user) => { - /** Whether the user was logged in before */ - const wasLoggedIn = currentUser$.value !== null if (!user && extraSnapshotStop) { extraSnapshotStop() From f73c8a45d97917ac70b35c30869cff4074874ef0 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 5 Oct 2021 22:17:29 +0530 Subject: [PATCH 14/17] chore(ui): minor ui improvements, empty teams state --- packages/hoppscotch-app/components/app/Header.vue | 10 +++------- packages/hoppscotch-app/components/teams/Modal.vue | 2 +- packages/hoppscotch-app/components/teams/index.vue | 14 +++++++++++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/hoppscotch-app/components/app/Header.vue b/packages/hoppscotch-app/components/app/Header.vue index ba2829831..d8556738f 100644 --- a/packages/hoppscotch-app/components/app/Header.vue +++ b/packages/hoppscotch-app/components/app/Header.vue @@ -54,10 +54,10 @@ :label="$t('team.invite')" svg="user-plus" class=" - !bg-green-400 + !bg-green-500 !text-green-500 !bg-opacity-10 - !hover:bg-opacity-10 !hover:text-green-600 !hover:bg-green-600 + !hover:bg-opacity-10 !hover:text-green-400 !hover:bg-green-400 " @click.native="showTeamsModal = true" /> @@ -110,11 +110,7 @@ diff --git a/packages/hoppscotch-app/components/teams/Team.vue b/packages/hoppscotch-app/components/teams/Team.vue index 49d37e695..33bd27105 100644 --- a/packages/hoppscotch-app/components/teams/Team.vue +++ b/packages/hoppscotch-app/components/teams/Team.vue @@ -21,6 +21,18 @@
+ @@ -87,6 +100,7 @@ defineProps<{ const showModalAdd = ref(false) const showModalEdit = ref(false) +const showModalInvite = ref(false) const editingTeam = ref({}) // TODO: Check this out const editingTeamID = ref("") @@ -109,12 +123,25 @@ const displayModalEdit = (shouldDisplay: boolean) => { if (!shouldDisplay) resetSelectedData() } + +const displayModalInvite = (shouldDisplay: boolean) => { + showModalInvite.value = shouldDisplay + + if (!shouldDisplay) resetSelectedData() +} + const editTeam = (team: any, teamID: any) => { editingTeam.value = team editingTeamID.value = teamID displayModalEdit(true) } +const inviteTeam = (team: any, teamID: any) => { + editingTeam.value = team + editingTeamID.value = teamID + displayModalInvite(true) +} + const resetSelectedData = () => { editingTeam.value = undefined editingTeamID.value = undefined