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:
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
5
packages/hoppscotch-common/src/platform/infra.ts
Normal file
5
packages/hoppscotch-common/src/platform/infra.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
export type InfraPlatformDef = {
|
||||
getIsSMTPEnabled?: () => Promise<E.Either<string, boolean>>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
query GetSMTPStatus {
|
||||
isSMTPEnabled
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user