|
|
|
@ -9,6 +9,78 @@ const inviteData = reactive({
|
|
|
|
|
roles: ProjectRoles.VIEWER, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const focusRef = ref<HTMLInputElement>() |
|
|
|
|
const isDivFocused = ref(false) |
|
|
|
|
const divRef = ref<HTMLDivElement>() |
|
|
|
|
|
|
|
|
|
const emailValidation = reactive({ |
|
|
|
|
isError: false, |
|
|
|
|
message: '', |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const validateEmail = (email: string): boolean => { |
|
|
|
|
const regEx = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |
|
|
|
|
return regEx.test(email) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// all user input emails are stored here |
|
|
|
|
const emailBadges = ref<Array<string>>([]) |
|
|
|
|
|
|
|
|
|
const insertOrUpdateString = (str: string) => { |
|
|
|
|
// Check if the string already exists in the array |
|
|
|
|
const index = emailBadges.value.indexOf(str) |
|
|
|
|
|
|
|
|
|
if (index !== -1) { |
|
|
|
|
// If the string exists, remove it |
|
|
|
|
emailBadges.value.splice(index, 1) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Add the new string to the array |
|
|
|
|
emailBadges.value.push(str) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
watch(inviteData, (newVal) => { |
|
|
|
|
const isNewEmail = newVal.email.charAt(newVal.email.length - 1) === ',' || newVal.email.charAt(newVal.email.length - 1) === ' ' |
|
|
|
|
if (isNewEmail && newVal.email.trim().length > 1) { |
|
|
|
|
const emailToAdd = newVal.email.split(',')[0].trim() || newVal.email.split(' ')[0].trim() |
|
|
|
|
if (!validateEmail(emailToAdd)) { |
|
|
|
|
emailValidation.isError = true |
|
|
|
|
emailValidation.message = 'INVALID EMAIL' |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
/** |
|
|
|
|
if email is already enterd we delete the already |
|
|
|
|
existing email and add new one |
|
|
|
|
**/ |
|
|
|
|
if (emailBadges.value.includes(emailToAdd)) { |
|
|
|
|
insertOrUpdateString(emailToAdd) |
|
|
|
|
inviteData.email = '' |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
emailBadges.value.push(emailToAdd) |
|
|
|
|
inviteData.email = '' |
|
|
|
|
} |
|
|
|
|
if (newVal.email.length < 1 && emailValidation.isError) { |
|
|
|
|
emailValidation.isError = false |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const handleEnter = () => { |
|
|
|
|
if (inviteData.email.length < 1) { |
|
|
|
|
emailValidation.isError = true |
|
|
|
|
emailValidation.message = 'EMAIL SHOULD NOT BE EMPTY' |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if (!validateEmail(inviteData.email.trim())) { |
|
|
|
|
emailValidation.isError = true |
|
|
|
|
emailValidation.message = 'INVALID EMAIL' |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
inviteData.email += ' ' |
|
|
|
|
emailValidation.isError = false |
|
|
|
|
emailValidation.message = '' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const { dashboardUrl } = useDashboard() |
|
|
|
|
|
|
|
|
|
const { inviteUser } = useManageUsers() |
|
|
|
@ -31,19 +103,29 @@ const inviteCollaborator = async () => {
|
|
|
|
|
isInvitingCollaborators.value = true |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
emailBadges.value.forEach((el, index) => { |
|
|
|
|
// prevent the last email from getting the "," |
|
|
|
|
if (index === emailBadges.value.length - 1) { |
|
|
|
|
inviteData.email += el |
|
|
|
|
} else { |
|
|
|
|
inviteData.email += `${el},` |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
usersData.value = await inviteUser(inviteData) |
|
|
|
|
usersData.roles = inviteData.roles |
|
|
|
|
if (usersData.value) { |
|
|
|
|
message.success('Invitation sent successfully') |
|
|
|
|
inviteData.email = '' |
|
|
|
|
emailBadges.value = [] |
|
|
|
|
emit('invited') |
|
|
|
|
} |
|
|
|
|
} catch (e: any) { |
|
|
|
|
message.error(await extractSdkResponseErrorMsg(e)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} finally { |
|
|
|
|
inviteData.email = '' |
|
|
|
|
isInvitingCollaborators.value = false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const inviteUrl = computed(() => |
|
|
|
|
usersData.value?.invite_token ? `${dashboardUrl.value}#/signup/${usersData.value.invite_token}` : null, |
|
|
|
@ -76,11 +158,60 @@ const copyUrl = async () => {
|
|
|
|
|
|
|
|
|
|
// Copied shareable base url to clipboard! |
|
|
|
|
message.success(t('msg.success.shareableURLCopied')) |
|
|
|
|
inviteData.email = '' |
|
|
|
|
emailBadges.value = [] |
|
|
|
|
} catch (e: any) { |
|
|
|
|
message.error(e.message) |
|
|
|
|
} |
|
|
|
|
$e('c:shared-base:copy-url') |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const focusOnDiv = () => { |
|
|
|
|
focusRef.value?.focus() |
|
|
|
|
isDivFocused.value = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// remove one email per backspace |
|
|
|
|
onKeyStroke('Backspace', () => { |
|
|
|
|
if (isDivFocused.value && inviteData.email.length < 1) { |
|
|
|
|
emailBadges.value.pop() |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// when bulk email is pasted |
|
|
|
|
const onPaste = (e: ClipboardEvent) => { |
|
|
|
|
const pastedText = e.clipboardData?.getData('text') |
|
|
|
|
const inputArray = pastedText?.split(',') || pastedText?.split(' ') |
|
|
|
|
// if data is pasted to a already existing text in input |
|
|
|
|
// we add existingInput + pasted data |
|
|
|
|
if (inputArray?.length === 1 && inviteData.email.length > 1) { |
|
|
|
|
inputArray[0] = inviteData.email += inputArray[0] |
|
|
|
|
} |
|
|
|
|
inputArray?.forEach((el) => { |
|
|
|
|
if (el.length < 1) { |
|
|
|
|
emailValidation.isError = true |
|
|
|
|
emailValidation.message = 'EMAIL SHOULD NOT BE EMPTY' |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if (!validateEmail(el.trim())) { |
|
|
|
|
emailValidation.isError = true |
|
|
|
|
emailValidation.message = 'INVALID EMAIL' |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
/** |
|
|
|
|
if email is already enterd we delete the already |
|
|
|
|
existing email and add new one |
|
|
|
|
**/ |
|
|
|
|
if (emailBadges.value.includes(el)) { |
|
|
|
|
insertOrUpdateString(el) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
emailBadges.value.push(el) |
|
|
|
|
inviteData.email = '' |
|
|
|
|
}) |
|
|
|
|
inviteData.email = '' |
|
|
|
|
} |
|
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
<template> |
|
|
|
@ -128,12 +259,44 @@ const copyUrl = async () => {
|
|
|
|
|
<div class="text-xl mb-4">Invite</div> |
|
|
|
|
<a-form> |
|
|
|
|
<div class="flex gap-2"> |
|
|
|
|
<a-input |
|
|
|
|
<div class="flex flex-col"> |
|
|
|
|
<div |
|
|
|
|
ref="divRef" |
|
|
|
|
class="flex w-130 border-1 gap-1 items-center min-h-8 flex-wrap max-h-30 overflow-y-scroll rounded-lg nc-scrollbar-md" |
|
|
|
|
tabindex="0" |
|
|
|
|
:class="{ |
|
|
|
|
'border-primary/100': isDivFocused, |
|
|
|
|
'p-1': emailBadges.length > 1, |
|
|
|
|
}" |
|
|
|
|
@click="focusOnDiv" |
|
|
|
|
@blur="isDivFocused = false" |
|
|
|
|
> |
|
|
|
|
<span |
|
|
|
|
v-for="(email, index) in emailBadges" |
|
|
|
|
:key="email" |
|
|
|
|
class="text-[14px] border-1 text-brand-500 bg-brand-50 rounded-md ml-1 p-0.5" |
|
|
|
|
> |
|
|
|
|
{{ email }} |
|
|
|
|
<component |
|
|
|
|
:is="iconMap.close" |
|
|
|
|
class="ml-0.5 hover:cursor-pointer w-3.5 h-3.5" |
|
|
|
|
@click="emailBadges.splice(index, 1)" |
|
|
|
|
/> |
|
|
|
|
</span> |
|
|
|
|
<input |
|
|
|
|
id="email" |
|
|
|
|
v-model:value="inviteData.email" |
|
|
|
|
placeholder="Enter emails to send invitation" |
|
|
|
|
class="!max-w-130 !rounded" |
|
|
|
|
ref="focusRef" |
|
|
|
|
v-model="inviteData.email" |
|
|
|
|
:placeholder="emailBadges.length < 1 ? 'Enter emails to send invitation' : ''" |
|
|
|
|
class="min-w-50 !outline-0 !focus:outline-0 ml-2 mr-3" |
|
|
|
|
data-testid="email-input" |
|
|
|
|
@keyup.enter="handleEnter" |
|
|
|
|
@paste.prevent="onPaste" |
|
|
|
|
@blur="isDivFocused = false" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
<span v-if="emailValidation.isError" class="ml-2 text-red-500 text-[12px] mt-1">{{ emailValidation.message }}</span> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<RolesSelector |
|
|
|
|
class="px-1" |
|
|
|
@ -146,13 +309,13 @@ const copyUrl = async () => {
|
|
|
|
|
<a-button |
|
|
|
|
type="primary" |
|
|
|
|
class="!rounded-md" |
|
|
|
|
:disabled="!inviteData.email?.length || isInvitingCollaborators" |
|
|
|
|
:disabled="!emailBadges.length || isInvitingCollaborators || emailValidation.isError" |
|
|
|
|
@click="inviteCollaborator" |
|
|
|
|
> |
|
|
|
|
<div class="flex flex-row items-center gap-x-2 pr-1"> |
|
|
|
|
<GeneralLoader v-if="isInvitingCollaborators" class="flex" /> |
|
|
|
|
<MdiPlus v-else /> |
|
|
|
|
{{ isInvitingCollaborators ? 'Adding' : 'Add' }} User/s |
|
|
|
|
{{ isInvitingCollaborators ? 'Adding' : 'Add' }} User(s) |
|
|
|
|
</div> |
|
|
|
|
</a-button> |
|
|
|
|
</div> |
|
|
|
|