Files
huly-platform/plugins/login-resources/src/components/InviteLink.svelte
T
Artyom SavchenkoandGitHub 48a28a0b81 Configure who should be able to send invitation link (#10555)
* Configure who should be able to send invitation link

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Add user role select

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Limit default user roles

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
2026-02-25 19:42:11 +07:00

245 lines
7.1 KiB
Svelte

<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { AccountRole, getCurrentAccount, hasAccountRole, Timestamp } from '@hcengineering/core'
import { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
import setting, { RoleCapability } from '@hcengineering/setting'
import { getResource } from '@hcengineering/platform'
import { AnySvelteComponent, Button, EditBox, Grid, Label, Loading, MiniToggle, ticker } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import login from '../plugin'
import { getInviteLink } from '../utils'
import InviteWorkspace from './icons/InviteWorkspace.svelte'
export let role: AccountRole | undefined = undefined
export let ignoreSettings: boolean = false
const dispatch = createEventDispatcher()
const query = createQuery()
const isSecureContext = window.isSecureContext
interface InviteParams {
expirationTime: number
emailMask: string
limit: number | undefined
}
$: !ignoreSettings &&
query.query(setting.class.InviteSettings, {}, (set) => {
if (set !== undefined && set.length > 0) {
expHours = set[0].expirationTime
emailMask = set[0].emailMask
limit = set[0].limit
if (role == null) {
role = set[0].defaultInviteRole ?? AccountRole.User
}
} else {
expHours = 48
limit = -1
if (role == null) {
role = AccountRole.User
}
}
if (limit === -1) noLimit = true
defaultValues = {
expirationTime: expHours,
emailMask,
limit
}
})
function setToDefault (): void {
expHours = defaultValues.expirationTime
emailMask = defaultValues.emailMask
limit = defaultValues.limit
}
async function getLink (expHours: number, mask: string, limit: number | undefined, role: AccountRole): Promise<void> {
loading = true
link = await getInviteLink(expHours, mask, limit ?? -1, role)
loading = false
}
let copiedTime: Timestamp | undefined
let copied = false
$: if (copiedTime !== undefined && copied && $ticker - copiedTime > 1000) {
copied = false
}
async function copy (): Promise<void> {
if (!isSecureContext) return
if (link === undefined) return
await copyTextToClipboard(link)
copied = true
copiedTime = Date.now()
}
let expHours: number = 48
let emailMask: string = ''
let limit: number | undefined = undefined
let useDefault: boolean | undefined = true
let noLimit: boolean = false
const currentAccount = getCurrentAccount()
const isOwnerOrMaintainer: boolean = hasAccountRole(currentAccount, AccountRole.Maintainer)
let userRoleSelectComponent: AnySvelteComponent | undefined
void getResource(setting.component.UserRoleSelect).then((component) => {
userRoleSelectComponent = component
})
let canGenerateInviteLinks = false
// Use shared HasRoleCapability function resource from setting package instead of setting-resources
void getResource(setting.function.HasRoleCapability).then((checkCapability) => {
if (checkCapability != null) {
void checkCapability(RoleCapability.GenerateInviteLink).then((value: boolean) => {
canGenerateInviteLinks = value
})
}
})
let defaultValues: InviteParams = {
expirationTime: 48,
emailMask: '',
limit: undefined
}
let link: string | undefined
let loading = false
function handleInviteRoleSelected (e: CustomEvent<AccountRole>): void {
role = e.detail
link = undefined
}
</script>
<div class="antiPopup popup" class:secure={isSecureContext}>
<div class="flex-between fs-title mb-9">
<Label label={login.string.InviteDescription} />
<InviteWorkspace size={'large'} />
</div>
{#if isOwnerOrMaintainer && !ignoreSettings}
<Grid column={1} rowGap={1.5}>
<MiniToggle
bind:on={useDefault}
label={login.string.UseWorkspaceInviteSettings}
on:click={() => {
setToDefault()
}}
/>
{#if !useDefault}
<EditBox
label={login.string.LinkValidHours}
bind:value={expHours}
format={'number'}
on:keypress={() => (link = undefined)}
disabled={useDefault || !isOwnerOrMaintainer}
/>
<MiniToggle bind:on={noLimit} label={login.string.NoLimit} on:change={() => noLimit && (limit = -1)} />
{#if !noLimit}
<EditBox
label={login.string.InviteLimit}
bind:value={limit}
format={'number'}
on:keypress={() => (link = undefined)}
disabled={useDefault || !isOwnerOrMaintainer}
/>
{/if}
{#if userRoleSelectComponent}
<svelte:component
this={userRoleSelectComponent}
selected={role ?? AccountRole.User}
on:selected={handleInviteRoleSelected}
/>
{/if}
{/if}
</Grid>
{/if}
{#if loading}
<Loading />
{:else if link !== undefined}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="link" class:notSecure={!isSecureContext} class:over-underline={isSecureContext} on:click={copy}>
{link}
</div>
<div class="buttons">
<Button
label={login.string.Close}
size={'medium'}
kind={'primary'}
on:click={() => {
dispatch('close')
}}
/>
{#if isSecureContext}
<Button label={copied ? login.string.Copied : login.string.Copy} size={'medium'} on:click={copy} />
{/if}
</div>
{:else}
<div class="buttons">
<Button
label={login.string.GetLink}
size={'medium'}
kind={'primary'}
disabled={!canGenerateInviteLinks}
on:click={() => {
if (!canGenerateInviteLinks) return
const effectiveLimit = limit ?? 0
if (effectiveLimit > 0 || noLimit) {
void getLink(expHours, emailMask, limit, role ?? AccountRole.User)
}
}}
/>
</div>
{/if}
</div>
<style lang="scss">
.popup {
display: flex;
flex-direction: column;
padding: 1.75rem;
width: 30rem;
max-width: 40rem;
background: var(--popup-bg-color);
border-radius: 1.25rem;
user-select: none;
box-shadow: var(--popup-shadow);
.link {
margin: 1.75rem 0 0;
overflow-wrap: break-word;
&.notSecure {
user-select: text;
}
}
.buttons {
margin-top: 1.75rem;
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: flex-start;
align-items: center;
column-gap: 0.5rem;
}
}
</style>