Edit user profile (#831)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-01-18 15:05:16 +06:00 committed by GitHub
parent e92cf3272a
commit 3d21b2de1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 645 additions and 87 deletions

View File

@ -44,50 +44,67 @@ export class TIntegrationType extends TDoc implements IntegrationType {
export function createModel (builder: Builder): void {
builder.createModel(TIntegration, TIntegrationType)
builder.createDoc(workbench.class.Application, core.space.Model, {
label: setting.string.Setting,
icon: setting.icon.Setting,
hidden: true,
navigatorModel: {
specials: [
{
id: 'setting',
label: setting.string.Setting,
icon: setting.icon.Setting,
component: setting.component.Setting
},
{
id: 'integrations',
label: setting.string.Integrations,
icon: setting.icon.Integrations,
component: setting.component.Integrations
},
{
id: 'statuses',
label: setting.string.ManageStatuses,
icon: task.icon.ManageStatuses,
component: setting.component.ManageStatuses
},
{
id: 'support',
label: setting.string.Support,
icon: setting.icon.Support,
component: setting.component.Support
},
{
id: 'privacy',
label: setting.string.Privacy,
icon: setting.icon.Privacy,
component: setting.component.Privacy
},
{
id: 'terms',
label: setting.string.Terms,
icon: setting.icon.Terms,
component: setting.component.Terms
}
],
spaces: []
}
}, setting.ids.SettingApp)
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: setting.string.Setting,
icon: setting.icon.Setting,
hidden: true,
navigatorModel: {
specials: [
{
id: 'profile',
label: setting.string.EditProfile,
icon: setting.icon.EditProfile,
component: setting.component.Profile
},
{
id: 'password',
label: setting.string.ChangePassword,
icon: setting.icon.Password,
component: setting.component.Password
},
{
id: 'setting',
label: setting.string.Setting,
icon: setting.icon.Setting,
component: setting.component.Setting
},
{
id: 'integrations',
label: setting.string.Integrations,
icon: setting.icon.Integrations,
component: setting.component.Integrations
},
{
id: 'statuses',
label: setting.string.ManageStatuses,
icon: task.icon.ManageStatuses,
component: setting.component.ManageStatuses
},
{
id: 'support',
label: setting.string.Support,
icon: setting.icon.Support,
component: setting.component.Support
},
{
id: 'privacy',
label: setting.string.Privacy,
icon: setting.icon.Privacy,
component: setting.component.Privacy
},
{
id: 'terms',
label: setting.string.Terms,
icon: setting.icon.Terms,
component: setting.component.Terms
}
],
spaces: []
}
},
setting.ids.SettingApp
)
}

View File

@ -213,6 +213,7 @@ p:last-child { margin-block-end: 0; }
.mt-3 { margin-top: .75rem; }
.mt-4 { margin-top: 1rem; }
.mt-5 { margin-top: 1.25rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-9 { margin-top: 2.25rem; }
.mt-10 { margin-top: 2.5rem; }
.mt-14 { margin-top: 3.5rem; }

View File

@ -17,8 +17,12 @@
import { setMetadata } from '@anticrm/platform'
import type { Metadata } from '@anticrm/platform'
export function setMetadataLocalStorage(id: Metadata<string>, value: string): void {
localStorage.setItem(id, value)
export function setMetadataLocalStorage(id: Metadata<string>, value: string | null): void {
if (value) {
localStorage.setItem(id, value)
} else {
localStorage.removeItem(id)
}
setMetadata(id, value)
}

View File

@ -28,3 +28,5 @@ export default async () => ({
LoginApp
}
})
export * from './utils'

View File

@ -163,12 +163,14 @@ export async function getWorkspaces (): Promise<Workspace[]> {
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
return [{
_id: '' as any,
workspace: 'DEV WORKSPACE',
organisation: '',
accounts: []
}]
return [
{
_id: '' as any,
workspace: 'DEV WORKSPACE',
organisation: '',
accounts: []
}
]
}
}
@ -361,3 +363,65 @@ export async function signUpJoin (
return [unknownError(err), undefined]
}
}
export async function changeName (first: string, last: string): Promise<void> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
throw new Error('accounts url not specified')
}
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
return
}
}
const token = fetchMetadataLocalStorage(login.metadata.LoginToken) as string
const request: Request<[string, string]> = {
method: 'changeName',
params: [first, last]
}
await fetch(accountsUrl, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: serialize(request)
})
}
export async function changePassword (oldPassword: string, password: string): Promise<void> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
throw new Error('accounts url not specified')
}
const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
if (overrideToken !== undefined) {
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
return
}
}
const token = fetchMetadataLocalStorage(login.metadata.LoginToken) as string
const request: Request<[string, string]> = {
method: 'changePassword',
params: [oldPassword, password]
}
await fetch(accountsUrl, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: serialize(request)
})
}

View File

@ -1,4 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="edit" fill="none" viewBox="0 0 20 20">
<path d="M11.4561 17.0355H17.4999" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.65 3.16233C11.2964 2.38982 12.4583 2.27655 13.2469 2.90978C13.2905 2.94413 14.6912 4.03232 14.6912 4.03232C15.5575 4.55599 15.8266 5.66925 15.2912 6.51882C15.2627 6.56432 7.34329 16.4704 7.34329 16.4704C7.07981 16.7991 6.67986 16.9931 6.25242 16.9978L3.21961 17.0358L2.53628 14.1436C2.44055 13.7369 2.53628 13.3098 2.79975 12.9811L10.65 3.16233Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.18408 5.00098L13.7276 8.49025" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="password" fill="none" viewBox="0 0 20 20">
<ellipse cx="7.11573" cy="12.231" rx="4.61573" ry="4.61573" stroke="currentColor"/>
<path d="M10.5762 8.76966L14.6149 4.7309M16.3458 3L14.6149 4.7309M14.6149 4.7309L17.4998 7.61573" stroke="currentColor" stroke-linecap="round"/>
</symbol>
<symbol id="settings" viewBox="0 0 16 16">
<path d="M14,5.8l-1.1-1.9c-0.6-1-0.9-1.5-1.5-1.8c-0.6-0.3-1.2-0.3-2.3-0.4L8,1.7l-1.1,0c-1.1,0-1.8,0-2.3,0.4 C4,2.4,3.7,2.9,3.1,3.9L2,5.8C1.4,6.8,1.1,7.4,1.1,8S1.4,9.2,2,10.2l1.1,1.9c0.6,1,0.9,1.5,1.5,1.8c0.6,0.3,1.2,0.3,2.3,0.4l1.1,0 l1.1,0c1.1,0,1.8,0,2.3-0.4c0.6-0.3,0.9-0.9,1.5-1.8l1.1-1.9c0.6-1,0.9-1.5,0.9-2.2C14.9,7.4,14.6,6.8,14,5.8z M13.1,9.7L12,11.6 c-0.5,0.9-0.8,1.3-1.1,1.5c-0.3,0.2-0.8,0.2-1.8,0.2l-1.1,0l-1.1,0c-1.1,0-1.5,0-1.8-0.2c-0.3-0.2-0.6-0.6-1.1-1.5L2.9,9.7 C2.3,8.8,2.1,8.4,2.1,8c0-0.4,0.2-0.8,0.7-1.7L4,4.4c0.5-0.9,0.8-1.3,1.1-1.5c0.3-0.2,0.8-0.2,1.8-0.2l1.1,0l1.1,0 c1,0,1.5,0,1.8,0.2c0.3,0.2,0.6,0.6,1.1,1.5l1.1,1.9c0.5,0.9,0.7,1.3,0.7,1.7S13.6,8.8,13.1,9.7z"/>
<path d="M8,5.5C6.6,5.5,5.5,6.6,5.5,8c0,1.4,1.1,2.5,2.5,2.5c1.4,0,2.5-1.1,2.5-2.5C10.5,6.6,9.4,5.5,8,5.5z M8,9.5 C7.2,9.5,6.5,8.8,6.5,8S7.2,6.5,8,6.5S9.5,7.2,9.5,8S8.8,9.5,8,9.5z"/>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -6,12 +6,17 @@
"Support": "Support",
"Privacy": "Privacy",
"Terms": "Terms",
"EditProfile": "Edit profile",
"Folders": "Folders",
"Templates": "Templates",
"Delete": "Delete",
"ChangePassword": "Change password",
"CurrentPassword": "Current password",
"NewPassword": "New password",
"Disconnect": "Disconnect",
"Save": "Save",
"Saving": "Saving...",
"Saved": "Saved",
"Add": "Add",
"LearnMore": "Learn more"
}

View File

@ -18,6 +18,8 @@ import setting, { settingId } from '@anticrm/setting'
const icons = require('../assets/icons.svg')
loadMetadata(setting.icon, {
EditProfile: `${icons}#edit`,
Password: `${icons}#password`,
Setting: `${icons}#settings`,
Integrations: `${icons}#integration`,
Support: `${icons}#support`,

View File

@ -35,10 +35,13 @@
"@anticrm/core": "~0.6.11",
"svelte": "^3.37.0",
"@anticrm/setting": "~0.6.0",
"@anticrm/contact": "~0.6.2",
"@anticrm/attachment": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/login-resources": "~0.6.2",
"@anticrm/task": "~0.6.0",
"@anticrm/task-resources": "~0.6.0",
"@anticrm/workbench": "~0.6.1"

View File

@ -0,0 +1,105 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 setting from '@anticrm/setting'
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
import { changePassword } from '@anticrm/login-resources'
let oldPassword: string = ''
let password: string = ''
let password2: string = ''
let label = setting.string.Save
let saved = false
$: disabled =
password.length === 0 || oldPassword.length === 0 || oldPassword === password || password !== password2 || saved
async function save (): Promise<void> {
label = setting.string.Saving
saved = true
try {
await changePassword(oldPassword, password)
label = setting.string.Saved
} catch (e) {
console.log(e)
label = setting.string.Save
saved = false
}
}
</script>
<div class="flex-col h-full">
<div class="flex-row-center header">
<div class="content-color mr-3"><Icon icon={setting.icon.Password} size={'medium'} /></div>
<div class="fs-title"><Label label={setting.string.ChangePassword} /></div>
</div>
<div class="container flex-row-streach flex-grow">
<div class="flex-grow flex-col">
<div class="flex-grow flex-col">
<div>
<EditBox
password
placeholder="Enter current password"
label={setting.string.CurrentPassword}
maxWidth="20rem"
bind:value={oldPassword}
/>
</div>
<div class="mt-6">
<EditBox
password
placeholder="Enter new password"
label={setting.string.NewPassword}
maxWidth="20rem"
bind:value={password}
/>
</div>
<div class="mt-6">
<EditBox
password
placeholder="Repeat new password"
label={setting.string.NewPassword}
maxWidth="20rem"
bind:value={password2}
/>
</div>
</div>
<div class="flex-row-reverse">
<Button
{label}
{disabled}
primary
on:click={() => {
save()
}}
/>
</div>
</div>
</div>
</div>
<style lang="scss">
.header {
padding: 0 1.75rem 0 2.5rem;
height: 4rem;
min-height: 4rem;
border-bottom: 1px solid var(--theme-menu-divider);
}
.container {
padding: 2.5rem;
}
</style>

View File

@ -0,0 +1,206 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 presentation, {
AttributeEditor,
Channels,
createQuery,
EditableAvatar,
getClient
} from '@anticrm/presentation'
import setting from '@anticrm/setting'
import { CircleButton, EditBox, Icon, IconAdd, Label, showPopup } from '@anticrm/ui'
import contact, { Employee, EmployeeAccount, getFirstName, getLastName } from '@anticrm/contact'
import { getCurrentAccount, Ref } from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import attachment from '@anticrm/attachment'
import { changeName } from '@anticrm/login-resources'
const client = getClient()
let account: EmployeeAccount | undefined
let employee: Employee | undefined
let firstName: string
let lastName: string
const accountQ = createQuery()
const employeeQ = createQuery()
$: accountQ.query(
contact.class.EmployeeAccount,
{
_id: getCurrentAccount()._id as Ref<EmployeeAccount>
},
(res) => {
account = res[0]
},
{ limit: 1 }
)
$: account && updateQuery(account.employee)
function updateQuery (id: Ref<Employee>): void {
employeeQ.query(
contact.class.Employee,
{
_id: id
},
(res) => {
employee = res[0]
firstName = getFirstName(employee.name)
lastName = getLastName(employee.name)
},
{ limit: 1 }
)
}
function saveChannels (result: any) {
if (employee !== undefined && result !== undefined) {
employee.channels = result
client.updateDoc(employee._class, employee.space, employee._id, { channels: result })
}
}
async function onAvatarDone (e: any) {
if (employee === undefined) return
const uploadFile = await getResource(attachment.helper.UploadFile)
const deleteFile = await getResource(attachment.helper.DeleteFile)
const { file: avatar } = e.detail
if (employee.avatar != null) {
await deleteFile(employee.avatar)
}
const uuid = await uploadFile(avatar)
await client.updateDoc(employee._class, employee.space, employee._id, {
avatar: uuid
})
}
</script>
<div class="flex-col h-full">
<div class="flex-row-center header">
<div class="content-color mr-3"><Icon icon={setting.icon.EditProfile} size={'medium'} /></div>
<div class="fs-title"><Label label={setting.string.EditProfile} /></div>
</div>
{#if employee}
<div class="container flex-row-streach flex-grow">
<div class="mr-8">
<EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} />
</div>
<div class="flex-grow flex-col">
<div class="flex-col">
<div class="name">
<EditBox
placeholder="John"
maxWidth="20rem"
bind:value={firstName}
on:change={() => {
changeName(firstName, lastName)
}}
/>
</div>
<div class="name">
<EditBox
placeholder="Appleseed"
maxWidth="20rem"
bind:value={lastName}
on:change={() => {
changeName(firstName, lastName)
}}
/>
</div>
<div class="location">
<AttributeEditor maxWidth="20rem" _class={contact.class.Person} object={employee} key="city" />
</div>
</div>
<div class="separator" />
<div class="flex-between channels">
<div class="flex-row-center">
{#if !employee.channels || employee.channels.length === 0}
<CircleButton
icon={IconAdd}
size={'small'}
selected
on:click={(ev) =>
showPopup(
contact.component.SocialEditor,
{ values: employee?.channels ?? [] },
ev.target,
(result) => {
saveChannels(result)
}
)}
/>
<span><Label label={presentation.string.AddSocialLinks} /></span>
{:else}
<Channels value={employee.channels} size={'small'} />
<div class="ml-1">
<CircleButton
icon={contact.icon.Edit}
size={'small'}
selected
on:click={(ev) =>
showPopup(
contact.component.SocialEditor,
{ values: employee?.channels ?? [] },
ev.target,
(result) => {
saveChannels(result)
}
)}
/>
</div>
{/if}
</div>
</div>
</div>
</div>
{/if}
</div>
<style lang="scss">
.header {
padding: 0 1.75rem 0 2.5rem;
height: 4rem;
min-height: 4rem;
border-bottom: 1px solid var(--theme-menu-divider);
}
.container {
padding: 2.5rem;
}
.location {
margin-top: 0.25rem;
font-size: 0.75rem;
}
.name {
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
.channels {
margin-top: 0.75rem;
span {
margin-left: 0.5rem;
}
}
.separator {
margin: 1rem 0;
height: 1px;
background-color: var(--theme-card-divider);
}
</style>

View File

@ -13,6 +13,9 @@
// limitations under the License.
//
import { Resources } from '@anticrm/platform'
import Profile from './components/Profile.svelte'
import Password from './components/Password.svelte'
import Setting from './components/Setting.svelte'
import Integrations from './components/Integrations.svelte'
import ManageStatuses from './components/statuses/ManageStatuses.svelte'
@ -20,8 +23,10 @@ import Support from './components/Support.svelte'
import Privacy from './components/Privacy.svelte'
import Terms from './components/Terms.svelte'
export default async () => ({
export default async (): Promise<Resources> => ({
component: {
Profile,
Password,
Setting,
Integrations,
Support,

View File

@ -56,6 +56,8 @@ export default plugin(settingId, {
IntegrationType: '' as Ref<Class<IntegrationType>>
},
component: {
Profile: '' as AnyComponent,
Password: '' as AnyComponent,
Setting: '' as AnyComponent,
Integrations: '' as AnyComponent,
ManageStatuses: '' as AnyComponent,
@ -75,9 +77,18 @@ export default plugin(settingId, {
Delete: '' as IntlString,
Disconnect: '' as IntlString,
Add: '' as IntlString,
LearnMore: '' as IntlString
LearnMore: '' as IntlString,
EditProfile: '' as IntlString,
ChangePassword: '' as IntlString,
CurrentPassword: '' as IntlString,
NewPassword: '' as IntlString,
Save: '' as IntlString,
Saving: '' as IntlString,
Saved: '' as IntlString
},
icon: {
EditProfile: '' as Asset,
Password: '' as Asset,
Setting: '' as Asset,
Integrations: '' as Asset,
Support: '' as Asset,

View File

@ -14,11 +14,13 @@
-->
<script lang="ts">
import { getCurrentLocation, Label, navigate } from '@anticrm/ui'
import { Avatar, getClient } from '@anticrm/presentation'
import { getCurrentLocation, Label, navigate, setMetadataLocalStorage } from '@anticrm/ui'
import { Avatar, createQuery, getClient } from '@anticrm/presentation'
import workbench, { Application, SpecialNavModel } from '@anticrm/workbench'
import setting from '@anticrm/setting'
import { Ref } from '@anticrm/core'
import login from '@anticrm/login'
import { getCurrentAccount, Ref } from '@anticrm/core'
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
const client = getClient()
async function getItems(): Promise<SpecialNavModel[] | undefined> {
@ -26,6 +28,23 @@
return app?.navigatorModel?.specials
}
let account: EmployeeAccount | undefined
let employee: Employee | undefined
const accountQ = createQuery()
const employeeQ = createQuery()
$: accountQ.query(contact.class.EmployeeAccount, {
_id: getCurrentAccount()._id as Ref<EmployeeAccount>
}, (res) => {
account = res[0]
}, { limit: 1 })
$: account && employeeQ.query(contact.class.Employee, {
_id: account.employee
}, (res) => {
employee = res[0]
}, { limit: 1 })
function selectSpecial (sp: SpecialNavModel): void {
const loc = getCurrentLocation()
loc.path[1] = setting.ids.SettingApp
@ -33,27 +52,48 @@
loc.path.length = 3
navigate(loc)
}
function signOut (): void {
setMetadataLocalStorage(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
navigate({ path: [login.component.LoginApp] })
}
function filterItems (items: SpecialNavModel[]): SpecialNavModel[] {
return items?.filter((p) => p.id !== 'profile' && p.id !== 'password')
}
function editProfile (items: SpecialNavModel[] | undefined): void {
const profile = items?.find((p) => p.id === 'profile')
if (profile === undefined) return
selectSpecial(profile)
}
</script>
<div class="account-popup">
<div class="popup-bg" />
<div class="flex-row-center header">
<Avatar size={'medium'} />
<div class="ml-2 flex-col">
<div class="overflow-label fs-bold caption-color">User Name</div>
<div class="overflow-label small-text content-dark-color">rosamund.chen@gmail.com</div>
{#await getItems() then items}
<div class="flex-row-center header item" on:click={() => { editProfile(items) }}>
{#if employee}
<Avatar avatar={employee.avatar} size={'medium'} />
{/if}
<div class="ml-2 flex-col">
{#if account}
<div class="overflow-label fs-bold caption-color">{formatName(account.name)}</div>
<div class="overflow-label small-text content-dark-color">{account.email}</div>
{/if}
</div>
</div>
</div>
<div class="content">
{#await getItems() then items}
<div class="content">
{#if items}
{#each items as item }
{#each filterItems(items) as item }
<div class="item" on:click={() => selectSpecial(item)}><Label label={item.label} /></div>
{/each}
{/if}
{/await}
<div class="item"><Label label={'Sign out'} /></div>
</div>
<div class="item" on:click={signOut}><Label label={'Sign out'} /></div>
</div>
{/await}
</div>
<style lang="scss">
@ -68,7 +108,8 @@
.header {
flex-shrink: 0;
margin: .75rem 1rem;
margin: 0.5rem;
margin-bottom: 0.25rem;
}
.content {
@ -76,17 +117,16 @@
flex-grow: 1;
margin: 0 .5rem 1rem;
height: fit-content;
}
.item {
padding: .5rem;
color: var(--theme-content-accent-color);
border-radius: .5rem;
cursor: pointer;
.item {
padding: .5rem;
color: var(--theme-content-accent-color);
border-radius: .5rem;
cursor: pointer;
&:hover {
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-focused);
}
&:hover {
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-focused);
}
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import type { Client, Ref, Space } from '@anticrm/core'
import { Client, getCurrentAccount, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import { Avatar, createQuery, setClient } from '@anticrm/presentation'
import {
@ -38,6 +38,7 @@
import NavHeader from './NavHeader.svelte'
import Navigator from './Navigator.svelte'
import SpaceView from './SpaceView.svelte'
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
export let client: Client
@ -107,6 +108,21 @@
visibileNav = !visibileNav
closeTooltip()
}
let account: EmployeeAccount | undefined
let employee: Employee | undefined
const accountQ = createQuery()
const employeeQ = createQuery()
$: accountQ.query(contact.class.EmployeeAccount, {
_id: getCurrentAccount()._id as Ref<EmployeeAccount>
}, (res) => {
account = res[0]
}, { limit: 1 })
$: account && employeeQ.query(contact.class.Employee, {
_id: account.employee
}, (res) => {
employee = res[0]
}, { limit: 1 })
</script>
{#if client}
@ -139,7 +155,9 @@
showPopup(AccountPopup, {}, 'account')
}}
>
<Avatar size={'medium'} />
{#if employee}
<Avatar avatar={employee.avatar} size={'medium'} />
{/if}
</div>
</div>
</div>

View File

@ -413,6 +413,70 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
}
}
/**
* @public
*/
export async function changePassword (db: Db, token: string, oldPassword: string, password: string): Promise<void> {
const { email } = decode(token, getSecret())
const account = await getAccountInfo(db, email, oldPassword)
const salt = randomBytes(32)
const hash = hashWithSalt(password, salt)
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } })
}
/**
* @public
*/
export async function changeName (db: Db, token: string, first: string, last: string): Promise<void> {
const { email } = decode(token, getSecret())
const account = await getAccount(db, email)
if (account === null) {
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
}
await db.collection<Account>(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { first, last } })
account.first = first
account.last = last
const workspaces = await db
.collection<Workspace>(WORKSPACE_COLLECTION)
.find({ _id: { $in: account.workspaces } })
.toArray()
const promises: Promise<void>[] = []
for (const ws of workspaces) {
promises.push(updateEmployeeAccount(account, ws.workspace))
}
await Promise.all(promises)
}
async function updateEmployeeAccount (account: Account, workspace: string): Promise<void> {
const connection = await connect(getTransactor(), workspace, account.email)
try {
const ops = new TxOperations(connection, core.account.System)
const name = combineName(account.first, account.last)
const employeeAccount = await ops.findOne(contact.class.EmployeeAccount, { email: account.email })
if (employeeAccount === undefined) return
await ops.update(employeeAccount, {
name
})
const employee = await ops.findOne(contact.class.Employee, { _id: employeeAccount.employee })
if (employee === undefined) return
await ops.update(employee, {
name
})
} finally {
await connection.close()
}
}
/**
* @public
*/
@ -483,7 +547,9 @@ export const methods = {
createWorkspace: wrap(createUserWorkspace),
assignWorkspace: wrap(assignWorkspace),
removeWorkspace: wrap(removeWorkspace),
listWorkspaces: wrap(listWorkspaces)
listWorkspaces: wrap(listWorkspaces),
changeName: wrap(changeName),
changePassword: wrap(changePassword)
// updateAccount: wrap(updateAccount)
}