mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Edit user profile (#831)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
e92cf3272a
commit
3d21b2de1e
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -28,3 +28,5 @@ export default async () => ({
|
||||
LoginApp
|
||||
}
|
||||
})
|
||||
|
||||
export * from './utils'
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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 |
@ -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"
|
||||
}
|
||||
|
@ -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`,
|
||||
|
@ -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"
|
||||
|
105
plugins/setting-resources/src/components/Password.svelte
Normal file
105
plugins/setting-resources/src/components/Password.svelte
Normal 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>
|
206
plugins/setting-resources/src/components/Profile.svelte
Normal file
206
plugins/setting-resources/src/components/Profile.svelte
Normal 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>
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user