@things-factory/auth-ui 8.0.0-beta.9 → 8.0.0

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 (100) hide show
  1. package/client/auth-style-sign.ts +194 -0
  2. package/client/bootstrap.ts +51 -0
  3. package/client/components/abstract-auth-page.ts +301 -0
  4. package/client/components/abstract-password-reset.ts +168 -0
  5. package/client/components/abstract-sign.ts +127 -0
  6. package/client/components/change-password.ts +153 -0
  7. package/client/components/contact-us.ts +113 -0
  8. package/client/components/create-domain-popup.ts +141 -0
  9. package/client/components/create-role.ts +123 -0
  10. package/client/components/create-user.ts +95 -0
  11. package/client/components/credential-manager.ts +64 -0
  12. package/client/components/delete-user-popup.ts +117 -0
  13. package/client/components/domain-switch.ts +127 -0
  14. package/client/components/invite-customer.ts +104 -0
  15. package/client/components/invite-user.ts +96 -0
  16. package/client/components/my-login-history.ts +101 -0
  17. package/client/components/ownership-transfer-popup.ts +110 -0
  18. package/client/components/partner-info-card.ts +89 -0
  19. package/client/components/partner-role-editor.ts +153 -0
  20. package/client/components/profile-component.ts +332 -0
  21. package/client/components/role-edit-form.ts +92 -0
  22. package/client/components/role-privilege-editor.ts +267 -0
  23. package/client/components/role-selector.ts +102 -0
  24. package/client/components/user-role-editor.ts +499 -0
  25. package/client/constants/application.ts +9 -0
  26. package/client/constants/index.ts +1 -0
  27. package/client/entries/auth/activate.ts +272 -0
  28. package/client/entries/auth/checkin.ts +190 -0
  29. package/client/entries/auth/forgot-password.ts +103 -0
  30. package/client/entries/auth/reset-password.ts +22 -0
  31. package/client/entries/auth/result.ts +193 -0
  32. package/client/entries/auth/signin.ts +18 -0
  33. package/client/entries/auth/signup.ts +109 -0
  34. package/client/entries/auth/unlock-user.ts +22 -0
  35. package/client/entries/oauth2/oauth2-decision-error-page.ts +50 -0
  36. package/client/entries/oauth2/oauth2-decision-page.ts +196 -0
  37. package/client/entries/public/home.ts +246 -0
  38. package/client/index.ts +124 -0
  39. package/client/pages/app-binding/app-binding.ts +423 -0
  40. package/client/pages/app-binding/app-bindings.ts +171 -0
  41. package/client/pages/appliance/appliance.ts +452 -0
  42. package/client/pages/appliance/home.ts +177 -0
  43. package/client/pages/appliance/register.ts +183 -0
  44. package/client/pages/application/application.ts +428 -0
  45. package/client/pages/application/applications.ts +182 -0
  46. package/client/pages/application/register.ts +211 -0
  47. package/client/pages/attribute/attribute-set-item-list.ts +237 -0
  48. package/client/pages/attribute/attribute-set-management.ts +282 -0
  49. package/client/pages/auth-provider/auth-provider-management.ts +381 -0
  50. package/client/pages/domain/domain-management.ts +410 -0
  51. package/client/pages/partner/partner-management.ts +112 -0
  52. package/client/pages/profile.ts +32 -0
  53. package/client/pages/role/role-management.ts +134 -0
  54. package/client/pages/user/user-management.ts +224 -0
  55. package/client/route.ts +67 -0
  56. package/client/themes/auth-theme.css +65 -0
  57. package/client/utils/password-rule.ts +37 -0
  58. package/dist-client/components/abstract-auth-page.js +10 -10
  59. package/dist-client/components/abstract-auth-page.js.map +1 -1
  60. package/dist-client/components/abstract-password-reset.d.ts +2 -1
  61. package/dist-client/components/abstract-password-reset.js +14 -7
  62. package/dist-client/components/abstract-password-reset.js.map +1 -1
  63. package/dist-client/components/abstract-sign.js +11 -12
  64. package/dist-client/components/abstract-sign.js.map +1 -1
  65. package/dist-client/components/contact-us.d.ts +1 -1
  66. package/dist-client/components/contact-us.js +7 -10
  67. package/dist-client/components/contact-us.js.map +1 -1
  68. package/dist-client/components/create-user.js +5 -28
  69. package/dist-client/components/create-user.js.map +1 -1
  70. package/dist-client/components/invite-user.js +11 -19
  71. package/dist-client/components/invite-user.js.map +1 -1
  72. package/dist-client/components/ownership-transfer-popup.js +3 -3
  73. package/dist-client/components/ownership-transfer-popup.js.map +1 -1
  74. package/dist-client/components/profile-component.d.ts +1 -5
  75. package/dist-client/components/profile-component.js +4 -64
  76. package/dist-client/components/profile-component.js.map +1 -1
  77. package/dist-client/components/role-privilege-editor.js +1 -2
  78. package/dist-client/components/role-privilege-editor.js.map +1 -1
  79. package/dist-client/components/user-role-editor.js +18 -18
  80. package/dist-client/components/user-role-editor.js.map +1 -1
  81. package/dist-client/entries/auth/checkin.js +1 -1
  82. package/dist-client/entries/auth/checkin.js.map +1 -1
  83. package/dist-client/entries/auth/forgot-password.js +2 -11
  84. package/dist-client/entries/auth/forgot-password.js.map +1 -1
  85. package/dist-client/entries/auth/signup.js +7 -13
  86. package/dist-client/entries/auth/signup.js.map +1 -1
  87. package/dist-client/index.js +1 -1
  88. package/dist-client/index.js.map +1 -1
  89. package/dist-client/pages/user/user-management.d.ts +1 -5
  90. package/dist-client/pages/user/user-management.js +7 -6
  91. package/dist-client/pages/user/user-management.js.map +1 -1
  92. package/dist-client/tsconfig.tsbuildinfo +1 -1
  93. package/dist-server/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +12 -12
  95. package/server/index.ts +0 -0
  96. package/translations/en.json +2 -6
  97. package/translations/ja.json +2 -6
  98. package/translations/ko.json +2 -6
  99. package/translations/ms.json +2 -6
  100. package/translations/zh.json +2 -6
@@ -0,0 +1,194 @@
1
+ import { css } from 'lit'
2
+
3
+ export const AUTH_STYLE_SIGN = css`
4
+ :host {
5
+ display: flex;
6
+ background-color: var(--md-sys-color-primary);
7
+ color: var(--md-sys-color-on-primary);
8
+
9
+ --md-filled-text-field-supporting-text-color: var(--md-sys-color-on-primary);
10
+ }
11
+
12
+ :host *:focus {
13
+ outline: none;
14
+ }
15
+
16
+ :host * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ .wrap {
21
+ display: block;
22
+ width: 450px;
23
+ min-width: 350px;
24
+ margin: 0 auto;
25
+ padding-bottom: 100px;
26
+ text-align: center;
27
+ }
28
+
29
+ .auth-brand {
30
+ color: var(--md-sys-color-on-primary);
31
+ }
32
+
33
+ .auth-brand img {
34
+ margin: 15% auto 5px auto;
35
+ width: 100px;
36
+ border: 3px solid var(--md-sys-color-on-primary);
37
+ border-radius: 25px;
38
+ box-shadow: var(--box-shadow);
39
+ }
40
+ .name {
41
+ display: block;
42
+ font: var(--auth-brand-name);
43
+ text-shadow: var(--auth-brand-name-shadow);
44
+ }
45
+ .auth-brand .welcome-msg {
46
+ font: var(--auth-brand-welcome-msg);
47
+ }
48
+ .auth-form {
49
+ display: grid;
50
+ grid-gap: var(--spacing-medium);
51
+ grid-template-columns: 1fr 1fr;
52
+ }
53
+
54
+ form {
55
+ grid-column: 1 / -1;
56
+ display: grid;
57
+ grid-template-columns: 1fr 1fr;
58
+ grid-gap: var(--spacing-medium);
59
+ align-items: center;
60
+ }
61
+
62
+ h3 {
63
+ grid-column: 1 / -1;
64
+ margin: 50px 0 0 0;
65
+ font: var(--auth-title-font);
66
+ color: var(--auth-title-color, var(--md-sys-color-on-primary));
67
+ text-transform: uppercase;
68
+ }
69
+
70
+ .field {
71
+ grid-column: 1 / -1;
72
+ text-align: left;
73
+ }
74
+
75
+ .submit-buttons-container {
76
+ grid-column: 1 / -1;
77
+ text-align: center;
78
+
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 10px;
82
+ }
83
+
84
+ .fingerprint {
85
+ color: var(--md-sys-color-on-primary);
86
+ border: 1.5px solid var(--md-sys-color-on-primary);
87
+ border-radius: 20%;
88
+ width: 36px;
89
+ height: 36px;
90
+ }
91
+
92
+ .field md-filled-text-field {
93
+ grid-column: 1 / -1;
94
+ width: 100%;
95
+ }
96
+
97
+ md-text-button,
98
+ md-elevated-button {
99
+ grid-column: 1 / -1;
100
+ flex: 1;
101
+ }
102
+
103
+ .wrap .link {
104
+ text-decoration: none;
105
+ justify-self: flex-start;
106
+ }
107
+
108
+ .wrap .link md-text-button {
109
+ --md-text-button-label-text-color: var(--md-sys-color-on-primary);
110
+ --md-text-button-focus-label-text-color: var(--md-sys-color-on-primary);
111
+ --md-text-button-hover-label-text-color: var(--md-sys-color-on-primary);
112
+ }
113
+
114
+ .wrap .link md-icon {
115
+ color: var(--md-sys-color-on-primary);
116
+ }
117
+
118
+ #locale-area {
119
+ display: flex;
120
+ grid-column: 1 / -1;
121
+ padding: 0 var(--spacing-medium);
122
+ }
123
+
124
+ #locale-area > label {
125
+ display: flex;
126
+ align-items: center;
127
+ color: var(--md-sys-color-on-primary);
128
+ --md-icon-size: 16px;
129
+ }
130
+
131
+ #locale-selector {
132
+ font-size: 16px;
133
+ width: 100%;
134
+ }
135
+
136
+ #locale-selector {
137
+ --i18n-selector-field-border: none;
138
+ --i18n-selector-field-background-color: none;
139
+ --i18n-selector-field-font-size: 14px;
140
+ --i18n-selector-field-color: var(--md-sys-color-on-primary);
141
+ }
142
+
143
+ .lottie-container {
144
+ width: 100%;
145
+ height: 300px;
146
+ position: absolute;
147
+ left: 0;
148
+ bottom: 0;
149
+ pointer-events: none;
150
+ }
151
+ .lottie-container lottie-player {
152
+ position: absolute;
153
+ bottom: -6%;
154
+ width: 100%;
155
+ height: auto;
156
+ }
157
+
158
+ @media (max-width: 450px) {
159
+ .wrap {
160
+ width: 85%;
161
+ min-width: 320px;
162
+ }
163
+ .auth-form {
164
+ grid-template-columns: 1fr;
165
+ }
166
+ .auth-brand img {
167
+ margin: 12% auto 5px auto;
168
+ width: 75px;
169
+ }
170
+ h3 {
171
+ margin: 15px 0 0 0;
172
+ }
173
+ .lottie-container {
174
+ overflow: hidden;
175
+ height: 200px;
176
+ pointer-events: none;
177
+ }
178
+ .lottie-container lottie-player {
179
+ width: 1200px;
180
+ left: -30%;
181
+ }
182
+ }
183
+
184
+ @media screen and (min-width: 1400px) {
185
+ .wrap {
186
+ padding-bottom: 150px;
187
+ }
188
+ }
189
+ @media screen and (min-width: 2500px) {
190
+ .wrap {
191
+ padding-bottom: 280px;
192
+ }
193
+ }
194
+ `
@@ -0,0 +1,51 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/i18n/ox-i18n.js'
3
+ import '@operato/attribute/grist-editor' /* for register data-grist editor type 'attributes' */
4
+
5
+ import { html } from 'lit-html'
6
+
7
+ import { navigate, store } from '@operato/shell'
8
+ import { TOOL_POSITION } from '@operato/layout'
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'
11
+
12
+ import { auth } from '@things-factory/auth-base/dist-client/auth.js'
13
+ import { ADD_MORENDA } from '@things-factory/more-base/client'
14
+
15
+ export default function bootstrap() {
16
+ registerGristEditor('privilege', OxGristEditorPrivilege)
17
+ registerGristRenderer('privilege', OxGristRendererJson5)
18
+
19
+ /* add user profile morenda */
20
+ store.dispatch({
21
+ type: ADD_MORENDA,
22
+ morenda: {
23
+ icon: html` <md-icon>account_circle</md-icon> `,
24
+ name: html` <ox-i18n msgid="text.auth profile"></ox-i18n> `,
25
+ position: TOOL_POSITION.FRONT_END,
26
+ action: () => {
27
+ navigate('profile')
28
+ }
29
+ }
30
+ })
31
+
32
+ /* add sign-out morenda */
33
+ store.dispatch({
34
+ type: ADD_MORENDA,
35
+ morenda: {
36
+ icon: html` <md-icon>exit_to_app</md-icon> `,
37
+ name: html` <ox-i18n msgid="field.sign out"></ox-i18n> `,
38
+ position: TOOL_POSITION.FRONT_END,
39
+ action: () => {
40
+ auth.signout()
41
+ }
42
+ }
43
+ })
44
+
45
+ /*
46
+ Get user profile information from server.
47
+ As soon as response received, auth.on('profile', ...) handlers start to work.
48
+ It's very important point to build UI for the user.
49
+ */
50
+ auth.profile()
51
+ }
@@ -0,0 +1,301 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@material/web/button/elevated-button.js'
3
+ import '@material/web/button/text-button.js'
4
+ import '@material/web/textfield/filled-text-field.js'
5
+
6
+ import '@operato/lottie-player'
7
+ import '@operato/i18n/ox-i18n.js'
8
+ import '@operato/i18n/ox-i18n-selector.js'
9
+ import '@operato/layout/ox-snack-bar.js'
10
+
11
+ import { css, html, LitElement, nothing } from 'lit'
12
+ import { property, query, state } from 'lit/decorators.js'
13
+
14
+ import { i18next, localize } from '@operato/i18n'
15
+ import { ScrollbarStyles } from '@operato/styles'
16
+ import { isSafari } from '@operato/utils'
17
+
18
+ import { AUTH_STYLE_SIGN } from '../auth-style-sign.js'
19
+
20
+ export abstract class AbstractAuthPage extends localize(i18next)(LitElement) {
21
+ static styles = [
22
+ ScrollbarStyles,
23
+ css`
24
+ :host {
25
+ position: relative;
26
+ overflow: hidden;
27
+
28
+ display: flex;
29
+ flex-direction: row;
30
+
31
+ width: 100vw;
32
+ height: 100vh;
33
+ height: 100dvh;
34
+ }
35
+
36
+ .content {
37
+ flex: 1;
38
+ overflow: auto;
39
+ }
40
+
41
+ .home {
42
+ position: absolute;
43
+ padding: var(--padding-dufault, 9px);
44
+ left: 20px;
45
+ top: 10px;
46
+ color: var(--md-sys-color-on-primary);
47
+ }
48
+
49
+ div.field {
50
+ margin-bottom: var(--spacing-medium);
51
+ }
52
+
53
+ [hidden] {
54
+ display: none;
55
+ }
56
+
57
+ #snackbar {
58
+ width: 100%;
59
+ z-index: 10;
60
+ }
61
+
62
+ @media print {
63
+ :host {
64
+ width: 100%;
65
+ height: 100%;
66
+ min-height: 100vh;
67
+ min-height: 100dvh;
68
+ }
69
+ }
70
+ `,
71
+ AUTH_STYLE_SIGN
72
+ ]
73
+
74
+ @property({ type: Object }) data: any
75
+ @property({ type: String }) message?: string
76
+ @property({ type: Object }) detail: any
77
+ @property({ type: String }) redirectTo?: string
78
+
79
+ @query('#form') formEl!: HTMLFormElement
80
+
81
+ private _applicationMeta?: {
82
+ icon: string
83
+ title: string
84
+ description: string
85
+ }
86
+
87
+ get autocompletable() {
88
+ return false
89
+ }
90
+
91
+ render() {
92
+ const { disableUserFavoredLanguage, languages } = this.data || {}
93
+ var { icon, title, description } = this.applicationMeta
94
+
95
+ return html`
96
+ <div class="content md-typescale-display-medium">
97
+ <div class="wrap">
98
+ <div class="auth-brand">
99
+ <img src=${icon} />
100
+ <strong class="name">${title}</strong>
101
+ <span class="welcome-msg">${description}</span>
102
+ </div>
103
+
104
+ <div class="auth-form">
105
+ <h3><ox-i18n msgid="title.${this.pageName}"></ox-i18n></h3>
106
+
107
+ <form
108
+ id="form"
109
+ action="${this.actionUrl}"
110
+ method="post"
111
+ autocomplete=${this.autocompletable ? 'on' : 'off'}
112
+ @keypress=${e => {
113
+ if (e.key == 'Enter') this._onSubmit(e)
114
+ }}
115
+ >
116
+ ${this.formfields}
117
+ </form>
118
+ ${this.links}
119
+ ${!disableUserFavoredLanguage
120
+ ? html` <div id="locale-area">
121
+ <label for="locale-selector"><md-icon>language</md-icon></label>
122
+ <ox-i18n-selector
123
+ id="locale-selector"
124
+ value=${i18next.language || 'en-US'}
125
+ .languages=${languages}
126
+ @change=${e => {
127
+ var locale = e.detail
128
+ if (!locale) return
129
+
130
+ i18next.changeLanguage(locale)
131
+ }}
132
+ ></ox-i18n-selector>
133
+ </div>`
134
+ : nothing}
135
+ </div>
136
+ </div>
137
+
138
+ <md-icon-button class="home" @click=${e => (window.location.href = '/')}
139
+ ><md-icon>home</md-icon></md-icon-button
140
+ >
141
+ <ox-snack-bar id="snackbar" level="error" .message=${this.message}></ox-snack-bar>
142
+
143
+ ${isSafari()
144
+ ? html``
145
+ : html`
146
+ <div class="lottie-container">
147
+ <lottie-player autoplay loop src="../../assets/images/background-animation.json"></lottie-player>
148
+ </div>
149
+ `}
150
+ </div>
151
+ `
152
+ }
153
+
154
+ firstUpdated() {
155
+ setTimeout(() => {
156
+ ;(this.renderRoot.querySelector('md-filled-text-field') as any).focus()
157
+ }, 100)
158
+
159
+ this.formEl.reset = () => {
160
+ this.formElements.filter(el => !(el.hidden || el.type == 'hidden')).forEach(el => (el.value = ''))
161
+ }
162
+ }
163
+
164
+ updated(changed) {
165
+ if (changed.has('data') && this.data) {
166
+ this.message = this.data.message
167
+ this.redirectTo = this.data.redirectTo
168
+ }
169
+ }
170
+
171
+ abstract get pageName(): string
172
+ abstract get actionUrl(): string
173
+
174
+ get formElements(): HTMLInputElement[] {
175
+ return Array.from(this.formEl.querySelectorAll('[name]'))
176
+ }
177
+
178
+ get formfields() {
179
+ const email = this.data?.email || ''
180
+ // .validationMessage=${String(i18next.t('text.invalid-email'))}
181
+
182
+ return html`
183
+ <input id="redirectTo" type="hidden" name="redirectTo" .value=${this.redirectTo || '/'} />
184
+
185
+ <div class="field">
186
+ <md-filled-text-field
187
+ name="email"
188
+ type="email"
189
+ label=${String(i18next.t('field.email'))}
190
+ required
191
+ .value=${email}
192
+ autocomplete="off"
193
+ autocapitalize="off"
194
+ ><md-icon slot="leading-icon">mail</md-icon></md-filled-text-field
195
+ >
196
+ </div>
197
+ <div class="field">
198
+ <md-filled-text-field
199
+ name="password"
200
+ type="password"
201
+ label=${String(i18next.t('field.password'))}
202
+ autocomplete="off"
203
+ required
204
+ ><md-icon slot="leading-icon">vpn_key</md-icon></md-filled-text-field
205
+ >
206
+ </div>
207
+
208
+ <md-elevated-button class="ui" type="submit" raised @click=${e => this._onSubmit(e)}>
209
+ <ox-i18n msgid="field.${this.pageName}"> </ox-i18n>
210
+ </md-elevated-button>
211
+ `
212
+ }
213
+
214
+ get links() {
215
+ const { disableUserSignupProcess, ssoLinks = [] } = this.data || {}
216
+
217
+ return html`
218
+ ${!disableUserSignupProcess
219
+ ? html`
220
+ <a class="link" href="/auth/signup">
221
+ <md-text-button>
222
+ <md-icon slot="icon">add_task</md-icon>
223
+ <ox-i18n msgid="field.sign up"></ox-i18n>
224
+ </md-text-button>
225
+ </a>
226
+ <a class="link" href="/auth/forgot-password">
227
+ <md-text-button>
228
+ <md-icon slot="icon">lock_open</md-icon>
229
+ <ox-i18n msgid="field.forgot-password"></ox-i18n>
230
+ </md-text-button>
231
+ </a>
232
+ `
233
+ : nothing}
234
+ ${ssoLinks.map(
235
+ sso => html`
236
+ <a class="link" href=${sso.link}>
237
+ <md-text-button>
238
+ <md-icon slot="icon">badge</md-icon>
239
+ ${i18next.t('label.signin with', { title: sso.title })}
240
+ </md-text-button>
241
+ </a>
242
+ `
243
+ )}
244
+ `
245
+ }
246
+
247
+ async _onSubmit(e) {
248
+ if (this.checkValidity()) {
249
+ this.submit()
250
+ }
251
+ }
252
+
253
+ checkValidity() {
254
+ return this.formElements.every(el => el.checkValidity())
255
+ }
256
+
257
+ abstract submit()
258
+
259
+ showSnackbar({ level, message, timer = 3000 }: { level?: string; message?: string; timer?: number } = {}) {
260
+ const snackbar = this.renderRoot.querySelector('#snackbar') as HTMLElement & {
261
+ level: string
262
+ message: string
263
+ active: boolean
264
+ }
265
+
266
+ if (level) snackbar.level = level
267
+ if (message) snackbar.message = message
268
+ snackbar.active = true
269
+
270
+ if (timer > -1)
271
+ setTimeout(() => {
272
+ this.hideSnackbar()
273
+ }, timer)
274
+ }
275
+
276
+ hideSnackbar() {
277
+ const snackbar = this.renderRoot.querySelector('#snackbar') as HTMLElement & {
278
+ level: string
279
+ message: string
280
+ active: boolean
281
+ }
282
+
283
+ snackbar.active = false
284
+ }
285
+
286
+ get applicationMeta() {
287
+ if (!this._applicationMeta) {
288
+ var iconLink: HTMLLinkElement | null = document.querySelector('link[rel="application-icon"]')
289
+ var titleMeta: HTMLMetaElement | null = document.querySelector('meta[name="application-name"]')
290
+ var descriptionMeta: HTMLMetaElement | null = document.querySelector('meta[name="application-description"]')
291
+
292
+ this._applicationMeta = {
293
+ icon: iconLink?.href || '',
294
+ title: titleMeta?.content || 'Things Factory',
295
+ description: descriptionMeta?.content || 'Reimagining Software'
296
+ }
297
+ }
298
+
299
+ return this._applicationMeta
300
+ }
301
+ }
@@ -0,0 +1,168 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@material/web/button/elevated-button.js'
3
+ import '@material/web/textfield/filled-text-field.js'
4
+
5
+ import '@operato/lottie-player'
6
+ import '../components/profile-component'
7
+ import '@operato/i18n/ox-i18n.js'
8
+ import '@operato/i18n/ox-i18n-selector.js'
9
+ import '@operato/layout/ox-snack-bar.js'
10
+
11
+ import { css, html, nothing } from 'lit'
12
+ import { property, query } from 'lit/decorators.js'
13
+
14
+ import { i18next } from '@operato/i18n'
15
+ import { isSafari } from '@operato/utils'
16
+
17
+ import { AUTH_STYLE_SIGN } from '../auth-style-sign'
18
+ import { generatePasswordPatternHelp, generatePasswordPatternRegExp } from '../utils/password-rule'
19
+ import { AbstractAuthPage } from './abstract-auth-page'
20
+
21
+ export abstract class AbstractPasswordReset extends AbstractAuthPage {
22
+ static styles = [
23
+ css`
24
+ :host {
25
+ position: relative;
26
+ overflow: hidden;
27
+
28
+ display: flex;
29
+ flex-direction: row;
30
+
31
+ width: 100vw;
32
+ height: 100vh;
33
+ height: 100dvh;
34
+ }
35
+
36
+ @media print {
37
+ :host {
38
+ width: 100%;
39
+ height: 100%;
40
+ min-height: 100vh;
41
+ min-height: 100dvh;
42
+ }
43
+ }
44
+ `,
45
+ AUTH_STYLE_SIGN
46
+ ]
47
+
48
+ @property({ type: Object }) data: any
49
+ @property({ type: String }) token?: string
50
+
51
+ @query('#confirm-password') confirmPass!: HTMLElement
52
+
53
+ private passwordPattern: string = ''
54
+ private passwordHelp: string = ''
55
+
56
+ abstract get submitButtonLabel(): string
57
+
58
+ render() {
59
+ var { icon, title, description } = this.applicationMeta
60
+ const { disableUserFavoredLanguage, languages } = this.data || {}
61
+
62
+ return html`
63
+ <div class="wrap">
64
+ <div class="auth-brand">
65
+ <img src=${icon} />
66
+ <strong class="name">${title}</strong>
67
+ <span class="welcome-msg">${description}</span>
68
+ </div>
69
+
70
+ <div class="auth-form">
71
+ <h3><ox-i18n msgid="title.${this.title}"></ox-i18n></h3>
72
+ <form
73
+ id="form"
74
+ action="${this.actionUrl}"
75
+ method="post"
76
+ @keypress=${e => {
77
+ if (e.key == 'Enter') this._onSubmit(e)
78
+ }}
79
+ >
80
+ <input name="token" type="hidden" .value=${this.token || ''} required />
81
+ <div class="field">
82
+ <md-filled-text-field
83
+ name="password"
84
+ type="password"
85
+ label=${String(i18next.t('label.password'))}
86
+ pattern=${this.passwordPattern || ''}
87
+ supporting-text=${this.passwordHelp}
88
+ autocomplete="off"
89
+ @input=${e => {
90
+ var val = e.target.value
91
+ this.confirmPass.setAttribute('pattern', val.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '[$&]'))
92
+ }}
93
+ required
94
+ ></md-filled-text-field>
95
+ </div>
96
+
97
+ <div class="field">
98
+ <md-filled-text-field
99
+ id="confirm-password"
100
+ name="confirm-password"
101
+ type="password"
102
+ label=${String(i18next.t('field.confirm password'))}
103
+ autocomplete="off"
104
+ required
105
+ ></md-filled-text-field>
106
+ </div>
107
+
108
+ <md-elevated-button id="submit-button" type="submit" @click=${e => this._onSubmit(e)}>
109
+ <ox-i18n msgid="${this.submitButtonLabel}"></ox-i18n>
110
+ </md-elevated-button>
111
+
112
+ ${!disableUserFavoredLanguage
113
+ ? html` <div id="locale-area">
114
+ <label for="locale-selector"><md-icon>language</md-icon></label>
115
+ <ox-i18n-selector
116
+ id="locale-selector"
117
+ value=${i18next.language || 'en-US'}
118
+ .languages=${languages}
119
+ @change=${e => {
120
+ var locale = e.detail
121
+ if (!locale) return
122
+
123
+ i18next.changeLanguage(locale)
124
+ }}
125
+ ></ox-i18n-selector>
126
+ </div>`
127
+ : nothing}
128
+ </form>
129
+ </div>
130
+ </div>
131
+ <ox-snack-bar id="snackbar" level="error" .message=${this.message}></ox-snack-bar>
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
+ `}
140
+ `
141
+ }
142
+
143
+ updated(changed) {
144
+ super.updated(changed)
145
+ if (changed.has('data')) {
146
+ this.token = this.data.token
147
+ }
148
+
149
+ if (changed.has('message')) {
150
+ if (!this.message) {
151
+ this.hideSnackbar()
152
+ } else {
153
+ this.showSnackbar({
154
+ timer: -1
155
+ })
156
+ }
157
+ }
158
+ }
159
+
160
+ languageUpdated() {
161
+ this.passwordPattern = generatePasswordPatternRegExp(this.data.passwordRule).source
162
+ this.passwordHelp = generatePasswordPatternHelp(this.data.passwordRule)
163
+ }
164
+
165
+ async submit() {
166
+ this.formEl.submit()
167
+ }
168
+ }