@things-factory/auth-ui 10.1.215 → 10.1.219
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.
- package/dist-client/bootstrap.js +18 -14
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/abstract-password-reset.d.ts +5 -0
- package/dist-client/components/abstract-password-reset.js +20 -0
- package/dist-client/components/abstract-password-reset.js.map +1 -1
- package/dist-client/components/ownership-transfer-popup.d.ts +3 -1
- package/dist-client/components/ownership-transfer-popup.js +38 -41
- package/dist-client/components/ownership-transfer-popup.js.map +1 -1
- package/dist-client/components/password-setup-link-popup.d.ts +46 -0
- package/dist-client/components/password-setup-link-popup.js +254 -0
- package/dist-client/components/password-setup-link-popup.js.map +1 -0
- package/dist-client/components/user-detail.d.ts +20 -30
- package/dist-client/components/user-detail.js +114 -186
- package/dist-client/components/user-detail.js.map +1 -1
- package/dist-client/components/user-invite.d.ts +18 -25
- package/dist-client/components/user-invite.js +157 -181
- package/dist-client/components/user-invite.js.map +1 -1
- package/dist-client/components/user-popups-standard.test.js +69 -0
- package/dist-client/components/user-popups-standard.test.js.map +1 -0
- package/dist-client/components/user-role-editor.js +5 -26
- package/dist-client/components/user-role-editor.js.map +1 -1
- package/dist-client/components/user-status-renderer.js +4 -0
- package/dist-client/components/user-status-renderer.js.map +1 -1
- package/dist-client/entries/auth/reset-password.d.ts +1 -0
- package/dist-client/entries/auth/reset-password.js +5 -1
- package/dist-client/entries/auth/reset-password.js.map +1 -1
- package/dist-client/index.js +10 -28
- package/dist-client/index.js.map +1 -1
- package/dist-client/pages/user/user-list-page.d.ts +5 -1
- package/dist-client/pages/user/user-list-page.js +14 -5
- package/dist-client/pages/user/user-list-page.js.map +1 -1
- package/dist-client/route.js +0 -3
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/link-token.d.ts +3 -3
- package/dist-client/utils/link-token.js +4 -4
- package/dist-client/utils/link-token.js.map +1 -1
- package/dist-client/utils/link-token.test.js +2 -2
- package/dist-client/utils/link-token.test.js.map +1 -1
- package/package.json +6 -5
- package/things-factory.config.js +0 -1
- package/translations/en.json +30 -20
- package/translations/es.json +30 -20
- package/translations/ja.json +30 -20
- package/translations/ko.json +30 -20
- package/translations/ms.json +30 -20
- package/translations/zh.json +30 -20
- package/dist-client/components/accept-invitation.d.ts +0 -47
- package/dist-client/components/accept-invitation.js +0 -258
- package/dist-client/components/accept-invitation.js.map +0 -1
- package/dist-client/entries/auth/accept-invitation.d.ts +0 -3
- package/dist-client/entries/auth/accept-invitation.js +0 -10
- package/dist-client/entries/auth/accept-invitation.js.map +0 -1
- package/dist-client/pages/invitation/invitation-list-query.test.js +0 -69
- package/dist-client/pages/invitation/invitation-list-query.test.js.map +0 -1
- package/dist-client/pages/invitation/invitations.d.ts +0 -63
- package/dist-client/pages/invitation/invitations.js +0 -284
- package/dist-client/pages/invitation/invitations.js.map +0 -1
- /package/dist-client/{pages/invitation/invitation-list-query.test.d.ts → components/user-popups-standard.test.d.ts} +0 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
-
import '@material/web/
|
|
3
|
-
import '@material/web/button/
|
|
4
|
-
import '@
|
|
5
|
-
import '@material/web/checkbox/checkbox.js';
|
|
2
|
+
import '@material/web/button/text-button.js';
|
|
3
|
+
import '@material/web/button/filled-button.js';
|
|
4
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
6
5
|
import gql from 'graphql-tag';
|
|
7
6
|
import { css, html, LitElement, nothing } from 'lit';
|
|
8
7
|
import { customElement, query, state } from 'lit/decorators.js';
|
|
9
8
|
import { client, gqlContext } from '@operato/graphql';
|
|
10
9
|
import { i18next, localize } from '@operato/i18n';
|
|
11
10
|
import { InheritedValueType } from '@operato/shell';
|
|
12
|
-
import {
|
|
11
|
+
import { CommonPopupStyles, FormFieldStyles, PopupBodyStyles, PopupFieldStyles } from '@operato/styles';
|
|
13
12
|
/**
|
|
14
13
|
* 사람을 이 도메인으로 부르는 자리.
|
|
15
14
|
*
|
|
@@ -23,16 +22,12 @@ import { ButtonContainerStyles, FormFieldStyles } from '@operato/styles';
|
|
|
23
22
|
* 자리는 기본 비밀번호가 설정된 도메인에서만 나타났다.
|
|
24
23
|
*
|
|
25
24
|
* ── 그래서 이메일 하나로 시작해 갈라 보여 준다 ──────────────────────────────
|
|
26
|
-
* `checkUserExistence` 가 시스템 전체에서 그 이메일을 찾는다. 관리자는
|
|
25
|
+
* `checkUserExistence` 가 시스템 전체에서 그 이메일을 찾는다. 관리자는 「넣기인가 만들기인가」를
|
|
27
26
|
* 먼저 고르지 않는다 — 이메일을 넣으면 화면이 무엇이 일어날지 말하고, 단추 글자도 그에 맞춘다.
|
|
28
27
|
*
|
|
29
|
-
* 계정이 있다
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* ⚠ **셋 중 어느 것도 「초대장」이 아니다.** 상대가 수락하는 흐름은 서버에 아직 없다
|
|
34
|
-
* (`sendInvitation` 도 가입한 사용자만 받고, `Invitation` 에 상태도 만료도 없다). 그래서 이
|
|
35
|
-
* 화면은 초대라는 말을 쓰지 않고 **실제로 일어나는 일**을 쓴다.
|
|
28
|
+
* 계정이 있다 이 도메인에 바로 들어온다 (`inviteUser`)
|
|
29
|
+
* 계정이 없다 「비밀번호 설정 대기」 계정을 만들고(`createUser`), 이어서 비밀번호 설정 링크를
|
|
30
|
+
* 건넨다(`password-setup-link-popup`). 관리자는 비밀번호를 정하지 않는다(ADR-0161).
|
|
36
31
|
*
|
|
37
32
|
* ── 역할을 함께 고른다 ──────────────────────────────────────────────────────
|
|
38
33
|
* 역할 없이 들어온 사람은 로그인해도 화면이 하나도 없다. 부르는 자리에서 함께 정하면 그 사람이
|
|
@@ -52,57 +47,65 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
52
47
|
this.selected = new Set();
|
|
53
48
|
this.working = false;
|
|
54
49
|
this.message = '';
|
|
55
|
-
/** 기본 비밀번호가 설정돼 있지 않으면 계정을 만드는 길이 없다 — 서버가 던진다. */
|
|
56
|
-
this.defaultPasswordConfigured = false;
|
|
57
50
|
}
|
|
51
|
+
/* 팝업의 집 모양(page-standard §7·§8): 본문만 스크롤, 칸은 라벨 위의 날것 입력, 단추 줄은 `ox-popup-actions`. */
|
|
58
52
|
static { this.styles = [
|
|
53
|
+
CommonPopupStyles,
|
|
54
|
+
PopupBodyStyles,
|
|
59
55
|
FormFieldStyles,
|
|
60
|
-
|
|
56
|
+
PopupFieldStyles,
|
|
61
57
|
css `
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
padding: var(--spacing-large, 12px);
|
|
67
|
-
min-width: min(420px, 86vw);
|
|
68
|
-
background-color: var(--md-sys-color-surface);
|
|
69
|
-
color: var(--md-sys-color-on-surface);
|
|
58
|
+
/* 오류 문장은 칸 바로 아래(§7). */
|
|
59
|
+
[invalid] {
|
|
60
|
+
color: var(--ui-crit, var(--md-sys-color-error));
|
|
61
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
70
62
|
}
|
|
71
63
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
font-size: 0.8rem;
|
|
64
|
+
[invalid]:empty {
|
|
65
|
+
display: none;
|
|
75
66
|
}
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
[guide] p {
|
|
69
|
+
margin: 0 0 var(--spacing-small, 4px);
|
|
70
|
+
line-height: var(--popup-line-height);
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
[
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
[guide] dl {
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: max-content 1fr;
|
|
76
|
+
gap: 2px var(--spacing-large, 12px);
|
|
77
|
+
margin: 0;
|
|
78
|
+
color: var(--ui-muted);
|
|
79
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
80
|
+
line-height: var(--popup-line-height);
|
|
85
81
|
}
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
padding: 8px 10px;
|
|
90
|
-
border-radius: var(--md-sys-shape-corner-small, 6px);
|
|
91
|
-
font-size: 0.82rem;
|
|
92
|
-
line-height: 1.5;
|
|
93
|
-
background-color: var(--md-sys-color-surface-variant);
|
|
94
|
-
color: var(--md-sys-color-on-surface-variant);
|
|
83
|
+
[guide] dl > div {
|
|
84
|
+
display: contents;
|
|
95
85
|
}
|
|
96
86
|
|
|
97
|
-
[
|
|
98
|
-
|
|
99
|
-
color: var(--md-sys-color-on-error-container);
|
|
87
|
+
[guide] dt {
|
|
88
|
+
font-weight: 600;
|
|
100
89
|
}
|
|
101
90
|
|
|
102
|
-
[
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
[guide] dd {
|
|
92
|
+
margin: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* 넣은 이메일이 해당하는 갈래 — 띠로 짚는다. */
|
|
96
|
+
[guide] dl > div > * {
|
|
97
|
+
padding: 1px var(--spacing-small, 4px);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
[guide] dl > div[current] > * {
|
|
101
|
+
background: var(--ui-line-soft, var(--md-sys-color-surface-container));
|
|
102
|
+
color: var(--ui-ink);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
[group-label] {
|
|
106
|
+
margin-top: var(--spacing-medium, 8px);
|
|
107
|
+
color: var(--ui-muted);
|
|
108
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
ul[roles] {
|
|
@@ -114,123 +117,119 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
114
117
|
ul[roles] li {
|
|
115
118
|
display: flex;
|
|
116
119
|
align-items: center;
|
|
117
|
-
gap: 8px;
|
|
118
|
-
padding:
|
|
120
|
+
gap: var(--spacing-medium, 8px);
|
|
121
|
+
padding: 3px 0;
|
|
119
122
|
}
|
|
120
123
|
`
|
|
121
124
|
]; }
|
|
122
125
|
/** 계정을 새로 만드는 경로인가. */
|
|
123
126
|
get creating() {
|
|
124
|
-
return this.lookup === 'absent'
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* 메일로 초대하는 경로인가.
|
|
128
|
-
*
|
|
129
|
-
* 계정이 없고 기본 비밀번호도 없을 때다. 관리자가 비밀번호를 대신 정해 줄 수 없으므로
|
|
130
|
-
* **본인이 메일을 받아 스스로 정하는 것이 유일한 길**이다.
|
|
131
|
-
*/
|
|
132
|
-
get inviting() {
|
|
133
|
-
return this.lookup === 'absent' && !this.defaultPasswordConfigured;
|
|
127
|
+
return this.lookup === 'absent';
|
|
134
128
|
}
|
|
135
129
|
/** 부를 수 있나 — 단추와 처리기가 같은 답을 본다. */
|
|
136
130
|
get callable() {
|
|
137
|
-
return !this.working && (this.lookup === 'found' || this.creating
|
|
131
|
+
return !this.working && (this.lookup === 'found' || this.creating);
|
|
138
132
|
}
|
|
139
133
|
render() {
|
|
140
134
|
return html `
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
name="email"
|
|
144
|
-
type="email"
|
|
145
|
-
label=${i18next.t('field.email')}
|
|
146
|
-
.value=${this.email}
|
|
147
|
-
placeholder="name@company.com"
|
|
148
|
-
@input=${(e) => this.onEmail(e.target.value)}
|
|
149
|
-
></md-filled-text-field>
|
|
150
|
-
<div invalid>${this.message}</div>
|
|
135
|
+
<div class="body">
|
|
136
|
+
${this.renderGuide()}
|
|
151
137
|
|
|
152
|
-
|
|
153
|
-
|
|
138
|
+
<label class="field">
|
|
139
|
+
<span>${i18next.t('field.email')}</span>
|
|
140
|
+
<input
|
|
141
|
+
name="email"
|
|
142
|
+
type="email"
|
|
143
|
+
.value=${this.email}
|
|
144
|
+
placeholder="name@company.com"
|
|
145
|
+
@input=${(e) => this.onEmail(e.target.value)}
|
|
146
|
+
/>
|
|
147
|
+
<span invalid>${this.message}</span>
|
|
148
|
+
</label>
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
? i18next.t('label.send invitation')
|
|
159
|
-
: this.creating
|
|
160
|
-
? i18next.t('label.create account and add')
|
|
161
|
-
: i18next.t('label.add to domain')}
|
|
162
|
-
</md-elevated-button>
|
|
163
|
-
<md-elevated-button @click=${() => this.dispatchEvent(new CustomEvent('close'))}>
|
|
164
|
-
${i18next.t('button.cancel')}
|
|
165
|
-
</md-elevated-button>
|
|
150
|
+
${this.lookup === 'checking' ? html `<div class="note">${i18next.t('text.loading')}</div>` : nothing}
|
|
151
|
+
${this.creating ? this.renderNewAccount() : nothing}
|
|
152
|
+
${this.lookup === 'found' || this.creating ? this.renderRoles() : nothing}
|
|
166
153
|
</div>
|
|
154
|
+
|
|
155
|
+
<ox-popup-actions>
|
|
156
|
+
<md-text-button slot="cancel" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>
|
|
157
|
+
${i18next.t('button.cancel')}
|
|
158
|
+
</md-text-button>
|
|
159
|
+
<md-filled-button slot="confirm" ?disabled=${!this.callable} @click=${() => this.onCall()}>
|
|
160
|
+
${this.creating ? i18next.t('label.create account and add') : i18next.t('label.add to domain')}
|
|
161
|
+
</md-filled-button>
|
|
162
|
+
</ox-popup-actions>
|
|
167
163
|
`;
|
|
168
164
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
165
|
+
/**
|
|
166
|
+
* 사용자 추가가 무엇인지 — 이메일을 넣기 전에 말한다.
|
|
167
|
+
*
|
|
168
|
+
* 칸 하나만 있으면 이 팝업이 무엇을 하는지 알 수 없다. 계정은 하나로 여러 도메인에서 쓰고, 추가는
|
|
169
|
+
* 그 계정을 이 도메인에 넣는 일이다. 이메일에 따라 일어나는 일이 갈리므로 갈래를 먼저 보이고,
|
|
170
|
+
* 이메일을 찾으면 해당하는 줄을 짚는다.
|
|
171
|
+
*/
|
|
172
|
+
renderGuide() {
|
|
173
|
+
return html `
|
|
174
|
+
<div guide>
|
|
175
|
+
<p>${i18next.t('text.add user puts an account into this domain')}</p>
|
|
176
|
+
<dl>
|
|
177
|
+
<div ?current=${this.lookup === 'found'}>
|
|
178
|
+
<dt>${i18next.t('label.account exists')}</dt>
|
|
179
|
+
<dd>${i18next.t('text.they join this domain at once')}</dd>
|
|
180
|
+
</div>
|
|
181
|
+
<div ?current=${this.lookup === 'absent'}>
|
|
182
|
+
<dt>${i18next.t('label.no account')}</dt>
|
|
183
|
+
<dd>${i18next.t('text.an account is made and a setup link handed over')}</dd>
|
|
184
|
+
</div>
|
|
185
|
+
<div>
|
|
186
|
+
<dt>${i18next.t('field.roles')}</dt>
|
|
187
|
+
<dd>${i18next.t('text.roles decide the screens they open')}</dd>
|
|
188
|
+
</div>
|
|
189
|
+
</dl>
|
|
190
|
+
</div>
|
|
191
|
+
`;
|
|
187
192
|
}
|
|
188
193
|
renderNewAccount() {
|
|
189
194
|
return html `
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
name="name"
|
|
193
|
-
|
|
194
|
-
label=${i18next.t('field.name')}
|
|
195
|
-
@input=${() => this.requestUpdate()}
|
|
196
|
-
></md-filled-text-field>
|
|
195
|
+
<label class="field">
|
|
196
|
+
<span>${i18next.t('field.name')}</span>
|
|
197
|
+
<input name="name" type="text" @input=${() => this.requestUpdate()} />
|
|
198
|
+
</label>
|
|
197
199
|
|
|
198
200
|
<!-- 서버가 받는 글자는 A-Z a-z 0-9 . _ @ - 다. 비우면 이메일을 그대로 쓴다. -->
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
name="username"
|
|
202
|
-
|
|
203
|
-
label=${i18next.t('field.username')}
|
|
204
|
-
placeholder=${this.email}
|
|
205
|
-
></md-filled-text-field>
|
|
201
|
+
<label class="field">
|
|
202
|
+
<span>${i18next.t('field.username')}</span>
|
|
203
|
+
<input name="username" type="text" placeholder=${this.email} />
|
|
204
|
+
</label>
|
|
206
205
|
`;
|
|
207
206
|
}
|
|
208
207
|
renderRoles() {
|
|
209
208
|
return html `
|
|
210
|
-
<div
|
|
209
|
+
<div group-label>${i18next.t('field.roles')}</div>
|
|
211
210
|
${this.roles.length
|
|
212
211
|
? html `<ul roles>
|
|
213
212
|
${this.roles.map(role => html `
|
|
214
213
|
<li>
|
|
215
|
-
<
|
|
214
|
+
<input
|
|
215
|
+
type="checkbox"
|
|
216
216
|
id="invite-role-${role.id}"
|
|
217
217
|
.checked=${this.selected.has(role.id)}
|
|
218
218
|
@change=${(e) => this.onToggle(role.id, e.target.checked)}
|
|
219
|
-
|
|
219
|
+
/>
|
|
220
220
|
<label for="invite-role-${role.id}">${role.name}</label>
|
|
221
221
|
</li>
|
|
222
222
|
`)}
|
|
223
223
|
</ul>`
|
|
224
|
-
: html `<div
|
|
224
|
+
: html `<div class="failure">${i18next.t('text.no role defined in this domain')}</div>`}
|
|
225
225
|
${this.roles.length && this.selected.size === 0
|
|
226
|
-
? html `<div
|
|
226
|
+
? html `<div class="failure">${i18next.t('text.without a role they see nothing')}</div>`
|
|
227
227
|
: nothing}
|
|
228
228
|
`;
|
|
229
229
|
}
|
|
230
230
|
connectedCallback() {
|
|
231
231
|
super.connectedCallback();
|
|
232
232
|
this.fetchRoles();
|
|
233
|
-
this.checkDefaultPassword();
|
|
234
233
|
}
|
|
235
234
|
disconnectedCallback() {
|
|
236
235
|
super.disconnectedCallback();
|
|
@@ -250,56 +249,33 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
250
249
|
async check() {
|
|
251
250
|
const email = this.email.trim();
|
|
252
251
|
this.lookup = 'checking';
|
|
253
|
-
|
|
254
|
-
query: gql `
|
|
255
|
-
query ($email: EmailAddress!) {
|
|
256
|
-
checkUserExistence(email: $email)
|
|
257
|
-
}
|
|
258
|
-
`,
|
|
259
|
-
variables: { email },
|
|
260
|
-
context: gqlContext()
|
|
261
|
-
});
|
|
262
|
-
/* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */
|
|
263
|
-
if (email !== this.email.trim()) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
this.lookup = response.data?.checkUserExistence ? 'found' : 'absent';
|
|
267
|
-
}
|
|
268
|
-
async sendInvitation(email) {
|
|
269
|
-
this.working = true;
|
|
252
|
+
let exists;
|
|
270
253
|
try {
|
|
271
|
-
const response = await client.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
id
|
|
276
|
-
}
|
|
254
|
+
const response = await client.query({
|
|
255
|
+
query: gql `
|
|
256
|
+
query ($email: EmailAddress!) {
|
|
257
|
+
checkUserExistence(email: $email)
|
|
277
258
|
}
|
|
278
259
|
`,
|
|
279
|
-
variables: { email
|
|
260
|
+
variables: { email },
|
|
280
261
|
context: gqlContext()
|
|
281
262
|
});
|
|
282
|
-
|
|
283
|
-
this.message = response.errors[0]?.message;
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
this.dispatchEvent(new CustomEvent('invited', { detail: { email }, bubbles: true, composed: true }));
|
|
287
|
-
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
|
|
263
|
+
exists = response.errors?.length ? undefined : response.data?.checkUserExistence;
|
|
288
264
|
}
|
|
289
|
-
|
|
290
|
-
|
|
265
|
+
catch (e) {
|
|
266
|
+
exists = undefined;
|
|
291
267
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
query: gql `
|
|
296
|
-
query {
|
|
297
|
-
checkDefaultPassword
|
|
268
|
+
/* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */
|
|
269
|
+
if (email !== this.email.trim()) {
|
|
270
|
+
return;
|
|
298
271
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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';
|
|
303
279
|
}
|
|
304
280
|
async fetchRoles() {
|
|
305
281
|
const response = await client.query({
|
|
@@ -328,14 +304,7 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
328
304
|
return;
|
|
329
305
|
}
|
|
330
306
|
const email = this.email.trim();
|
|
331
|
-
|
|
332
|
-
/*
|
|
333
|
-
* 초대는 여기서 끝난다 — 역할은 초대장이 싣고 가고, 수락할 때 함께 붙는다. 아래의
|
|
334
|
-
* 「넣고 나서 역할」 두 호출과 달리 한 번이다.
|
|
335
|
-
*/
|
|
336
|
-
await this.sendInvitation(email);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
307
|
+
const creating = this.creating;
|
|
339
308
|
const username = this.creating ? this.usernameInput?.value.trim() || email : email;
|
|
340
309
|
if (this.creating && !this.nameInput?.value.trim()) {
|
|
341
310
|
this.message = i18next.t('text.x is required', { x: i18next.t('field.name') });
|
|
@@ -343,7 +312,7 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
343
312
|
}
|
|
344
313
|
this.working = true;
|
|
345
314
|
try {
|
|
346
|
-
const entered =
|
|
315
|
+
const entered = creating ? await this.createAccount(email, username) : await this.addToDomain(username);
|
|
347
316
|
if (!entered) {
|
|
348
317
|
return;
|
|
349
318
|
}
|
|
@@ -371,7 +340,12 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
371
340
|
context: gqlContext()
|
|
372
341
|
});
|
|
373
342
|
}
|
|
374
|
-
|
|
343
|
+
/* 새 계정은 「설정 대기」다 — 받는 쪽(목록)이 이어서 설정 링크를 건네게 한다(`detail.created`). */
|
|
344
|
+
this.dispatchEvent(new CustomEvent('user-invited', {
|
|
345
|
+
detail: { username, created: creating ? entered : null },
|
|
346
|
+
bubbles: true,
|
|
347
|
+
composed: true
|
|
348
|
+
}));
|
|
375
349
|
}
|
|
376
350
|
finally {
|
|
377
351
|
this.working = false;
|
|
@@ -395,10 +369,12 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
395
369
|
return true;
|
|
396
370
|
}
|
|
397
371
|
/**
|
|
398
|
-
* 계정이 없는 사람 —
|
|
372
|
+
* 계정이 없는 사람 — 「비밀번호 설정 대기」로 만든다. 비밀번호는 본인이 설정 링크로 정한다(ADR-0161).
|
|
399
373
|
*
|
|
400
374
|
* 역할은 여기서 안 넘긴다. `createUser` 는 **이 도메인 소유의 역할만** 붙이므로 상속·파트너
|
|
401
375
|
* 역할이 조용히 빠진다. 부르는 쪽에서 `updateUserRoles` 로 한 번에 준다.
|
|
376
|
+
*
|
|
377
|
+
* @returns 만든 사람(설정 링크 팝업이 읽는 칸들), 못 만들었으면 null
|
|
402
378
|
*/
|
|
403
379
|
async createAccount(email, username) {
|
|
404
380
|
const response = await client.mutate({
|
|
@@ -406,6 +382,10 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
406
382
|
mutation ($user: NewUser!) {
|
|
407
383
|
createUser(user: $user) {
|
|
408
384
|
id
|
|
385
|
+
username
|
|
386
|
+
email
|
|
387
|
+
name
|
|
388
|
+
status
|
|
409
389
|
}
|
|
410
390
|
}
|
|
411
391
|
`,
|
|
@@ -421,9 +401,9 @@ let UserInvite = class UserInvite extends localize(i18next)(LitElement) {
|
|
|
421
401
|
});
|
|
422
402
|
if (response.errors) {
|
|
423
403
|
this.message = response.errors[0]?.message || '';
|
|
424
|
-
return
|
|
404
|
+
return null;
|
|
425
405
|
}
|
|
426
|
-
return
|
|
406
|
+
return response.data?.createUser || null;
|
|
427
407
|
}
|
|
428
408
|
};
|
|
429
409
|
__decorate([
|
|
@@ -450,10 +430,6 @@ __decorate([
|
|
|
450
430
|
state(),
|
|
451
431
|
__metadata("design:type", String)
|
|
452
432
|
], UserInvite.prototype, "message", void 0);
|
|
453
|
-
__decorate([
|
|
454
|
-
state(),
|
|
455
|
-
__metadata("design:type", Boolean)
|
|
456
|
-
], UserInvite.prototype, "defaultPasswordConfigured", void 0);
|
|
457
433
|
__decorate([
|
|
458
434
|
query('[name=name]'),
|
|
459
435
|
__metadata("design:type", HTMLInputElement)
|
|
@@ -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/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\nimport '@material/web/textfield/filled-text-field.js'\nimport '@material/web/checkbox/checkbox.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 { ButtonContainerStyles, FormFieldStyles } 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 * 없고 기본 비밀번호 없다 **길이 없다.** 단추를 내주지 않고 무엇이 막고 있는지 말한다\n *\n * ⚠ **셋 중 어느 것도 「초대장」이 아니다.** 상대가 수락하는 흐름은 서버에 아직 없다\n * (`sendInvitation` 도 가입한 사용자만 받고, `Invitation` 에 상태도 만료도 없다). 그래서 이\n * 화면은 초대라는 말을 쓰지 않고 **실제로 일어나는 일**을 쓴다.\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 static styles: CSSResultGroup = [\n FormFieldStyles,\n ButtonContainerStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n min-width: min(420px, 86vw);\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n\n label {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 0.8rem;\n }\n\n input {\n width: 100%;\n }\n\n [section] {\n margin-top: var(--spacing-medium, 8px);\n color: var(--md-sys-color-on-surface-variant);\n font-size: 0.8rem;\n }\n\n /* 색은 짝으로. 막고 있는 것만 눈에 걸리게 한다. */\n [note] {\n padding: 8px 10px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.82rem;\n line-height: 1.5;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n\n [note][attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n\n [invalid] {\n color: var(--md-sys-color-error);\n font-size: 0.8rem;\n min-height: 1.1rem;\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: 8px;\n padding: 4px 2px;\n }\n `\n ]\n\n @state() private email: string = ''\n @state() private lookup: 'idle' | 'checking' | 'found' | 'absent' = '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 /** 기본 비밀번호가 설정돼 있지 않으면 계정을 만드는 길이 없다 — 서버가 던진다. */\n @state() private defaultPasswordConfigured: boolean = false\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' && this.defaultPasswordConfigured\n }\n\n /**\n * 메일로 초대하는 경로인가.\n *\n * 계정이 없고 기본 비밀번호도 없을 때다. 관리자가 비밀번호를 대신 정해 줄 수 없으므로\n * **본인이 메일을 받아 스스로 정하는 것이 유일한 길**이다.\n */\n private get inviting(): boolean {\n return this.lookup === 'absent' && !this.defaultPasswordConfigured\n }\n\n /** 부를 수 있나 — 단추와 처리기가 같은 답을 본다. */\n private get callable(): boolean {\n return !this.working && (this.lookup === 'found' || this.creating || this.inviting)\n }\n\n render() {\n return html`\n <md-filled-text-field\n id=\"email\"\n name=\"email\"\n type=\"email\"\n label=${i18next.t('field.email')}\n .value=${this.email}\n placeholder=\"name@company.com\"\n @input=${(e: Event) => this.onEmail((e.target as HTMLInputElement).value)}\n ></md-filled-text-field>\n <div invalid>${this.message}</div>\n\n ${this.renderLookup()} ${this.creating ? this.renderNewAccount() : nothing}\n ${this.lookup === 'found' || this.creating || this.inviting ? this.renderRoles() : nothing}\n\n <div class=\"button-container\">\n <md-elevated-button ?disabled=${!this.callable} @click=${() => this.onCall()}>\n ${this.inviting\n ? i18next.t('label.send invitation')\n : this.creating\n ? i18next.t('label.create account and add')\n : i18next.t('label.add to domain')}\n </md-elevated-button>\n <md-elevated-button @click=${() => this.dispatchEvent(new CustomEvent('close'))}>\n ${i18next.t('button.cancel')}\n </md-elevated-button>\n </div>\n `\n }\n\n private renderLookup() {\n switch (this.lookup) {\n case 'checking':\n return html`<div note>${i18next.t('text.loading')}</div>`\n case 'found':\n return html`<div note>${i18next.t('text.account exists so joins immediately')}</div>`\n case 'absent':\n return this.defaultPasswordConfigured\n ? html`<div note>${i18next.t('text.account will be created with the default password')}</div>`\n : /*\n * 전에는 여기가 막다른 자리였다 — 관리자가 비밀번호를 대신 정해 줄 수 없으니 계정을\n * 만들 길이 없었다. 이제 초대장이 그 길이다. 본인이 메일을 받아 스스로 비밀번호를\n * 정하고 들어온다.\n */\n html`<div note>${i18next.t('text.no account so an invitation will be mailed')}</div>`\n default:\n return nothing\n }\n }\n\n private renderNewAccount() {\n return html`\n <md-filled-text-field\n id=\"new-name\"\n name=\"name\"\n type=\"text\"\n label=${i18next.t('field.name')}\n @input=${() => this.requestUpdate()}\n ></md-filled-text-field>\n\n <!-- 서버가 받는 글자는 A-Z a-z 0-9 . _ @ - 다. 비우면 이메일을 그대로 쓴다. -->\n <md-filled-text-field\n id=\"new-username\"\n name=\"username\"\n type=\"text\"\n label=${i18next.t('field.username')}\n placeholder=${this.email}\n ></md-filled-text-field>\n `\n }\n\n private renderRoles() {\n return html`\n <div section>${i18next.t('field.roles')}</div>\n ${\n this.roles.length\n ? html`<ul roles>\n ${this.roles.map(\n role => html`\n <li>\n <md-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 ></md-checkbox>\n <label for=\"invite-role-${role.id}\">${role.name}</label>\n </li>\n `\n )}\n </ul>`\n : html`<div note attention>${i18next.t('text.no role defined in this domain')}</div>`\n }\n ${\n this.roles.length && this.selected.size === 0\n ? html`<div note attention>${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 this.checkDefaultPassword()\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 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 /* 묻는 사이에 사람이 더 쳤으면 그 답은 이미 옛것이다. */\n if (email !== this.email.trim()) {\n return\n }\n\n this.lookup = response.data?.checkUserExistence ? 'found' : 'absent'\n }\n\n private async sendInvitation(email: string) {\n this.working = true\n\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation ($email: String!, $roleIds: [String!]) {\n sendInvitation(email: $email, roleIds: $roleIds) {\n id\n }\n }\n `,\n variables: { email, roleIds: [...this.selected] },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n this.message = response.errors[0]?.message\n return\n }\n\n this.dispatchEvent(new CustomEvent('invited', { detail: { email }, bubbles: true, composed: true }))\n this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))\n } finally {\n this.working = false\n }\n }\n\n private async checkDefaultPassword() {\n const response = await client.query({\n query: gql`\n query {\n checkDefaultPassword\n }\n `,\n context: gqlContext()\n })\n\n this.defaultPasswordConfigured = Boolean(response.data?.checkDefaultPassword)\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\n if (this.inviting) {\n /*\n * 초대는 여기서 끝난다 — 역할은 초대장이 싣고 가고, 수락할 때 함께 붙는다. 아래의\n * 「넣고 나서 역할」 두 호출과 달리 한 번이다.\n */\n await this.sendInvitation(email)\n return\n }\n\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 = this.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 this.dispatchEvent(new CustomEvent('user-invited', { detail: { username }, bubbles: true, composed: true }))\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 * 계정이 없는 사람 — 기본 비밀번호로 만든다.\n *\n * 역할은 여기서 안 넘긴다. `createUser` 는 **이 도메인 소유의 역할만** 붙이므로 상속·파트너\n * 역할이 조용히 빠진다. 부르는 쪽에서 `updateUserRoles` 로 한 번에 준다.\n */\n private async createAccount(email: string, username: string): Promise<boolean> {\n const response = await client.mutate({\n mutation: gql`\n mutation ($user: NewUser!) {\n createUser(user: $user) {\n id\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 false\n }\n\n return true\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 { 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"]}
|