mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-02 11:06:55 +00:00
194 lines
6.0 KiB
Vue
194 lines
6.0 KiB
Vue
<script setup lang="ts">
|
|
import type { VNodeRef } from '@vue/runtime-core'
|
|
import type { OrgUserReqType } from 'nocodb-sdk'
|
|
import { OrgUserRoles } from 'nocodb-sdk'
|
|
import { extractEmail } from '~/helpers/parsers/parserHelpers'
|
|
|
|
interface Props {
|
|
show: boolean
|
|
selectedUser?: User
|
|
}
|
|
|
|
const { show } = defineProps<Props>()
|
|
|
|
const emit = defineEmits(['closed', 'reload'])
|
|
|
|
const { t } = useI18n()
|
|
|
|
const { $api, $e } = useNuxtApp()
|
|
|
|
const { dashboardUrl } = useDashboard()
|
|
|
|
const { clearBasesUser } = useBases()
|
|
|
|
const usersData = ref<Users>({ emails: '', role: OrgUserRoles.VIEWER, invitationToken: undefined })
|
|
|
|
const formRef = ref()
|
|
|
|
const useForm = Form.useForm
|
|
|
|
const validators = computed(() => {
|
|
return {
|
|
emails: [emailValidator],
|
|
}
|
|
})
|
|
|
|
const { validateInfos } = useForm(usersData.value, validators)
|
|
|
|
const saveUser = async () => {
|
|
$e('a:org-user:invite', { role: usersData.value.role })
|
|
|
|
await formRef.value?.validateFields()
|
|
|
|
try {
|
|
const res = await $api.orgUsers.add({
|
|
roles: usersData.value.role,
|
|
email: usersData.value.emails,
|
|
} as unknown as OrgUserReqType)
|
|
|
|
usersData.value.invitationToken = res.invite_token
|
|
emit('reload')
|
|
|
|
// Successfully updated the user details
|
|
message.success(t('msg.success.userAdded'))
|
|
|
|
clearBasesUser()
|
|
} catch (e: any) {
|
|
console.error(e)
|
|
message.error(await extractSdkResponseErrorMsg(e))
|
|
}
|
|
}
|
|
|
|
const inviteUrl = computed(() =>
|
|
usersData.value.invitationToken ? `${dashboardUrl.value}/signup/${usersData.value.invitationToken}` : null,
|
|
)
|
|
|
|
const clickInviteMore = () => {
|
|
$e('c:user:invite-more')
|
|
usersData.value.invitationToken = undefined
|
|
usersData.value.role = OrgUserRoles.VIEWER
|
|
usersData.value.emails = ''
|
|
}
|
|
|
|
const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
|
|
|
|
const onPaste = (e: ClipboardEvent) => {
|
|
const pastedText = e.clipboardData?.getData('text') ?? ''
|
|
|
|
usersData.value.emails = extractEmail(pastedText) || pastedText
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<a-modal
|
|
:class="{ active: show }"
|
|
:footer="null"
|
|
centered
|
|
:visible="show"
|
|
:closable="false"
|
|
width="max(50vw, 44rem)"
|
|
wrap-class-name="nc-modal-invite-user"
|
|
@cancel="emit('closed')"
|
|
>
|
|
<div class="flex flex-col">
|
|
<div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full">
|
|
<h4 class="select-none text-nc-content-gray text-subHeading1" data-rec="true">
|
|
{{ $t('activity.inviteUser') }}
|
|
</h4>
|
|
|
|
<a-button type="text" class="!rounded-md mr-1 -mt-1.5" @click="emit('closed')">
|
|
<template #icon>
|
|
<MaterialSymbolsCloseRounded data-testid="nc-root-user-invite-modal-close" class="flex mx-auto" />
|
|
</template>
|
|
</a-button>
|
|
</div>
|
|
|
|
<div class="px-2 mt-1.5">
|
|
<template v-if="usersData.invitationToken">
|
|
<div class="flex flex-col mt-1 pb-5">
|
|
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
|
|
<component :is="iconMap.account" />
|
|
<div class="text-xs ml-0.5 mt-0.5" data-rec="true">{{ $t('activity.copyInviteURL') }}</div>
|
|
</div>
|
|
|
|
<NcAlert
|
|
type="success"
|
|
:message="inviteUrl"
|
|
message-class="!text-green-700 !text-bodyDefaultSm"
|
|
background
|
|
:copy-text="inviteUrl"
|
|
:copy-text-toast-message="$t('msg.toast.inviteUrlCopy')"
|
|
class="mt-2 !p-3"
|
|
/>
|
|
|
|
<div class="flex text-xs text-nc-content-gray-muted mt-2 justify-start ml-2" data-rec="true">
|
|
{{ $t('msg.info.userInviteNoSMTP') }}
|
|
{{ usersData.invitationToken && usersData.emails }}
|
|
</div>
|
|
|
|
<div class="flex flex-row justify-end mt-4 ml-2">
|
|
<NcButton size="small" type="secondary" text-color="primary" @click="clickInviteMore">
|
|
<div class="flex flex-row justify-center items-center space-x-0.5">
|
|
<MaterialSymbolsSendOutline class="flex mx-auto h-[0.8rem]" />
|
|
|
|
<div class="text-xs" data-rec="true">{{ $t('activity.inviteMore') }}</div>
|
|
</div>
|
|
</NcButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-else class="flex flex-col pb-4">
|
|
<div class="border-1 py-3 px-4 rounded-md mt-1">
|
|
<a-form
|
|
ref="formRef"
|
|
:validate-on-rule-change="false"
|
|
:model="usersData"
|
|
validate-trigger="onBlur"
|
|
@finish="saveUser"
|
|
>
|
|
<div class="flex flex-row space-x-4">
|
|
<div class="flex flex-col w-full">
|
|
<a-form-item
|
|
v-bind="validateInfos.emails"
|
|
validate-trigger="onBlur"
|
|
name="emails"
|
|
:rules="[{ required: true, message: $t('msg.plsInputEmail') }]"
|
|
>
|
|
<div class="ml-1 mb-1 text-xs text-nc-content-gray-muted" data-rec="true">{{ $t('datatype.Email') }}:</div>
|
|
|
|
<a-input
|
|
:ref="emailInput"
|
|
v-model:value="usersData.emails"
|
|
size="middle"
|
|
class="nc-input-sm"
|
|
validate-trigger="onBlur"
|
|
:placeholder="$t('labels.email')"
|
|
@paste.prevent="onPaste"
|
|
/>
|
|
</a-form-item>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-row justify-end">
|
|
<NcButton type="primary" size="small" html-type="submit">
|
|
<div class="flex flex-row justify-center items-center space-x-1.5">
|
|
<MaterialSymbolsSendOutline class="flex h-[0.8rem]" />
|
|
<div data-rec="true">{{ $t('activity.invite') }}</div>
|
|
</div>
|
|
</NcButton>
|
|
</div>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a-modal>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.nc-input-sm {
|
|
@apply !rounded-md;
|
|
}
|
|
</style>
|