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") }}
|
{{ $t("request.name") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label class="px-4 pt-4 pb-4">
|
<label class="p-4">
|
||||||
{{ $t("collection.select_location") }}
|
{{ $t("collection.select_location") }}
|
||||||
</label>
|
</label>
|
||||||
<CollectionsGraphql
|
<CollectionsGraphql
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
/>
|
/>
|
||||||
</tippy>
|
</tippy>
|
||||||
<div class="flex flex-1 justify-between">
|
<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") }}
|
{{ t("request.generated_code") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
{{ $t("action.label") }}
|
{{ $t("action.label") }}
|
||||||
</label>
|
</label>
|
||||||
</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">
|
<label for="memberList" class="p-4">
|
||||||
{{ $t("team.members") }}
|
{{ $t("team.members") }}
|
||||||
</label>
|
</label>
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
svg="user-plus"
|
svg="user-plus"
|
||||||
:label="$t('team.invite')"
|
:label="$t('team.invite')"
|
||||||
|
filled
|
||||||
@click.native="
|
@click.native="
|
||||||
() => {
|
() => {
|
||||||
$emit('invite-team')
|
$emit('invite-team')
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col px-2">
|
<div class="flex flex-col px-2">
|
||||||
<div class="flex flex-1 justify-between items-center">
|
<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") }}
|
{{ $t("team.pending_invites") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<label for="memberList" class="p-4">
|
||||||
{{ $t("team.invite_tooltip") }}
|
{{ $t("team.invite_tooltip") }}
|
||||||
</label>
|
</label>
|
||||||
@@ -101,6 +101,7 @@
|
|||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
svg="plus"
|
svg="plus"
|
||||||
:label="$t('add.new')"
|
:label="$t('add.new')"
|
||||||
|
filled
|
||||||
@click.native="addNewInvitee"
|
@click.native="addNewInvitee"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,9 +183,9 @@
|
|||||||
justify-center
|
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">
|
<span class="text-center pb-4">
|
||||||
{{ $t("empty.members") }}
|
{{ $t("empty.invites") }}
|
||||||
</span>
|
</span>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
:label="$t('add.new')"
|
:label="$t('add.new')"
|
||||||
@@ -208,8 +209,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 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 { useGQLQuery } from "~/helpers/backend/GQLClient"
|
||||||
import {
|
import {
|
||||||
GetPendingInvitesDocument,
|
GetPendingInvitesDocument,
|
||||||
@@ -217,6 +228,12 @@ import {
|
|||||||
GetPendingInvitesQueryVariables,
|
GetPendingInvitesQueryVariables,
|
||||||
} from "~/helpers/backend/graphql"
|
} from "~/helpers/backend/graphql"
|
||||||
|
|
||||||
|
const {
|
||||||
|
$toast,
|
||||||
|
app: { i18n },
|
||||||
|
} = useContext()
|
||||||
|
const t = i18n.t.bind(i18n)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
editingteamID: { type: String, default: null },
|
editingteamID: { type: String, default: null },
|
||||||
@@ -256,20 +273,29 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const removeInvitee = (id: string) => {
|
const removeInvitee = async (id: string) => {
|
||||||
console.log(id)
|
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 = () => {
|
const addNewInvitee = () => {
|
||||||
newInvites.value.push({
|
newInvites.value.push({
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: TeamMemberRole.Viewer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateNewInviteeRole = (index: number, role: string) => {
|
const updateNewInviteeRole = (index: number, role: TeamMemberRole) => {
|
||||||
newInvites.value[index].value = role
|
newInvites.value[index].value = role
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,8 +303,61 @@ const removeNewInvitee = (id: number) => {
|
|||||||
newInvites.value.splice(id, 1)
|
newInvites.value.splice(id, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendInvites = () => {
|
const result = ref<
|
||||||
console.log(newInvites.value)
|
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 = () => {
|
const hideModal = () => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
<span>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-if="team.myRole === 'OWNER'"
|
v-if="team.myRole === 'OWNER'"
|
||||||
|
|||||||
@@ -1,32 +1,52 @@
|
|||||||
import { runMutation } from "../GQLClient";
|
import { pipe } from "fp-ts/function"
|
||||||
import { AcceptTeamInvitationDocument, AcceptTeamInvitationMutation, AcceptTeamInvitationMutationVariables, CreateTeamInvitationDocument, CreateTeamInvitationMutation, CreateTeamInvitationMutationVariables, RevokeTeamInvitationDocument, RevokeTeamInvitationMutation, RevokeTeamInvitationMutationVariables, TeamMemberRole } from "../graphql";
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { Email } from "../types/Email";
|
import { runMutation } from "../GQLClient"
|
||||||
import { pipe } from "fp-ts/function";
|
import {
|
||||||
import * as TE from "fp-ts/TaskEither";
|
AcceptTeamInvitationDocument,
|
||||||
|
AcceptTeamInvitationMutation,
|
||||||
|
AcceptTeamInvitationMutationVariables,
|
||||||
|
CreateTeamInvitationDocument,
|
||||||
|
CreateTeamInvitationMutation,
|
||||||
|
CreateTeamInvitationMutationVariables,
|
||||||
|
RevokeTeamInvitationDocument,
|
||||||
|
RevokeTeamInvitationMutation,
|
||||||
|
RevokeTeamInvitationMutationVariables,
|
||||||
|
TeamMemberRole,
|
||||||
|
} from "../graphql"
|
||||||
|
import { Email } from "../types/Email"
|
||||||
|
|
||||||
type CreateTeamInvitationErrors
|
type CreateTeamInvitationErrors =
|
||||||
= "invalid/email" | "team/invalid_id" | "team/member_not_found" | "team_invite/already_member" | "team_invite/member_has_invite"
|
| "invalid/email"
|
||||||
|
| "team/invalid_id"
|
||||||
|
| "team/member_not_found"
|
||||||
|
| "team_invite/already_member"
|
||||||
|
| "team_invite/member_has_invite"
|
||||||
|
|
||||||
type RevokeTeamInvitationErrors
|
type RevokeTeamInvitationErrors =
|
||||||
= "team/not_required_role" | "team_invite/no_invite_found"
|
| "team/not_required_role"
|
||||||
|
| "team_invite/no_invite_found"
|
||||||
|
|
||||||
type AcceptTeamInvitationErrors
|
type AcceptTeamInvitationErrors =
|
||||||
= "team_invite/no_invite_found" | "team_invitee/not_invitee" | "team_invite/already_member" | "team_invite/email_do_not_match"
|
| "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(
|
pipe(
|
||||||
runMutation<
|
runMutation<
|
||||||
CreateTeamInvitationMutation,
|
CreateTeamInvitationMutation,
|
||||||
CreateTeamInvitationMutationVariables,
|
CreateTeamInvitationMutationVariables,
|
||||||
CreateTeamInvitationErrors
|
CreateTeamInvitationErrors
|
||||||
>(
|
>(CreateTeamInvitationDocument, {
|
||||||
CreateTeamInvitationDocument,
|
inviteeEmail,
|
||||||
{
|
inviteeRole,
|
||||||
inviteeEmail,
|
teamID,
|
||||||
inviteeRole,
|
}),
|
||||||
teamID
|
|
||||||
}
|
|
||||||
),
|
|
||||||
TE.map((x) => x.createTeamInvitation)
|
TE.map((x) => x.createTeamInvitation)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,21 +55,15 @@ export const revokeTeamInvitation = (inviteID: string) =>
|
|||||||
RevokeTeamInvitationMutation,
|
RevokeTeamInvitationMutation,
|
||||||
RevokeTeamInvitationMutationVariables,
|
RevokeTeamInvitationMutationVariables,
|
||||||
RevokeTeamInvitationErrors
|
RevokeTeamInvitationErrors
|
||||||
>(
|
>(RevokeTeamInvitationDocument, {
|
||||||
RevokeTeamInvitationDocument,
|
inviteID,
|
||||||
{
|
})
|
||||||
inviteID
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const acceptTeamInvitation = (inviteID: string) =>
|
export const acceptTeamInvitation = (inviteID: string) =>
|
||||||
runMutation<
|
runMutation<
|
||||||
AcceptTeamInvitationMutation,
|
AcceptTeamInvitationMutation,
|
||||||
AcceptTeamInvitationMutationVariables,
|
AcceptTeamInvitationMutationVariables,
|
||||||
AcceptTeamInvitationErrors
|
AcceptTeamInvitationErrors
|
||||||
>(
|
>(AcceptTeamInvitationDocument, {
|
||||||
AcceptTeamInvitationDocument,
|
inviteID,
|
||||||
{
|
})
|
||||||
inviteID
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as t from "io-ts"
|
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 {
|
interface EmailBrand {
|
||||||
readonly Email: unique symbol
|
readonly Email: unique symbol
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
"folder": "Folder is empty",
|
"folder": "Folder is empty",
|
||||||
"headers": "This request does not have any headers",
|
"headers": "This request does not have any headers",
|
||||||
"history": "History is empty",
|
"history": "History is empty",
|
||||||
|
"invites": "Invite list is empty",
|
||||||
"members": "Team is empty",
|
"members": "Team is empty",
|
||||||
"parameters": "This request does not have any parameters",
|
"parameters": "This request does not have any parameters",
|
||||||
"pending_invites": "There are no pending invites for this team",
|
"pending_invites": "There are no pending invites for this team",
|
||||||
@@ -167,6 +168,7 @@
|
|||||||
"empty_req_name": "Empty Request Name",
|
"empty_req_name": "Empty Request Name",
|
||||||
"f12_details": "(F12 for details)",
|
"f12_details": "(F12 for details)",
|
||||||
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
|
"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",
|
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||||
"network_fail": "Could not send request",
|
"network_fail": "Could not send request",
|
||||||
"no_duration": "No duration",
|
"no_duration": "No duration",
|
||||||
|
|||||||
Reference in New Issue
Block a user