diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index 4abee5afa4..d3ee83ead6 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -32,7 +32,8 @@ import { type Person, type PersonSpace, type SocialIdentity, - type Status + type Status, + type SocialIdentityProvider } from '@hcengineering/contact' import { AccountRole, @@ -40,6 +41,7 @@ import { DOMAIN_MODEL, DateRangeMode, IndexKind, + SocialIdType, type AccountUuid, type Blob, type Class, @@ -49,7 +51,6 @@ import { type PersonId, type PersonUuid, type Ref, - type SocialIdType, type Timestamp } from '@hcengineering/core' import { createSystemType } from '@hcengineering/model-card' @@ -112,6 +113,14 @@ export class TChannelProvider extends TDoc implements ChannelProvider { placeholder!: IntlString } +@Model(contact.class.SocialIdentityProvider, core.class.Doc, DOMAIN_MODEL) +export class TSocialIdentityProvider extends TDoc implements SocialIdentityProvider { + label!: IntlString + icon?: Asset + type!: SocialIdType + creator?: AnyComponent +} + @Model(contact.class.Contact, core.class.Doc, DOMAIN_CONTACT) @UX(contact.string.Contact, contact.icon.Person, 'CONT', 'name', undefined, contact.string.Persons) export class TContact extends TDoc implements Contact { @@ -191,6 +200,9 @@ export class TSocialIdentity extends TAttachedDoc implements SocialIdentity { @Prop(TypeNumber(), contact.string.Confirmed) @ReadOnly() verifiedOn?: number + + @Prop(TypeBoolean(), contact.string.Deleted) + isDeleted?: boolean } @Model(contact.class.Person, contact.class.Contact) @@ -286,6 +298,7 @@ export function createModel (builder: Builder): void { builder.createModel( TAvatarProvider, TChannelProvider, + TSocialIdentityProvider, TContact, TPerson, TSocialIdentity, @@ -826,6 +839,73 @@ export function createModel (builder: Builder): void { contact.avatarProvider.Color ) + builder.createDoc( + contact.class.SocialIdentityProvider, + core.space.Model, + { + label: contact.string.Email, + icon: contact.icon.Email, + type: SocialIdType.EMAIL, + creator: setting.component.AddEmailSocialId + }, + contact.socialIdentityProvider.Email + ) + + builder.createDoc( + contact.class.SocialIdentityProvider, + core.space.Model, + { + label: getEmbeddedLabel('Huly'), + icon: contact.icon.Huly, + type: SocialIdType.HULY + }, + contact.socialIdentityProvider.Huly + ) + + builder.createDoc( + contact.class.SocialIdentityProvider, + core.space.Model, + { + label: contact.string.Phone, + icon: contact.icon.Phone, + type: SocialIdType.PHONE + }, + contact.socialIdentityProvider.Phone + ) + + builder.createDoc( + contact.class.SocialIdentityProvider, + core.space.Model, + { + label: contact.string.Google, + icon: contact.icon.Google, + type: SocialIdType.GOOGLE + }, + contact.socialIdentityProvider.Google + ) + + builder.createDoc( + contact.class.SocialIdentityProvider, + core.space.Model, + { + label: contact.string.GitHub, + icon: contact.icon.GitHub, + type: SocialIdType.GITHUB + }, + contact.socialIdentityProvider.GitHub + ) + + builder.createDoc( + contact.class.SocialIdentityProvider, + core.space.Model, + { + label: contact.string.Telegram, + icon: contact.icon.Telegram, + type: SocialIdType.TELEGRAM + }, + contact.socialIdentityProvider.Telegram + ) + builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectPresenter, { presenter: contact.component.PersonPresenter }) diff --git a/models/contact/src/plugin.ts b/models/contact/src/plugin.ts index 89eb4c2e74..9f1d6a4b11 100644 --- a/models/contact/src/plugin.ts +++ b/models/contact/src/plugin.ts @@ -77,6 +77,8 @@ export default mergeIds(contactId, contact, { FacebookPlaceholder: '' as IntlString, HomepagePlaceholder: '' as IntlString, Twitter: '' as IntlString, + Google: '' as IntlString, + Telegram: '' as IntlString, GitHub: '' as IntlString, Facebook: '' as IntlString, TypeLabel: '' as IntlString, diff --git a/packages/account-client/src/client.ts b/packages/account-client/src/client.ts index 361dcc3559..fed174cd98 100644 --- a/packages/account-client/src/client.ts +++ b/packages/account-client/src/client.ts @@ -62,7 +62,7 @@ export interface AccountClient { kind?: 'external' | 'internal' | 'byregion', externalRegions?: string[] ) => Promise - validateOtp: (email: string, code: string, password?: string) => Promise + validateOtp: (email: string, code: string, password?: string, action?: 'verify') => Promise loginOtp: (email: string) => Promise getLoginInfoByToken: () => Promise getLoginWithWorkspaceInfo: () => Promise @@ -109,7 +109,7 @@ export interface AccountClient { isReadOnlyGuest: () => Promise getPerson: () => Promise getPersonInfo: (account: PersonUuid) => Promise - getSocialIds: () => Promise + getSocialIds: (includeDeleted?: boolean) => Promise getWorkspaceMembers: () => Promise updateWorkspaceRole: (account: string, role: AccountRole) => Promise updateAllowReadOnlyGuests: ( @@ -164,7 +164,21 @@ export interface AccountClient { ) => Promise updateSocialId: (personId: PersonId, displayValue: string) => Promise exchangeGuestToken: (token: string) => Promise - releaseSocialId: (personUuid: PersonUuid, type: SocialIdType, value: string) => Promise + /** + * Releases the target social id for the target account. + * If called with user's token it releases the social id for the user's account. + * @param personUuid Required for services + * @param type Social id type + * @param value Social id value + * @param deleteIntegrations Deletes associated integrations if true. Otherwise, throws an error if any. + * @returns Deleted social id with updated isDeleted flag and key/value + */ + releaseSocialId: ( + personUuid: PersonUuid | undefined, + type: SocialIdType, + value: string, + deleteIntegrations?: boolean + ) => Promise createIntegration: (integration: Integration) => Promise updateIntegration: (integration: Integration) => Promise deleteIntegration: (integrationKey: IntegrationKey) => Promise @@ -178,6 +192,7 @@ export interface AccountClient { getAccountInfo: (uuid: AccountUuid) => Promise mergeSpecifiedPersons: (primaryPerson: PersonUuid, secondaryPerson: PersonUuid) => Promise mergeSpecifiedAccounts: (primaryAccount: AccountUuid, secondaryAccount: AccountUuid) => Promise + addEmailSocialId: (email: string) => Promise setCookie: () => Promise deleteCookie: () => Promise @@ -295,10 +310,10 @@ class AccountClientImpl implements AccountClient { return await this.rpc(request) } - async validateOtp (email: string, code: string, password?: string): Promise { + async validateOtp (email: string, code: string, password?: string, action?: 'verify'): Promise { const request = { method: 'validateOtp' as const, - params: { email, code, password } + params: { email, code, password, action } } return await this.rpc(request) @@ -578,10 +593,10 @@ class AccountClientImpl implements AccountClient { return await this.rpc(request) } - async getSocialIds (): Promise { + async getSocialIds (includeDeleted?: boolean): Promise { const request = { method: 'getSocialIds' as const, - params: {} + params: { includeDeleted } } return await this.rpc(request) @@ -842,13 +857,18 @@ class AccountClientImpl implements AccountClient { await this.rpc(request) } - async releaseSocialId (personUuid: PersonUuid, type: SocialIdType, value: string): Promise { + async releaseSocialId ( + personUuid: PersonUuid | undefined, + type: SocialIdType, + value: string, + deleteIntegrations = false + ): Promise { const request = { method: 'releaseSocialId' as const, - params: { personUuid, type, value } + params: { personUuid, type, value, deleteIntegrations } } - await this.rpc(request) + return await this.rpc(request) } async createIntegration (integration: Integration): Promise { @@ -968,6 +988,15 @@ class AccountClientImpl implements AccountClient { await this.rpc(request) } + async addEmailSocialId (email: string): Promise { + const request = { + method: 'addEmailSocialId' as const, + params: { email } + } + + return await this.rpc(request) + } + async setCookie (): Promise { const url = concatLink(this.url, '/cookie') const response = await fetch(url, { ...this.request, method: 'PUT' }) diff --git a/packages/api-client/src/client.ts b/packages/api-client/src/client.ts index be114b39bb..118fde1760 100644 --- a/packages/api-client/src/client.ts +++ b/packages/api-client/src/client.ts @@ -61,7 +61,7 @@ export async function connect (url: string, options: ConnectOptions): Promise si.isDeleted !== true) + if (activeSocialIds.length === 0) { + throw new Error('No active social ids provided') } - const hulySocialIds = socialIds.filter((si) => si.type === SocialIdType.HULY) + const hulySocialIds = activeSocialIds.filter((si) => si.type === SocialIdType.HULY) - return hulySocialIds[0] ?? socialIds[0] + return hulySocialIds[0] ?? activeSocialIds[0] } +export const loginSocialTypes = [SocialIdType.EMAIL, SocialIdType.GOOGLE, SocialIdType.GITHUB] + export function notEmpty (id: T | undefined | null): id is T { return id !== undefined && id !== null && id !== '' } diff --git a/packages/platform/lang/cs.json b/packages/platform/lang/cs.json index 99180e80bd..d039942853 100644 --- a/packages/platform/lang/cs.json +++ b/packages/platform/lang/cs.json @@ -5,6 +5,7 @@ "InvalidId": "Neplatné ID: {id}", "BadRequest": "Špatný požadavek", "Forbidden": "Zakázáno", + "Conflict": "Konflikt", "ExpiredLink": "Tento odkaz na pozvánku vypršel", "Unauthorized": "Neoprávněný přístup", "UnknownMethod": "Neznámá metoda: {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "Pozvánka s email:{email} nebyla nalezena.", "WorkspaceLimitReached": "Dosáhli jste limitu pracovních prostorů. Kontaktujte nás...", "ReadOnlyAccount": "Ukázka pro hosty", - "SystemAccount": "Systémový účet" + "SystemAccount": "Systémový účet", + "SocialIdAlreadyExists": "Sociální ID již existuje" } } diff --git a/packages/platform/lang/de.json b/packages/platform/lang/de.json index 6b9c6c428b..5fc4f89627 100644 --- a/packages/platform/lang/de.json +++ b/packages/platform/lang/de.json @@ -5,6 +5,7 @@ "InvalidId": "Ungültige ID: {id}", "BadRequest": "Fehlerhafte Anfrage", "Forbidden": "Zugriff verweigert", + "Conflict": "Konflikt", "ExpiredLink": "Dieser Einladungslink ist abgelaufen", "Unauthorized": "Nicht autorisiert", "UnknownMethod": "Unbekannte Methode: {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "Einladung mit E-Mail: {email} nicht gefunden.", "WorkspaceLimitReached": "Sie haben das Arbeitsbereichslimit erreicht. Bitte kontaktieren Sie uns...", "ReadOnlyAccount": "Gast-Demo", - "SystemAccount": "Systemkonto" + "SystemAccount": "Systemkonto", + "SocialIdAlreadyExists": "Social ID existiert bereits" } } diff --git a/packages/platform/lang/en.json b/packages/platform/lang/en.json index a9a3d6b9ac..00376332f2 100644 --- a/packages/platform/lang/en.json +++ b/packages/platform/lang/en.json @@ -5,6 +5,7 @@ "InvalidId": "Invalid Id: {id}", "BadRequest": "Bad request", "Forbidden": "Forbidden", + "Conflict": "Conflict", "ExpiredLink": "This invite link is expired", "Unauthorized": "Unauthorized", "UnknownMethod": "Unknown method: {method}", @@ -22,6 +23,7 @@ "InviteNotFound": "Invitation with email:{email} not found.", "WorkspaceLimitReached": "You have reached the workspace limit. Please contact us...", "ReadOnlyAccount": "Guest Demo", - "SystemAccount": "System account" + "SystemAccount": "System account", + "SocialIdAlreadyExists": "Social ID already exists" } } diff --git a/packages/platform/lang/es.json b/packages/platform/lang/es.json index df47d4bc95..18be7bc920 100644 --- a/packages/platform/lang/es.json +++ b/packages/platform/lang/es.json @@ -5,6 +5,7 @@ "InvalidId": "Id no válido: {id}", "BadRequest": "Solicitud incorrecta", "Forbidden": "Prohibido", + "Conflict": "Conflicto", "ExpiredLink": "Este enlace de invitación ha caducado", "Unauthorized": "No autorizado", "UnknownMethod": "Método desconocido: {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "No se encontró la invitación con email:{email}.", "WorkspaceLimitReached": "Ha alcanzado el límite de espacios de trabajo. Póngase en contacto con nosotros...", "ReadOnlyAccount": "Demo de Invitado", - "SystemAccount": "Cuenta del sistema" + "SystemAccount": "Cuenta del sistema", + "SocialIdAlreadyExists": "El ID social ya existe" } } diff --git a/packages/platform/lang/fr.json b/packages/platform/lang/fr.json index 6534bd4e00..2b15f74d37 100644 --- a/packages/platform/lang/fr.json +++ b/packages/platform/lang/fr.json @@ -5,6 +5,7 @@ "InvalidId": "Id invalide : {id}", "BadRequest": "Mauvaise requête", "Forbidden": "Interdit", + "Conflict": "Conflit", "ExpiredLink": "Ce lien d'invitation est expiré", "Unauthorized": "Non autorisé", "UnknownMethod": "Méthode inconnue : {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "Invitation avec l'email:{email} introuvable.", "WorkspaceLimitReached": "Vous avez atteint la limite d'espace de travail. Veuillez contacter nous...", "ReadOnlyAccount": "Démo Invité", - "SystemAccount": "Compte système" + "SystemAccount": "Compte système", + "SocialIdAlreadyExists": "L'ID social existe déjà" } } diff --git a/packages/platform/lang/it.json b/packages/platform/lang/it.json index 242b96d4b4..f9d22b643d 100644 --- a/packages/platform/lang/it.json +++ b/packages/platform/lang/it.json @@ -5,6 +5,7 @@ "InvalidId": "Id non valido: {id}", "BadRequest": "Richiesta non valida", "Forbidden": "Proibito", + "Conflict": "Conflitto", "ExpiredLink": "Questo link di invito è scaduto", "Unauthorized": "Non autorizzato", "UnknownMethod": "Metodo sconosciuto: {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "Invito con email:{email} non trovato.", "WorkspaceLimitReached": "Hai raggiunto il limite di spazi di lavoro. Contattaci...", "ReadOnlyAccount": "Demo Ospite", - "SystemAccount": "Account di sistema" + "SystemAccount": "Account di sistema", + "SocialIdAlreadyExists": "L'ID social esiste già" } } diff --git a/packages/platform/lang/ja.json b/packages/platform/lang/ja.json index 3b17f1ecd4..f71ea5c74e 100644 --- a/packages/platform/lang/ja.json +++ b/packages/platform/lang/ja.json @@ -5,6 +5,7 @@ "InvalidId": "無効なID: {id}", "BadRequest": "不正なリクエスト", "Forbidden": "アクセスが禁止されています", + "Conflict": "競合", "ExpiredLink": "この招待リンクは期限切れです", "Unauthorized": "認証されていません", "UnknownMethod": "不明なメソッド: {method}", @@ -22,6 +23,7 @@ "InviteNotFound": "メールアドレス {email} に対する招待が見つかりませんでした", "WorkspaceLimitReached": "作成可能なワークスペースの上限に達しました。お問い合わせください", "ReadOnlyAccount": "ゲストデモ", - "SystemAccount": "システムアカウント" + "SystemAccount": "システムアカウント", + "SocialIdAlreadyExists": "ソーシャルIDは既に存在します" } } diff --git a/packages/platform/lang/pt.json b/packages/platform/lang/pt.json index 427e7b2862..4879b877c2 100644 --- a/packages/platform/lang/pt.json +++ b/packages/platform/lang/pt.json @@ -5,6 +5,7 @@ "InvalidId": "Id inválido: {id}", "BadRequest": "Pedido inválido", "Forbidden": "Proibido", + "Conflict": "Conflito", "ExpiredLink": "Este link de convite expirou", "Unauthorized": "Não autorizado", "UnknownMethod": "Método desconhecido: {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "Convite com email:{email} não encontrado.", "WorkspaceLimitReached": "Você atingiu o limite de espaço de trabalho. Entre em contato conosco...", "ReadOnlyAccount": "Demonstração para Convidados", - "SystemAccount": "Conta do sistema" + "SystemAccount": "Conta do sistema", + "SocialIdAlreadyExists": "ID social já existe" } } diff --git a/packages/platform/lang/ru.json b/packages/platform/lang/ru.json index 1dcd77926c..475a9b9f69 100644 --- a/packages/platform/lang/ru.json +++ b/packages/platform/lang/ru.json @@ -5,6 +5,7 @@ "InvalidId": "Некорректный Id: {id}", "BadRequest": "Некорректный запрос", "Forbidden": "Запрещено", + "Conflict": "Конфликт", "ExpiredLink": "Ссылка истекла", "Unauthorized": "Неавторизован", "UnknownMethod": "Неизвестный метод: {method}", @@ -23,6 +24,7 @@ "InviteNotFound": "Приглашение с email:{email} не найдено.", "WorkspaceLimitReached": "Вы достигли лимита рабочих пространств. Свяжитесь с нами...", "ReadOnlyAccount": "Гостевое демо", - "SystemAccount": "Системный аккаунт" + "SystemAccount": "Системный аккаунт", + "SocialIdAlreadyExists": "Социальный ID уже существует" } } diff --git a/packages/platform/lang/zh.json b/packages/platform/lang/zh.json index 28ec0c305d..f16226ba7d 100644 --- a/packages/platform/lang/zh.json +++ b/packages/platform/lang/zh.json @@ -5,6 +5,7 @@ "InvalidId": "无效的 Id: {id}", "BadRequest": "错误的请求", "Forbidden": "禁止访问", + "Conflict": "冲突", "ExpiredLink": "此邀请链接已过期", "Unauthorized": "未授权", "UnknownMethod": "未知方法: {method}", @@ -22,6 +23,7 @@ "InvalidOtp": "无效的代码", "InviteNotFound": "未找到 id 为 {email} 的邀请。", "WorkspaceLimitReached": "您已达到工作区限制。请联系我们...", - "ReadOnlyAccount": "访客演示" + "ReadOnlyAccount": "访客演示", + "SocialIdAlreadyExists": "社交ID已存在" } } diff --git a/packages/platform/src/platform.ts b/packages/platform/src/platform.ts index 37d2690a93..604aa0d4b1 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -139,8 +139,9 @@ export default plugin(platformId, { NoLoaderForStrings: '' as StatusCode<{ plugin: Plugin }>, BadRequest: '' as StatusCode, - Forbidden: '' as StatusCode, - Unauthorized: '' as StatusCode, + Forbidden: '' as StatusCode, // 403 + Unauthorized: '' as StatusCode, // 401 + Conflict: '' as StatusCode, // 409 ExpiredLink: '' as StatusCode, UnknownMethod: '' as StatusCode<{ method: string }>, InternalServerError: '' as StatusCode, @@ -155,6 +156,7 @@ export default plugin(platformId, { SocialIdNotFound: '' as StatusCode<{ value?: string, type?: string, _id?: string }>, SocialIdNotConfirmed: '' as StatusCode<{ socialId: string, type: string }>, SocialIdAlreadyConfirmed: '' as StatusCode<{ socialId: string, type: string }>, + IntegrationExists: '' as StatusCode, IntegrationAlreadyExists: '' as StatusCode, IntegrationNotFound: '' as StatusCode, IntegrationSecretAlreadyExists: '' as StatusCode, diff --git a/packages/ui/src/components/CodeForm.svelte b/packages/ui/src/components/CodeForm.svelte index 0c885e26cf..85d277b4e2 100644 --- a/packages/ui/src/components/CodeForm.svelte +++ b/packages/ui/src/components/CodeForm.svelte @@ -33,6 +33,12 @@ formData[field] = formData[field].trim() } + export function clear (): void { + Object.keys(formData).forEach((key) => { + formData[key] = '' + }) + } + async function validateCode (): Promise { const code = Object.values(formData).join('') diff --git a/packages/ui/src/components/ModernDialog.svelte b/packages/ui/src/components/ModernDialog.svelte index 3c5d4605b4..5d31fd060e 100644 --- a/packages/ui/src/components/ModernDialog.svelte +++ b/packages/ui/src/components/ModernDialog.svelte @@ -31,6 +31,7 @@ export let submitKind: ButtonKind = 'primary' export let cancelLabel: IntlString = ui.string.Cancel export let canSubmit: boolean = false + export let hideSubmit: boolean = false export let shouldSubmitOnEnter: boolean = false export let shouldCloseOnCancel: boolean = true export let hasBack: boolean = false @@ -139,15 +140,17 @@