@things-factory/auth-ui 8.0.5 → 8.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/client/bootstrap.ts +7 -3
  2. package/client/components/abstract-auth-page.ts +10 -10
  3. package/client/components/abstract-password-reset.ts +8 -13
  4. package/client/components/abstract-sign.ts +13 -13
  5. package/client/components/contact-us.ts +11 -8
  6. package/client/components/create-user.ts +27 -5
  7. package/client/components/invite-user.ts +18 -10
  8. package/client/components/ownership-transfer-popup.ts +3 -3
  9. package/client/components/profile-component.ts +64 -4
  10. package/client/components/user-role-editor.ts +18 -18
  11. package/client/entries/auth/checkin.ts +2 -9
  12. package/client/entries/auth/forgot-password.ts +2 -1
  13. package/client/entries/auth/signup.ts +13 -7
  14. package/client/index.ts +1 -1
  15. package/client/pages/domain/domain-management.ts +22 -2
  16. package/client/pages/user/user-management.ts +8 -9
  17. package/dist-client/bootstrap.d.ts +1 -1
  18. package/dist-client/bootstrap.js +2 -2
  19. package/dist-client/bootstrap.js.map +1 -1
  20. package/dist-client/components/abstract-auth-page.js +10 -10
  21. package/dist-client/components/abstract-auth-page.js.map +1 -1
  22. package/dist-client/components/abstract-password-reset.d.ts +1 -2
  23. package/dist-client/components/abstract-password-reset.js +6 -13
  24. package/dist-client/components/abstract-password-reset.js.map +1 -1
  25. package/dist-client/components/abstract-sign.js +12 -11
  26. package/dist-client/components/abstract-sign.js.map +1 -1
  27. package/dist-client/components/contact-us.d.ts +1 -1
  28. package/dist-client/components/contact-us.js +10 -7
  29. package/dist-client/components/contact-us.js.map +1 -1
  30. package/dist-client/components/create-user.js +28 -5
  31. package/dist-client/components/create-user.js.map +1 -1
  32. package/dist-client/components/invite-user.js +19 -11
  33. package/dist-client/components/invite-user.js.map +1 -1
  34. package/dist-client/components/ownership-transfer-popup.js +3 -3
  35. package/dist-client/components/ownership-transfer-popup.js.map +1 -1
  36. package/dist-client/components/profile-component.d.ts +5 -1
  37. package/dist-client/components/profile-component.js +64 -4
  38. package/dist-client/components/profile-component.js.map +1 -1
  39. package/dist-client/components/user-role-editor.js +18 -18
  40. package/dist-client/components/user-role-editor.js.map +1 -1
  41. package/dist-client/entries/auth/checkin.d.ts +1 -1
  42. package/dist-client/entries/auth/checkin.js +5 -12
  43. package/dist-client/entries/auth/checkin.js.map +1 -1
  44. package/dist-client/entries/auth/forgot-password.js +2 -1
  45. package/dist-client/entries/auth/forgot-password.js.map +1 -1
  46. package/dist-client/entries/auth/signup.js +13 -7
  47. package/dist-client/entries/auth/signup.js.map +1 -1
  48. package/dist-client/index.js +1 -1
  49. package/dist-client/index.js.map +1 -1
  50. package/dist-client/pages/domain/domain-management.d.ts +2 -0
  51. package/dist-client/pages/domain/domain-management.js +18 -2
  52. package/dist-client/pages/domain/domain-management.js.map +1 -1
  53. package/dist-client/pages/user/user-management.d.ts +5 -1
  54. package/dist-client/pages/user/user-management.js +6 -7
  55. package/dist-client/pages/user/user-management.js.map +1 -1
  56. package/dist-client/tsconfig.tsbuildinfo +1 -1
  57. package/dist-server/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +4 -4
  59. package/translations/en.json +4 -3
  60. package/translations/ja.json +4 -3
  61. package/translations/ko.json +4 -3
  62. package/translations/ms.json +4 -3
  63. package/translations/zh.json +4 -3
@@ -7,12 +7,16 @@ import { html } from 'lit-html'
7
7
  import { navigate, store } from '@operato/shell'
8
8
  import { TOOL_POSITION } from '@operato/layout'
9
9
  import { OxGristEditorPrivilege } from '@operato/app/grist-editor/ox-grist-editor-privilege.js'
10
- import { registerEditor as registerGristEditor, registerRenderer as registerGristRenderer, OxGristRendererJson5 } from '@operato/data-grist'
10
+ import {
11
+ registerEditor as registerGristEditor,
12
+ registerRenderer as registerGristRenderer,
13
+ OxGristRendererJson5
14
+ } from '@operato/data-grist'
11
15
 
12
16
  import { auth } from '@things-factory/auth-base/dist-client/auth.js'
13
17
  import { ADD_MORENDA } from '@things-factory/more-base/client'
14
18
 
15
- export default function bootstrap() {
19
+ export default async function bootstrap() {
16
20
  registerGristEditor('privilege', OxGristEditorPrivilege)
17
21
  registerGristRenderer('privilege', OxGristRendererJson5)
18
22
 
@@ -47,5 +51,5 @@ export default function bootstrap() {
47
51
  As soon as response received, auth.on('profile', ...) handlers start to work.
48
52
  It's very important point to build UI for the user.
49
53
  */
50
- auth.profile()
54
+ await auth.profile()
51
55
  }
@@ -176,22 +176,22 @@ export abstract class AbstractAuthPage extends localize(i18next)(LitElement) {
176
176
  }
177
177
 
178
178
  get formfields() {
179
- const email = this.data?.email || ''
180
- // .validationMessage=${String(i18next.t('text.invalid-email'))}
179
+ const username = this.data?.username || ''
180
+ // .validationMessage=${String(i18next.t('text.invalid-username'))}
181
181
 
182
182
  return html`
183
183
  <input id="redirectTo" type="hidden" name="redirectTo" .value=${this.redirectTo || '/'} />
184
184
 
185
185
  <div class="field">
186
186
  <md-filled-text-field
187
- name="email"
188
- type="email"
189
- label=${String(i18next.t('field.email'))}
187
+ name="username"
188
+ type="text"
189
+ label=${String(i18next.t('field.user-id or email'))}
190
190
  required
191
- .value=${email}
191
+ .value=${username}
192
192
  autocomplete="off"
193
193
  autocapitalize="off"
194
- ><md-icon slot="leading-icon">mail</md-icon></md-filled-text-field
194
+ ><md-icon slot="leading-icon">id_card</md-icon></md-filled-text-field
195
195
  >
196
196
  </div>
197
197
  <div class="field">
@@ -201,11 +201,11 @@ export abstract class AbstractAuthPage extends localize(i18next)(LitElement) {
201
201
  label=${String(i18next.t('field.password'))}
202
202
  autocomplete="off"
203
203
  required
204
- ><md-icon slot="leading-icon">vpn_key</md-icon></md-filled-text-field
204
+ ><md-icon slot="leading-icon">password</md-icon></md-filled-text-field
205
205
  >
206
206
  </div>
207
207
 
208
- <md-elevated-button class="ui" type="submit" raised @click=${e => this._onSubmit(e)}>
208
+ <md-elevated-button class="ui" type="button" raised @click=${e => this._onSubmit(e)}>
209
209
  <ox-i18n msgid="field.${this.pageName}"> </ox-i18n>
210
210
  </md-elevated-button>
211
211
  `
@@ -235,7 +235,7 @@ export abstract class AbstractAuthPage extends localize(i18next)(LitElement) {
235
235
  sso => html`
236
236
  <a class="link" href=${sso.link}>
237
237
  <md-text-button>
238
- <md-icon slot="icon">badge</md-icon>
238
+ <md-icon slot="icon">id_card</md-icon>
239
239
  ${i18next.t('label.signin with', { title: sso.title })}
240
240
  </md-text-button>
241
241
  </a>
@@ -2,17 +2,16 @@ import '@material/web/icon/icon.js'
2
2
  import '@material/web/button/elevated-button.js'
3
3
  import '@material/web/textfield/filled-text-field.js'
4
4
 
5
- import '@operato/lottie-player'
6
- import '../components/profile-component'
7
5
  import '@operato/i18n/ox-i18n.js'
8
6
  import '@operato/i18n/ox-i18n-selector.js'
9
7
  import '@operato/layout/ox-snack-bar.js'
10
8
 
9
+ import '../components/profile-component'
10
+
11
11
  import { css, html, nothing } from 'lit'
12
12
  import { property, query } from 'lit/decorators.js'
13
13
 
14
14
  import { i18next } from '@operato/i18n'
15
- import { isSafari } from '@operato/utils'
16
15
 
17
16
  import { AUTH_STYLE_SIGN } from '../auth-style-sign'
18
17
  import { generatePasswordPatternHelp, generatePasswordPatternRegExp } from '../utils/password-rule'
@@ -91,7 +90,8 @@ export abstract class AbstractPasswordReset extends AbstractAuthPage {
91
90
  this.confirmPass.setAttribute('pattern', val.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '[$&]'))
92
91
  }}
93
92
  required
94
- ></md-filled-text-field>
93
+ ><md-icon slot="leading-icon">password</md-icon></md-filled-text-field
94
+ >
95
95
  </div>
96
96
 
97
97
  <div class="field">
@@ -102,7 +102,8 @@ export abstract class AbstractPasswordReset extends AbstractAuthPage {
102
102
  label=${String(i18next.t('field.confirm password'))}
103
103
  autocomplete="off"
104
104
  required
105
- ></md-filled-text-field>
105
+ ><md-icon slot="leading-icon">password</md-icon></md-filled-text-field
106
+ >
106
107
  </div>
107
108
 
108
109
  <md-elevated-button id="submit-button" type="submit" @click=${e => this._onSubmit(e)}>
@@ -128,20 +129,14 @@ export abstract class AbstractPasswordReset extends AbstractAuthPage {
128
129
  </form>
129
130
  </div>
130
131
  </div>
131
- <ox-snack-bar id="snackbar" level="error" .message=${this.message}></ox-snack-bar>
132
132
 
133
- ${isSafari()
134
- ? html``
135
- : html`
136
- <div class="lottie-container">
137
- <lottie-player autoplay loop src="../../assets/images/background-animation.json"></lottie-player>
138
- </div>
139
- `}
133
+ <ox-snack-bar id="snackbar" level="error" .message=${this.message}></ox-snack-bar>
140
134
  `
141
135
  }
142
136
 
143
137
  updated(changed) {
144
138
  super.updated(changed)
139
+
145
140
  if (changed.has('data')) {
146
141
  this.token = this.data.token
147
142
  }
@@ -44,7 +44,7 @@ export abstract class AbstractSign extends AbstractAuthPage {
44
44
  }
45
45
 
46
46
  get formfields() {
47
- const email = this.data?.email || ''
47
+ const username = this.data?.username || ''
48
48
  const autocompletable = this.autocompletable
49
49
 
50
50
  return html`
@@ -52,22 +52,23 @@ export abstract class AbstractSign extends AbstractAuthPage {
52
52
 
53
53
  <div class="field">
54
54
  <md-filled-text-field
55
- name="email"
56
- type="email"
57
- label=${String(i18next.t('field.email'))}
55
+ name="username"
56
+ type="text"
57
+ label=${String(i18next.t('field.user-id or email'))}
58
58
  required
59
- .value=${email}
60
- autocomplete=${autocompletable ? "username" : "off"}
59
+ .value=${username}
60
+ autocomplete=${autocompletable ? 'username' : 'off'}
61
61
  autocapitalize="off"
62
+ pattern="^(?:[A-Za-z0-9]*|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,})$"
62
63
  @input=${(e: Event) => {
63
64
  const target = e.target as HTMLInputElement
64
65
  if (target.validity.typeMismatch) {
65
- target.setCustomValidity(i18next.t('text.invalid-email'))
66
+ target.setCustomValidity(i18next.t('text.invalid-username'))
66
67
  } else {
67
68
  target.setCustomValidity('')
68
69
  }
69
70
  }}
70
- ><md-icon slot="leading-icon">mail</md-icon></md-filled-text-field
71
+ ><md-icon slot="leading-icon">id_card</md-icon></md-filled-text-field
71
72
  >
72
73
  </div>
73
74
  <div class="field">
@@ -75,14 +76,14 @@ export abstract class AbstractSign extends AbstractAuthPage {
75
76
  name="password"
76
77
  type="password"
77
78
  label=${String(i18next.t('field.password'))}
78
- autocomplete=${autocompletable ? "current-password" : "off"}
79
+ autocomplete=${autocompletable ? 'current-password' : 'off'}
79
80
  required
80
- ><md-icon slot="leading-icon">vpn_key</md-icon></md-filled-text-field
81
+ ><md-icon slot="leading-icon">password</md-icon></md-filled-text-field
81
82
  >
82
83
  </div>
83
84
 
84
85
  <div class="submit-buttons-container">
85
- <md-elevated-button class="submit-button" type="submit" raised @click=${e => this._onSubmit(e)}>
86
+ <md-elevated-button class="submit-button" type="button" raised @click=${e => this._onSubmit(e)}>
86
87
  <ox-i18n msgid="field.${this.pageName}"> </ox-i18n>
87
88
  </md-elevated-button>
88
89
  ${isAvailableWebauthn
@@ -106,7 +107,7 @@ export abstract class AbstractSign extends AbstractAuthPage {
106
107
 
107
108
  if (verification.verified) {
108
109
  const { redirectURL } = verification
109
-
110
+
110
111
  if (redirectURL) {
111
112
  window.location.href = redirectURL
112
113
  }
@@ -116,7 +117,6 @@ export abstract class AbstractSign extends AbstractAuthPage {
116
117
  message: verification.message
117
118
  })
118
119
  }
119
-
120
120
  } catch (error) {
121
121
  notify({
122
122
  level: 'error',
@@ -33,22 +33,23 @@ export class ContactUs extends localize(i18next)(LitElement) {
33
33
  ]
34
34
  }
35
35
 
36
- @query('#dialog') dialog!: HTMLElement & { open: boolean }
36
+ @query('#dialog') dialog!: HTMLElement & { show: () => void }
37
37
  @query('#subject-input') subjectInput!: HTMLInputElement
38
38
  @query('#sender-input') senderInput!: HTMLInputElement
39
39
  @query('#content-input') contentInput!: HTMLInputElement
40
40
 
41
41
  render() {
42
42
  return html`
43
- <md-elevated-button @click=${e => (this.dialog.open = true)}>${i18next.t('button.need help')}</md-elevated-button>
43
+ <md-elevated-button @click=${e => this.dialog.show()}>${i18next.t('button.need help')}</md-elevated-button>
44
44
 
45
45
  <md-dialog id="dialog" heading=${i18next.t('title.need help')}>
46
- <form action="" method="POST">
46
+ <form action="" method="post">
47
47
  <input id="subject-input" name="subject" type="hidden" />
48
48
  <input id="sender-input" name="sender" type="hidden" />
49
49
  <input id="content-input" name="content" type="hidden" />
50
50
  </form>
51
- <div id="input-form">
51
+
52
+ <div id="input-form" slot="content">
52
53
  <md-filled-text-field
53
54
  type="text"
54
55
  label=${i18next.t('label.subject')}
@@ -59,6 +60,7 @@ export class ContactUs extends localize(i18next)(LitElement) {
59
60
  this.subjectInput.value = val
60
61
  }}
61
62
  ></md-filled-text-field>
63
+
62
64
  <md-filled-text-field
63
65
  type="text"
64
66
  name="sender"
@@ -69,6 +71,7 @@ export class ContactUs extends localize(i18next)(LitElement) {
69
71
  this.senderInput.value = val
70
72
  }}
71
73
  ></md-filled-text-field>
74
+
72
75
  <md-filled-text-field
73
76
  name="content"
74
77
  type="textarea"
@@ -80,11 +83,11 @@ export class ContactUs extends localize(i18next)(LitElement) {
80
83
  this.contentInput.value = val
81
84
  }}
82
85
  ></md-filled-text-field>
86
+
87
+ <md-elevated-button slot="primaryAction" type="button" @click=${e => this._submit(e)}
88
+ >${i18next.t('button.submit')}</md-elevated-button
89
+ >
83
90
  </div>
84
- <md-elevated-button slot="primaryAction" type="submit" @click=${e => this._submit(e)}
85
- >${i18next.t('button.submit')}</md-elevated-button
86
- >
87
- <md-text-button slot="secondaryAction" dialogAction="cancel">${i18next.t('button.cancel')}</md-text-button>
88
91
  </md-dialog>
89
92
  `
90
93
  }
@@ -6,6 +6,10 @@ import { customElement, query } from 'lit/decorators.js'
6
6
 
7
7
  import { i18next } from '@operato/i18n'
8
8
 
9
+ function capitalize(str) {
10
+ return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''
11
+ }
12
+
9
13
  @customElement('create-user')
10
14
  class CreateUser extends LitElement {
11
15
  static styles = css`
@@ -18,7 +22,7 @@ class CreateUser extends LitElement {
18
22
  box-shadow: var(--box-shadow);
19
23
 
20
24
  display: grid;
21
- grid-template-columns: 1fr 2fr auto;
25
+ grid-template-columns: 1fr 2fr 2fr auto;
22
26
  gap: 5px 15px;
23
27
  clear: both;
24
28
  max-width: var(--input-container-max-width);
@@ -44,26 +48,42 @@ class CreateUser extends LitElement {
44
48
  `
45
49
 
46
50
  @query('[name=name]') nameInput!: HTMLInputElement
51
+ @query('[name=username]') usernameInput!: HTMLInputElement
47
52
  @query('[name=email]') emailInput!: HTMLInputElement
48
53
 
49
54
  render() {
50
55
  return html`
56
+ <md-filled-text-field
57
+ type="text"
58
+ name="username"
59
+ label=${capitalize(i18next.t('label.user-id'))}
60
+ pattern="^(?:[A-Za-z0-9]*|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,})$"
61
+ ><md-icon slot="leading-icon">badge</md-icon></md-filled-text-field
62
+ >
63
+
51
64
  <md-filled-text-field
52
65
  type="text"
53
66
  name="name"
54
- label=${String(i18next.t('label.x name', { x: i18next.t('label.user') }))}
55
- ></md-filled-text-field>
67
+ label=${capitalize(i18next.t('label.x name', { x: i18next.t('label.user') }))}
68
+ ><md-icon slot="leading-icon">id_card</md-icon></md-filled-text-field
69
+ >
56
70
 
57
- <md-filled-text-field type="email" name="email" label=${String(i18next.t('field.email'))}></md-filled-text-field>
71
+ <md-filled-text-field type="email" name="email" label=${capitalize(i18next.t('field.email'))}
72
+ ><md-icon slot="leading-icon">mail</md-icon></md-filled-text-field
73
+ >
58
74
 
59
75
  <md-outlined-button @click=${this.onCreateUser.bind(this)}
60
- >${String(i18next.t('button.create'))}</md-outlined-button
76
+ >${capitalize(i18next.t('button.create'))}</md-outlined-button
61
77
  >
62
78
  `
63
79
  }
64
80
 
65
81
  async onCreateUser() {
66
82
  try {
83
+ if (!this.usernameInput.value) {
84
+ throw new Error(i18next.t('error.value is empty', { value: 'name' }))
85
+ }
86
+
67
87
  if (!this.emailInput.checkValidity()) {
68
88
  throw new Error(i18next.t('error.not valid pattern of type', { type: 'e-mail' }))
69
89
  }
@@ -73,12 +93,14 @@ class CreateUser extends LitElement {
73
93
  }
74
94
 
75
95
  const user = {
96
+ username: this.usernameInput.value.trim(),
76
97
  name: this.nameInput.value.trim(),
77
98
  email: this.emailInput.value.trim()
78
99
  }
79
100
 
80
101
  await this.dispatchEvent(new CustomEvent('create-user', { detail: user }))
81
102
 
103
+ this.usernameInput.value = ''
82
104
  this.nameInput.value = ''
83
105
  this.emailInput.value = ''
84
106
  } catch (e: any) {
@@ -32,11 +32,19 @@ class InviteUser extends localize(i18next)(LitElement) {
32
32
  }
33
33
  `
34
34
 
35
- @query('input[name=email]') emailInput!: HTMLInputElement
35
+ @query('input[name=username]') userIdInput!: HTMLInputElement
36
36
 
37
37
  render() {
38
38
  return html`
39
- <input name="email" type="email" required name="invite-email" autocapitalize="off" />
39
+ <input
40
+ name="username"
41
+ type="text"
42
+ required
43
+ name="username"
44
+ autocapitalize="off"
45
+ placeholder=${String(i18next.t('text.user invitation prompt'))}
46
+ pattern="^(?:[A-Za-z0-9]*|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,})$"
47
+ />
40
48
  <md-outlined-button @click=${this.invite.bind(this)}>
41
49
  <md-icon slot="icon">group_add</md-icon>${String(i18next.t('label.invite user'))}
42
50
  </md-outlined-button>
@@ -45,8 +53,8 @@ class InviteUser extends localize(i18next)(LitElement) {
45
53
 
46
54
  async invite() {
47
55
  try {
48
- if (!this.emailInput.checkValidity()) {
49
- throw new Error(i18next.t('error.not valid pattern of type', { type: 'e-mail' }))
56
+ if (!this.userIdInput.checkValidity()) {
57
+ throw new Error(i18next.t('error.not valid pattern of type', { type: 'user-id or email' }))
50
58
  }
51
59
 
52
60
  if (
@@ -57,7 +65,7 @@ class InviteUser extends localize(i18next)(LitElement) {
57
65
  cancelButton: { text: i18next.t('button.cancel') }
58
66
  })
59
67
  ) {
60
- await this.inviteUser(this.emailInput.value)
68
+ await this.inviteUser(this.userIdInput.value)
61
69
 
62
70
  this.dispatchEvent(new CustomEvent('invitationCompleted'))
63
71
  }
@@ -73,14 +81,14 @@ class InviteUser extends localize(i18next)(LitElement) {
73
81
  }
74
82
  }
75
83
 
76
- async inviteUser(email) {
84
+ async inviteUser(username) {
77
85
  const response = await client.mutate({
78
86
  mutation: gql`
79
- mutation inviteUser($email: EmailAddress!) {
80
- inviteUser(email: $email)
87
+ mutation inviteUser($username: String!) {
88
+ inviteUser(username: $username)
81
89
  }
82
90
  `,
83
- variables: { email },
91
+ variables: { username },
84
92
  context: gqlContext()
85
93
  })
86
94
 
@@ -90,7 +98,7 @@ class InviteUser extends localize(i18next)(LitElement) {
90
98
  confirmButton: { text: i18next.t('button.confirm') }
91
99
  })
92
100
 
93
- this.emailInput.value = ''
101
+ this.userIdInput.value = ''
94
102
  }
95
103
  }
96
104
  }
@@ -75,11 +75,11 @@ class OwnershipTransferPopup extends localize(i18next)(LitElement) {
75
75
  ) {
76
76
  const response = await client.mutate({
77
77
  mutation: gql`
78
- mutation transferOwner($email: EmailAddress!) {
79
- transferOwner(email: $email)
78
+ mutation transferOwner($username: String!) {
79
+ transferOwner(username: $username)
80
80
  }
81
81
  `,
82
- variables: { email: this.user.email },
82
+ variables: { username: this.user.username || this.user.email },
83
83
  context: gqlContext()
84
84
  })
85
85
 
@@ -111,7 +111,7 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
111
111
  `
112
112
  ]
113
113
 
114
- @property({ type: String }) userId?: string
114
+ @property({ type: String }) username?: string
115
115
  @property({ type: String }) email?: string
116
116
  @property({ type: String }) name?: string
117
117
 
@@ -128,7 +128,9 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
128
128
  looseCharacterLength?: number
129
129
  } = {}
130
130
 
131
+ @query('#username') usernameEl!: HTMLInputElement
131
132
  @query('#name') nameEl!: HTMLInputElement
133
+ @query('#email') emailEl!: HTMLInputElement
132
134
  @query('#locale') localeEl!: HTMLInputElement
133
135
 
134
136
  async connectedCallback(): Promise<void> {
@@ -167,11 +169,11 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
167
169
 
168
170
  setCredential(credential) {
169
171
  if (credential) {
170
- this.userId = credential.id
172
+ this.username = credential.username
171
173
  this.name = credential.name
172
174
  this.email = credential.email
173
175
  } else {
174
- this.userId = ''
176
+ this.username = ''
175
177
  this.name = ''
176
178
  this.email = ''
177
179
  }
@@ -180,13 +182,23 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
180
182
  render() {
181
183
  return html`
182
184
  <div class="wrap">
183
- <div class="user">${this.email || ''}</div>
185
+ <div class="user">${this.username || ''}</div>
186
+
187
+ <label for="username"><ox-i18n slot="title" msgid="label.user-id"></ox-i18n></label>
188
+ <input id="username" @change=${e => this.onUsernameChanged(e.target.value)} .value=${this.username || ''} />
189
+
190
+ <hr />
184
191
 
185
192
  <label for="name"><ox-i18n slot="title" msgid="label.name"></ox-i18n></label>
186
193
  <input id="name" @change=${e => this.onNameChanged(e.target.value)} .value=${this.name || ''} />
187
194
 
188
195
  <hr />
189
196
 
197
+ <label for="email"><ox-i18n slot="title" msgid="label.email"></ox-i18n></label>
198
+ <input id="email" type="email" @change=${e => this.onEmailChanged(e.target.value)} .value=${this.email || ''} />
199
+
200
+ <hr />
201
+
190
202
  <label for="locale"><ox-i18n slot="title" msgid="label.language"></ox-i18n></label>
191
203
  <ox-i18n-selector
192
204
  id="locale"
@@ -223,6 +235,30 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
223
235
  `
224
236
  }
225
237
 
238
+ async onUsernameChanged(username) {
239
+ if (!username) return
240
+
241
+ var oldUsername = this.username
242
+
243
+ try {
244
+ const message = await auth.updateProfile({
245
+ username
246
+ })
247
+
248
+ notify({
249
+ level: 'info',
250
+ message
251
+ })
252
+ } catch (e: any) {
253
+ this.usernameEl.value = oldUsername || ''
254
+
255
+ notify({
256
+ level: 'error',
257
+ message: 'message' in e ? e.message : e
258
+ })
259
+ }
260
+ }
261
+
226
262
  async onNameChanged(name) {
227
263
  if (!name) return
228
264
 
@@ -247,6 +283,30 @@ export class ProfileComponent extends localize(i18next)(LitElement) {
247
283
  }
248
284
  }
249
285
 
286
+ async onEmailChanged(email) {
287
+ if (!email) return
288
+
289
+ var oldEmail = this.email
290
+
291
+ try {
292
+ const message = await auth.updateProfile({
293
+ email
294
+ })
295
+
296
+ notify({
297
+ level: 'info',
298
+ message
299
+ })
300
+ } catch (e: any) {
301
+ this.emailEl.value = oldEmail || ''
302
+
303
+ notify({
304
+ level: 'error',
305
+ message: 'message' in e ? e.message : e
306
+ })
307
+ }
308
+ }
309
+
250
310
  async onLocaleChanged(value) {
251
311
  if (!value) return
252
312
 
@@ -113,8 +113,8 @@ class UserRoleEditor extends connect(store)(LitElement) {
113
113
  ? html`
114
114
  <li>
115
115
  <span>
116
- <md-icon>email</md-icon>
117
- ${user.email}
116
+ ${user.username || user.email} (<md-icon>email</md-icon>
117
+ ${user.email})
118
118
  </span>
119
119
  </li>
120
120
  `
@@ -330,8 +330,8 @@ class UserRoleEditor extends connect(store)(LitElement) {
330
330
  if (user?.id) {
331
331
  const response = await client.mutate({
332
332
  mutation: gql`
333
- mutation ($userId: String!, $availableRoles: [ObjectRef!]!, $selectedRoles: [ObjectRef!]!) {
334
- updateUserRoles(userId: $userId, availableRoles: $availableRoles, selectedRoles: $selectedRoles) {
333
+ mutation ($username: String!, $availableRoles: [ObjectRef!]!, $selectedRoles: [ObjectRef!]!) {
334
+ updateUserRoles(username: $username, availableRoles: $availableRoles, selectedRoles: $selectedRoles) {
335
335
  id
336
336
  name
337
337
  roles {
@@ -341,7 +341,7 @@ class UserRoleEditor extends connect(store)(LitElement) {
341
341
  }
342
342
  }
343
343
  `,
344
- variables: { userId: user.id, availableRoles, selectedRoles },
344
+ variables: { username: user.username || user.email, availableRoles, selectedRoles },
345
345
  context: gqlContext()
346
346
  })
347
347
 
@@ -355,11 +355,11 @@ class UserRoleEditor extends connect(store)(LitElement) {
355
355
  async onActivate(user) {
356
356
  const response = await client.mutate({
357
357
  mutation: gql`
358
- mutation activateUser($userId: String!) {
359
- activateUser(userId: $userId)
358
+ mutation activateUser($username: String!) {
359
+ activateUser(username: $username)
360
360
  }
361
361
  `,
362
- variables: { userId: user.id },
362
+ variables: { username: user.username || user.email },
363
363
  context: gqlContext()
364
364
  })
365
365
 
@@ -373,16 +373,16 @@ class UserRoleEditor extends connect(store)(LitElement) {
373
373
  async onInactivate(user) {
374
374
  const response = await client.mutate({
375
375
  mutation: gql`
376
- mutation inactivateUser($userId: String!) {
377
- inactivateUser(userId: $userId)
376
+ mutation inactivateUser($username: String!) {
377
+ inactivateUser(username: $username)
378
378
  }
379
379
  `,
380
- variables: { userId: user.id },
380
+ variables: { username: user.username || user.email },
381
381
  context: gqlContext()
382
382
  })
383
383
 
384
384
  if (!response.errors) {
385
- this.showToast(i18next.t('text.user activated successfully'))
385
+ this.showToast(i18next.t('text.user inactivated successfully'))
386
386
 
387
387
  this.dispatchUserUpdated()
388
388
  }
@@ -414,11 +414,11 @@ class UserRoleEditor extends connect(store)(LitElement) {
414
414
  ) {
415
415
  const response = await client.mutate({
416
416
  mutation: gql`
417
- mutation deleteDomaineUser($email: EmailAddress!) {
418
- deleteDomainUser(email: $email)
417
+ mutation deleteDomaineUser($username: String!) {
418
+ deleteDomainUser(username: $username)
419
419
  }
420
420
  `,
421
- variables: { email: user.email },
421
+ variables: { username: user.username || user.email },
422
422
  context: gqlContext()
423
423
  })
424
424
 
@@ -460,11 +460,11 @@ class UserRoleEditor extends connect(store)(LitElement) {
460
460
  ) {
461
461
  const response = await client.mutate({
462
462
  mutation: gql`
463
- mutation resetPasswordToDefault($userId: String!) {
464
- resetPasswordToDefault(userId: $userId)
463
+ mutation resetPasswordToDefault($username: String!) {
464
+ resetPasswordToDefault(username: $username)
465
465
  }
466
466
  `,
467
- variables: { userId: user.id },
467
+ variables: { username: user.username || user.email },
468
468
  context: gqlContext()
469
469
  })
470
470