From 73fa668681635ef5be57a3cae5053995c9bc36fa Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Fri, 11 Jul 2025 13:19:37 +0700 Subject: [PATCH] Readonly allow signup (#9517) Signed-off-by: Anton Alexeyev --- models/activity/src/index.ts | 7 +- models/chunter/src/index.ts | 5 + models/contact/src/index.ts | 12 ++ models/core/src/security.ts | 14 ++- models/core/src/spaceType.ts | 3 +- models/love/src/index.ts | 8 ++ models/notification/src/index.ts | 4 + models/workbench/src/index.ts | 10 +- packages/account-client/src/client.ts | 14 ++- packages/core/src/classes.ts | 6 + packages/core/src/component.ts | 7 +- packages/core/src/index.ts | 9 +- packages/platform/src/platform.ts | 1 + .../ui/src/components/internal/Root.svelte | 3 + plugins/chunter-resources/src/utils.ts | 6 + .../src/components/Join.svelte | 10 +- plugins/login-resources/src/utils.ts | 10 +- .../src/components/General.svelte | 10 +- plugins/view-assets/lang/cs.json | 7 +- plugins/view-assets/lang/de.json | 7 +- plugins/view-assets/lang/en.json | 7 +- plugins/view-assets/lang/es.json | 7 +- plugins/view-assets/lang/fr.json | 7 +- plugins/view-assets/lang/it.json | 7 +- plugins/view-assets/lang/ja.json | 7 +- plugins/view-assets/lang/pt.json | 7 +- plugins/view-assets/lang/ru.json | 7 +- plugins/view-assets/lang/zh.json | 7 +- .../components/ForbiddenNotification.svelte | 12 ++ .../components/ReadOnlyNotification.svelte | 9 +- plugins/view-resources/src/middleware.ts | 22 +++- plugins/view/src/index.ts | 3 + plugins/workbench-resources/src/connect.ts | 2 + server-plugins/contact-resources/src/index.ts | 52 ++++++++- .../notification-resources/src/index.ts | 5 +- .../account/src/__tests__/operations.test.ts | 5 +- server/account/src/operations.ts | 84 ++++++------- server/account/src/serviceOperations.ts | 6 +- server/account/src/types.ts | 6 + server/account/src/utils.ts | 98 ++++++++++++---- server/middleware/src/guestPermissions.ts | 110 ++++++++++++++++++ server/middleware/src/index.ts | 1 + server/middleware/src/spacePermissions.ts | 8 +- server/middleware/src/spaceSecurity.ts | 53 +++------ server/server-pipeline/src/pipeline.ts | 4 +- 45 files changed, 511 insertions(+), 178 deletions(-) create mode 100644 plugins/view-resources/src/components/ForbiddenNotification.svelte create mode 100644 server/middleware/src/guestPermissions.ts diff --git a/models/activity/src/index.ts b/models/activity/src/index.ts index 161f418666..01c603b0c9 100644 --- a/models/activity/src/index.ts +++ b/models/activity/src/index.ts @@ -48,7 +48,8 @@ import core, { type Blob, type Timestamp, type Tx, - type TxCUD + type TxCUD, + AccountRole } from '@hcengineering/core' import { ArrOf, @@ -277,6 +278,10 @@ export function createModel (builder: Builder): void { TUserMentionInfo ) + builder.mixin(activity.class.Reaction, core.class.Class, core.mixin.TxAccessLevel, { + removeAccessLevel: AccountRole.Guest + }) + builder.mixin(activity.class.DocUpdateMessage, core.class.Class, view.mixin.ObjectPresenter, { presenter: activity.component.DocUpdateMessagePresenter }) diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index 5d566620eb..66bd8cb59a 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -37,6 +37,7 @@ import { TObjectChatPanel, TThreadMessage } from './types' +import { AccountRole } from '@hcengineering/core' export { chunterId } from '@hcengineering/chunter' export { chunterOperation } from './migration' @@ -88,6 +89,10 @@ export function createModel (builder: Builder): void { component: chunter.component.WorkbenchTabExtension }) + builder.mixin(chunter.class.DirectMessage, core.class.Class, core.mixin.TxAccessLevel, { + createAccessLevel: AccountRole.Guest + }) + const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage] spaceClasses.forEach((spaceClass) => { diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index f0cc98fd88..90ef36e6af 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -309,6 +309,18 @@ export function createModel (builder: Builder): void { TUserRole ) + builder.mixin(contact.class.PersonSpace, core.class.Class, core.mixin.TxAccessLevel, { + createAccessLevel: AccountRole.Guest + }) + + builder.mixin(contact.class.Person, core.class.Class, core.mixin.TxAccessLevel, { + createAccessLevel: AccountRole.Guest + }) + + builder.mixin(contact.class.SocialIdentity, core.class.Class, core.mixin.TxAccessLevel, { + createAccessLevel: AccountRole.Guest + }) + builder.mixin(contact.class.Contact, core.class.Class, activity.mixin.ActivityDoc, {}) builder.mixin(contact.class.Person, core.class.Class, activity.mixin.ActivityDoc, { diff --git a/models/core/src/security.ts b/models/core/src/security.ts index a7ad3ea6dc..54234e3009 100644 --- a/models/core/src/security.ts +++ b/models/core/src/security.ts @@ -17,6 +17,7 @@ import { DOMAIN_MODEL, DOMAIN_SPACE, IndexKind, + type AccountRole, type Class, type CollectionSize, type Permission, @@ -27,7 +28,8 @@ import { type SpaceType, type SpaceTypeDescriptor, type TypedSpace, - type AccountUuid + type AccountUuid, + type TxAccessLevel } from '@hcengineering/core' import { ArrOf, @@ -45,8 +47,7 @@ import { } from '@hcengineering/model' import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/platform' import core from './component' -import { TAttachedDoc, TDoc } from './core' - +import { TAttachedDoc, TDoc, TClass } from './core' // S P A C E @Model(core.class.Space, core.class.Doc, DOMAIN_SPACE) @@ -163,3 +164,10 @@ export class TPermission extends TDoc implements Permission { export class TSpacesTypeData extends TSpace implements RolesAssignment { [key: Ref]: AccountUuid[] } + +@Mixin(core.mixin.TxAccessLevel, core.class.Class) +export class TTxAccessLevel extends TClass implements TxAccessLevel { + createAccessLevel?: AccountRole + removeAccessLevel?: AccountRole + updateAccessLevel?: AccountRole +} diff --git a/models/core/src/spaceType.ts b/models/core/src/spaceType.ts index b1330fc702..f982484700 100644 --- a/models/core/src/spaceType.ts +++ b/models/core/src/spaceType.ts @@ -17,7 +17,7 @@ import { ArrOf, Prop, TypeString, type Builder } from '@hcengineering/model' import { type Asset } from '@hcengineering/platform' import { getRoleAttributeLabel } from '@hcengineering/core' -import { TSpacesTypeData } from './security' +import { TSpacesTypeData, TTxAccessLevel } from './security' import core from './component' const roles = [ @@ -37,6 +37,7 @@ export function defineSpaceType (builder: Builder): void { } builder.createModel(TSpacesTypeData) + builder.createModel(TTxAccessLevel) builder.createDoc( core.class.SpaceTypeDescriptor, diff --git a/models/love/src/index.ts b/models/love/src/index.ts index 98fa64b375..2b20e64f79 100644 --- a/models/love/src/index.ts +++ b/models/love/src/index.ts @@ -513,6 +513,14 @@ export function createModel (builder: Builder): void { components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } } }) + builder.mixin(love.class.JoinRequest, core.class.Class, core.mixin.TxAccessLevel, { + removeAccessLevel: AccountRole.Guest + }) + + builder.mixin(love.class.ParticipantInfo, core.class.Class, core.mixin.TxAccessLevel, { + updateAccessLevel: AccountRole.Guest + }) + builder.mixin(love.class.MeetingMinutes, core.class.Class, activity.mixin.ActivityDoc, {}) builder.mixin(love.class.Room, core.class.Class, activity.mixin.ActivityDoc, {}) diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 7394e0a07b..4dfff8d99e 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -555,6 +555,10 @@ export function createModel (builder: Builder): void { presenter: notification.component.MentionInboxNotificationPresenter }) + builder.mixin(notification.class.BrowserNotification, core.class.Class, core.mixin.TxAccessLevel, { + removeAccessLevel: AccountRole.Guest + }) + builder.createDoc( notification.class.NotificationType, core.space.Model, diff --git a/models/workbench/src/index.ts b/models/workbench/src/index.ts index 6daa2958b1..1d927ee3fc 100644 --- a/models/workbench/src/index.ts +++ b/models/workbench/src/index.ts @@ -13,11 +13,11 @@ // limitations under the License. // -import { type Class, DOMAIN_MODEL, type Ref, type Space, type AccountRole, type AccountUuid } from '@hcengineering/core' +import { AccountRole, type AccountUuid, type Class, DOMAIN_MODEL, type Ref, type Space } from '@hcengineering/core' import { type Builder, Mixin, Model, Prop, TypeRef, UX } from '@hcengineering/model' import preference, { TPreference } from '@hcengineering/model-preference' import { createAction } from '@hcengineering/model-view' -import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@hcengineering/platform' +import { type Asset, getEmbeddedLabel, type IntlString, type Resource } from '@hcengineering/platform' import view, { type KeyBinding } from '@hcengineering/view' import type { Application, @@ -115,6 +115,12 @@ export function createModel (builder: Builder): void { TWorkbenchTab ) + builder.mixin(workbench.class.WorkbenchTab, core.class.Class, core.mixin.TxAccessLevel, { + createAccessLevel: AccountRole.Guest, + removeAccessLevel: AccountRole.Guest, + updateAccessLevel: AccountRole.Guest + }) + builder.mixin(workbench.class.Application, core.class.Class, view.mixin.ObjectPresenter, { presenter: workbench.component.ApplicationPresenter }) diff --git a/packages/account-client/src/client.ts b/packages/account-client/src/client.ts index 338ab3eb2b..09336dd1e7 100644 --- a/packages/account-client/src/client.ts +++ b/packages/account-client/src/client.ts @@ -89,9 +89,10 @@ export interface AccountClient { password: string, first: string, last: string, - inviteId: string + inviteId: string, + workspaceUrl: string ) => Promise - join: (email: string, password: string, inviteId: string) => Promise + join: (email: string, password: string, inviteId: string, workspaceUrl: string) => Promise createInvite: (exp: number, emailMask: string, limit: number, role: AccountRole) => Promise checkJoin: (inviteId: string) => Promise checkAutoJoin: (inviteId: string, firstName?: string, lastName?: string) => Promise @@ -442,20 +443,21 @@ class AccountClientImpl implements AccountClient { password: string, first: string, last: string, - inviteId: string + inviteId: string, + workspaceUrl: string ): Promise { const request = { method: 'signUpJoin' as const, - params: { email, password, first, last, inviteId } + params: { email, password, first, last, inviteId, workspaceUrl } } return await this.rpc(request) } - async join (email: string, password: string, inviteId: string): Promise { + async join (email: string, password: string, inviteId: string, workspaceUrl: string): Promise { const request = { method: 'join' as const, - params: { email, password, inviteId } + params: { email, password, inviteId, workspaceUrl } } return await this.rpc(request) diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index b2fc9c8c0b..de03fbdf76 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -550,6 +550,12 @@ export const roleOrder: Record = { [AccountRole.Admin]: 100 } +export interface TxAccessLevel extends Class { + createAccessLevel?: AccountRole + removeAccessLevel?: AccountRole + updateAccessLevel?: AccountRole +} + /** * @public */ diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 11064279c3..b61451663b 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -15,7 +15,7 @@ import type { Asset, IntlString, Metadata, Plugin, StatusCode } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' import type { BenchmarkDoc } from './benchmark' -import { AccountRole } from './classes' +import { AccountRole, TxAccessLevel } from './classes' import type { Account, AnyAttribute, @@ -99,6 +99,8 @@ export const systemAccount: Account = { export const configUserAccountUuid = '0d94731c-0787-4bcd-aefe-304efc3706b1' as AccountUuid +export const readOnlyGuestAccountUuid = '83bbed9a-0867-4851-be32-31d49d1d42ce' as AccountUuid + export default plugin(coreId, { class: { Obj: '' as Ref>, @@ -189,7 +191,8 @@ export default plugin(coreId, { ConfigurationElement: '' as Ref>, IndexConfiguration: '' as Ref>>, SpacesTypeData: '' as Ref>, - TransientConfiguration: '' as Ref> + TransientConfiguration: '' as Ref>, + TxAccessLevel: '' as Ref> }, space: { Tx: '' as Ref, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7e0d446fc7..10c84d8997 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,7 +17,14 @@ import core from './component' export * from './classes' export * from './client' export * from './collaboration' -export { coreId, systemAccountUuid, systemAccountEmail, systemAccount, configUserAccountUuid } from './component' +export { + coreId, + systemAccountUuid, + readOnlyGuestAccountUuid, + systemAccountEmail, + systemAccount, + configUserAccountUuid +} from './component' export * from './hierarchy' export * from './measurements' export * from './memdb' diff --git a/packages/platform/src/platform.ts b/packages/platform/src/platform.ts index 604aa0d4b1..c4e9116aa6 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -172,6 +172,7 @@ export default plugin(platformId, { MailboxError: '' as StatusCode<{ reason: string }>, SocialIdAlreadyExists: '' as StatusCode, ReadOnlyAccount: '' as StatusCode, + RegularAccount: '' as StatusCode, SystemAccount: '' as StatusCode }, metadata: { diff --git a/packages/ui/src/components/internal/Root.svelte b/packages/ui/src/components/internal/Root.svelte index c15e12382b..981026efb8 100644 --- a/packages/ui/src/components/internal/Root.svelte +++ b/packages/ui/src/components/internal/Root.svelte @@ -104,6 +104,9 @@ maintenanceTime = _status.params.time maintenanceMessage = _status.params.message } else { + if (_status.code === platform.status.RegularAccount) { + readonlyAccount = false + } if (readonlyAccount) return if (_status.code === platform.status.ReadOnlyAccount) { readonlyAccount = true diff --git a/plugins/chunter-resources/src/utils.ts b/plugins/chunter-resources/src/utils.ts index b6fefc08f3..d450b328b0 100644 --- a/plugins/chunter-resources/src/utils.ts +++ b/plugins/chunter-resources/src/utils.ts @@ -26,11 +26,13 @@ import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage import contact, { type Employee, getCurrentEmployee, getName, type Person } from '@hcengineering/contact' import { employeeByAccountStore, employeeByIdStore, PersonIcon } from '@hcengineering/contact-resources' import core, { + AccountRole, type AccountUuid, type Class, type Client, type Doc, getCurrentAccount, + hasAccountRole, notEmpty, type Ref, type Space, @@ -120,6 +122,10 @@ export async function canDeleteMessage (doc?: ChatMessage): Promise { const me = getCurrentAccount() + if (hasAccountRole(me, AccountRole.Maintainer)) { + return true + } + return doc.createdBy !== undefined && me.socialIds.includes(doc.createdBy) } diff --git a/plugins/login-resources/src/components/Join.svelte b/plugins/login-resources/src/components/Join.svelte index 17c73c512e..4b020eb2bd 100644 --- a/plugins/login-resources/src/components/Join.svelte +++ b/plugins/login-resources/src/components/Join.svelte @@ -66,13 +66,19 @@ const [loginStatus, result] = page === 'login' - ? await join(object.username, object.password, location.query?.inviteId ?? '') + ? await join( + object.username, + object.password, + location.query?.inviteId ?? '', + location.query?.workspace ?? '' + ) : await signUpJoin( object.username, object.password, object.first, object.last, - location.query?.inviteId ?? '' + location.query?.inviteId ?? '', + location.query?.workspace ?? '' ) status = loginStatus diff --git a/plugins/login-resources/src/utils.ts b/plugins/login-resources/src/utils.ts index eff26efa57..e7d85baeee 100644 --- a/plugins/login-resources/src/utils.ts +++ b/plugins/login-resources/src/utils.ts @@ -615,10 +615,11 @@ export async function getInviteLinkId ( export async function join ( email: string, password: string, - inviteId: string + inviteId: string, + workspace: string ): Promise<[Status, WorkspaceLoginInfo | null]> { try { - const workspaceLoginInfo = await getAccountClient().join(email, password, inviteId) + const workspaceLoginInfo = await getAccountClient().join(email, password, inviteId, workspace) Analytics.handleEvent('Join', { email, ok: true }) @@ -643,10 +644,11 @@ export async function signUpJoin ( password: string, first: string, last: string, - inviteId: string + inviteId: string, + workspace: string ): Promise<[Status, WorkspaceLoginInfo | null]> { try { - const workspaceLoginInfo = await getAccountClient().signUpJoin(email, password, first, last, inviteId) + const workspaceLoginInfo = await getAccountClient().signUpJoin(email, password, first, last, inviteId, workspace) Analytics.handleEvent('Signup Join', { email, ok: true }) diff --git a/plugins/setting-resources/src/components/General.svelte b/plugins/setting-resources/src/components/General.svelte index d7cc7613f7..e9ba78fb84 100644 --- a/plugins/setting-resources/src/components/General.svelte +++ b/plugins/setting-resources/src/components/General.svelte @@ -19,7 +19,8 @@ AccountUuid, Configuration, getCurrentAccount, - pickPrimarySocialId + pickPrimarySocialId, + readOnlyGuestAccountUuid } from '@hcengineering/core' import { Breadcrumb, @@ -49,7 +50,7 @@ import { translateCB } from '@hcengineering/platform' import { createQuery, getClient, MessageBox, uiContext } from '@hcengineering/presentation' import { WorkspaceSetting } from '@hcengineering/setting' - import { AvatarType, ensureEmployeeForPerson } from '@hcengineering/contact' + import contact, { AvatarType, ensureEmployeeForPerson } from '@hcengineering/contact' import settingsRes from '../plugin' let loading = true @@ -172,6 +173,11 @@ guestUserInfo.guestSocialIds, guestUserInfo.guestPerson ) + } else { + const readonlyEmployee = await client.findOne(contact.mixin.Employee, { personUuid: readOnlyGuestAccountUuid }) + if (readonlyEmployee !== undefined) { + await client.update(readonlyEmployee, { active: false }) + } } } diff --git a/plugins/view-assets/lang/cs.json b/plugins/view-assets/lang/cs.json index 807dcc0f3e..1962a85a39 100644 --- a/plugins/view-assets/lang/cs.json +++ b/plugins/view-assets/lang/cs.json @@ -136,8 +136,11 @@ "Loading": "Načítání...", "NoRelations": "Žádné vztahy", "ReadOnlyWarningTitle": "✨ Ukázka pro hosty", - "ReadOnlyWarningMessage": "Jste v režimu prohlížení. Chcete spravovat jakékoli projekty v Huly? Úkoly, termíny, tým — zaregistrujte se pro plný přístup!", - "ReadOnlySignUp": "Zaregistrovat se \uD83D\uDE80", + "ReadOnlyWarningMessage": "Jste v režimu prohlížení. Chcete spravovat jakékoli projekty v Huly? Úkoly, termíny, tým — připojte se k tomuto prostoru pro spolupráci nebo si vytvořte vlastní!", + "ReadOnlyJoinWorkspace": "Připojit se k prostoru 👥", + "ReadOnlySignUp": "Vytvořit účet 🚀", + "PermissionWarningTitle": "✨ Zvyšte svůj přístup!", + "PermissionWarningMessage": "Vaše současná role má omezená oprávnění. Chcete-li odemknout tuto a další pokročilé funkce, požádejte správce svého pracovního prostoru o zvýšení úrovně přístupu!", "Icon": "Ikona", "Color": "Barva", "AutomationOnly": "Pouze automatizace" diff --git a/plugins/view-assets/lang/de.json b/plugins/view-assets/lang/de.json index 7fb5178fdd..139e744ae9 100644 --- a/plugins/view-assets/lang/de.json +++ b/plugins/view-assets/lang/de.json @@ -136,8 +136,11 @@ "Loading": "Laden...", "NoRelations": "Keine Beziehungen", "ReadOnlyWarningTitle": "✨ Gast-Demo", - "ReadOnlyWarningMessage": "Sie sind im Ansichtsmodus. Möchten Sie beliebige Projekte in Huly verwalten? Aufgaben, Fristen, Team — registrieren Sie sich für vollen Zugriff!", - "ReadOnlySignUp": "Registrieren \uD83D\uDE80", + "ReadOnlyWarningMessage": "Sie sind im Ansichtsmodus. Möchten Sie beliebige Projekte in Huly verwalten? Aufgaben, Fristen, Team — treten Sie diesem Arbeitsbereich bei oder erstellen Sie Ihren eigenen!", + "ReadOnlyJoinWorkspace": "Arbeitsbereich beitreten 👥", + "ReadOnlySignUp": "Konto erstellen 🚀", + "PermissionWarningTitle": "✨ Erweitern Sie Ihren Zugriff!", + "PermissionWarningMessage": "Ihre aktuelle Rolle hat eingeschränkte Berechtigungen. Um diese und andere erweiterte Funktionen freizuschalten, bitten Sie Ihren Arbeitsbereich-Administrator, Ihr Zugriffslevel zu erhöhen!", "Icon": "Symbol", "Color": "Farbe", "AutomationOnly": "Nur Automatisierung" diff --git a/plugins/view-assets/lang/en.json b/plugins/view-assets/lang/en.json index 4fb286b1f0..a80e2c79f7 100644 --- a/plugins/view-assets/lang/en.json +++ b/plugins/view-assets/lang/en.json @@ -136,8 +136,11 @@ "Loading": "Loading...", "NoRelations": "No relations", "ReadOnlyWarningTitle": "✨ Guest Demo", - "ReadOnlyWarningMessage": "You're in view-only mode. Want to manage any project in Huly? Tasks, deadlines, team — sign up for full access!", - "ReadOnlySignUp": "Sign up \uD83D\uDE80", + "ReadOnlyWarningMessage": "You're in view-only mode. Want to manage any project in Huly? Tasks, deadlines, team — join this workspace to collaborate or create your own space!", + "ReadOnlyJoinWorkspace": "Join workspace 👥", + "ReadOnlySignUp": "Create account 🚀", + "PermissionWarningTitle": "✨ Level up your access!", + "PermissionWarningMessage": "Your current role has limited permissions. To unlock this and other advanced features, ask your workspace admin to upgrade your access level!", "Icon": "Icon", "Color": "Color", "AutomationOnly": "Automation only" diff --git a/plugins/view-assets/lang/es.json b/plugins/view-assets/lang/es.json index d94693b3b4..d8166a5868 100644 --- a/plugins/view-assets/lang/es.json +++ b/plugins/view-assets/lang/es.json @@ -131,8 +131,11 @@ "Loading": "Cargando...", "NoRelations": "No hay relaciones", "ReadOnlyWarningTitle": "✨ Demo de Invitado", - "ReadOnlyWarningMessage": "Estás en modo de solo lectura. ¿Quieres gestionar cualquier proyecto en Huly? Tareas, plazos, equipo — ¡regístrate para acceso completo!", - "ReadOnlySignUp": "Registrarse \uD83D\uDE80", + "ReadOnlyWarningMessage": "Estás en modo de solo lectura. ¿Quieres gestionar cualquier proyecto en Huly? Tareas, plazos, equipo — ¡únete a este espacio para colaborar o crea el tuyo!", + "ReadOnlyJoinWorkspace": "Unirse al espacio 👥", + "ReadOnlySignUp": "Crear cuenta 🚀", + "PermissionWarningTitle": "✨ ¡Mejora tu acceso!", + "PermissionWarningMessage": "Tu rol actual tiene permisos limitados. ¡Para desbloquear esta y otras funciones avanzadas, pídele al administrador de tu espacio de trabajo que actualice tu nivel de acceso!", "Icon": "Icono", "Color": "Color", "AutomationOnly": "Solo automatización" diff --git a/plugins/view-assets/lang/fr.json b/plugins/view-assets/lang/fr.json index b3408cbe9a..9eb14b991b 100644 --- a/plugins/view-assets/lang/fr.json +++ b/plugins/view-assets/lang/fr.json @@ -131,8 +131,11 @@ "Loading": "Chargement...", "NoRelations": "Aucune relation", "ReadOnlyWarningTitle": "✨ Démo Invité", - "ReadOnlyWarningMessage": "Vous êtes en mode consultation. Voulez-vous gérer tous types de projets dans Huly ? Tâches, délais, équipe — inscrivez-vous pour un accès complet !", - "ReadOnlySignUp": "Inscription \uD83D\uDE80", + "ReadOnlyWarningMessage": "Vous êtes en mode consultation. Voulez-vous gérer tous types de projets dans Huly ? Tâches, délais, équipe — rejoignez cet espace pour collaborer ou créez le vôtre !", + "ReadOnlyJoinWorkspace": "Rejoindre l'espace 👥", + "ReadOnlySignUp": "Créer un compte 🚀", + "PermissionWarningTitle": "✨ Améliorez votre accès !", + "PermissionWarningMessage": "Votre rôle actuel a des autorisations limitées. Pour débloquer cette fonctionnalité et d'autres fonctionnalités avancées, demandez à l'administrateur de votre espace de travail de mettre à jour votre niveau d'accès !", "Icon": "Icône", "Color": "Couleur", "AutomationOnly": "Automatisation uniquement" diff --git a/plugins/view-assets/lang/it.json b/plugins/view-assets/lang/it.json index 336028d6a7..93339d48a9 100644 --- a/plugins/view-assets/lang/it.json +++ b/plugins/view-assets/lang/it.json @@ -131,8 +131,11 @@ "Loading": "Caricamento...", "NoRelations": "Nessuna relazione", "ReadOnlyWarningTitle": "✨ Demo Ospite", - "ReadOnlyWarningMessage": "Sei in modalità di sola lettura. Vuoi gestire qualsiasi progetto su Huly? Task, scadenze, team — registrati per l'accesso completo!", - "ReadOnlySignUp": "Registrati \uD83D\uDE80", + "ReadOnlyWarningMessage": "Sei in modalità di sola lettura. Vuoi gestire qualsiasi progetto su Huly? Task, scadenze, team — unisciti a questo spazio per collaborare o creane uno tuo!", + "ReadOnlyJoinWorkspace": "Unisciti allo spazio 👥", + "ReadOnlySignUp": "Crea account 🚀", + "PermissionWarningTitle": "✨ Migliora il tuo accesso!", + "PermissionWarningMessage": "Il tuo ruolo attuale ha autorizzazioni limitate. Per sbloccare questa e altre funzionalità avanzate, chiedi all'amministratore del tuo spazio di lavoro di aggiornare il tuo livello di accesso!", "Icon": "Icona", "Color": "Colore", "AutomationOnly": "Solo automazione" diff --git a/plugins/view-assets/lang/ja.json b/plugins/view-assets/lang/ja.json index 5e677dec91..a93d99ab86 100644 --- a/plugins/view-assets/lang/ja.json +++ b/plugins/view-assets/lang/ja.json @@ -131,8 +131,11 @@ "Loading": "読み込み中...", "NoRelations": "関係なし", "ReadOnlyWarningTitle": "✨ ゲストデモ", - "ReadOnlyWarningMessage": "閲覧モードです。Hulyでどんなプロジェクトも管理しませんか? タスク、期限、チーム — 完全アクセスには登録してください!", - "ReadOnlySignUp": "サインアップ \uD83D\uDE80", + "ReadOnlyWarningMessage": "閲覧モードです。Hulyでどんなプロジェクトも管理しませんか? タスク、期限、チーム — このワークスペースに参加して共同作業するか、自分自身のスペースを作成しましょう!", + "ReadOnlyJoinWorkspace": "ワークスペースに参加 👥", + "ReadOnlySignUp": "アカウント作成 🚀", + "PermissionWarningTitle": "✨ アクセスレベルを上げましょう!", + "PermissionWarningMessage": "現在のロールの権限は制限されています。この機能や他の高度な機能を利用するには、ワークスペース管理者にアクセスレベルのアップグレードを依頼してください!", "Icon": "アイコン", "Color": "色", "AutomationOnly": "自動化のみ" diff --git a/plugins/view-assets/lang/pt.json b/plugins/view-assets/lang/pt.json index 677bef9ab2..92857601e1 100644 --- a/plugins/view-assets/lang/pt.json +++ b/plugins/view-assets/lang/pt.json @@ -131,8 +131,11 @@ "Loading": "Carregando...", "NoRelations": "Sem relações", "ReadOnlyWarningTitle": "✨ Demonstração para Convidados", - "ReadOnlyWarningMessage": "Você está no modo de visualização. Quer gerenciar qualquer projeto no Huly? Tarefas, prazos, equipe — cadastre-se para acesso total!", - "ReadOnlySignUp": "Registar \uD83D\uDE80", + "ReadOnlyWarningMessage": "Você está no modo de visualização. Quer gerenciar qualquer projeto no Huly? Tarefas, prazos, equipe — junte-se a este espaço para colaborar ou crie o seu!", + "ReadOnlyJoinWorkspace": "Entrar no espaço 👥", + "ReadOnlySignUp": "Criar conta 🚀", + "PermissionWarningTitle": "✨ Melhore seu acesso!", + "PermissionWarningMessage": "Seu cargo atual tem permissões limitadas. Para desbloquear este e outros recursos avançados, peça ao administrador do seu espaço de trabalho para atualizar seu nível de acesso!", "Icon": "Ícone", "Color": "Cor", "AutomationOnly": "Apenas automação" diff --git a/plugins/view-assets/lang/ru.json b/plugins/view-assets/lang/ru.json index e9055291fa..2442730889 100644 --- a/plugins/view-assets/lang/ru.json +++ b/plugins/view-assets/lang/ru.json @@ -133,8 +133,11 @@ "Loading": "Загрузка...", "NoRelations": "Нет связей", "ReadOnlyWarningTitle": "✨ Гостевое демо", - "ReadOnlyWarningMessage": "Вы в режиме просмотра. Хотите управлять любыми проектами в Huly? Задачи, сроки, команда — регистрируйтесь для полного доступа!", - "ReadOnlySignUp": "Регистрация \uD83D\uDE80", + "ReadOnlyWarningMessage": "Вы в режиме просмотра. Хотите управлять любыми проектами в Huly? Задачи, сроки, команда — присоединитесь к этому пространству для сотрудничества или создайте свое!", + "ReadOnlyJoinWorkspace": "Присоединиться 👥", + "ReadOnlySignUp": "Создать аккаунт 🚀", + "PermissionWarningTitle": "✨ Повысьте свой уровень доступа!", + "PermissionWarningMessage": "Ваша текущая роль имеет ограниченные права. Чтобы разблокировать эту и другие продвинутые функции, попросите администратора вашего пространства повысить ваш уровень доступа!", "Icon": "Иконка", "Color": "Цвет", "AutomationOnly": "Только автоматизация" diff --git a/plugins/view-assets/lang/zh.json b/plugins/view-assets/lang/zh.json index ba81df9f9d..7c7fc587a6 100644 --- a/plugins/view-assets/lang/zh.json +++ b/plugins/view-assets/lang/zh.json @@ -136,8 +136,11 @@ "Loading": "加载中...", "NoRelations": "无关系", "ReadOnlyWarningTitle": "✨ 访客演示", - "ReadOnlyWarningMessage": "您处于只读模式。想在Huly管理任何项目吗?任务、截止日期、团队 — 立即注册获取完整权限!", - "ReadOnlySignUp": "注册 \uD83D\uDE80", + "ReadOnlyWarningMessage": "您处于只读模式。想在Huly管理任何项目吗?任务、截止日期、团队 — 加入此工作区协作或创建您自己的空间!", + "ReadOnlyJoinWorkspace": "加入工作区 👥", + "ReadOnlySignUp": "创建账户 🚀", + "PermissionWarningTitle": "✨ 提升您的访问权限!", + "PermissionWarningMessage": "您当前角色的权限有限。要解锁此功能和其他高级功能,请让您的工作区管理员升级您的访问权限!", "Icon": "图标", "Color": "颜色", "AutomationOnly": "仅限自动化" diff --git a/plugins/view-resources/src/components/ForbiddenNotification.svelte b/plugins/view-resources/src/components/ForbiddenNotification.svelte new file mode 100644 index 0000000000..4a2fd3b439 --- /dev/null +++ b/plugins/view-resources/src/components/ForbiddenNotification.svelte @@ -0,0 +1,12 @@ + + + + + {notification.subTitle} + + diff --git a/plugins/view-resources/src/components/ReadOnlyNotification.svelte b/plugins/view-resources/src/components/ReadOnlyNotification.svelte index 434d032460..9367991e6d 100644 --- a/plugins/view-resources/src/components/ReadOnlyNotification.svelte +++ b/plugins/view-resources/src/components/ReadOnlyNotification.svelte @@ -1,9 +1,14 @@ @@ -11,7 +16,7 @@ {notification.subTitle} -
+