@things-factory/auth-ui 10.1.254 → 10.1.257

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 (83) hide show
  1. package/dist-client/bootstrap.js +2 -2
  2. package/dist-client/bootstrap.js.map +1 -1
  3. package/dist-client/components/access-decision-popup.d.ts +9 -8
  4. package/dist-client/components/access-decision-popup.js +46 -58
  5. package/dist-client/components/access-decision-popup.js.map +1 -1
  6. package/dist-client/components/access-request-popup.d.ts +13 -8
  7. package/dist-client/components/access-request-popup.js +78 -102
  8. package/dist-client/components/access-request-popup.js.map +1 -1
  9. package/dist-client/components/app-binding-detail.d.ts +7 -5
  10. package/dist-client/components/app-binding-detail.js +62 -82
  11. package/dist-client/components/app-binding-detail.js.map +1 -1
  12. package/dist-client/components/appliance-editor.d.ts +10 -9
  13. package/dist-client/components/appliance-editor.js +87 -138
  14. package/dist-client/components/appliance-editor.js.map +1 -1
  15. package/dist-client/components/application-editor.d.ts +7 -5
  16. package/dist-client/components/application-editor.js +58 -92
  17. package/dist-client/components/application-editor.js.map +1 -1
  18. package/dist-client/components/my-login-history.js +18 -40
  19. package/dist-client/components/my-login-history.js.map +1 -1
  20. package/dist-client/components/my-passkeys.d.ts +4 -3
  21. package/dist-client/components/my-passkeys.js +29 -51
  22. package/dist-client/components/my-passkeys.js.map +1 -1
  23. package/dist-client/components/my-roles.d.ts +3 -4
  24. package/dist-client/components/my-roles.js +36 -55
  25. package/dist-client/components/my-roles.js.map +1 -1
  26. package/dist-client/components/partner-role-editor.js +27 -50
  27. package/dist-client/components/partner-role-editor.js.map +1 -1
  28. package/dist-client/components/profile-component.d.ts +2 -2
  29. package/dist-client/components/profile-component.js +18 -26
  30. package/dist-client/components/profile-component.js.map +1 -1
  31. package/dist-client/components/role-template-picker.d.ts +3 -3
  32. package/dist-client/components/role-template-picker.js +28 -42
  33. package/dist-client/components/role-template-picker.js.map +1 -1
  34. package/dist-client/components/user-detail.d.ts +23 -22
  35. package/dist-client/components/user-detail.js +125 -179
  36. package/dist-client/components/user-detail.js.map +1 -1
  37. package/dist-client/components/user-invite.d.ts +8 -6
  38. package/dist-client/components/user-invite.js +106 -141
  39. package/dist-client/components/user-invite.js.map +1 -1
  40. package/dist-client/components/user-popups-standard.test.js +1 -1
  41. package/dist-client/components/user-popups-standard.test.js.map +1 -1
  42. package/dist-client/components/user-role-editor.js +72 -122
  43. package/dist-client/components/user-role-editor.js.map +1 -1
  44. package/dist-client/entity360/person-360.d.ts +8 -11
  45. package/dist-client/entity360/person-360.js +49 -68
  46. package/dist-client/entity360/person-360.js.map +1 -1
  47. package/dist-client/entries/oauth2/oauth2-decision-page.js +13 -25
  48. package/dist-client/entries/oauth2/oauth2-decision-page.js.map +1 -1
  49. package/dist-client/identity-confirmation/identity-confirmation.d.ts +13 -14
  50. package/dist-client/identity-confirmation/identity-confirmation.js +39 -45
  51. package/dist-client/identity-confirmation/identity-confirmation.js.map +1 -1
  52. package/dist-client/pages/audit/audit-page.d.ts +4 -4
  53. package/dist-client/pages/audit/audit-page.js +28 -39
  54. package/dist-client/pages/audit/audit-page.js.map +1 -1
  55. package/dist-client/pages/domain-link/domain-link-management.js +1 -1
  56. package/dist-client/pages/domain-link/domain-link-management.js.map +1 -1
  57. package/dist-client/pages/partner/partner-management.js +30 -68
  58. package/dist-client/pages/partner/partner-management.js.map +1 -1
  59. package/dist-client/pages/role/role-extra-removal.test.d.mts +1 -0
  60. package/dist-client/pages/role/role-extra-removal.test.mjs +73 -0
  61. package/dist-client/pages/role/role-extra-removal.test.mjs.map +1 -0
  62. package/dist-client/pages/role/role-list-page.js +20 -22
  63. package/dist-client/pages/role/role-list-page.js.map +1 -1
  64. package/dist-client/pages/role/role-privilege-page.d.ts +36 -8
  65. package/dist-client/pages/role/role-privilege-page.js +216 -158
  66. package/dist-client/pages/role/role-privilege-page.js.map +1 -1
  67. package/dist-client/pages/role/role-task-review.test.js +20 -16
  68. package/dist-client/pages/role/role-task-review.test.js.map +1 -1
  69. package/dist-client/pages/role/role-template-gap.test.js +8 -3
  70. package/dist-client/pages/role/role-template-gap.test.js.map +1 -1
  71. package/dist-client/tsconfig.tsbuildinfo +1 -1
  72. package/helps/auth/roles.en.md +13 -49
  73. package/helps/auth/roles.ko.md +13 -32
  74. package/package.json +9 -8
  75. package/tests/page-state.test.ts +2 -1
  76. package/tests/password-rule-shown.test.ts +2 -2
  77. package/tests/person-360-sections.test.ts +9 -5
  78. package/translations/en.json +5 -1
  79. package/translations/es.json +5 -1
  80. package/translations/ja.json +5 -1
  81. package/translations/ko.json +5 -1
  82. package/translations/ms.json +5 -1
  83. package/translations/zh.json +5 -1
@@ -5,7 +5,7 @@ import '@operato/popup/ox-popup-actions.js';
5
5
  import gql from 'graphql-tag';
6
6
  import { css, html, LitElement, nothing } from 'lit';
7
7
  import { customElement, query, state } from 'lit/decorators.js';
8
- import { client, gqlContext } from '@operato/graphql';
8
+ import { gqlContext, MutationController, QueryController } from '@operato/graphql';
9
9
  import { i18next, localize } from '@operato/i18n';
10
10
  import { InheritedValueType } from '@operato/shell';
11
11
  import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles';
@@ -42,11 +42,51 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
42
42
  constructor() {
43
43
  super(...arguments);
44
44
  this.email = '';
45
- this.lookup = 'idle';
46
- this.roles = [];
45
+ /** The email asked about - set once the person stops typing. */
46
+ this.asked = '';
47
47
  this.selected = new Set();
48
- this.working = false;
49
48
  this.message = '';
49
+ this.existence = new QueryController(this, gql `
50
+ query ($email: EmailAddress!) {
51
+ checkUserExistence(email: $email)
52
+ }
53
+ `, { variables: () => (this.asked ? { email: this.asked } : undefined), context: gqlContext() });
54
+ this.assignable = new QueryController(this, gql `
55
+ query ($inherited: InheritedValueType) {
56
+ roles(inherited: $inherited) {
57
+ items {
58
+ id
59
+ name
60
+ }
61
+ }
62
+ }
63
+ `, { variables: () => ({ inherited: InheritedValueType.Include }), context: gqlContext() });
64
+ this.saves = {
65
+ /** 이미 계정이 있는 사람 — 이 도메인에 바로 들어온다. */
66
+ invite: new MutationController(this, gql `
67
+ mutation ($username: String!) {
68
+ inviteUser(username: $username)
69
+ }
70
+ `, { context: gqlContext(), showsError: true }),
71
+ create: new MutationController(this, gql `
72
+ mutation ($user: NewUser!) {
73
+ createUser(user: $user) {
74
+ id
75
+ username
76
+ email
77
+ name
78
+ status
79
+ }
80
+ }
81
+ `, { context: gqlContext(), showsError: true }),
82
+ roles: new MutationController(this, gql `
83
+ mutation ($username: String!, $availableRoles: [ObjectRef!]!, $selectedRoles: [ObjectRef!]!) {
84
+ updateUserRoles(username: $username, availableRoles: $availableRoles, selectedRoles: $selectedRoles) {
85
+ id
86
+ }
87
+ }
88
+ `, { context: gqlContext() })
89
+ };
50
90
  }
51
91
  /* 팝업의 집 모양(page-standard §7·§8): 본문만 스크롤, 칸은 라벨 위의 날것 입력, 단추 줄은 `ox-popup-actions`. */
52
92
  static { this.styles = [
@@ -122,6 +162,27 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
122
162
  }
123
163
  `
124
164
  ]; }
165
+ get roles() {
166
+ return this.assignable.data?.roles?.items || [];
167
+ }
168
+ get working() {
169
+ return Object.values(this.saves).some(save => save.pending);
170
+ }
171
+ get lookup() {
172
+ /* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */
173
+ if (!this.asked || this.asked !== this.email.trim()) {
174
+ return 'idle';
175
+ }
176
+ if (this.existence.loading) {
177
+ return 'checking';
178
+ }
179
+ /* 못 물었으면 「없다」가 아니다 — 없다고 그리면 있는 사람에게 계정을 만들러 간다. */
180
+ const exists = this.existence.error ? undefined : this.existence.data?.checkUserExistence;
181
+ if (typeof exists !== 'boolean') {
182
+ return 'failed';
183
+ }
184
+ return exists ? 'found' : 'absent';
185
+ }
125
186
  /** 계정을 새로 만드는 경로인가. */
126
187
  get creating() {
127
188
  return this.lookup === 'absent';
@@ -144,7 +205,7 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
144
205
  placeholder="name@company.com"
145
206
  @input=${(e) => this.onEmail(e.target.value)}
146
207
  />
147
- <span invalid>${this.message}</span>
208
+ <span invalid>${this.lookup === 'failed' ? i18next.t('text.could not look up the account') : this.message}</span>
148
209
  </label>
149
210
 
150
211
  ${this.lookup === 'checking' ? html `<div class="note">${i18next.t('text.loading')}</div>` : nothing}
@@ -227,10 +288,6 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
227
288
  : nothing}
228
289
  `;
229
290
  }
230
- connectedCallback() {
231
- super.connectedCallback();
232
- this.fetchRoles();
233
- }
234
291
  disconnectedCallback() {
235
292
  super.disconnectedCallback();
236
293
  clearTimeout(this.timer);
@@ -238,61 +295,12 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
238
295
  onEmail(value) {
239
296
  this.email = value;
240
297
  this.message = '';
241
- this.lookup = 'idle';
242
298
  clearTimeout(this.timer);
243
299
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())) {
244
300
  return;
245
301
  }
246
302
  /* 한 글자마다 묻지 않는다. 사람이 멈추면 묻는다. */
247
- this.timer = setTimeout(() => this.check(), 400);
248
- }
249
- async check() {
250
- const email = this.email.trim();
251
- this.lookup = 'checking';
252
- let exists;
253
- try {
254
- const response = await client.query({
255
- query: gql `
256
- query ($email: EmailAddress!) {
257
- checkUserExistence(email: $email)
258
- }
259
- `,
260
- variables: { email },
261
- context: gqlContext()
262
- });
263
- exists = response.errors?.length ? undefined : response.data?.checkUserExistence;
264
- }
265
- catch (e) {
266
- exists = undefined;
267
- }
268
- /* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */
269
- if (email !== this.email.trim()) {
270
- return;
271
- }
272
- /* 못 물었으면 「없다」가 아니다 — 없다고 그리면 있는 사람에게 계정을 만들러 간다. */
273
- if (typeof exists !== 'boolean') {
274
- this.lookup = 'failed';
275
- this.message = i18next.t('text.could not look up the account');
276
- return;
277
- }
278
- this.lookup = exists ? 'found' : 'absent';
279
- }
280
- async fetchRoles() {
281
- const response = await client.query({
282
- query: gql `
283
- query ($inherited: InheritedValueType) {
284
- roles(inherited: $inherited) {
285
- items {
286
- id
287
- name
288
- }
289
- }
290
- }
291
- `,
292
- variables: { inherited: InheritedValueType.Include },
293
- context: gqlContext()
294
- });
295
- this.roles = response.data?.roles?.items || [];
303
+ this.timer = setTimeout(() => (this.asked = this.email.trim()), 400);
296
304
  }
297
305
  onToggle(id, checked) {
298
306
  const next = new Set(this.selected);
@@ -310,60 +318,35 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
310
318
  this.message = i18next.t('text.x is required', { x: i18next.t('field.name') });
311
319
  return;
312
320
  }
313
- this.working = true;
314
- try {
315
- const entered = creating ? await this.createAccount(email, username) : await this.addToDomain(username);
316
- if (!entered) {
317
- return;
318
- }
319
- if (this.selected.size) {
320
- /*
321
- * 넣기와 역할 부여가 두 호출이다. 여기가 실패하면 그 사람은 역할 없이 들어와 있고,
322
- * 목록이 그것을 빨갛게 말한다 — 사라지지 않는다.
323
- *
324
- * `createUser` 도 역할을 받지만 **이 도메인 소유의 역할만** 붙인다. 상속 역할과 파트너
325
- * 역할을 조용히 버리므로, 고른 것을 그대로 주려면 이 호출을 쓴다.
326
- */
327
- await client.mutate({
328
- mutation: gql `
329
- mutation ($username: String!, $availableRoles: [ObjectRef!]!, $selectedRoles: [ObjectRef!]!) {
330
- updateUserRoles(username: $username, availableRoles: $availableRoles, selectedRoles: $selectedRoles) {
331
- id
332
- }
333
- }
334
- `,
335
- variables: {
336
- username,
337
- availableRoles: this.roles.map(role => ({ id: role.id })),
338
- selectedRoles: this.roles.filter(role => this.selected.has(role.id)).map(role => ({ id: role.id }))
339
- },
340
- context: gqlContext()
341
- });
342
- }
343
- /* 새 계정은 「설정 대기」다 — 받는 쪽(목록)이 이어서 설정 링크를 건네게 한다(`detail.created`). */
344
- this.dispatchEvent(new CustomEvent('user-invited', {
345
- detail: { username, created: creating ? entered : null },
346
- bubbles: true,
347
- composed: true
348
- }));
321
+ const entered = creating ? await this.createAccount(email, username) : await this.addToDomain(username);
322
+ if (!entered) {
323
+ return;
349
324
  }
350
- finally {
351
- this.working = false;
325
+ if (this.selected.size) {
326
+ /*
327
+ * 넣기와 역할 부여가 두 호출이다. 여기가 실패하면 그 사람은 역할 없이 들어와 있고,
328
+ * 목록이 그것을 빨갛게 말한다 — 사라지지 않는다.
329
+ *
330
+ * `createUser` 도 역할을 받지만 **이 도메인 소유의 역할만** 붙인다. 상속 역할과 파트너
331
+ * 역할을 조용히 버리므로, 고른 것을 그대로 주려면 이 호출을 쓴다.
332
+ */
333
+ await this.saves.roles.run({
334
+ username,
335
+ availableRoles: this.roles.map(role => ({ id: role.id })),
336
+ selectedRoles: this.roles.filter(role => this.selected.has(role.id)).map(role => ({ id: role.id }))
337
+ });
352
338
  }
339
+ /* 새 계정은 「설정 대기」다 — 받는 쪽(목록)이 이어서 설정 링크를 건네게 한다(`detail.created`). */
340
+ this.dispatchEvent(new CustomEvent('user-invited', {
341
+ detail: { username, created: creating ? entered : null },
342
+ bubbles: true,
343
+ composed: true
344
+ }));
353
345
  }
354
346
  /** 이미 계정이 있는 사람 — 이 도메인에 바로 들어온다. */
355
347
  async addToDomain(username) {
356
- const response = await client.mutate({
357
- mutation: gql `
358
- mutation ($username: String!) {
359
- inviteUser(username: $username)
360
- }
361
- `,
362
- variables: { username },
363
- context: gqlContext()
364
- });
365
- if (response.errors) {
366
- this.message = response.errors[0]?.message || '';
348
+ if (!(await this.saves.invite.run({ username }))) {
349
+ this.message = messageOf(this.saves.invite.error);
367
350
  return false;
368
351
  }
369
352
  return true;
@@ -377,33 +360,19 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
377
360
  * @returns 만든 사람(설정 링크 팝업이 읽는 칸들), 못 만들었으면 null
378
361
  */
379
362
  async createAccount(email, username) {
380
- const response = await client.mutate({
381
- mutation: gql `
382
- mutation ($user: NewUser!) {
383
- createUser(user: $user) {
384
- id
385
- username
386
- email
387
- name
388
- status
389
- }
390
- }
391
- `,
392
- variables: {
393
- user: {
394
- username,
395
- name: this.nameInput.value.trim(),
396
- email,
397
- userType: 'user'
398
- }
399
- },
400
- context: gqlContext()
363
+ const created = await this.saves.create.run({
364
+ user: {
365
+ username,
366
+ name: this.nameInput.value.trim(),
367
+ email,
368
+ userType: 'user'
369
+ }
401
370
  });
402
- if (response.errors) {
403
- this.message = response.errors[0]?.message || '';
371
+ if (!created) {
372
+ this.message = messageOf(this.saves.create.error);
404
373
  return null;
405
374
  }
406
- return response.data?.createUser || null;
375
+ return created.createUser || null;
407
376
  }
408
377
  };
409
378
  __decorate([
@@ -413,19 +382,11 @@ __decorate([
413
382
  __decorate([
414
383
  state(),
415
384
  __metadata("design:type", String)
416
- ], UserInvite.prototype, "lookup", void 0);
417
- __decorate([
418
- state(),
419
- __metadata("design:type", Array)
420
- ], UserInvite.prototype, "roles", void 0);
385
+ ], UserInvite.prototype, "asked", void 0);
421
386
  __decorate([
422
387
  state(),
423
388
  __metadata("design:type", Set)
424
389
  ], UserInvite.prototype, "selected", void 0);
425
- __decorate([
426
- state(),
427
- __metadata("design:type", Boolean)
428
- ], UserInvite.prototype, "working", void 0);
429
390
  __decorate([
430
391
  state(),
431
392
  __metadata("design:type", String)
@@ -442,4 +403,8 @@ UserInvite = __decorate([
442
403
  customElement('user-invite')
443
404
  ], UserInvite);
444
405
  export { UserInvite };
406
+ /** The server's sentence for a refused save - the first one, as it was drawn under the email. */
407
+ function messageOf(error) {
408
+ return error?.graphQLErrors?.[0]?.message || error?.message || '';
409
+ }
445
410
  //# sourceMappingURL=user-invite.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-invite.js","sourceRoot":"","sources":["../../client/components/user-invite.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport type { CSSResultGroup } from 'lit'\n\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { InheritedValueType } from '@operato/shell'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\n\n/**\n * 사람을 이 도메인으로 부르는 자리.\n *\n * ── 하나의 단추가 두 일을 덮고 있었다 ───────────────────────────────────────\n * 옛 화면의 「초대」는 `inviteUser` 를 부르는데, 그것이 하는 일은 **이미 계정이 있는 사람을 이\n * 도메인에 즉시 넣는 것**이다(`user-mutation.ts` 의 `inviteUser` — `invitee.domains` 에 도메인을\n * 더해 저장하고 끝난다). 메일도 가지 않고 상대의 동의도 없다.\n *\n * 그리고 계정이 없는 사람은 **거절했다** — 「사용자를 찾을 수 없습니다」. 관리자는 초대장을\n * 보냈다고 믿고 기다린다. 계정을 만드는 길은 화면의 **다른 자리**(`create-user`)에 있었고, 그\n * 자리는 기본 비밀번호가 설정된 도메인에서만 나타났다.\n *\n * ── 그래서 이메일 하나로 시작해 갈라 보여 준다 ──────────────────────────────\n * `checkUserExistence` 가 시스템 전체에서 그 이메일을 찾는다. 관리자는 「넣기인가 만들기인가」를\n * 먼저 고르지 않는다 — 이메일을 넣으면 화면이 무엇이 일어날지 말하고, 단추 글자도 그에 맞춘다.\n *\n * 계정이 있다 이 도메인에 바로 들어온다 (`inviteUser`)\n * 계정이 없다 「비밀번호 설정 대기」 계정을 만들고(`createUser`), 이어서 비밀번호 설정 링크를\n * 건넨다(`password-setup-link-popup`). 관리자는 비밀번호를 정하지 않는다(ADR-0161).\n *\n * ── 역할을 함께 고른다 ──────────────────────────────────────────────────────\n * 역할 없이 들어온 사람은 로그인해도 화면이 하나도 없다. 부르는 자리에서 함께 정하면 그 사람이\n * 처음 들어왔을 때 빈 화면을 보지 않는다.\n *\n * 넣기와 역할 부여는 **호출이 둘**이다(`inviteUser` 는 역할을 받지 않고, `createUser` 는 받지만\n * **이 도메인 소유의 역할만** 붙인다 — 상속 역할과 파트너 역할을 조용히 버린다). 그래서 두\n * 경로 모두 넣은 뒤 `updateUserRoles` 로 한 번에 준다. 앞이 되고 뒤가 실패하면 역할 없이 들어온\n * 상태가 되는데, 목록이 그것을 빨갛게 말하므로 관리자가 이어서 고칠 수 있다.\n */\n@customElement('user-invite')\nexport class UserInvite extends localize(i18next)(LitElement) {\n /* 팝업의 집 모양(page-standard §7·§8): 본문만 스크롤, 칸은 라벨 위의 날것 입력, 단추 줄은 `ox-popup-actions`. */\n static styles: CSSResultGroup = [\n CommonPopupStyles,\n PopupBodyStyles,\n FormFieldStyles,\n PopupFieldStyles,\n css`\n /* 오류 문장은 칸 바로 아래(§7). */\n [invalid] {\n color: var(--ui-crit, var(--md-sys-color-error));\n font-size: var(--ui-t-small, 11.5px);\n }\n\n [invalid]:empty {\n display: none;\n }\n\n [guide] p {\n margin: 0 0 var(--spacing-small, 4px);\n line-height: var(--popup-line-height);\n }\n\n [guide] dl {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 2px var(--spacing-large, 12px);\n margin: 0;\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 11.5px);\n line-height: var(--popup-line-height);\n }\n\n [guide] dl > div {\n display: contents;\n }\n\n [guide] dt {\n font-weight: 600;\n }\n\n [guide] dd {\n margin: 0;\n }\n\n /* 넣은 이메일이 해당하는 갈래 — 띠로 짚는다. */\n [guide] dl > div > * {\n padding: 1px var(--spacing-small, 4px);\n }\n\n [guide] dl > div[current] > * {\n background: var(--ui-line-soft, var(--md-sys-color-surface-container));\n color: var(--ui-ink);\n }\n\n [group-label] {\n margin-top: var(--spacing-medium, 8px);\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n ul[roles] {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n ul[roles] li {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n padding: 3px 0;\n }\n `\n ]\n\n @state() private email: string = ''\n @state() private lookup: 'idle' | 'checking' | 'found' | 'absent' | 'failed' = 'idle'\n @state() private roles: any[] = []\n @state() private selected: Set<string> = new Set()\n @state() private working: boolean = false\n @state() private message: string = ''\n\n @query('[name=name]') private nameInput!: HTMLInputElement\n @query('[name=username]') private usernameInput!: HTMLInputElement\n\n private timer?: ReturnType<typeof setTimeout>\n\n /** 계정을 새로 만드는 경로인가. */\n private get creating(): boolean {\n return this.lookup === 'absent'\n }\n\n /** 부를 수 있나 — 단추와 처리기가 같은 답을 본다. */\n private get callable(): boolean {\n return !this.working && (this.lookup === 'found' || this.creating)\n }\n\n render() {\n return html`\n <div class=\"body\">\n ${this.renderGuide()}\n\n <label class=\"field\">\n <span>${i18next.t('field.email')}</span>\n <input\n name=\"email\"\n type=\"email\"\n .value=${this.email}\n placeholder=\"name@company.com\"\n @input=${(e: Event) => this.onEmail((e.target as HTMLInputElement).value)}\n />\n <span invalid>${this.message}</span>\n </label>\n\n ${this.lookup === 'checking' ? html`<div class=\"note\">${i18next.t('text.loading')}</div>` : nothing}\n ${this.creating ? this.renderNewAccount() : nothing}\n ${this.lookup === 'found' || this.creating ? this.renderRoles() : nothing}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>\n ${i18next.t('button.cancel')}\n </md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.callable} @click=${() => this.onCall()}>\n ${this.creating ? i18next.t('label.create account and add') : i18next.t('label.add to domain')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /**\n * 사용자 추가가 무엇인지 — 이메일을 넣기 전에 말한다.\n *\n * 칸 하나만 있으면 이 팝업이 무엇을 하는지 알 수 없다. 계정은 하나로 여러 도메인에서 쓰고, 추가는\n * 그 계정을 이 도메인에 넣는 일이다. 이메일에 따라 일어나는 일이 갈리므로 갈래를 먼저 보이고,\n * 이메일을 찾으면 해당하는 줄을 짚는다.\n */\n private renderGuide() {\n return html`\n <div guide>\n <p>${i18next.t('text.add user puts an account into this domain')}</p>\n <dl>\n <div ?current=${this.lookup === 'found'}>\n <dt>${i18next.t('label.account exists')}</dt>\n <dd>${i18next.t('text.they join this domain at once')}</dd>\n </div>\n <div ?current=${this.lookup === 'absent'}>\n <dt>${i18next.t('label.no account')}</dt>\n <dd>${i18next.t('text.an account is made and a setup link handed over')}</dd>\n </div>\n <div>\n <dt>${i18next.t('field.roles')}</dt>\n <dd>${i18next.t('text.roles decide the screens they open')}</dd>\n </div>\n </dl>\n </div>\n `\n }\n\n private renderNewAccount() {\n return html`\n <label class=\"field\">\n <span>${i18next.t('field.name')}</span>\n <input name=\"name\" type=\"text\" @input=${() => this.requestUpdate()} />\n </label>\n\n <!-- 서버가 받는 글자는 A-Z a-z 0-9 . _ @ - 다. 비우면 이메일을 그대로 쓴다. -->\n <label class=\"field\">\n <span>${i18next.t('field.username')}</span>\n <input name=\"username\" type=\"text\" placeholder=${this.email} />\n </label>\n `\n }\n\n private renderRoles() {\n return html`\n <div group-label>${i18next.t('field.roles')}</div>\n ${\n this.roles.length\n ? html`<ul roles>\n ${this.roles.map(\n role => html`\n <li>\n <input\n type=\"checkbox\"\n id=\"invite-role-${role.id}\"\n .checked=${this.selected.has(role.id)}\n @change=${(e: Event) => this.onToggle(role.id, (e.target as HTMLInputElement).checked)}\n />\n <label for=\"invite-role-${role.id}\">${role.name}</label>\n </li>\n `\n )}\n </ul>`\n : html`<div class=\"failure\">${i18next.t('text.no role defined in this domain')}</div>`\n }\n ${\n this.roles.length && this.selected.size === 0\n ? html`<div class=\"failure\">${i18next.t('text.without a role they see nothing')}</div>`\n : nothing\n }\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.fetchRoles()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n clearTimeout(this.timer)\n }\n\n private onEmail(value: string) {\n this.email = value\n this.message = ''\n this.lookup = 'idle'\n\n clearTimeout(this.timer)\n\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim())) {\n return\n }\n\n /* 한 글자마다 묻지 않는다. 사람이 멈추면 묻는다. */\n this.timer = setTimeout(() => this.check(), 400)\n }\n\n private async check() {\n const email = this.email.trim()\n this.lookup = 'checking'\n\n let exists: boolean | undefined\n\n try {\n const response = await client.query({\n query: gql`\n query ($email: EmailAddress!) {\n checkUserExistence(email: $email)\n }\n `,\n variables: { email },\n context: gqlContext()\n })\n\n exists = response.errors?.length ? undefined : response.data?.checkUserExistence\n } catch (e) {\n exists = undefined\n }\n\n /* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */\n if (email !== this.email.trim()) {\n return\n }\n\n /* 못 물었으면 「없다」가 아니다 — 없다고 그리면 있는 사람에게 계정을 만들러 간다. */\n if (typeof exists !== 'boolean') {\n this.lookup = 'failed'\n this.message = i18next.t('text.could not look up the account')\n return\n }\n\n this.lookup = exists ? 'found' : 'absent'\n }\n\n private async fetchRoles() {\n const response = await client.query({\n query: gql`\n query ($inherited: InheritedValueType) {\n roles(inherited: $inherited) {\n items {\n id\n name\n }\n }\n }\n `,\n variables: { inherited: InheritedValueType.Include },\n context: gqlContext()\n })\n\n this.roles = response.data?.roles?.items || []\n }\n\n private onToggle(id: string, checked: boolean) {\n const next = new Set(this.selected)\n checked ? next.add(id) : next.delete(id)\n this.selected = next\n }\n\n private async onCall() {\n if (!this.callable) {\n return\n }\n\n const email = this.email.trim()\n const creating = this.creating\n const username = this.creating ? this.usernameInput?.value.trim() || email : email\n\n if (this.creating && !this.nameInput?.value.trim()) {\n this.message = i18next.t('text.x is required', { x: i18next.t('field.name') })\n return\n }\n\n this.working = true\n\n try {\n const entered = creating ? await this.createAccount(email, username) : await this.addToDomain(username)\n\n if (!entered) {\n return\n }\n\n if (this.selected.size) {\n /*\n * 넣기와 역할 부여가 두 호출이다. 여기가 실패하면 그 사람은 역할 없이 들어와 있고,\n * 목록이 그것을 빨갛게 말한다 — 사라지지 않는다.\n *\n * `createUser` 도 역할을 받지만 **이 도메인 소유의 역할만** 붙인다. 상속 역할과 파트너\n * 역할을 조용히 버리므로, 고른 것을 그대로 주려면 이 호출을 쓴다.\n */\n await client.mutate({\n mutation: gql`\n mutation ($username: String!, $availableRoles: [ObjectRef!]!, $selectedRoles: [ObjectRef!]!) {\n updateUserRoles(username: $username, availableRoles: $availableRoles, selectedRoles: $selectedRoles) {\n id\n }\n }\n `,\n variables: {\n username,\n availableRoles: this.roles.map(role => ({ id: role.id })),\n selectedRoles: this.roles.filter(role => this.selected.has(role.id)).map(role => ({ id: role.id }))\n },\n context: gqlContext()\n })\n }\n\n /* 새 계정은 「설정 대기」다 — 받는 쪽(목록)이 이어서 설정 링크를 건네게 한다(`detail.created`). */\n this.dispatchEvent(\n new CustomEvent('user-invited', {\n detail: { username, created: creating ? entered : null },\n bubbles: true,\n composed: true\n })\n )\n } finally {\n this.working = false\n }\n }\n\n /** 이미 계정이 있는 사람 — 이 도메인에 바로 들어온다. */\n private async addToDomain(username: string): Promise<boolean> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($username: String!) {\n inviteUser(username: $username)\n }\n `,\n variables: { username },\n context: gqlContext()\n })\n\n if (response.errors) {\n this.message = response.errors[0]?.message || ''\n return false\n }\n\n return true\n }\n\n /**\n * 계정이 없는 사람 — 「비밀번호 설정 대기」로 만든다. 비밀번호는 본인이 설정 링크로 정한다(ADR-0161).\n *\n * 역할은 여기서 안 넘긴다. `createUser` 는 **이 도메인 소유의 역할만** 붙이므로 상속·파트너\n * 역할이 조용히 빠진다. 부르는 쪽에서 `updateUserRoles` 로 한 번에 준다.\n *\n * @returns 만든 사람(설정 링크 팝업이 읽는 칸들), 못 만들었으면 null\n */\n private async createAccount(email: string, username: string): Promise<any> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($user: NewUser!) {\n createUser(user: $user) {\n id\n username\n email\n name\n status\n }\n }\n `,\n variables: {\n user: {\n username,\n name: this.nameInput.value.trim(),\n email,\n userType: 'user'\n }\n },\n context: gqlContext()\n })\n\n if (response.errors) {\n this.message = response.errors[0]?.message || ''\n return null\n }\n\n return response.data?.createUser || null\n }\n}\n"]}
1
+ {"version":3,"file":"user-invite.js","sourceRoot":"","sources":["../../client/components/user-invite.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/text-button.js'\nimport '@material/web/button/filled-button.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport type { CSSResultGroup } from 'lit'\n\nimport { gqlContext, MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { InheritedValueType } from '@operato/shell'\nimport { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles'\n\n/**\n * 사람을 이 도메인으로 부르는 자리.\n *\n * ── 하나의 단추가 두 일을 덮고 있었다 ───────────────────────────────────────\n * 옛 화면의 「초대」는 `inviteUser` 를 부르는데, 그것이 하는 일은 **이미 계정이 있는 사람을 이\n * 도메인에 즉시 넣는 것**이다(`user-mutation.ts` 의 `inviteUser` — `invitee.domains` 에 도메인을\n * 더해 저장하고 끝난다). 메일도 가지 않고 상대의 동의도 없다.\n *\n * 그리고 계정이 없는 사람은 **거절했다** — 「사용자를 찾을 수 없습니다」. 관리자는 초대장을\n * 보냈다고 믿고 기다린다. 계정을 만드는 길은 화면의 **다른 자리**(`create-user`)에 있었고, 그\n * 자리는 기본 비밀번호가 설정된 도메인에서만 나타났다.\n *\n * ── 그래서 이메일 하나로 시작해 갈라 보여 준다 ──────────────────────────────\n * `checkUserExistence` 가 시스템 전체에서 그 이메일을 찾는다. 관리자는 「넣기인가 만들기인가」를\n * 먼저 고르지 않는다 — 이메일을 넣으면 화면이 무엇이 일어날지 말하고, 단추 글자도 그에 맞춘다.\n *\n * 계정이 있다 이 도메인에 바로 들어온다 (`inviteUser`)\n * 계정이 없다 「비밀번호 설정 대기」 계정을 만들고(`createUser`), 이어서 비밀번호 설정 링크를\n * 건넨다(`password-setup-link-popup`). 관리자는 비밀번호를 정하지 않는다(ADR-0161).\n *\n * ── 역할을 함께 고른다 ──────────────────────────────────────────────────────\n * 역할 없이 들어온 사람은 로그인해도 화면이 하나도 없다. 부르는 자리에서 함께 정하면 그 사람이\n * 처음 들어왔을 때 빈 화면을 보지 않는다.\n *\n * 넣기와 역할 부여는 **호출이 둘**이다(`inviteUser` 는 역할을 받지 않고, `createUser` 는 받지만\n * **이 도메인 소유의 역할만** 붙인다 — 상속 역할과 파트너 역할을 조용히 버린다). 그래서 두\n * 경로 모두 넣은 뒤 `updateUserRoles` 로 한 번에 준다. 앞이 되고 뒤가 실패하면 역할 없이 들어온\n * 상태가 되는데, 목록이 그것을 빨갛게 말하므로 관리자가 이어서 고칠 수 있다.\n */\n@customElement('user-invite')\nexport class UserInvite extends localize(i18next)(LitElement) {\n /* 팝업의 집 모양(page-standard §7·§8): 본문만 스크롤, 칸은 라벨 위의 날것 입력, 단추 줄은 `ox-popup-actions`. */\n static styles: CSSResultGroup = [\n CommonPopupStyles,\n PopupBodyStyles,\n FormFieldStyles,\n PopupFieldStyles,\n css`\n /* 오류 문장은 칸 바로 아래(§7). */\n [invalid] {\n color: var(--ui-crit, var(--md-sys-color-error));\n font-size: var(--ui-t-small, 11.5px);\n }\n\n [invalid]:empty {\n display: none;\n }\n\n [guide] p {\n margin: 0 0 var(--spacing-small, 4px);\n line-height: var(--popup-line-height);\n }\n\n [guide] dl {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 2px var(--spacing-large, 12px);\n margin: 0;\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 11.5px);\n line-height: var(--popup-line-height);\n }\n\n [guide] dl > div {\n display: contents;\n }\n\n [guide] dt {\n font-weight: 600;\n }\n\n [guide] dd {\n margin: 0;\n }\n\n /* 넣은 이메일이 해당하는 갈래 — 띠로 짚는다. */\n [guide] dl > div > * {\n padding: 1px var(--spacing-small, 4px);\n }\n\n [guide] dl > div[current] > * {\n background: var(--ui-line-soft, var(--md-sys-color-surface-container));\n color: var(--ui-ink);\n }\n\n [group-label] {\n margin-top: var(--spacing-medium, 8px);\n color: var(--ui-muted);\n font-size: var(--ui-t-small, 11.5px);\n }\n\n ul[roles] {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n ul[roles] li {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n padding: 3px 0;\n }\n `\n ]\n\n @state() private email: string = ''\n /** The email asked about - set once the person stops typing. */\n @state() private asked: string = ''\n @state() private selected: Set<string> = new Set()\n @state() private message: string = ''\n\n private existence = new QueryController(\n this,\n gql`\n query ($email: EmailAddress!) {\n checkUserExistence(email: $email)\n }\n `,\n { variables: () => (this.asked ? { email: this.asked } : undefined), context: gqlContext() }\n )\n\n private assignable = new QueryController(\n this,\n gql`\n query ($inherited: InheritedValueType) {\n roles(inherited: $inherited) {\n items {\n id\n name\n }\n }\n }\n `,\n { variables: () => ({ inherited: InheritedValueType.Include }), context: gqlContext() }\n )\n\n private saves = {\n /** 이미 계정이 있는 사람 — 이 도메인에 바로 들어온다. */\n invite: new MutationController(\n this,\n gql`\n mutation ($username: String!) {\n inviteUser(username: $username)\n }\n `,\n { context: gqlContext(), showsError: true }\n ),\n create: new MutationController(\n this,\n gql`\n mutation ($user: NewUser!) {\n createUser(user: $user) {\n id\n username\n email\n name\n status\n }\n }\n `,\n { context: gqlContext(), showsError: true }\n ),\n roles: new MutationController(\n this,\n gql`\n mutation ($username: String!, $availableRoles: [ObjectRef!]!, $selectedRoles: [ObjectRef!]!) {\n updateUserRoles(username: $username, availableRoles: $availableRoles, selectedRoles: $selectedRoles) {\n id\n }\n }\n `,\n { context: gqlContext() }\n )\n }\n\n private get roles(): any[] {\n return this.assignable.data?.roles?.items || []\n }\n\n private get working(): boolean {\n return Object.values(this.saves).some(save => save.pending)\n }\n\n private get lookup(): 'idle' | 'checking' | 'found' | 'absent' | 'failed' {\n /* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */\n if (!this.asked || this.asked !== this.email.trim()) {\n return 'idle'\n }\n if (this.existence.loading) {\n return 'checking'\n }\n\n /* 못 물었으면 「없다」가 아니다 — 없다고 그리면 있는 사람에게 계정을 만들러 간다. */\n const exists = this.existence.error ? undefined : this.existence.data?.checkUserExistence\n if (typeof exists !== 'boolean') {\n return 'failed'\n }\n\n return exists ? 'found' : 'absent'\n }\n\n @query('[name=name]') private nameInput!: HTMLInputElement\n @query('[name=username]') private usernameInput!: HTMLInputElement\n\n private timer?: ReturnType<typeof setTimeout>\n\n /** 계정을 새로 만드는 경로인가. */\n private get creating(): boolean {\n return this.lookup === 'absent'\n }\n\n /** 부를 수 있나 — 단추와 처리기가 같은 답을 본다. */\n private get callable(): boolean {\n return !this.working && (this.lookup === 'found' || this.creating)\n }\n\n render() {\n return html`\n <div class=\"body\">\n ${this.renderGuide()}\n\n <label class=\"field\">\n <span>${i18next.t('field.email')}</span>\n <input\n name=\"email\"\n type=\"email\"\n .value=${this.email}\n placeholder=\"name@company.com\"\n @input=${(e: Event) => this.onEmail((e.target as HTMLInputElement).value)}\n />\n <span invalid>${this.lookup === 'failed' ? i18next.t('text.could not look up the account') : this.message}</span>\n </label>\n\n ${this.lookup === 'checking' ? html`<div class=\"note\">${i18next.t('text.loading')}</div>` : nothing}\n ${this.creating ? this.renderNewAccount() : nothing}\n ${this.lookup === 'found' || this.creating ? this.renderRoles() : nothing}\n </div>\n\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>\n ${i18next.t('button.cancel')}\n </md-text-button>\n <md-filled-button slot=\"confirm\" ?disabled=${!this.callable} @click=${() => this.onCall()}>\n ${this.creating ? i18next.t('label.create account and add') : i18next.t('label.add to domain')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /**\n * 사용자 추가가 무엇인지 — 이메일을 넣기 전에 말한다.\n *\n * 칸 하나만 있으면 이 팝업이 무엇을 하는지 알 수 없다. 계정은 하나로 여러 도메인에서 쓰고, 추가는\n * 그 계정을 이 도메인에 넣는 일이다. 이메일에 따라 일어나는 일이 갈리므로 갈래를 먼저 보이고,\n * 이메일을 찾으면 해당하는 줄을 짚는다.\n */\n private renderGuide() {\n return html`\n <div guide>\n <p>${i18next.t('text.add user puts an account into this domain')}</p>\n <dl>\n <div ?current=${this.lookup === 'found'}>\n <dt>${i18next.t('label.account exists')}</dt>\n <dd>${i18next.t('text.they join this domain at once')}</dd>\n </div>\n <div ?current=${this.lookup === 'absent'}>\n <dt>${i18next.t('label.no account')}</dt>\n <dd>${i18next.t('text.an account is made and a setup link handed over')}</dd>\n </div>\n <div>\n <dt>${i18next.t('field.roles')}</dt>\n <dd>${i18next.t('text.roles decide the screens they open')}</dd>\n </div>\n </dl>\n </div>\n `\n }\n\n private renderNewAccount() {\n return html`\n <label class=\"field\">\n <span>${i18next.t('field.name')}</span>\n <input name=\"name\" type=\"text\" @input=${() => this.requestUpdate()} />\n </label>\n\n <!-- 서버가 받는 글자는 A-Z a-z 0-9 . _ @ - 다. 비우면 이메일을 그대로 쓴다. -->\n <label class=\"field\">\n <span>${i18next.t('field.username')}</span>\n <input name=\"username\" type=\"text\" placeholder=${this.email} />\n </label>\n `\n }\n\n private renderRoles() {\n return html`\n <div group-label>${i18next.t('field.roles')}</div>\n ${\n this.roles.length\n ? html`<ul roles>\n ${this.roles.map(\n role => html`\n <li>\n <input\n type=\"checkbox\"\n id=\"invite-role-${role.id}\"\n .checked=${this.selected.has(role.id)}\n @change=${(e: Event) => this.onToggle(role.id, (e.target as HTMLInputElement).checked)}\n />\n <label for=\"invite-role-${role.id}\">${role.name}</label>\n </li>\n `\n )}\n </ul>`\n : html`<div class=\"failure\">${i18next.t('text.no role defined in this domain')}</div>`\n }\n ${\n this.roles.length && this.selected.size === 0\n ? html`<div class=\"failure\">${i18next.t('text.without a role they see nothing')}</div>`\n : nothing\n }\n `\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n clearTimeout(this.timer)\n }\n\n private onEmail(value: string) {\n this.email = value\n this.message = ''\n\n clearTimeout(this.timer)\n\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim())) {\n return\n }\n\n /* 한 글자마다 묻지 않는다. 사람이 멈추면 묻는다. */\n this.timer = setTimeout(() => (this.asked = this.email.trim()), 400)\n }\n\n private onToggle(id: string, checked: boolean) {\n const next = new Set(this.selected)\n checked ? next.add(id) : next.delete(id)\n this.selected = next\n }\n\n private async onCall() {\n if (!this.callable) {\n return\n }\n\n const email = this.email.trim()\n const creating = this.creating\n const username = this.creating ? this.usernameInput?.value.trim() || email : email\n\n if (this.creating && !this.nameInput?.value.trim()) {\n this.message = i18next.t('text.x is required', { x: i18next.t('field.name') })\n return\n }\n\n const entered = creating ? await this.createAccount(email, username) : await this.addToDomain(username)\n\n if (!entered) {\n return\n }\n\n if (this.selected.size) {\n /*\n * 넣기와 역할 부여가 두 호출이다. 여기가 실패하면 그 사람은 역할 없이 들어와 있고,\n * 목록이 그것을 빨갛게 말한다 — 사라지지 않는다.\n *\n * `createUser` 도 역할을 받지만 **이 도메인 소유의 역할만** 붙인다. 상속 역할과 파트너\n * 역할을 조용히 버리므로, 고른 것을 그대로 주려면 이 호출을 쓴다.\n */\n await this.saves.roles.run({\n username,\n availableRoles: this.roles.map(role => ({ id: role.id })),\n selectedRoles: this.roles.filter(role => this.selected.has(role.id)).map(role => ({ id: role.id }))\n })\n }\n\n /* 새 계정은 「설정 대기」다 — 받는 쪽(목록)이 이어서 설정 링크를 건네게 한다(`detail.created`). */\n this.dispatchEvent(\n new CustomEvent('user-invited', {\n detail: { username, created: creating ? entered : null },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /** 이미 계정이 있는 사람 — 이 도메인에 바로 들어온다. */\n private async addToDomain(username: string): Promise<boolean> {\n if (!(await this.saves.invite.run({ username }))) {\n this.message = messageOf(this.saves.invite.error)\n return false\n }\n\n return true\n }\n\n /**\n * 계정이 없는 사람 — 「비밀번호 설정 대기」로 만든다. 비밀번호는 본인이 설정 링크로 정한다(ADR-0161).\n *\n * 역할은 여기서 안 넘긴다. `createUser` 는 **이 도메인 소유의 역할만** 붙이므로 상속·파트너\n * 역할이 조용히 빠진다. 부르는 쪽에서 `updateUserRoles` 로 한 번에 준다.\n *\n * @returns 만든 사람(설정 링크 팝업이 읽는 칸들), 못 만들었으면 null\n */\n private async createAccount(email: string, username: string): Promise<any> {\n const created = await this.saves.create.run({\n user: {\n username,\n name: this.nameInput.value.trim(),\n email,\n userType: 'user'\n }\n })\n\n if (!created) {\n this.message = messageOf(this.saves.create.error)\n return null\n }\n\n return created.createUser || null\n }\n}\n\n/** The server's sentence for a refused save - the first one, as it was drawn under the email. */\nfunction messageOf(error: any): string {\n return error?.graphQLErrors?.[0]?.message || error?.message || ''\n}\n"]}
@@ -50,7 +50,7 @@ describe('user-invite', () => {
50
50
  expect(/<div class="body">\s*\$\{this\.renderGuide\(\)\}/.test(source)).toBe(true);
51
51
  });
52
52
  it('★ does not take a failed lookup for "no account"', () => {
53
- expect(source.includes("this.lookup = 'failed'")).toBe(true);
53
+ expect(/if \(typeof exists !== 'boolean'\) \{\s*return 'failed'/.test(source)).toBe(true);
54
54
  });
55
55
  });
56
56
  describe('the password setup link (ADR-0161)', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"user-popups-standard.test.js","sourceRoot":"","sources":["../../client/components/user-popups-standard.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'fs'\nimport path from 'path'\n\n/**\n * The user management popups keep the house shape of a popup (operato-application design/ui/page-standard.md §7·§8).\n *\n * They drew their own: Material buttons in the middle of the body, save and cancel above a separate \"cannot be undone\"\n * strip, Material text fields 56px tall with the label on the border, and the whole card scrolling with its buttons.\n * Now only the body scrolls, every command is in the one `ox-popup-actions` row (irreversible ones at the start in red),\n * and fields are labels above native inputs.\n */\n\nconst POPUPS = ['user-detail.ts', 'user-invite.ts', 'ownership-transfer-popup.ts', 'password-setup-link-popup.ts']\n\nconst read = (name: string) => readFileSync(path.join(__dirname, name), 'utf-8')\n\ndescribe.each(POPUPS)('%s', name => {\n const source = read(name)\n\n it('scrolls its body only and ends in the shared action row', () => {\n expect(source.includes('PopupBodyStyles')).toBe(true)\n expect(source.includes('<div class=\"body\">')).toBe(true)\n expect(source.includes(\"import '@operato/popup/ox-popup-actions.js'\")).toBe(true)\n expect((source.match(/<ox-popup-actions>/g) || []).length).toBe(1)\n })\n\n it('has exactly one way out and one main action', () => {\n expect((source.match(/slot=\"cancel\"/g) || []).length).toBe(1)\n expect((source.match(/slot=\"confirm\"/g) || []).length).toBe(1)\n })\n\n it('draws no Material field, checkbox or raised button, and no row of its own', () => {\n expect(/<md-(filled|outlined)-text-field|<md-checkbox|<md-elevated-button/.test(source)).toBe(false)\n expect(/class=\"button-container\"/.test(source)).toBe(false)\n })\n\n it('writes no font size of its own', () => {\n expect(/font-size:\\s*[\\d.]+(rem|px|em)/.test(source)).toBe(false)\n })\n})\n\ndescribe('user-detail', () => {\n const source = read('user-detail.ts')\n\n it('★ puts every irreversible action at the start, in red', () => {\n for (const action of ['onPasswordSetup', 'onTransfer', 'onRemoveFromDomain']) {\n expect(new RegExp(`<md-text-button slot=\"start\" destructive @click=\\\\$\\\\{\\\\(\\\\) => this\\\\.${action}\\\\(\\\\)\\\\}>`).test(source)).toBe(\n true\n )\n }\n })\n})\n\ndescribe('the user list', () => {\n it('★ titles the detail popup by what it is, not by whom', () => {\n const page = readFileSync(path.join(__dirname, '..', 'pages', 'user', 'user-list-page.ts'), 'utf-8')\n expect(page.includes(\"title: i18next.t('title.user details')\")).toBe(true)\n })\n})\n\ndescribe('user-invite', () => {\n const source = read('user-invite.ts')\n\n it('★ says what adding a user is before an email is typed', () => {\n expect(/<div class=\"body\">\\s*\\$\\{this\\.renderGuide\\(\\)\\}/.test(source)).toBe(true)\n })\n\n it('★ does not take a failed lookup for \"no account\"', () => {\n expect(source.includes(\"this.lookup = 'failed'\")).toBe(true)\n })\n})\n\ndescribe('the password setup link (ADR-0161)', () => {\n it('★ every way to take back or give a password opens the one popup', () => {\n for (const name of ['user-detail.ts', 'user-role-editor.ts', path.join('..', 'pages', 'user', 'user-list-page.ts')]) {\n const source = read(name)\n expect(source.includes('openPasswordSetupLink(')).toBe(true)\n expect(/resetPasswordToDefault|checkDefaultPassword/.test(source)).toBe(false)\n }\n })\n\n it('★ the add-user popup sets no password and sends no invitation', () => {\n const source = read('user-invite.ts')\n expect(/password|sendInvitation|checkDefaultPassword/i.test(source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\/\\/.*$/gm, ''))).toBe(\n false\n )\n })\n})\n"]}
1
+ {"version":3,"file":"user-popups-standard.test.js","sourceRoot":"","sources":["../../client/components/user-popups-standard.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'fs'\nimport path from 'path'\n\n/**\n * The user management popups keep the house shape of a popup (operato-application design/ui/page-standard.md §7·§8).\n *\n * They drew their own: Material buttons in the middle of the body, save and cancel above a separate \"cannot be undone\"\n * strip, Material text fields 56px tall with the label on the border, and the whole card scrolling with its buttons.\n * Now only the body scrolls, every command is in the one `ox-popup-actions` row (irreversible ones at the start in red),\n * and fields are labels above native inputs.\n */\n\nconst POPUPS = ['user-detail.ts', 'user-invite.ts', 'ownership-transfer-popup.ts', 'password-setup-link-popup.ts']\n\nconst read = (name: string) => readFileSync(path.join(__dirname, name), 'utf-8')\n\ndescribe.each(POPUPS)('%s', name => {\n const source = read(name)\n\n it('scrolls its body only and ends in the shared action row', () => {\n expect(source.includes('PopupBodyStyles')).toBe(true)\n expect(source.includes('<div class=\"body\">')).toBe(true)\n expect(source.includes(\"import '@operato/popup/ox-popup-actions.js'\")).toBe(true)\n expect((source.match(/<ox-popup-actions>/g) || []).length).toBe(1)\n })\n\n it('has exactly one way out and one main action', () => {\n expect((source.match(/slot=\"cancel\"/g) || []).length).toBe(1)\n expect((source.match(/slot=\"confirm\"/g) || []).length).toBe(1)\n })\n\n it('draws no Material field, checkbox or raised button, and no row of its own', () => {\n expect(/<md-(filled|outlined)-text-field|<md-checkbox|<md-elevated-button/.test(source)).toBe(false)\n expect(/class=\"button-container\"/.test(source)).toBe(false)\n })\n\n it('writes no font size of its own', () => {\n expect(/font-size:\\s*[\\d.]+(rem|px|em)/.test(source)).toBe(false)\n })\n})\n\ndescribe('user-detail', () => {\n const source = read('user-detail.ts')\n\n it('★ puts every irreversible action at the start, in red', () => {\n for (const action of ['onPasswordSetup', 'onTransfer', 'onRemoveFromDomain']) {\n expect(new RegExp(`<md-text-button slot=\"start\" destructive @click=\\\\$\\\\{\\\\(\\\\) => this\\\\.${action}\\\\(\\\\)\\\\}>`).test(source)).toBe(\n true\n )\n }\n })\n})\n\ndescribe('the user list', () => {\n it('★ titles the detail popup by what it is, not by whom', () => {\n const page = readFileSync(path.join(__dirname, '..', 'pages', 'user', 'user-list-page.ts'), 'utf-8')\n expect(page.includes(\"title: i18next.t('title.user details')\")).toBe(true)\n })\n})\n\ndescribe('user-invite', () => {\n const source = read('user-invite.ts')\n\n it('★ says what adding a user is before an email is typed', () => {\n expect(/<div class=\"body\">\\s*\\$\\{this\\.renderGuide\\(\\)\\}/.test(source)).toBe(true)\n })\n\n it('★ does not take a failed lookup for \"no account\"', () => {\n expect(/if \\(typeof exists !== 'boolean'\\) \\{\\s*return 'failed'/.test(source)).toBe(true)\n })\n})\n\ndescribe('the password setup link (ADR-0161)', () => {\n it('★ every way to take back or give a password opens the one popup', () => {\n for (const name of ['user-detail.ts', 'user-role-editor.ts', path.join('..', 'pages', 'user', 'user-list-page.ts')]) {\n const source = read(name)\n expect(source.includes('openPasswordSetupLink(')).toBe(true)\n expect(/resetPasswordToDefault|checkDefaultPassword/.test(source)).toBe(false)\n }\n })\n\n it('★ the add-user popup sets no password and sends no invitation', () => {\n const source = read('user-invite.ts')\n expect(/password|sendInvitation|checkDefaultPassword/i.test(source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\/\\/.*$/gm, ''))).toBe(\n false\n )\n })\n})\n"]}