@things-factory/auth-ui 10.1.20 → 10.1.26

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.
@@ -8,7 +8,7 @@ import { customElement, property, query, state } from 'lit/decorators.js';
8
8
  import { client } from '@operato/graphql';
9
9
  import { i18next, localize } from '@operato/i18n';
10
10
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
11
- import { CommonHeaderStyles, CommonPopupStyles } from '@operato/styles';
11
+ import { CommonHeaderStyles, CommonPopupStyles, ScrollbarStyles } from '@operato/styles';
12
12
  import './secret-field.js';
13
13
  import { APPLICATION_TYPES } from '../constants/application.js';
14
14
  import { changedValues, filledValues, missingRequired } from '../utils/form-values.js';
@@ -67,27 +67,62 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
67
67
  this.failed = false;
68
68
  this.loadFailed = false;
69
69
  }
70
+ /*
71
+ * ── Where the scrolling happens ─────────────────────────────────────────
72
+ * The button row stays put and only the middle scrolls. That is the house shape for a
73
+ * popup, and this had it wrong: the host itself scrolled, so the row you press to finish
74
+ * walked off the bottom of the card along with everything else.
75
+ *
76
+ * So the host clips, and the content area between the title and the buttons is the one
77
+ * thing with overflow. Two details carry it:
78
+ *
79
+ * min-height: 0 A flex item defaults to min-height auto, which refuses to shrink below
80
+ * its own content. Without this the overflow rule sits there doing
81
+ * nothing and the card grows instead — it reads as a working rule.
82
+ * no padding CommonPopupStyles already pads the host. Padding here again doubles the
83
+ * inset and pushes the scrollbar in off the card edge.
84
+ *
85
+ * ScrollbarStyles gives the scrollbar the product's own shape rather than the browser's.
86
+ *
87
+ * ── Why the in-content buttons need a rule of their own ─────────────────
88
+ * CommonPopupStyles makes controls pill-shaped because a popup is a rounded card and the
89
+ * things inside it follow the card. But it says that through `.buttons`, so only the commit
90
+ * row got it: the actions sitting inside a fieldset — generate a new token, generate a new
91
+ * secret — kept the page radius and read as a different kind of button on the same card.
92
+ * Measured on a live popup: 5px inside the content, 13986px in the row below it.
93
+ */
70
94
  static { this.styles = [
71
95
  CommonHeaderStyles,
72
96
  CommonPopupStyles,
97
+ ScrollbarStyles,
73
98
  css `
74
- :host {
75
- display: flex;
76
- flex-direction: column;
99
+ /* in-content actions follow the card too, not just the commit row. see the note on styles. */
100
+ fieldset md-text-button,
101
+ fieldset md-filled-button {
102
+ --md-text-button-container-shape: var(--popup-control-radius);
103
+ --md-filled-button-container-shape: var(--popup-control-radius);
104
+ }
77
105
 
78
- background-color: var(--md-sys-color-surface);
79
- overflow: auto;
106
+ /* the host does not scroll; the content area below does. see the note on styles. */
107
+ :host {
108
+ overflow: hidden;
80
109
  }
81
110
 
111
+ /* min-height:0 is load-bearing — see the note on styles. */
82
112
  form {
83
113
  flex: 1;
84
- padding: var(--popup-padding);
114
+ min-height: 0;
115
+ overflow-y: auto;
116
+
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: var(--popup-gap);
85
120
  }
86
121
 
87
122
  fieldset {
88
123
  border-radius: var(--border-radius);
89
124
  border: var(--border-dim-color);
90
- margin: 0 0 var(--popup-gap) 0;
125
+ margin: 0;
91
126
  padding: var(--popup-padding);
92
127
  }
93
128
 
@@ -97,19 +132,11 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
97
132
  color: var(--legend-color);
98
133
  }
99
134
 
100
- [field-2column] {
101
- display: grid;
102
- grid-template-columns: 1fr 1fr;
103
- grid-gap: var(--popup-gap);
104
- }
105
-
135
+ /* one labelled box. .row puts two of them side by side; alone it spans the card. */
106
136
  [field] {
107
137
  display: flex;
108
138
  flex-direction: column;
109
- }
110
-
111
- [grid-span] {
112
- grid-column: span 2;
139
+ flex: 1;
113
140
  }
114
141
 
115
142
  label {
@@ -180,60 +207,58 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
180
207
  <form @submit=${(e) => e.preventDefault()}>
181
208
  <fieldset>
182
209
  <legend>${i18next.t('text.application')}</legend>
183
- <div field-2column>
184
- <div field grid-span>
185
- <label for="name">${i18next.t('field.name')}</label>
186
- <input
187
- id="name"
188
- type="text"
189
- name="name"
190
- .value=${app.name || ''}
191
- required
192
- autofocus
193
- ?invalid=${this.missing.includes('name')}
194
- />
195
- ${this.missing.includes('name')
210
+ <div field>
211
+ <label for="name">${i18next.t('field.name')}</label>
212
+ <input
213
+ id="name"
214
+ type="text"
215
+ name="name"
216
+ .value=${app.name || ''}
217
+ required
218
+ autofocus
219
+ ?invalid=${this.missing.includes('name')}
220
+ />
221
+ ${this.missing.includes('name')
196
222
  ? html `<div field-error>${i18next.t('text.this field is required')}</div>`
197
223
  : ''}
198
- </div>
224
+ </div>
199
225
 
200
- <div field grid-span>
201
- <label for="description">${i18next.t('field.description')}</label>
202
- <input id="description" type="text" name="description" .value=${app.description || ''} />
203
- </div>
226
+ <div field>
227
+ <label for="description">${i18next.t('field.description')}</label>
228
+ <input id="description" type="text" name="description" .value=${app.description || ''} />
229
+ </div>
204
230
 
205
- <!-- type=email so the browser checks the shape the server's scalar will check. -->
206
- <div field grid-span>
207
- <label for="email">${i18next.t('field.contact email')}</label>
208
- <input id="email" type="email" name="email" .value=${app.email || ''} />
209
- </div>
231
+ <!-- type=email so the browser checks the shape the server's scalar will check. -->
232
+ <div field>
233
+ <label for="email">${i18next.t('field.contact email')}</label>
234
+ <input id="email" type="email" name="email" .value=${app.email || ''} />
235
+ </div>
210
236
 
211
- <div field grid-span>
212
- <label for="type">${i18next.t('field.type')}</label>
213
- <select id="type" name="type">
214
- ${APPLICATION_TYPES.map(type => html `<option value=${type} ?selected=${type === (app.type || DEFAULT_TYPE)}>${type}</option>`)}
215
- </select>
216
- </div>
237
+ <div field>
238
+ <label for="type">${i18next.t('field.type')}</label>
239
+ <select id="type" name="type">
240
+ ${APPLICATION_TYPES.map(type => html `<option value=${type} ?selected=${type === (app.type || DEFAULT_TYPE)}>${type}</option>`)}
241
+ </select>
242
+ </div>
217
243
 
218
- <div field grid-span>
219
- <label for="url">${i18next.t('field.application url')}</label>
220
- <input id="url" type="url" name="url" .value=${app.url || ''} />
221
- </div>
244
+ <div field>
245
+ <label for="url">${i18next.t('field.application url')}</label>
246
+ <input id="url" type="url" name="url" .value=${app.url || ''} />
247
+ </div>
222
248
 
223
- <div field grid-span>
224
- <label for="redirect-url">${i18next.t('field.redirect url')}</label>
225
- <input id="redirect-url" type="url" name="redirectUrl" .value=${app.redirectUrl || ''} />
226
- </div>
249
+ <div field>
250
+ <label for="redirect-url">${i18next.t('field.redirect url')}</label>
251
+ <input id="redirect-url" type="url" name="redirectUrl" .value=${app.redirectUrl || ''} />
252
+ </div>
227
253
 
228
- <div field grid-span>
229
- <label for="icon">${i18next.t('field.application icon')}</label>
230
- <input id="icon" type="url" name="icon" .value=${app.icon || ''} />
231
- </div>
254
+ <div field>
255
+ <label for="icon">${i18next.t('field.application icon')}</label>
256
+ <input id="icon" type="url" name="icon" .value=${app.icon || ''} />
257
+ </div>
232
258
 
233
- <div field grid-span>
234
- <label for="webhook">${i18next.t('field.webhook')}</label>
235
- <input id="webhook" type="url" name="webhook" .value=${app.webhook || ''} />
236
- </div>
259
+ <div field>
260
+ <label for="webhook">${i18next.t('field.webhook')}</label>
261
+ <input id="webhook" type="url" name="webhook" .value=${app.webhook || ''} />
237
262
  </div>
238
263
  </fieldset>
239
264
 
@@ -265,23 +290,21 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
265
290
  return html `
266
291
  <fieldset>
267
292
  <legend>${i18next.t('text.endpoints')}</legend>
268
- <div field-2column>
269
- <div field grid-span>
270
- <label for="auth-url">${i18next.t('field.auth url')}</label>
271
- <input id="auth-url" type="text" .value=${app.authUrl || ''} readonly />
272
- <div class="note">${i18next.t('text.where an integration asks for an authorization code')}</div>
273
- </div>
293
+ <div field>
294
+ <label for="auth-url">${i18next.t('field.auth url')}</label>
295
+ <input id="auth-url" type="text" .value=${app.authUrl || ''} readonly />
296
+ <div class="note">${i18next.t('text.where an integration asks for an authorization code')}</div>
297
+ </div>
274
298
 
275
- <div field grid-span>
276
- <label for="access-token-url">${i18next.t('field.access token url')}</label>
277
- <input id="access-token-url" type="text" .value=${app.accessTokenUrl || ''} readonly />
278
- <div class="note">${i18next.t('text.where that code is exchanged for an access token')}</div>
279
- </div>
299
+ <div field>
300
+ <label for="access-token-url">${i18next.t('field.access token url')}</label>
301
+ <input id="access-token-url" type="text" .value=${app.accessTokenUrl || ''} readonly />
302
+ <div class="note">${i18next.t('text.where that code is exchanged for an access token')}</div>
303
+ </div>
280
304
 
281
- <div field grid-span>
282
- <label for="available-scopes">${i18next.t('field.available scopes')}</label>
283
- <input id="available-scopes" type="text" .value=${app.availableScopes || ''} readonly />
284
- </div>
305
+ <div field>
306
+ <label for="available-scopes">${i18next.t('field.available scopes')}</label>
307
+ <input id="available-scopes" type="text" .value=${app.availableScopes || ''} readonly />
285
308
  </div>
286
309
  </fieldset>
287
310
  `;
@@ -295,20 +318,18 @@ let ApplicationEditor = class ApplicationEditor extends localize(i18next)(LitEle
295
318
  return html `
296
319
  <fieldset>
297
320
  <legend>${i18next.t('text.credentials')}</legend>
298
- <div field-2column>
299
- <div field grid-span>
300
- <label for="app-key">${i18next.t('field.app key')}</label>
301
- <input id="app-key" type="text" .value=${app.appKey || ''} readonly />
302
- </div>
321
+ <div field>
322
+ <label for="app-key">${i18next.t('field.app key')}</label>
323
+ <input id="app-key" type="text" .value=${app.appKey || ''} readonly />
324
+ </div>
303
325
 
304
- <div field grid-span>
305
- <label for="app-secret">${i18next.t('field.client-secret')}</label>
306
- <secret-field id="app-secret" .value=${app.appSecret || ''}></secret-field>
307
- <div class="row">
308
- <md-text-button @click=${this.generateSecret.bind(this)}>
309
- ${i18next.t('button.generate new secret')}
310
- </md-text-button>
311
- </div>
326
+ <div field>
327
+ <label for="app-secret">${i18next.t('field.client-secret')}</label>
328
+ <secret-field id="app-secret" .value=${app.appSecret || ''}></secret-field>
329
+ <div class="row">
330
+ <md-text-button @click=${this.generateSecret.bind(this)}>
331
+ ${i18next.t('button.generate new secret')}
332
+ </md-text-button>
312
333
  </div>
313
334
  </div>
314
335
  </fieldset>
@@ -1 +1 @@
1
- {"version":3,"file":"application-editor.js","sourceRoot":"","sources":["../../client/components/application-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, CommonPopupStyles } from '@operato/styles'\n\nimport './secret-field.js'\nimport { APPLICATION_TYPES } from '../constants/application.js'\nimport { changedValues, filledValues, missingRequired } from '../utils/form-values.js'\n\n/**\n * Registering and editing an application, in a popup.\n *\n * ── Why this replaced two pages ─────────────────────────────────────────────\n * Same reason as `appliance-editor`: `application-register` and `application/{id}` were the\n * only routes of their kind left in a package where every other resource is a list with a\n * popup over it, and nothing outside auth-ui linked to either.\n *\n * ── Three defects the old detail page carried ───────────────────────────────\n * 1. **The type could not be changed.** Every option in the dropdown was written as\n * `<option value=\"${app.type}\">`, so all six options submitted the *current* value. The\n * label differed, the value did not.\n * 2. **`generate new secret` did nothing.** The button had no handler, while\n * `generateApplicationSecret` had been on the server the whole time.\n * 3. **Saving with an empty contact email was refused.** The page sent every box, so an\n * untouched `email` went as `''`, and `ApplicationPatch.email` is a GraphQLEmailAddress —\n * `''` is not \"no email\" to it, it is an invalid one. The screen printed `update fail` to\n * the console.\n *\n * The type list itself had drifted too: the client constant named five types, the server enum\n * declares six. `application-type-parity.test.ts` now fails when they part again.\n */\n\n/** The one field `NewApplication` declares non-null. The schema's list, not a house rule. */\nconst REQUIRED = ['name']\n\n/*\n * What a new application is when nobody chooses. A `<select>` always has a value, so leaving\n * this out would not mean \"unset\" — it would mean whichever option happens to be listed\n * first. `Application.type` declares `default: ApplicationType.OTHERS`, so that is the one to\n * preselect; anything else would quietly disagree with the server about untouched records.\n */\nconst DEFAULT_TYPE = 'OTHERS'\n\nconst APPLICATION_FIELDS = `\n id\n name\n description\n email\n url\n icon\n redirectUrl\n authUrl\n accessTokenUrl\n webhook\n availableScopes\n appKey\n appSecret\n type\n`\n\n@customElement('application-editor')\nexport class ApplicationEditor extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n background-color: var(--md-sys-color-surface);\n overflow: auto;\n }\n\n form {\n flex: 1;\n padding: var(--popup-padding);\n }\n\n fieldset {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: 0 0 var(--popup-gap) 0;\n padding: var(--popup-padding);\n }\n\n legend {\n padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n [field-2column] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: var(--popup-gap);\n }\n\n [field] {\n display: flex;\n flex-direction: column;\n }\n\n [grid-span] {\n grid-column: span 2;\n }\n\n label {\n font: var(--label-font);\n color: var(--label-color, var(--md-sys-color-on-surface));\n text-transform: var(--label-text-transform);\n }\n\n input,\n select {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n font: var(--input-font);\n }\n\n input:focus,\n select:focus {\n outline: none;\n }\n\n input[readonly] {\n opacity: 0.7;\n }\n\n input[invalid] {\n border-color: var(--md-sys-color-error);\n }\n\n [field-error] {\n color: var(--md-sys-color-error);\n font-size: var(--popup-font-size-small);\n margin-top: var(--spacing-tiny, 2px);\n }\n\n [field] .note {\n margin-top: var(--spacing-tiny, 2px);\n }\n\n .buttons .left {\n margin-right: auto;\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n /** The application to edit. Absent means this popup is registering a new one. */\n @property({ type: String }) applicationId?: string\n\n @query('form') form!: HTMLFormElement\n\n @state() private application: any = null\n\n @state() private missing: string[] = []\n\n @state() private failed: boolean = false\n\n @state() private loadFailed: boolean = false\n\n private get editing(): boolean {\n return !!this.applicationId\n }\n\n /*\n * ⚠ The type dropdown: each option carries its OWN value. The page this replaced wrote the\n * current type as the value of every option, so all six submitted the same thing and the\n * type could not be changed from this screen at all.\n */\n render() {\n const app = this.application || {}\n\n if (this.editing && this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the application could not be loaded')}</div>`\n }\n\n return html`\n <form @submit=${(e: Event) => e.preventDefault()}>\n <fieldset>\n <legend>${i18next.t('text.application')}</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"name\">${i18next.t('field.name')}</label>\n <input\n id=\"name\"\n type=\"text\"\n name=\"name\"\n .value=${app.name || ''}\n required\n autofocus\n ?invalid=${this.missing.includes('name')}\n />\n ${\n this.missing.includes('name')\n ? html`<div field-error>${i18next.t('text.this field is required')}</div>`\n : ''\n }\n </div>\n\n <div field grid-span>\n <label for=\"description\">${i18next.t('field.description')}</label>\n <input id=\"description\" type=\"text\" name=\"description\" .value=${app.description || ''} />\n </div>\n\n <!-- type=email so the browser checks the shape the server's scalar will check. -->\n <div field grid-span>\n <label for=\"email\">${i18next.t('field.contact email')}</label>\n <input id=\"email\" type=\"email\" name=\"email\" .value=${app.email || ''} />\n </div>\n\n <div field grid-span>\n <label for=\"type\">${i18next.t('field.type')}</label>\n <select id=\"type\" name=\"type\">\n ${APPLICATION_TYPES.map(\n type => html`<option value=${type} ?selected=${type === (app.type || DEFAULT_TYPE)}>${type}</option>`\n )}\n </select>\n </div>\n\n <div field grid-span>\n <label for=\"url\">${i18next.t('field.application url')}</label>\n <input id=\"url\" type=\"url\" name=\"url\" .value=${app.url || ''} />\n </div>\n\n <div field grid-span>\n <label for=\"redirect-url\">${i18next.t('field.redirect url')}</label>\n <input id=\"redirect-url\" type=\"url\" name=\"redirectUrl\" .value=${app.redirectUrl || ''} />\n </div>\n\n <div field grid-span>\n <label for=\"icon\">${i18next.t('field.application icon')}</label>\n <input id=\"icon\" type=\"url\" name=\"icon\" .value=${app.icon || ''} />\n </div>\n\n <div field grid-span>\n <label for=\"webhook\">${i18next.t('field.webhook')}</label>\n <input id=\"webhook\" type=\"url\" name=\"webhook\" .value=${app.webhook || ''} />\n </div>\n </div>\n </fieldset>\n\n ${this.editing ? this.renderEndpoints(app) : ''} ${this.editing ? this.renderCredential(app) : ''}\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </form>\n\n <div class=\"buttons\">\n ${\n this.editing\n ? html`\n <md-text-button class=\"left\" @click=${this.deleteApplication.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n `\n : ''\n }\n <md-text-button @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button @click=${this.save.bind(this)}>\n ${this.editing ? i18next.t('button.update') : i18next.t('button.register')}\n </md-filled-button>\n </div>\n `\n }\n\n /*\n * What the server hands back rather than what the person types: the OAuth endpoints and the\n * scopes this application may ask for. Read-only and outside the form's named inputs, so\n * they are never part of a patch.\n */\n private renderEndpoints(app: any) {\n return html`\n <fieldset>\n <legend>${i18next.t('text.endpoints')}</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"auth-url\">${i18next.t('field.auth url')}</label>\n <input id=\"auth-url\" type=\"text\" .value=${app.authUrl || ''} readonly />\n <div class=\"note\">${i18next.t('text.where an integration asks for an authorization code')}</div>\n </div>\n\n <div field grid-span>\n <label for=\"access-token-url\">${i18next.t('field.access token url')}</label>\n <input id=\"access-token-url\" type=\"text\" .value=${app.accessTokenUrl || ''} readonly />\n <div class=\"note\">${i18next.t('text.where that code is exchanged for an access token')}</div>\n </div>\n\n <div field grid-span>\n <label for=\"available-scopes\">${i18next.t('field.available scopes')}</label>\n <input id=\"available-scopes\" type=\"text\" .value=${app.availableScopes || ''} readonly />\n </div>\n </div>\n </fieldset>\n `\n }\n\n /*\n * appKey stays a plain field: it is the client identifier, meant to be read and quoted.\n * appSecret is the other half — it carries its own @privilege for security:query, and it is\n * the value an integration signs with.\n */\n private renderCredential(app: any) {\n return html`\n <fieldset>\n <legend>${i18next.t('text.credentials')}</legend>\n <div field-2column>\n <div field grid-span>\n <label for=\"app-key\">${i18next.t('field.app key')}</label>\n <input id=\"app-key\" type=\"text\" .value=${app.appKey || ''} readonly />\n </div>\n\n <div field grid-span>\n <label for=\"app-secret\">${i18next.t('field.client-secret')}</label>\n <secret-field id=\"app-secret\" .value=${app.appSecret || ''}></secret-field>\n <div class=\"row\">\n <md-text-button @click=${this.generateSecret.bind(this)}>\n ${i18next.t('button.generate new secret')}\n </md-text-button>\n </div>\n </div>\n </div>\n </fieldset>\n `\n }\n\n async connectedCallback() {\n super.connectedCallback()\n\n if (this.editing) {\n await this.fetchApplication()\n }\n }\n\n async fetchApplication() {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n application(id: $id) { ${APPLICATION_FIELDS} }\n }\n `,\n variables: { id: this.applicationId }\n })\n\n /*\n * A refusal arrives in `errors` rather than throwing. An unchecked read would render an\n * empty form that looks like a record with every field blank, and saving it would write\n * those blanks.\n */\n if (response.errors || !response.data?.application) {\n this.loadFailed = true\n return\n }\n\n this.loadFailed = false\n this.application = response.data.application\n }\n\n private cancel(e: Event) {\n e.preventDefault()\n this.close()\n }\n\n async save(e: Event) {\n e.preventDefault()\n\n const formData = new FormData(this.form)\n\n const missing = missingRequired(formData, REQUIRED)\n\n if (missing.length) {\n this.missing = missing\n return\n }\n\n this.missing = []\n\n return this.editing ? this.updateApplication(formData) : this.createApplication(formData)\n }\n\n private async createApplication(formData: FormData) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($application: NewApplication!) {\n createApplication(application: $application) {\n id\n }\n }\n `,\n variables: { application: filledValues(formData) }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async updateApplication(formData: FormData) {\n const patch = changedValues(formData, this.application)\n\n if (!Object.keys(patch).length) {\n this.close()\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: ApplicationPatch!) {\n updateApplication(id: $id, patch: $patch) { ${APPLICATION_FIELDS} }\n }\n `,\n variables: { id: this.applicationId, patch }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async generateSecret(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.generate new secret'),\n text: i18next.t('text.the current secret stops working immediately'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n generateApplicationSecret(id: $id) { ${APPLICATION_FIELDS} }\n }\n `,\n variables: { id: this.applicationId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n /*\n * Stays open on purpose — a fresh secret is shown once and has to be copied before it is\n * out of reach.\n */\n this.failed = false\n this.application = response.data.generateApplicationSecret\n this.dispatchEvent(new CustomEvent('application-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteApplication(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.are you sure you want to delete x', { x: this.application?.name || '' }),\n confirmButton: { text: i18next.t('button.delete') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteApplication(id: $id)\n }\n `,\n variables: { id: this.applicationId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private saved() {\n this.failed = false\n this.dispatchEvent(new CustomEvent('application-changed', { bubbles: true, composed: true }))\n this.close()\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))\n }\n}\n"]}
1
+ {"version":3,"file":"application-editor.js","sourceRoot":"","sources":["../../client/components/application-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, CommonPopupStyles, ScrollbarStyles } from '@operato/styles'\n\nimport './secret-field.js'\nimport { APPLICATION_TYPES } from '../constants/application.js'\nimport { changedValues, filledValues, missingRequired } from '../utils/form-values.js'\n\n/**\n * Registering and editing an application, in a popup.\n *\n * ── Why this replaced two pages ─────────────────────────────────────────────\n * Same reason as `appliance-editor`: `application-register` and `application/{id}` were the\n * only routes of their kind left in a package where every other resource is a list with a\n * popup over it, and nothing outside auth-ui linked to either.\n *\n * ── Three defects the old detail page carried ───────────────────────────────\n * 1. **The type could not be changed.** Every option in the dropdown was written as\n * `<option value=\"${app.type}\">`, so all six options submitted the *current* value. The\n * label differed, the value did not.\n * 2. **`generate new secret` did nothing.** The button had no handler, while\n * `generateApplicationSecret` had been on the server the whole time.\n * 3. **Saving with an empty contact email was refused.** The page sent every box, so an\n * untouched `email` went as `''`, and `ApplicationPatch.email` is a GraphQLEmailAddress —\n * `''` is not \"no email\" to it, it is an invalid one. The screen printed `update fail` to\n * the console.\n *\n * The type list itself had drifted too: the client constant named five types, the server enum\n * declares six. `application-type-parity.test.ts` now fails when they part again.\n */\n\n/** The one field `NewApplication` declares non-null. The schema's list, not a house rule. */\nconst REQUIRED = ['name']\n\n/*\n * What a new application is when nobody chooses. A `<select>` always has a value, so leaving\n * this out would not mean \"unset\" — it would mean whichever option happens to be listed\n * first. `Application.type` declares `default: ApplicationType.OTHERS`, so that is the one to\n * preselect; anything else would quietly disagree with the server about untouched records.\n */\nconst DEFAULT_TYPE = 'OTHERS'\n\nconst APPLICATION_FIELDS = `\n id\n name\n description\n email\n url\n icon\n redirectUrl\n authUrl\n accessTokenUrl\n webhook\n availableScopes\n appKey\n appSecret\n type\n`\n\n@customElement('application-editor')\nexport class ApplicationEditor extends localize(i18next)(LitElement) {\n /*\n * ── Where the scrolling happens ─────────────────────────────────────────\n * The button row stays put and only the middle scrolls. That is the house shape for a\n * popup, and this had it wrong: the host itself scrolled, so the row you press to finish\n * walked off the bottom of the card along with everything else.\n *\n * So the host clips, and the content area between the title and the buttons is the one\n * thing with overflow. Two details carry it:\n *\n * min-height: 0 A flex item defaults to min-height auto, which refuses to shrink below\n * its own content. Without this the overflow rule sits there doing\n * nothing and the card grows instead — it reads as a working rule.\n * no padding CommonPopupStyles already pads the host. Padding here again doubles the\n * inset and pushes the scrollbar in off the card edge.\n *\n * ScrollbarStyles gives the scrollbar the product's own shape rather than the browser's.\n *\n * ── Why the in-content buttons need a rule of their own ─────────────────\n * CommonPopupStyles makes controls pill-shaped because a popup is a rounded card and the\n * things inside it follow the card. But it says that through `.buttons`, so only the commit\n * row got it: the actions sitting inside a fieldset — generate a new token, generate a new\n * secret — kept the page radius and read as a different kind of button on the same card.\n * Measured on a live popup: 5px inside the content, 13986px in the row below it.\n */\n static styles = [\n CommonHeaderStyles,\n CommonPopupStyles,\n ScrollbarStyles,\n css`\n /* in-content actions follow the card too, not just the commit row. see the note on styles. */\n fieldset md-text-button,\n fieldset md-filled-button {\n --md-text-button-container-shape: var(--popup-control-radius);\n --md-filled-button-container-shape: var(--popup-control-radius);\n }\n\n /* the host does not scroll; the content area below does. see the note on styles. */\n :host {\n overflow: hidden;\n }\n\n /* min-height:0 is load-bearing — see the note on styles. */\n form {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n\n display: flex;\n flex-direction: column;\n gap: var(--popup-gap);\n }\n\n fieldset {\n border-radius: var(--border-radius);\n border: var(--border-dim-color);\n margin: 0;\n padding: var(--popup-padding);\n }\n\n legend {\n padding: var(--legend-padding);\n font-weight: bold;\n color: var(--legend-color);\n }\n\n /* one labelled box. .row puts two of them side by side; alone it spans the card. */\n [field] {\n display: flex;\n flex-direction: column;\n flex: 1;\n }\n\n label {\n font: var(--label-font);\n color: var(--label-color, var(--md-sys-color-on-surface));\n text-transform: var(--label-text-transform);\n }\n\n input,\n select {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n font: var(--input-font);\n }\n\n input:focus,\n select:focus {\n outline: none;\n }\n\n input[readonly] {\n opacity: 0.7;\n }\n\n input[invalid] {\n border-color: var(--md-sys-color-error);\n }\n\n [field-error] {\n color: var(--md-sys-color-error);\n font-size: var(--popup-font-size-small);\n margin-top: var(--spacing-tiny, 2px);\n }\n\n [field] .note {\n margin-top: var(--spacing-tiny, 2px);\n }\n\n .buttons .left {\n margin-right: auto;\n }\n\n @media screen and (max-width: 480px) {\n [field] {\n grid-column: span 2;\n }\n }\n `\n ]\n\n /** The application to edit. Absent means this popup is registering a new one. */\n @property({ type: String }) applicationId?: string\n\n @query('form') form!: HTMLFormElement\n\n @state() private application: any = null\n\n @state() private missing: string[] = []\n\n @state() private failed: boolean = false\n\n @state() private loadFailed: boolean = false\n\n private get editing(): boolean {\n return !!this.applicationId\n }\n\n /*\n * ⚠ The type dropdown: each option carries its OWN value. The page this replaced wrote the\n * current type as the value of every option, so all six submitted the same thing and the\n * type could not be changed from this screen at all.\n */\n render() {\n const app = this.application || {}\n\n if (this.editing && this.loadFailed) {\n return html`<div class=\"failure\">${i18next.t('text.the application could not be loaded')}</div>`\n }\n\n return html`\n <form @submit=${(e: Event) => e.preventDefault()}>\n <fieldset>\n <legend>${i18next.t('text.application')}</legend>\n <div field>\n <label for=\"name\">${i18next.t('field.name')}</label>\n <input\n id=\"name\"\n type=\"text\"\n name=\"name\"\n .value=${app.name || ''}\n required\n autofocus\n ?invalid=${this.missing.includes('name')}\n />\n ${\n this.missing.includes('name')\n ? html`<div field-error>${i18next.t('text.this field is required')}</div>`\n : ''\n }\n </div>\n\n <div field>\n <label for=\"description\">${i18next.t('field.description')}</label>\n <input id=\"description\" type=\"text\" name=\"description\" .value=${app.description || ''} />\n </div>\n\n <!-- type=email so the browser checks the shape the server's scalar will check. -->\n <div field>\n <label for=\"email\">${i18next.t('field.contact email')}</label>\n <input id=\"email\" type=\"email\" name=\"email\" .value=${app.email || ''} />\n </div>\n\n <div field>\n <label for=\"type\">${i18next.t('field.type')}</label>\n <select id=\"type\" name=\"type\">\n ${APPLICATION_TYPES.map(\n type => html`<option value=${type} ?selected=${type === (app.type || DEFAULT_TYPE)}>${type}</option>`\n )}\n </select>\n </div>\n\n <div field>\n <label for=\"url\">${i18next.t('field.application url')}</label>\n <input id=\"url\" type=\"url\" name=\"url\" .value=${app.url || ''} />\n </div>\n\n <div field>\n <label for=\"redirect-url\">${i18next.t('field.redirect url')}</label>\n <input id=\"redirect-url\" type=\"url\" name=\"redirectUrl\" .value=${app.redirectUrl || ''} />\n </div>\n\n <div field>\n <label for=\"icon\">${i18next.t('field.application icon')}</label>\n <input id=\"icon\" type=\"url\" name=\"icon\" .value=${app.icon || ''} />\n </div>\n\n <div field>\n <label for=\"webhook\">${i18next.t('field.webhook')}</label>\n <input id=\"webhook\" type=\"url\" name=\"webhook\" .value=${app.webhook || ''} />\n </div>\n </fieldset>\n\n ${this.editing ? this.renderEndpoints(app) : ''} ${this.editing ? this.renderCredential(app) : ''}\n ${this.failed ? html`<div class=\"failure\">${i18next.t('text.could not be saved')}</div>` : ''}\n </form>\n\n <div class=\"buttons\">\n ${\n this.editing\n ? html`\n <md-text-button class=\"left\" @click=${this.deleteApplication.bind(this)}>\n ${i18next.t('button.delete')}\n </md-text-button>\n `\n : ''\n }\n <md-text-button @click=${this.cancel.bind(this)}>${i18next.t('button.cancel')}</md-text-button>\n <md-filled-button @click=${this.save.bind(this)}>\n ${this.editing ? i18next.t('button.update') : i18next.t('button.register')}\n </md-filled-button>\n </div>\n `\n }\n\n /*\n * What the server hands back rather than what the person types: the OAuth endpoints and the\n * scopes this application may ask for. Read-only and outside the form's named inputs, so\n * they are never part of a patch.\n */\n private renderEndpoints(app: any) {\n return html`\n <fieldset>\n <legend>${i18next.t('text.endpoints')}</legend>\n <div field>\n <label for=\"auth-url\">${i18next.t('field.auth url')}</label>\n <input id=\"auth-url\" type=\"text\" .value=${app.authUrl || ''} readonly />\n <div class=\"note\">${i18next.t('text.where an integration asks for an authorization code')}</div>\n </div>\n\n <div field>\n <label for=\"access-token-url\">${i18next.t('field.access token url')}</label>\n <input id=\"access-token-url\" type=\"text\" .value=${app.accessTokenUrl || ''} readonly />\n <div class=\"note\">${i18next.t('text.where that code is exchanged for an access token')}</div>\n </div>\n\n <div field>\n <label for=\"available-scopes\">${i18next.t('field.available scopes')}</label>\n <input id=\"available-scopes\" type=\"text\" .value=${app.availableScopes || ''} readonly />\n </div>\n </fieldset>\n `\n }\n\n /*\n * appKey stays a plain field: it is the client identifier, meant to be read and quoted.\n * appSecret is the other half — it carries its own @privilege for security:query, and it is\n * the value an integration signs with.\n */\n private renderCredential(app: any) {\n return html`\n <fieldset>\n <legend>${i18next.t('text.credentials')}</legend>\n <div field>\n <label for=\"app-key\">${i18next.t('field.app key')}</label>\n <input id=\"app-key\" type=\"text\" .value=${app.appKey || ''} readonly />\n </div>\n\n <div field>\n <label for=\"app-secret\">${i18next.t('field.client-secret')}</label>\n <secret-field id=\"app-secret\" .value=${app.appSecret || ''}></secret-field>\n <div class=\"row\">\n <md-text-button @click=${this.generateSecret.bind(this)}>\n ${i18next.t('button.generate new secret')}\n </md-text-button>\n </div>\n </div>\n </fieldset>\n `\n }\n\n async connectedCallback() {\n super.connectedCallback()\n\n if (this.editing) {\n await this.fetchApplication()\n }\n }\n\n async fetchApplication() {\n const response = await client.query({\n query: gql`\n query ($id: String!) {\n application(id: $id) { ${APPLICATION_FIELDS} }\n }\n `,\n variables: { id: this.applicationId }\n })\n\n /*\n * A refusal arrives in `errors` rather than throwing. An unchecked read would render an\n * empty form that looks like a record with every field blank, and saving it would write\n * those blanks.\n */\n if (response.errors || !response.data?.application) {\n this.loadFailed = true\n return\n }\n\n this.loadFailed = false\n this.application = response.data.application\n }\n\n private cancel(e: Event) {\n e.preventDefault()\n this.close()\n }\n\n async save(e: Event) {\n e.preventDefault()\n\n const formData = new FormData(this.form)\n\n const missing = missingRequired(formData, REQUIRED)\n\n if (missing.length) {\n this.missing = missing\n return\n }\n\n this.missing = []\n\n return this.editing ? this.updateApplication(formData) : this.createApplication(formData)\n }\n\n private async createApplication(formData: FormData) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($application: NewApplication!) {\n createApplication(application: $application) {\n id\n }\n }\n `,\n variables: { application: filledValues(formData) }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async updateApplication(formData: FormData) {\n const patch = changedValues(formData, this.application)\n\n if (!Object.keys(patch).length) {\n this.close()\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $patch: ApplicationPatch!) {\n updateApplication(id: $id, patch: $patch) { ${APPLICATION_FIELDS} }\n }\n `,\n variables: { id: this.applicationId, patch }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private async generateSecret(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.generate new secret'),\n text: i18next.t('text.the current secret stops working immediately'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n generateApplicationSecret(id: $id) { ${APPLICATION_FIELDS} }\n }\n `,\n variables: { id: this.applicationId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n /*\n * Stays open on purpose — a fresh secret is shown once and has to be copied before it is\n * out of reach.\n */\n this.failed = false\n this.application = response.data.generateApplicationSecret\n this.dispatchEvent(new CustomEvent('application-changed', { bubbles: true, composed: true }))\n }\n\n private async deleteApplication(e: Event) {\n e.preventDefault()\n\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.are you sure you want to delete x', { x: this.application?.name || '' }),\n confirmButton: { text: i18next.t('button.delete') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n deleteApplication(id: $id)\n }\n `,\n variables: { id: this.applicationId }\n })\n\n if (response.errors) {\n this.failed = true\n return\n }\n\n this.saved()\n }\n\n private saved() {\n this.failed = false\n this.dispatchEvent(new CustomEvent('application-changed', { bubbles: true, composed: true }))\n this.close()\n }\n\n private close() {\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))\n }\n}\n"]}
@@ -69,7 +69,7 @@ let ChangePassword = class ChangePassword extends localize(i18next)(LitElement)
69
69
  /* fallback 도 테마 토큰으로 — 하드코딩 색은 다크에서 어긋난다 */
70
70
  background-color: var(--secondary-color, var(--md-sys-color-secondary));
71
71
  margin: 2px 2px 10px 2px;
72
- height: var(--button-height, 28px);
72
+ height: var(--form-action-height);
73
73
  color: var(--button-color, var(--md-sys-color-on-secondary));
74
74
  font: var(--button-font);
75
75
  border-radius: var(--button-radius, var(--md-sys-shape-corner-small, 5px));
@@ -1 +1 @@
1
- {"version":3,"file":"change-password.js","sourceRoot":"","sources":["../../client/components/change-password.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { FormFieldStyles } from '@operato/styles'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\nimport { generatePasswordPatternHelp, generatePasswordPatternRegExp } from '../utils/password-rule.js'\n\n@customElement('change-password')\nexport class ChangePassword extends localize(i18next)(LitElement) {\n static styles = [\n FormFieldStyles,\n css`\n * {\n box-sizing: border-box;\n }\n\n *:focus {\n outline: none;\n }\n\n form {\n display: flex;\n flex-direction: column;\n }\n\n /*\n * 색·테두리·모서리·포커스·placeholder 색은 FormFieldStyles 가 담당한다.\n * 여기서는 이 화면의 치수만 둔다 — 같은 프로필 화면의 위쪽 인풋(profile-component)과\n * 같은 값을 써서 한 화면에 두 가지 인상이 생기지 않게 한다.\n */\n input {\n border-radius: var(--change-password-field-border-radius, var(--border-radius));\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: var(--change-password-field-padding, 9px);\n font: var(--change-password-field-font, var(--auth-input-field-font));\n width: var(--change-password-field-width, var(--auth-input-field-width));\n }\n\n ::placeholder {\n /*\n * 크기를 줄이지 않는다. 이전에는 0.8rem 이라 위쪽 인풋의 값(16px)보다 작게 보여\n * 같은 화면에서 패스워드 칸만 다른 스타일처럼 느껴졌다.\n */\n text-transform: capitalize;\n }\n\n md-elevated-button {\n margin: var(--spacing-small) auto var(--spacing-medium) auto;\n text-transform: capitalize;\n }\n\n button {\n /* fallback 도 테마 토큰으로 — 하드코딩 색은 다크에서 어긋난다 */\n background-color: var(--secondary-color, var(--md-sys-color-secondary));\n margin: 2px 2px 10px 2px;\n height: var(--button-height, 28px);\n color: var(--button-color, var(--md-sys-color-on-secondary));\n font: var(--button-font);\n border-radius: var(--button-radius, var(--md-sys-shape-corner-small, 5px));\n border: var(--button-border, 1px solid transparent);\n line-height: 1.5;\n }\n\n button:hover,\n button:active {\n background-color: var(--button-active-background-color, var(--md-sys-color-primary));\n border: var(--button-active-border);\n }\n\n /* 비밀번호 규칙 안내문 — 본문보다 낮은 강조. 라이트/다크 모두 읽히도록 토큰 파생. */\n /* The statement that replaces the form once the change took. */\n .changed {\n display: flex;\n align-items: center;\n gap: 8px;\n /*\n * 같은 여백을 위쪽 인풋과 나눠 쓴다. 폼 상태에서는 첫 인풋의 margin 이 「비밀번호」\n * 라벨과의 간격을 만드는데, 이 문장은 margin 이 없어 라벨에 붙어 있었다 — 같은 자리에\n * 두 가지 간격이 생겼다. 값을 새로 정하지 않고 인풋과 같은 토큰을 쓴다.\n */\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: 12px 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-left: 3px solid var(--md-sys-color-primary);\n border-radius: 7px;\n background: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n\n .changed md-icon {\n color: var(--md-sys-color-primary);\n }\n\n .changed button {\n margin-left: auto;\n border: 1px solid var(--md-sys-color-outline-variant);\n background: transparent;\n color: inherit;\n border-radius: 7px;\n padding: 5px 11px;\n cursor: pointer;\n }\n\n .helper-text {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant);\n margin-top: 4px;\n display: block; /* 텍스트를 입력 필드 아래에 배치 */\n line-height: 1.5; /* 텍스트 줄 간격 조절 */\n }\n `\n ]\n\n @property({ type: Object }) passwordRule: {\n lowerCase?: boolean\n upperCase?: boolean\n digit?: boolean\n specialCharacter?: boolean\n allowRepeat?: boolean\n useTightPattern?: boolean\n useLoosePattern?: boolean\n tightCharacterLength?: number\n looseCharacterLength?: number\n } = {}\n\n @query('form') form!: HTMLFormElement\n\n /**\n * Set once the change went through.\n *\n * The server answers with a message and the shell shows it as a toast, but a toast leaves and\n * the same three empty boxes stay behind. A person who has just changed their password reads\n * that as \"nothing happened\" — the report that brought this in said exactly that.\n *\n * So the form is replaced by a statement that stays. It also stops the boxes from inviting a\n * second attempt at something already done.\n */\n @state() private changed = false\n\n private passwordPattern: string = ''\n private passwordHelp: string = ''\n\n render() {\n if (this.changed) {\n return html`\n <div class=\"changed\" role=\"status\">\n <md-icon>check_circle</md-icon>\n <span>${i18next.t('text.password changed successfully')}</span>\n <button type=\"button\" @click=${() => (this.changed = false)}>\n ${i18next.t('text.change password')}\n </button>\n </div>\n `\n }\n\n return html`\n <form>\n <div class=\"field\">\n <input type=\"password\" name=\"current_pass\" placeholder=${i18next.t('text.current password')} required />\n </div>\n <!--\n The rule belongs under the field the rule applies to.\n \n It used to sit between the two, directly beneath the current password, so a sighted\n reader took it as the rule for that field — and the current password has no reason to\n satisfy today's rule. It may predate it. The server only checks that it matches\n (change-pwd.ts verifies the current one and validates the new one against the rule).\n aria-describedby always pointed at the new field, so a screen reader had it right and\n only the eye was misled.\n -->\n <div class=\"field\">\n <input\n type=\"password\"\n name=\"new_pass\"\n placeholder=${i18next.t('text.new password')}\n required\n pattern=${this.passwordPattern}\n aria-describedby=\"password-helper\"\n />\n </div>\n <span id=\"password-helper\" class=\"helper-text\">${this.passwordHelp}</span>\n <div class=\"field\">\n <input type=\"password\" name=\"confirm_pass\" placeholder=${i18next.t('text.confirm password')} required />\n </div>\n\n <!--\n type=\"button\", the way abstract-sign and abstract-auth-page already declare it.\n \n A Material button inside a form defaults to type=\"submit\" and submits unless the click\n is default-prevented. So one click ran submit() and then let the browser submit the\n form as well. submit() had already called form.reset(), so constraint validation found\n three empty required fields, rejected the first one, and put the caret back on the\n current password with an English \"Please fill out this field.\" bubble on a Korean\n screen. It read as the current password being refused.\n [확인함] 2026-09-09 · 사용자 보고 · 화면 사진\n -->\n <md-elevated-button type=\"button\" @click=${this.submit.bind(this)}\n >${i18next.t('text.change password')}</md-elevated-button\n >\n </form>\n `\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('passwordRule')) {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n }\n\n languageUpdated() {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n\n async submit() {\n const formData = new FormData(this.form)\n let params = {}\n for (const [key, value] of formData.entries()) {\n if (!value) {\n const placeholder = (this.form.querySelector(`[name=${key}]`) as HTMLInputElement)?.placeholder\n return this.showToast(i18next.t('error.value is empty', { value: placeholder || key }))\n }\n params[key] = value\n }\n\n if (params['new_pass'] !== params['confirm_pass']) {\n return this.showToast(i18next.t('error.new-password-and-confirm-password-do-not-match'))\n }\n\n /* Clear only once it took — a refused change used to wipe all three fields. */\n if (await auth.changePassword(params)) {\n this.form.reset()\n this.changed = true\n }\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
1
+ {"version":3,"file":"change-password.js","sourceRoot":"","sources":["../../client/components/change-password.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { FormFieldStyles } from '@operato/styles'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\nimport { generatePasswordPatternHelp, generatePasswordPatternRegExp } from '../utils/password-rule.js'\n\n@customElement('change-password')\nexport class ChangePassword extends localize(i18next)(LitElement) {\n static styles = [\n FormFieldStyles,\n css`\n * {\n box-sizing: border-box;\n }\n\n *:focus {\n outline: none;\n }\n\n form {\n display: flex;\n flex-direction: column;\n }\n\n /*\n * 색·테두리·모서리·포커스·placeholder 색은 FormFieldStyles 가 담당한다.\n * 여기서는 이 화면의 치수만 둔다 — 같은 프로필 화면의 위쪽 인풋(profile-component)과\n * 같은 값을 써서 한 화면에 두 가지 인상이 생기지 않게 한다.\n */\n input {\n border-radius: var(--change-password-field-border-radius, var(--border-radius));\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: var(--change-password-field-padding, 9px);\n font: var(--change-password-field-font, var(--auth-input-field-font));\n width: var(--change-password-field-width, var(--auth-input-field-width));\n }\n\n ::placeholder {\n /*\n * 크기를 줄이지 않는다. 이전에는 0.8rem 이라 위쪽 인풋의 값(16px)보다 작게 보여\n * 같은 화면에서 패스워드 칸만 다른 스타일처럼 느껴졌다.\n */\n text-transform: capitalize;\n }\n\n md-elevated-button {\n margin: var(--spacing-small) auto var(--spacing-medium) auto;\n text-transform: capitalize;\n }\n\n button {\n /* fallback 도 테마 토큰으로 — 하드코딩 색은 다크에서 어긋난다 */\n background-color: var(--secondary-color, var(--md-sys-color-secondary));\n margin: 2px 2px 10px 2px;\n height: var(--form-action-height);\n color: var(--button-color, var(--md-sys-color-on-secondary));\n font: var(--button-font);\n border-radius: var(--button-radius, var(--md-sys-shape-corner-small, 5px));\n border: var(--button-border, 1px solid transparent);\n line-height: 1.5;\n }\n\n button:hover,\n button:active {\n background-color: var(--button-active-background-color, var(--md-sys-color-primary));\n border: var(--button-active-border);\n }\n\n /* 비밀번호 규칙 안내문 — 본문보다 낮은 강조. 라이트/다크 모두 읽히도록 토큰 파생. */\n /* The statement that replaces the form once the change took. */\n .changed {\n display: flex;\n align-items: center;\n gap: 8px;\n /*\n * 같은 여백을 위쪽 인풋과 나눠 쓴다. 폼 상태에서는 첫 인풋의 margin 이 「비밀번호」\n * 라벨과의 간격을 만드는데, 이 문장은 margin 이 없어 라벨에 붙어 있었다 — 같은 자리에\n * 두 가지 간격이 생겼다. 값을 새로 정하지 않고 인풋과 같은 토큰을 쓴다.\n */\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: 12px 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-left: 3px solid var(--md-sys-color-primary);\n border-radius: 7px;\n background: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n\n .changed md-icon {\n color: var(--md-sys-color-primary);\n }\n\n .changed button {\n margin-left: auto;\n border: 1px solid var(--md-sys-color-outline-variant);\n background: transparent;\n color: inherit;\n border-radius: 7px;\n padding: 5px 11px;\n cursor: pointer;\n }\n\n .helper-text {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant);\n margin-top: 4px;\n display: block; /* 텍스트를 입력 필드 아래에 배치 */\n line-height: 1.5; /* 텍스트 줄 간격 조절 */\n }\n `\n ]\n\n @property({ type: Object }) passwordRule: {\n lowerCase?: boolean\n upperCase?: boolean\n digit?: boolean\n specialCharacter?: boolean\n allowRepeat?: boolean\n useTightPattern?: boolean\n useLoosePattern?: boolean\n tightCharacterLength?: number\n looseCharacterLength?: number\n } = {}\n\n @query('form') form!: HTMLFormElement\n\n /**\n * Set once the change went through.\n *\n * The server answers with a message and the shell shows it as a toast, but a toast leaves and\n * the same three empty boxes stay behind. A person who has just changed their password reads\n * that as \"nothing happened\" — the report that brought this in said exactly that.\n *\n * So the form is replaced by a statement that stays. It also stops the boxes from inviting a\n * second attempt at something already done.\n */\n @state() private changed = false\n\n private passwordPattern: string = ''\n private passwordHelp: string = ''\n\n render() {\n if (this.changed) {\n return html`\n <div class=\"changed\" role=\"status\">\n <md-icon>check_circle</md-icon>\n <span>${i18next.t('text.password changed successfully')}</span>\n <button type=\"button\" @click=${() => (this.changed = false)}>\n ${i18next.t('text.change password')}\n </button>\n </div>\n `\n }\n\n return html`\n <form>\n <div class=\"field\">\n <input type=\"password\" name=\"current_pass\" placeholder=${i18next.t('text.current password')} required />\n </div>\n <!--\n The rule belongs under the field the rule applies to.\n \n It used to sit between the two, directly beneath the current password, so a sighted\n reader took it as the rule for that field — and the current password has no reason to\n satisfy today's rule. It may predate it. The server only checks that it matches\n (change-pwd.ts verifies the current one and validates the new one against the rule).\n aria-describedby always pointed at the new field, so a screen reader had it right and\n only the eye was misled.\n -->\n <div class=\"field\">\n <input\n type=\"password\"\n name=\"new_pass\"\n placeholder=${i18next.t('text.new password')}\n required\n pattern=${this.passwordPattern}\n aria-describedby=\"password-helper\"\n />\n </div>\n <span id=\"password-helper\" class=\"helper-text\">${this.passwordHelp}</span>\n <div class=\"field\">\n <input type=\"password\" name=\"confirm_pass\" placeholder=${i18next.t('text.confirm password')} required />\n </div>\n\n <!--\n type=\"button\", the way abstract-sign and abstract-auth-page already declare it.\n \n A Material button inside a form defaults to type=\"submit\" and submits unless the click\n is default-prevented. So one click ran submit() and then let the browser submit the\n form as well. submit() had already called form.reset(), so constraint validation found\n three empty required fields, rejected the first one, and put the caret back on the\n current password with an English \"Please fill out this field.\" bubble on a Korean\n screen. It read as the current password being refused.\n [확인함] 2026-09-09 · 사용자 보고 · 화면 사진\n -->\n <md-elevated-button type=\"button\" @click=${this.submit.bind(this)}\n >${i18next.t('text.change password')}</md-elevated-button\n >\n </form>\n `\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('passwordRule')) {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n }\n\n languageUpdated() {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n\n async submit() {\n const formData = new FormData(this.form)\n let params = {}\n for (const [key, value] of formData.entries()) {\n if (!value) {\n const placeholder = (this.form.querySelector(`[name=${key}]`) as HTMLInputElement)?.placeholder\n return this.showToast(i18next.t('error.value is empty', { value: placeholder || key }))\n }\n params[key] = value\n }\n\n if (params['new_pass'] !== params['confirm_pass']) {\n return this.showToast(i18next.t('error.new-password-and-confirm-password-do-not-match'))\n }\n\n /* Clear only once it took — a refused change used to wipe all three fields. */\n if (await auth.changePassword(params)) {\n this.form.reset()\n this.changed = true\n }\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
@@ -6,9 +6,10 @@ import { customElement, query } from 'lit/decorators.js';
6
6
  import { client } from '@operato/graphql';
7
7
  import { i18next, localize } from '@operato/i18n';
8
8
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
9
- import { CommonHeaderStyles } from '@operato/styles';
9
+ import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles';
10
10
  let CreateDomainPopup = class CreateDomainPopup extends localize(i18next)(LitElement) {
11
11
  static { this.styles = [
12
+ ScrollbarStyles,
12
13
  CommonHeaderStyles,
13
14
  css `
14
15
  :host {
@@ -1 +1 @@
1
- {"version":3,"file":"create-domain-popup.js","sourceRoot":"","sources":["../../client/components/create-domain-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, query } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles } from '@operato/styles'\n\n@customElement('create-domain-popup')\nclass CreateDomainPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n background-color: var(--md-sys-color-background);\n overflow: auto;\n }\n\n form {\n flex: 1;\n padding: var(--spacing-large);\n }\n\n input.checkValidName {\n background-color: #fce6e6;\n }\n\n label {\n display: flex;\n flex-direction: column;\n\n font: var(--label-font);\n color: var(--label-color, var(--md-sys-color-on-surface));\n text-transform: var(--label-text-transform);\n }\n\n input {\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n background-color: var(--md-sys-color-surface);\n font: var(--input-font);\n }\n\n [field] {\n grid-column: span 2;\n }\n `\n ]\n\n @query('input[name=\"name\"]') nameInput!: HTMLInputElement\n\n render() {\n return html`\n <form>\n <div field grid-span>\n <label\n >${i18next.t('label.x name', { x: i18next.t('label.domain') })}<input\n type=\"text\"\n name=\"name\"\n @input=${this.checkValidation}\n autofocus\n /></label>\n </div>\n\n <div field grid-span>\n <label>${i18next.t('label.description')}<input type=\"text\" name=\"description\" /></label>\n </div>\n </form>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => this.onCreateDomain()} done><md-icon>add</md-icon>${i18next.t('button.create')}</button>\n </div>\n `\n }\n\n firstUpdated() {\n this.nameInput.focus() // autofocus\n }\n\n get inputData() {\n return this.renderRoot.querySelectorAll('input')\n }\n\n checkValidation(e) {\n const currentInput = e.currentTarget\n const regExp = /^[a-zA-Z ]+$/\n\n if (!regExp.test(currentInput.value)) {\n currentInput.classList.add('checkValidName')\n } else {\n currentInput.classList.remove('checkValidName')\n }\n }\n\n async onCreateDomain() {\n const domainInput: { [prop: string]: string } = {}\n this.inputData.forEach(data => (domainInput[data.name] = data.value))\n const regExp = /^[a-zA-z0-9- ]+$/\n\n if (!regExp.test(domainInput.name)) {\n return this.showToast(i18next.t('error: domain name should consist only of letters or dashes'))\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation domainRegister($domainInput: DomainGeneratorInput!) {\n domainRegister(domainInput: $domainInput) {\n id\n name\n }\n }\n `,\n variables: { domainInput }\n })\n\n if (!response.errors) {\n await OxPrompt.open({\n type: 'success',\n title: i18next.t('text.completed'),\n text: i18next.t('text.x_created_successfully', { x: i18next.t('label.domain') }),\n confirmButton: { text: i18next.t('button.confirm') }\n })\n\n history.back()\n\n this.dispatchEvent(new CustomEvent('fetch-data'))\n }\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
1
+ {"version":3,"file":"create-domain-popup.js","sourceRoot":"","sources":["../../client/components/create-domain-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, query } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\n\n@customElement('create-domain-popup')\nclass CreateDomainPopup extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n background-color: var(--md-sys-color-background);\n overflow: auto;\n }\n\n form {\n flex: 1;\n padding: var(--spacing-large);\n }\n\n input.checkValidName {\n background-color: #fce6e6;\n }\n\n label {\n display: flex;\n flex-direction: column;\n\n font: var(--label-font);\n color: var(--label-color, var(--md-sys-color-on-surface));\n text-transform: var(--label-text-transform);\n }\n\n input {\n border: var(--border-dim-color);\n border-radius: var(--border-radius);\n margin: var(--input-margin);\n padding: var(--input-padding);\n background-color: var(--md-sys-color-surface);\n font: var(--input-font);\n }\n\n [field] {\n grid-column: span 2;\n }\n `\n ]\n\n @query('input[name=\"name\"]') nameInput!: HTMLInputElement\n\n render() {\n return html`\n <form>\n <div field grid-span>\n <label\n >${i18next.t('label.x name', { x: i18next.t('label.domain') })}<input\n type=\"text\"\n name=\"name\"\n @input=${this.checkValidation}\n autofocus\n /></label>\n </div>\n\n <div field grid-span>\n <label>${i18next.t('label.description')}<input type=\"text\" name=\"description\" /></label>\n </div>\n </form>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${e => this.onCreateDomain()} done><md-icon>add</md-icon>${i18next.t('button.create')}</button>\n </div>\n `\n }\n\n firstUpdated() {\n this.nameInput.focus() // autofocus\n }\n\n get inputData() {\n return this.renderRoot.querySelectorAll('input')\n }\n\n checkValidation(e) {\n const currentInput = e.currentTarget\n const regExp = /^[a-zA-Z ]+$/\n\n if (!regExp.test(currentInput.value)) {\n currentInput.classList.add('checkValidName')\n } else {\n currentInput.classList.remove('checkValidName')\n }\n }\n\n async onCreateDomain() {\n const domainInput: { [prop: string]: string } = {}\n this.inputData.forEach(data => (domainInput[data.name] = data.value))\n const regExp = /^[a-zA-z0-9- ]+$/\n\n if (!regExp.test(domainInput.name)) {\n return this.showToast(i18next.t('error: domain name should consist only of letters or dashes'))\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation domainRegister($domainInput: DomainGeneratorInput!) {\n domainRegister(domainInput: $domainInput) {\n id\n name\n }\n }\n `,\n variables: { domainInput }\n })\n\n if (!response.errors) {\n await OxPrompt.open({\n type: 'success',\n title: i18next.t('text.completed'),\n text: i18next.t('text.x_created_successfully', { x: i18next.t('label.domain') }),\n confirmButton: { text: i18next.t('button.confirm') }\n })\n\n history.back()\n\n this.dispatchEvent(new CustomEvent('fetch-data'))\n }\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
@@ -54,7 +54,7 @@ let DeleteUserPopup = class DeleteUserPopup extends localize(i18next)(LitElement
54
54
  button {
55
55
  background-color: var(--status-danger-color, #d14946);
56
56
  margin: 2px 2px 10px 2px;
57
- height: var(--button-height, 28px);
57
+ height: var(--form-action-height);
58
58
  color: var(--button-color, #fff);
59
59
  font: var(--button-font);
60
60
  border-radius: var(--button-radius, 5px);
@@ -1 +1 @@
1
- {"version":3,"file":"delete-user-popup.js","sourceRoot":"","sources":["../../client/components/delete-user-popup.ts"],"names":[],"mappings":";AAAA,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAA;AAG7D,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;aACzD,WAAM,GAAG;QACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2DF;KACF,AA7DY,CA6DZ;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;sBAGO,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;;;;;;;;;;KAUpC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,CAAQ;QACnB,CAAC,CAAC,cAAc,EAAE,CAAA;QAElB,MAAM,IAAI,GAAG,CAAC,CAAC,MAAyB,CAAA;QAExC,IAAI,MAAM,GAAG,EAAE,CAAA;QACf,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACxC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACrB,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;YAC7C,MAAM,CAAC;gBACL,KAAK,EAAE,MAAM;gBACb,OAAO;aACR,CAAC,CAAA;YAEF,IAAI,CAAC,OAAO,EAAE,CAAA;QAChB,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,EAAE,CAAA;YAEZ,MAAM,CAAC;gBACL,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;aACxC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;;AA3GU,eAAe;IAD3B,aAAa,CAAC,mBAAmB,CAAC;GACtB,eAAe,CA4G3B","sourcesContent":["import '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\n\n@customElement('delete-user-popup')\nexport class DeleteUserPopup extends localize(i18next)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n color: var(--popup-content-color);\n background-color: var(--popup-content-background-color);\n padding: var(--popup-content-padding);\n }\n\n * {\n box-sizing: border-box;\n }\n *:focus {\n outline: none;\n }\n\n label {\n font: bold 14px var(--theme-font);\n color: var(--md-sys-color-primary);\n }\n\n input {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--change-password-field-border);\n border-radius: var(--change-password-field-border-radius);\n padding: var(--change-password-field-padding);\n\n font: var(--change-password-field-font);\n width: var(--change-password-field-width);\n }\n input:focus {\n border: 1px solid var(--focus-background-color);\n }\n\n div.field {\n padding-bottom: 10px;\n }\n\n ::placeholder {\n font-size: 0.8rem;\n text-transform: capitalize;\n }\n\n button {\n background-color: var(--status-danger-color, #d14946);\n margin: 2px 2px 10px 2px;\n height: var(--button-height, 28px);\n color: var(--button-color, #fff);\n font: var(--button-font);\n border-radius: var(--button-radius, 5px);\n border: var(--button-border, 1px solid transparent);\n line-height: 1.5;\n }\n button:hover,\n button:active {\n background-color: var(--button-active-background-color, #22a6a7);\n border: var(--button-active-border);\n }\n `\n ]\n\n render() {\n return html`\n <h1><ox-i18n msgid=\"label.delete account\"></ox-i18n></h1>\n <span><ox-i18n msgid=\"text.delete account warning message\"></ox-i18n></span>\n <form @submit=${e => this.submit(e)}>\n <div class=\"field\">\n <label for=\"email\"><ox-i18n msgid=\"label.email\"></ox-i18n></label>\n <input id=\"email\" type=\"email\" name=\"email\" autocapitalize=\"off\" required />\n <label for=\"password\"><ox-i18n msgid=\"label.password\"></ox-i18n></label>\n <input id=\"password\" type=\"password\" name=\"password\" required />\n </div>\n\n <button class=\"ui button\" type=\"submit\"><ox-i18n msgid=\"label.delete account\"></ox-i18n></button>\n </form>\n `\n }\n\n async submit(e: Event) {\n e.preventDefault()\n\n const form = e.target as HTMLFormElement\n\n var params = {}\n new FormData(form).forEach((value, key) => {\n params[key] = value\n })\n\n try {\n const message = await auth.deleteUser(params)\n notify({\n level: 'info',\n message\n })\n\n auth.signout()\n } catch (e: any) {\n form.reset()\n\n notify({\n level: 'error',\n message: 'message' in e ? e.message : e\n })\n }\n }\n}\n"]}
1
+ {"version":3,"file":"delete-user-popup.js","sourceRoot":"","sources":["../../client/components/delete-user-popup.ts"],"names":[],"mappings":";AAAA,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAA;AAG7D,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;aACzD,WAAM,GAAG;QACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2DF;KACF,AA7DY,CA6DZ;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;sBAGO,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;;;;;;;;;;KAUpC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,CAAQ;QACnB,CAAC,CAAC,cAAc,EAAE,CAAA;QAElB,MAAM,IAAI,GAAG,CAAC,CAAC,MAAyB,CAAA;QAExC,IAAI,MAAM,GAAG,EAAE,CAAA;QACf,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACxC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACrB,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;YAC7C,MAAM,CAAC;gBACL,KAAK,EAAE,MAAM;gBACb,OAAO;aACR,CAAC,CAAA;YAEF,IAAI,CAAC,OAAO,EAAE,CAAA;QAChB,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,EAAE,CAAA;YAEZ,MAAM,CAAC;gBACL,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;aACxC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;;AA3GU,eAAe;IAD3B,aAAa,CAAC,mBAAmB,CAAC;GACtB,eAAe,CA4G3B","sourcesContent":["import '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\n\n@customElement('delete-user-popup')\nexport class DeleteUserPopup extends localize(i18next)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n color: var(--popup-content-color);\n background-color: var(--popup-content-background-color);\n padding: var(--popup-content-padding);\n }\n\n * {\n box-sizing: border-box;\n }\n *:focus {\n outline: none;\n }\n\n label {\n font: bold 14px var(--theme-font);\n color: var(--md-sys-color-primary);\n }\n\n input {\n background-color: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n color: var(--input-field-color, var(--md-sys-color-on-surface));\n border: var(--change-password-field-border);\n border-radius: var(--change-password-field-border-radius);\n padding: var(--change-password-field-padding);\n\n font: var(--change-password-field-font);\n width: var(--change-password-field-width);\n }\n input:focus {\n border: 1px solid var(--focus-background-color);\n }\n\n div.field {\n padding-bottom: 10px;\n }\n\n ::placeholder {\n font-size: 0.8rem;\n text-transform: capitalize;\n }\n\n button {\n background-color: var(--status-danger-color, #d14946);\n margin: 2px 2px 10px 2px;\n height: var(--form-action-height);\n color: var(--button-color, #fff);\n font: var(--button-font);\n border-radius: var(--button-radius, 5px);\n border: var(--button-border, 1px solid transparent);\n line-height: 1.5;\n }\n button:hover,\n button:active {\n background-color: var(--button-active-background-color, #22a6a7);\n border: var(--button-active-border);\n }\n `\n ]\n\n render() {\n return html`\n <h1><ox-i18n msgid=\"label.delete account\"></ox-i18n></h1>\n <span><ox-i18n msgid=\"text.delete account warning message\"></ox-i18n></span>\n <form @submit=${e => this.submit(e)}>\n <div class=\"field\">\n <label for=\"email\"><ox-i18n msgid=\"label.email\"></ox-i18n></label>\n <input id=\"email\" type=\"email\" name=\"email\" autocapitalize=\"off\" required />\n <label for=\"password\"><ox-i18n msgid=\"label.password\"></ox-i18n></label>\n <input id=\"password\" type=\"password\" name=\"password\" required />\n </div>\n\n <button class=\"ui button\" type=\"submit\"><ox-i18n msgid=\"label.delete account\"></ox-i18n></button>\n </form>\n `\n }\n\n async submit(e: Event) {\n e.preventDefault()\n\n const form = e.target as HTMLFormElement\n\n var params = {}\n new FormData(form).forEach((value, key) => {\n params[key] = value\n })\n\n try {\n const message = await auth.deleteUser(params)\n notify({\n level: 'info',\n message\n })\n\n auth.signout()\n } catch (e: any) {\n form.reset()\n\n notify({\n level: 'error',\n message: 'message' in e ? e.message : e\n })\n }\n }\n}\n"]}
@@ -6,23 +6,27 @@ import { customElement, property } from 'lit/decorators.js';
6
6
  import { client } from '@operato/graphql';
7
7
  import { i18next } from '@operato/i18n';
8
8
  import { isMobileDevice } from '@operato/utils';
9
+ import { ScrollbarStyles } from '@operato/styles';
9
10
  let MyLoginHistory = class MyLoginHistory extends LitElement {
10
11
  constructor() {
11
12
  super(...arguments);
12
13
  this.histories = [];
13
14
  }
14
- static { this.styles = css `
15
- :host {
16
- display: flex;
17
- flex-direction: column;
18
- background-color: var(--md-sys-color-background);
19
- padding: var(--spacing-large);
20
- overflow: auto;
21
- }
22
- ox-grist {
23
- flex: 1;
24
- }
25
- `; }
15
+ static { this.styles = [
16
+ ScrollbarStyles,
17
+ css `
18
+ :host {
19
+ display: flex;
20
+ flex-direction: column;
21
+ background-color: var(--md-sys-color-background);
22
+ padding: var(--spacing-large);
23
+ overflow: auto;
24
+ }
25
+ ox-grist {
26
+ flex: 1;
27
+ }
28
+ `
29
+ ]; }
26
30
  render() {
27
31
  if (!this.histories?.length)
28
32
  return html ``;