feat: copyable invite links (#4153)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
Akash K
2024-06-28 21:18:10 +05:30
committed by GitHub
parent 0c06f26893
commit 2917d50c6a
7 changed files with 119 additions and 4 deletions

View File

@@ -1009,7 +1009,10 @@
"success_invites": "Success invites",
"title": "Workspaces",
"we_sent_invite_link": "We sent an invite link to all invitees!",
"invite_sent_smtp_disabled": "Invite links generated",
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace.",
"invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.",
"copy_invite_link": "Copy Invite Link",
"search_title": "Team Requests"
},
"team_environment": {

View File

@@ -10,10 +10,18 @@
<div class="mb-8 flex max-w-md flex-col items-center justify-center">
<icon-lucide-users class="h-6 w-6 text-accent" />
<h3 class="my-2 text-center text-lg">
{{ t("team.we_sent_invite_link") }}
{{
inviteMethod === "email"
? t("team.we_sent_invite_link")
: t("team.invite_sent_smtp_disabled")
}}
</h3>
<p class="text-center">
{{ t("team.we_sent_invite_link_description") }}
{{
inviteMethod === "email"
? t("team.we_sent_invite_link_description")
: t("team.invite_sent_smtp_disabled_description")
}}
</p>
</div>
<div v-if="successInvites.length">
@@ -33,6 +41,20 @@
class="svg-icons mr-4 text-green-500"
/>
<span class="truncate">{{ invitee.email }}</span>
<span class="flex items-center gap-1 ml-auto">
<HoppButtonSecondary
outline
filled
:icon="getCopyIcon(invitee.invitationID).value"
class="rounded-md"
:label="t('team.copy_invite_link')"
@click="
() => {
copyInviteLink(invitee.invitationID)
}
"
/>
</span>
</p>
</div>
</div>
@@ -107,6 +129,20 @@
:value="invitee.inviteeRole"
readonly
/>
<div class="flex">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
outline
:icon="getCopyIcon(invitee.id).value"
class="rounded-md"
:title="t('team.copy_invite_link')"
@click="
() => {
copyInviteLink(invitee.id)
}
"
/>
</div>
<div class="flex">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
@@ -352,7 +388,7 @@
</template>
<script setup lang="ts">
import { watch, ref, reactive, computed } from "vue"
import { watch, ref, reactive, computed, Ref, onMounted } from "vue"
import * as T from "fp-ts/Task"
import * as E from "fp-ts/Either"
import * as A from "fp-ts/Array"
@@ -386,7 +422,24 @@ import IconMailCheck from "~icons/lucide/mail-check"
import IconCircleDot from "~icons/lucide/circle-dot"
import IconCircle from "~icons/lucide/circle"
import IconArrowLeft from "~icons/lucide/arrow-left"
import IconCopy from "~icons/lucide/copy"
import IconCheck from "~icons/lucide/check"
import { TippyComponent } from "vue-tippy"
import { refAutoReset } from "@vueuse/core"
import { copyToClipboard } from "~/helpers/utils/clipboard"
import { platform } from "~/platform"
const copyIcons: Record<string, Ref<typeof IconCopy | typeof IconCheck>> = {}
const getCopyIcon = (id: string) => {
if (!copyIcons[id]) {
copyIcons[id] = refAutoReset<typeof IconCopy | typeof IconCheck>(
IconCopy,
1000
)
}
return copyIcons[id]
}
const t = useI18n()
@@ -406,6 +459,20 @@ const emit = defineEmits<{
(e: "hide-modal"): void
}>()
const inviteMethod = ref<"email" | "link">("email")
onMounted(async () => {
const getIsSMTPEnabled = platform.infra?.getIsSMTPEnabled
if (getIsSMTPEnabled) {
const res = await getIsSMTPEnabled()
if (E.isRight(res)) {
inviteMethod.value = res.right ? "email" : "link"
}
}
})
const pendingInvites = useGQLQuery<
GetPendingInvitesQuery,
GetPendingInvitesQueryVariables,
@@ -496,6 +563,14 @@ const removeNewInvitee = (id: number) => {
newInvites.value.splice(id, 1)
}
const copyInviteLink = (invitationID: string) => {
copyToClipboard(
`${import.meta.env.VITE_BASE_URL}/join-team?id=${invitationID}`
)
getCopyIcon(invitationID).value = IconCheck
}
type SendInvitesErrorType =
| {
email: Email
@@ -505,6 +580,7 @@ type SendInvitesErrorType =
| {
email: Email
status: "success"
invitationID: string
}
const sendInvitesResult = ref<Array<SendInvitesErrorType>>([])
@@ -555,9 +631,10 @@ const sendInvites = async () => {
email: newInvites.value[i].key as Email,
error: err,
}),
() => ({
(invitation) => ({
status: "success" as const,
email: newInvites.value[i].key as Email,
invitationID: invitation.id,
})
)
)

View File

@@ -11,6 +11,7 @@ import { InspectorsPlatformDef } from "./inspectors"
import { ServiceClassInstance } from "dioc"
import { IOPlatformDef } from "./io"
import { SpotlightPlatformDef } from "./spotlight"
import { InfraPlatformDef } from "./infra"
import { Ref } from "vue"
export type PlatformDef = {
@@ -52,6 +53,7 @@ export type PlatformDef = {
*/
workspaceSwitcherLogin?: Ref<boolean>
}
infra?: InfraPlatformDef
}
export let platform: PlatformDef

View File

@@ -0,0 +1,5 @@
import * as E from "fp-ts/Either"
export type InfraPlatformDef = {
getIsSMTPEnabled?: () => Promise<E.Either<string, boolean>>
}

View File

@@ -0,0 +1,3 @@
query GetSMTPStatus {
isSMTPEnabled
}

View File

@@ -11,6 +11,7 @@ import { ExtensionInterceptorService } from "@hoppscotch/common/platform/std/int
import { stdFooterItems } from "@hoppscotch/common/platform/std/ui/footerItem"
import { stdSupportOptionItems } from "@hoppscotch/common/platform/std/ui/supportOptionsItem"
import { browserIODef } from "@hoppscotch/common/platform/std/io"
import { InfraPlatform } from "@platform/infra/infra.platform"
createHoppApp("#app", {
ui: {
@@ -40,4 +41,5 @@ createHoppApp("#app", {
exportAsGIST: false,
hasTelemetry: false,
},
infra: InfraPlatform,
})

View File

@@ -0,0 +1,23 @@
import { runGQLQuery } from "@hoppscotch/common/helpers/backend/GQLClient"
import { InfraPlatformDef } from "@hoppscotch/common/platform/infra"
import { GetSmtpStatusDocument } from "../../api/generated/graphql"
import * as E from "fp-ts/Either"
const getSMTPStatus = () => {
return runGQLQuery({
query: GetSmtpStatusDocument,
variables: {},
})
}
export const InfraPlatform: InfraPlatformDef = {
getIsSMTPEnabled: async () => {
const res = await getSMTPStatus()
if (E.isRight(res)) {
return E.right(res.right.isSMTPEnabled)
}
return E.left("SMTP_STATUS_FETCH_FAILED")
},
}