feat: init invitation wiring
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
{{ $t("request.name") }}
|
||||
</label>
|
||||
</div>
|
||||
<label class="px-4 pt-4 pb-4">
|
||||
<label class="p-4">
|
||||
{{ $t("collection.select_location") }}
|
||||
</label>
|
||||
<CollectionsGraphql
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
/>
|
||||
</tippy>
|
||||
<div class="flex flex-1 justify-between">
|
||||
<label for="generatedCode" class="px-4 pt-4 pb-4">
|
||||
<label for="generatedCode" class="p-4">
|
||||
{{ t("request.generated_code") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ $t("action.label") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-between items-center">
|
||||
<div class="flex pt-4 flex-1 justify-between items-center">
|
||||
<label for="memberList" class="p-4">
|
||||
{{ $t("team.members") }}
|
||||
</label>
|
||||
@@ -25,6 +25,7 @@
|
||||
<ButtonSecondary
|
||||
svg="user-plus"
|
||||
:label="$t('team.invite')"
|
||||
filled
|
||||
@click.native="
|
||||
() => {
|
||||
$emit('invite-team')
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #body>
|
||||
<div class="flex flex-col px-2">
|
||||
<div class="flex flex-1 justify-between items-center">
|
||||
<label for="memberList" class="p-4">
|
||||
<label for="memberList" class="pb-4 px-4">
|
||||
{{ $t("team.pending_invites") }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -93,7 +93,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-between items-center">
|
||||
<div class="flex pt-4 flex-1 justify-between items-center">
|
||||
<label for="memberList" class="p-4">
|
||||
{{ $t("team.invite_tooltip") }}
|
||||
</label>
|
||||
@@ -101,6 +101,7 @@
|
||||
<ButtonSecondary
|
||||
svg="plus"
|
||||
:label="$t('add.new')"
|
||||
filled
|
||||
@click.native="addNewInvitee"
|
||||
/>
|
||||
</div>
|
||||
@@ -182,9 +183,9 @@
|
||||
justify-center
|
||||
"
|
||||
>
|
||||
<SmartIcon class="opacity-75 pb-2" name="users" />
|
||||
<SmartIcon class="opacity-75 pb-2" name="user-plus" />
|
||||
<span class="text-center pb-4">
|
||||
{{ $t("empty.members") }}
|
||||
{{ $t("empty.invites") }}
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
:label="$t('add.new')"
|
||||
@@ -208,8 +209,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, ref, reactive } from "@nuxtjs/composition-api"
|
||||
import { watch, ref, reactive, useContext } from "@nuxtjs/composition-api"
|
||||
import * as T from "fp-ts/Task"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import { Email, EmailCodec } from "../../helpers/backend/types/Email"
|
||||
import { TeamMemberRole } from "../../helpers/backend/graphql"
|
||||
import {
|
||||
createTeamInvitation,
|
||||
revokeTeamInvitation,
|
||||
} from "../../helpers/backend/mutations/TeamInvitation"
|
||||
import { useGQLQuery } from "~/helpers/backend/GQLClient"
|
||||
import {
|
||||
GetPendingInvitesDocument,
|
||||
@@ -217,6 +228,12 @@ import {
|
||||
GetPendingInvitesQueryVariables,
|
||||
} from "~/helpers/backend/graphql"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
app: { i18n },
|
||||
} = useContext()
|
||||
const t = i18n.t.bind(i18n)
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
editingteamID: { type: String, default: null },
|
||||
@@ -256,20 +273,29 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const removeInvitee = (id: string) => {
|
||||
console.log(id)
|
||||
const removeInvitee = async (id: string) => {
|
||||
const result = await revokeTeamInvitation(id)()
|
||||
if (E.isLeft(result)) {
|
||||
$toast.error(`${t("error.something_went_wrong")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
} else {
|
||||
$toast.success(`${t("team.member_removed")}`, {
|
||||
icon: "person",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const newInvites = ref([])
|
||||
const newInvites = ref<Array<{ key: string; value: TeamMemberRole }>>([])
|
||||
|
||||
const addNewInvitee = () => {
|
||||
newInvites.value.push({
|
||||
key: "",
|
||||
value: "",
|
||||
value: TeamMemberRole.Viewer,
|
||||
})
|
||||
}
|
||||
|
||||
const updateNewInviteeRole = (index: number, role: string) => {
|
||||
const updateNewInviteeRole = (index: number, role: TeamMemberRole) => {
|
||||
newInvites.value[index].value = role
|
||||
}
|
||||
|
||||
@@ -277,8 +303,61 @@ const removeNewInvitee = (id: number) => {
|
||||
newInvites.value.splice(id, 1)
|
||||
}
|
||||
|
||||
const sendInvites = () => {
|
||||
console.log(newInvites.value)
|
||||
const result = ref<
|
||||
Array<{
|
||||
email: Email
|
||||
status: "error" | "success"
|
||||
}>
|
||||
>([])
|
||||
|
||||
const sendInvites = async () => {
|
||||
const validationResult = pipe(
|
||||
newInvites.value,
|
||||
O.fromPredicate(
|
||||
(invites): invites is Array<{ key: Email; value: TeamMemberRole }> =>
|
||||
pipe(
|
||||
invites,
|
||||
A.every((invitee) => EmailCodec.is(invitee.key))
|
||||
)
|
||||
),
|
||||
O.map(
|
||||
A.map((invitee) =>
|
||||
createTeamInvitation(invitee.key, invitee.value, props.editingteamID)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if (O.isNone(validationResult)) {
|
||||
// Error handling for no validation
|
||||
$toast.error(`${t("error.incorrect_email")}`, {
|
||||
icon: "error_outline",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
result.value = await pipe(
|
||||
A.sequence(T.task)(validationResult.value),
|
||||
T.chain(
|
||||
flow(
|
||||
A.mapWithIndex((i, el) =>
|
||||
pipe(
|
||||
el,
|
||||
E.foldW(
|
||||
() => ({
|
||||
status: "error" as const,
|
||||
email: newInvites.value[i].key as Email,
|
||||
}),
|
||||
() => ({
|
||||
status: "success" as const,
|
||||
email: newInvites.value[i].key as Email,
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
T.of
|
||||
)
|
||||
)
|
||||
)()
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!compact" class="flex items-center justify-between">
|
||||
<div v-if="!compact" class="flex flex-shrink-0 items-end justify-between">
|
||||
<span>
|
||||
<ButtonSecondary
|
||||
v-if="team.myRole === 'OWNER'"
|
||||
|
||||
@@ -1,32 +1,52 @@
|
||||
import { runMutation } from "../GQLClient";
|
||||
import { AcceptTeamInvitationDocument, AcceptTeamInvitationMutation, AcceptTeamInvitationMutationVariables, CreateTeamInvitationDocument, CreateTeamInvitationMutation, CreateTeamInvitationMutationVariables, RevokeTeamInvitationDocument, RevokeTeamInvitationMutation, RevokeTeamInvitationMutationVariables, TeamMemberRole } from "../graphql";
|
||||
import { Email } from "../types/Email";
|
||||
import { pipe } from "fp-ts/function";
|
||||
import * as TE from "fp-ts/TaskEither";
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { runMutation } from "../GQLClient"
|
||||
import {
|
||||
AcceptTeamInvitationDocument,
|
||||
AcceptTeamInvitationMutation,
|
||||
AcceptTeamInvitationMutationVariables,
|
||||
CreateTeamInvitationDocument,
|
||||
CreateTeamInvitationMutation,
|
||||
CreateTeamInvitationMutationVariables,
|
||||
RevokeTeamInvitationDocument,
|
||||
RevokeTeamInvitationMutation,
|
||||
RevokeTeamInvitationMutationVariables,
|
||||
TeamMemberRole,
|
||||
} from "../graphql"
|
||||
import { Email } from "../types/Email"
|
||||
|
||||
type CreateTeamInvitationErrors
|
||||
= "invalid/email" | "team/invalid_id" | "team/member_not_found" | "team_invite/already_member" | "team_invite/member_has_invite"
|
||||
type CreateTeamInvitationErrors =
|
||||
| "invalid/email"
|
||||
| "team/invalid_id"
|
||||
| "team/member_not_found"
|
||||
| "team_invite/already_member"
|
||||
| "team_invite/member_has_invite"
|
||||
|
||||
type RevokeTeamInvitationErrors
|
||||
= "team/not_required_role" | "team_invite/no_invite_found"
|
||||
type RevokeTeamInvitationErrors =
|
||||
| "team/not_required_role"
|
||||
| "team_invite/no_invite_found"
|
||||
|
||||
type AcceptTeamInvitationErrors
|
||||
= "team_invite/no_invite_found" | "team_invitee/not_invitee" | "team_invite/already_member" | "team_invite/email_do_not_match"
|
||||
type AcceptTeamInvitationErrors =
|
||||
| "team_invite/no_invite_found"
|
||||
| "team_invitee/not_invitee"
|
||||
| "team_invite/already_member"
|
||||
| "team_invite/email_do_not_match"
|
||||
|
||||
export const createTeamInvitation = (inviteeEmail: Email, inviteeRole: TeamMemberRole, teamID: string) =>
|
||||
export const createTeamInvitation = (
|
||||
inviteeEmail: Email,
|
||||
inviteeRole: TeamMemberRole,
|
||||
teamID: string
|
||||
) =>
|
||||
pipe(
|
||||
runMutation<
|
||||
CreateTeamInvitationMutation,
|
||||
CreateTeamInvitationMutationVariables,
|
||||
CreateTeamInvitationErrors
|
||||
>(
|
||||
CreateTeamInvitationDocument,
|
||||
{
|
||||
inviteeEmail,
|
||||
inviteeRole,
|
||||
teamID
|
||||
}
|
||||
),
|
||||
>(CreateTeamInvitationDocument, {
|
||||
inviteeEmail,
|
||||
inviteeRole,
|
||||
teamID,
|
||||
}),
|
||||
TE.map((x) => x.createTeamInvitation)
|
||||
)
|
||||
|
||||
@@ -35,21 +55,15 @@ export const revokeTeamInvitation = (inviteID: string) =>
|
||||
RevokeTeamInvitationMutation,
|
||||
RevokeTeamInvitationMutationVariables,
|
||||
RevokeTeamInvitationErrors
|
||||
>(
|
||||
RevokeTeamInvitationDocument,
|
||||
{
|
||||
inviteID
|
||||
}
|
||||
)
|
||||
>(RevokeTeamInvitationDocument, {
|
||||
inviteID,
|
||||
})
|
||||
|
||||
export const acceptTeamInvitation = (inviteID: string) =>
|
||||
runMutation<
|
||||
AcceptTeamInvitationMutation,
|
||||
AcceptTeamInvitationMutationVariables,
|
||||
AcceptTeamInvitationErrors
|
||||
>(
|
||||
AcceptTeamInvitationDocument,
|
||||
{
|
||||
inviteID
|
||||
}
|
||||
)
|
||||
>(AcceptTeamInvitationDocument, {
|
||||
inviteID,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as t from "io-ts"
|
||||
|
||||
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
const emailRegex =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
|
||||
interface EmailBrand {
|
||||
readonly Email: unique symbol
|
||||
@@ -12,4 +13,4 @@ export const EmailCodec = t.brand(
|
||||
"Email"
|
||||
)
|
||||
|
||||
export type Email = t.TypeOf<typeof EmailCodec>
|
||||
export type Email = t.TypeOf<typeof EmailCodec>
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
"folder": "Folder is empty",
|
||||
"headers": "This request does not have any headers",
|
||||
"history": "History is empty",
|
||||
"invites": "Invite list is empty",
|
||||
"members": "Team is empty",
|
||||
"parameters": "This request does not have any parameters",
|
||||
"pending_invites": "There are no pending invites for this team",
|
||||
@@ -167,6 +168,7 @@
|
||||
"empty_req_name": "Empty Request Name",
|
||||
"f12_details": "(F12 for details)",
|
||||
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
|
||||
"incorrect_email": "Incorrect email",
|
||||
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||
"network_fail": "Could not send request",
|
||||
"no_duration": "No duration",
|
||||
|
||||
Reference in New Issue
Block a user