@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.
Files changed (59) hide show
  1. package/dist-client/bootstrap.js +18 -14
  2. package/dist-client/bootstrap.js.map +1 -1
  3. package/dist-client/components/abstract-password-reset.d.ts +5 -0
  4. package/dist-client/components/abstract-password-reset.js +20 -0
  5. package/dist-client/components/abstract-password-reset.js.map +1 -1
  6. package/dist-client/components/ownership-transfer-popup.d.ts +3 -1
  7. package/dist-client/components/ownership-transfer-popup.js +38 -41
  8. package/dist-client/components/ownership-transfer-popup.js.map +1 -1
  9. package/dist-client/components/password-setup-link-popup.d.ts +46 -0
  10. package/dist-client/components/password-setup-link-popup.js +254 -0
  11. package/dist-client/components/password-setup-link-popup.js.map +1 -0
  12. package/dist-client/components/user-detail.d.ts +20 -30
  13. package/dist-client/components/user-detail.js +114 -186
  14. package/dist-client/components/user-detail.js.map +1 -1
  15. package/dist-client/components/user-invite.d.ts +18 -25
  16. package/dist-client/components/user-invite.js +157 -181
  17. package/dist-client/components/user-invite.js.map +1 -1
  18. package/dist-client/components/user-popups-standard.test.js +69 -0
  19. package/dist-client/components/user-popups-standard.test.js.map +1 -0
  20. package/dist-client/components/user-role-editor.js +5 -26
  21. package/dist-client/components/user-role-editor.js.map +1 -1
  22. package/dist-client/components/user-status-renderer.js +4 -0
  23. package/dist-client/components/user-status-renderer.js.map +1 -1
  24. package/dist-client/entries/auth/reset-password.d.ts +1 -0
  25. package/dist-client/entries/auth/reset-password.js +5 -1
  26. package/dist-client/entries/auth/reset-password.js.map +1 -1
  27. package/dist-client/index.js +10 -28
  28. package/dist-client/index.js.map +1 -1
  29. package/dist-client/pages/user/user-list-page.d.ts +5 -1
  30. package/dist-client/pages/user/user-list-page.js +14 -5
  31. package/dist-client/pages/user/user-list-page.js.map +1 -1
  32. package/dist-client/route.js +0 -3
  33. package/dist-client/route.js.map +1 -1
  34. package/dist-client/tsconfig.tsbuildinfo +1 -1
  35. package/dist-client/utils/link-token.d.ts +3 -3
  36. package/dist-client/utils/link-token.js +4 -4
  37. package/dist-client/utils/link-token.js.map +1 -1
  38. package/dist-client/utils/link-token.test.js +2 -2
  39. package/dist-client/utils/link-token.test.js.map +1 -1
  40. package/package.json +6 -5
  41. package/things-factory.config.js +0 -1
  42. package/translations/en.json +30 -20
  43. package/translations/es.json +30 -20
  44. package/translations/ja.json +30 -20
  45. package/translations/ko.json +30 -20
  46. package/translations/ms.json +30 -20
  47. package/translations/zh.json +30 -20
  48. package/dist-client/components/accept-invitation.d.ts +0 -47
  49. package/dist-client/components/accept-invitation.js +0 -258
  50. package/dist-client/components/accept-invitation.js.map +0 -1
  51. package/dist-client/entries/auth/accept-invitation.d.ts +0 -3
  52. package/dist-client/entries/auth/accept-invitation.js +0 -10
  53. package/dist-client/entries/auth/accept-invitation.js.map +0 -1
  54. package/dist-client/pages/invitation/invitation-list-query.test.js +0 -69
  55. package/dist-client/pages/invitation/invitation-list-query.test.js.map +0 -1
  56. package/dist-client/pages/invitation/invitations.d.ts +0 -63
  57. package/dist-client/pages/invitation/invitations.js +0 -284
  58. package/dist-client/pages/invitation/invitations.js.map +0 -1
  59. /package/dist-client/{pages/invitation/invitation-list-query.test.d.ts → components/user-popups-standard.test.d.ts} +0 -0
@@ -1,63 +0,0 @@
1
- import '@material/web/icon/icon.js';
2
- import '@operato/data-grist/ox-grist.js';
3
- import type { FetchOption } from '@operato/data-grist';
4
- import { PageView } from '@operato/shell';
5
- declare const Invitations_base: (new (...args: any[]) => {
6
- __preferenceProviders: {
7
- [element: string]: import("@operato/p13n").PagePreferenceProvider;
8
- };
9
- getPagePreferenceProvider(element: string): import("@operato/p13n").PagePreferenceProvider | undefined;
10
- }) & (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
11
- /**
12
- * Who was invited into this domain, and what became of it.
13
- *
14
- * ── Why this screen is the reason the table exists ──────────────────────────
15
- * Accepting used to delete the row, so the moment somebody joined, the fact that they had been
16
- * invited stopped existing. There was nothing to put on a screen — which is why no screen was
17
- * built, and why nothing called any of the invitation doors at all. The rows stay now and carry
18
- * a state, and this is what that is for.
19
- *
20
- * ── ⚠ The token is not a column and cannot be ───────────────────────────────
21
- * The token in an invitation mail is what opens the door. This is a screen an administrator
22
- * reads, so printing it here would hand every pending invitation to whoever can open the list,
23
- * and to any screenshot of it. It is not a GraphQL field on `Invitation` at all — the same
24
- * reason `app-bindings` does not ask for a refresh token.
25
- *
26
- * ── The state is read as of now ─────────────────────────────────────────────
27
- * `state` comes from the server's field resolver, which measures a pending row against the
28
- * clock rather than trusting the stored column. A row whose expiry passed an hour ago says
29
- * expired here even though nothing has been round to write that down — showing it as live would
30
- * be the more dangerous of the two ways to be wrong.
31
- */
32
- export declare class Invitations extends Invitations_base {
33
- static styles: import("lit").CSSResult[];
34
- active: boolean;
35
- config: any;
36
- mode: 'CARD' | 'LIST' | 'GRID';
37
- /** The last fetch did not answer. Distinct from answering with nothing. */
38
- failed: boolean;
39
- private grist;
40
- get context(): {
41
- title: string;
42
- help: string;
43
- search: {
44
- handler: (search: string) => void;
45
- value: string;
46
- };
47
- actions: {
48
- title: string;
49
- action: () => Promise<boolean>;
50
- icon: string;
51
- }[];
52
- };
53
- render(): import("lit-html").TemplateResult<1>;
54
- pageInitialized(): Promise<void>;
55
- private resend;
56
- private withdraw;
57
- pageUpdated(_changes: any, _lifecycle: any): Promise<void>;
58
- fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
59
- total: number;
60
- records: any[];
61
- }>;
62
- }
63
- export {};
@@ -1,284 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import '@material/web/icon/icon.js';
3
- import '@operato/data-grist/ox-grist.js';
4
- import gql from 'graphql-tag';
5
- import { css, html } from 'lit';
6
- import { customElement, property, query } from 'lit/decorators.js';
7
- import { client, gqlContext } from '@operato/graphql';
8
- import { i18next, localize } from '@operato/i18n';
9
- import { OxPrompt } from '@operato/popup/ox-prompt.js';
10
- import { navigate, PageView } from '@operato/shell';
11
- import { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
12
- import { isMobileDevice } from '@operato/utils';
13
- import { p13n } from '@operato/p13n';
14
- import { listOutcome, pageFromResponse } from '../../utils/list-result.js';
15
- /**
16
- * Who was invited into this domain, and what became of it.
17
- *
18
- * ── Why this screen is the reason the table exists ──────────────────────────
19
- * Accepting used to delete the row, so the moment somebody joined, the fact that they had been
20
- * invited stopped existing. There was nothing to put on a screen — which is why no screen was
21
- * built, and why nothing called any of the invitation doors at all. The rows stay now and carry
22
- * a state, and this is what that is for.
23
- *
24
- * ── ⚠ The token is not a column and cannot be ───────────────────────────────
25
- * The token in an invitation mail is what opens the door. This is a screen an administrator
26
- * reads, so printing it here would hand every pending invitation to whoever can open the list,
27
- * and to any screenshot of it. It is not a GraphQL field on `Invitation` at all — the same
28
- * reason `app-bindings` does not ask for a refresh token.
29
- *
30
- * ── The state is read as of now ─────────────────────────────────────────────
31
- * `state` comes from the server's field resolver, which measures a pending row against the
32
- * clock rather than trusting the stored column. A row whose expiry passed an hour ago says
33
- * expired here even though nothing has been round to write that down — showing it as live would
34
- * be the more dangerous of the two ways to be wrong.
35
- */
36
- let Invitations = class Invitations extends p13n(localize(i18next)(PageView)) {
37
- constructor() {
38
- super(...arguments);
39
- this.active = false;
40
- this.mode = isMobileDevice() ? 'CARD' : 'GRID';
41
- /** The last fetch did not answer. Distinct from answering with nothing. */
42
- this.failed = false;
43
- }
44
- static { this.styles = [
45
- WorkSurfacePageStyles,
46
- CommonGristStyles,
47
- CommonHeaderStyles,
48
- ScrollbarStyles,
49
- css `
50
- [failed] {
51
- padding: var(--spacing-medium);
52
- color: var(--md-sys-color-error);
53
- font: var(--label-font, inherit);
54
- }
55
- `
56
- ]; }
57
- get context() {
58
- return {
59
- title: i18next.t('label.invitations'),
60
- help: 'auth/user',
61
- search: {
62
- handler: (search) => {
63
- this.grist.searchText = search;
64
- },
65
- value: this.grist?.searchText || ''
66
- },
67
- actions: [
68
- {
69
- title: i18next.t('text.users'),
70
- action: () => navigate('users'),
71
- icon: 'group'
72
- }
73
- ]
74
- };
75
- }
76
- render() {
77
- return html `
78
- <ox-grist
79
- class="page-fill"
80
- .mode=${this.mode}
81
- .config=${this.config}
82
- .fetchHandler=${this.fetchHandler.bind(this)}
83
- .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')}
84
- >
85
- </ox-grist>
86
-
87
- ${this.failed ? html `<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}
88
- `;
89
- }
90
- async pageInitialized() {
91
- this.config = {
92
- rows: { appendable: false, selectable: { multiple: false } },
93
- columns: [
94
- { type: 'gutter', gutterName: 'sequence' },
95
- {
96
- type: 'string',
97
- name: 'email',
98
- header: i18next.t('field.email'),
99
- record: { editable: false },
100
- /* The title-bar search scans only columns that declare it; the server answers on email. */
101
- filter: 'search',
102
- sortable: true,
103
- width: 240
104
- },
105
- {
106
- type: 'string',
107
- name: 'state',
108
- header: i18next.t('field.state'),
109
- record: { align: 'center', editable: false },
110
- width: 120
111
- },
112
- {
113
- type: 'string',
114
- name: 'roleNames',
115
- header: i18next.t('field.roles'),
116
- record: { editable: false },
117
- width: 200
118
- },
119
- {
120
- type: 'string',
121
- name: 'creatorName',
122
- header: i18next.t('field.invited'),
123
- record: { editable: false },
124
- width: 160
125
- },
126
- {
127
- type: 'datetime',
128
- name: 'createdAt',
129
- header: i18next.t('field.created at'),
130
- sortable: true,
131
- width: 180
132
- },
133
- {
134
- type: 'datetime',
135
- name: 'expiresAt',
136
- header: i18next.t('field.expires at'),
137
- sortable: true,
138
- width: 180
139
- },
140
- {
141
- type: 'datetime',
142
- name: 'acceptedAt',
143
- header: i18next.t('field.accepted at'),
144
- sortable: true,
145
- width: 180
146
- },
147
- /*
148
- * Both actions only mean anything while an invitation is still open, so they are offered
149
- * only there. A button that is always present and refuses on half the rows reads as the
150
- * screen being broken rather than as the row being finished.
151
- */
152
- {
153
- type: 'gutter',
154
- gutterName: 'button',
155
- icon: 'refresh',
156
- title: i18next.t('button.resend'),
157
- handlers: {
158
- click: (_c, _d, _col, record) => record?.state === 'pending' && this.resend(record)
159
- }
160
- },
161
- {
162
- type: 'gutter',
163
- gutterName: 'button',
164
- icon: 'block',
165
- title: i18next.t('button.withdraw'),
166
- handlers: {
167
- click: (_c, _d, _col, record) => record?.state === 'pending' && this.withdraw(record)
168
- }
169
- }
170
- ]
171
- };
172
- }
173
- async resend(record) {
174
- /*
175
- * A fresh token each time. Re-mailing the old one would leave two live links for one
176
- * invitation, and the older mail is the one more likely to have been forwarded.
177
- */
178
- const response = await client.mutate({
179
- mutation: gql `
180
- mutation ($id: String!) {
181
- resendInvitation(id: $id)
182
- }
183
- `,
184
- variables: { id: record.id },
185
- context: gqlContext()
186
- });
187
- if (!response.errors?.length) {
188
- document.dispatchEvent(new CustomEvent('notify', { detail: { level: 'info', message: i18next.t('text.invitation resent') } }));
189
- }
190
- this.grist.fetch();
191
- }
192
- async withdraw(record) {
193
- const confirmed = await OxPrompt.open({
194
- type: 'warning',
195
- title: i18next.t('button.withdraw'),
196
- text: i18next.t('text.are you sure you want to delete x', { x: record.email }),
197
- confirmButton: { text: i18next.t('button.withdraw') },
198
- cancelButton: { text: i18next.t('button.cancel') }
199
- });
200
- if (!confirmed) {
201
- return;
202
- }
203
- await client.mutate({
204
- mutation: gql `
205
- mutation ($id: String!) {
206
- cancelInvitation(id: $id)
207
- }
208
- `,
209
- variables: { id: record.id },
210
- context: gqlContext()
211
- });
212
- this.grist.fetch();
213
- }
214
- async pageUpdated(_changes, _lifecycle) {
215
- if (this.active) {
216
- await this.updateComplete;
217
- this.grist.fetch();
218
- }
219
- }
220
- async fetchHandler({ page, limit, sortings = [], filters = [] }) {
221
- const response = await client.query({
222
- query: gql `
223
- query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
224
- invitations(filters: $filters, pagination: $pagination, sortings: $sortings) {
225
- items {
226
- id
227
- email
228
- state
229
- createdAt
230
- expiresAt
231
- acceptedAt
232
- roles {
233
- id
234
- name
235
- }
236
- creator {
237
- id
238
- name
239
- }
240
- }
241
- total
242
- }
243
- }
244
- `,
245
- variables: { filters, pagination: { page, limit }, sortings },
246
- context: gqlContext()
247
- });
248
- const { items, total, failed } = listOutcome(pageFromResponse(response, 'invitations'));
249
- this.failed = failed;
250
- return {
251
- total,
252
- records: items.map((item) => ({
253
- ...item,
254
- roleNames: (item.roles || []).map((role) => role.name).join(', '),
255
- creatorName: item.creator?.name || ''
256
- }))
257
- };
258
- }
259
- };
260
- __decorate([
261
- property({ type: Boolean }),
262
- __metadata("design:type", Boolean)
263
- ], Invitations.prototype, "active", void 0);
264
- __decorate([
265
- property({ type: Object }),
266
- __metadata("design:type", Object)
267
- ], Invitations.prototype, "config", void 0);
268
- __decorate([
269
- property({ type: String }),
270
- __metadata("design:type", String)
271
- ], Invitations.prototype, "mode", void 0);
272
- __decorate([
273
- property({ type: Boolean }),
274
- __metadata("design:type", Boolean)
275
- ], Invitations.prototype, "failed", void 0);
276
- __decorate([
277
- query('ox-grist'),
278
- __metadata("design:type", Function)
279
- ], Invitations.prototype, "grist", void 0);
280
- Invitations = __decorate([
281
- customElement('invitations-page')
282
- ], Invitations);
283
- export { Invitations };
284
- //# sourceMappingURL=invitations.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"invitations.js","sourceRoot":"","sources":["../../../client/pages/invitation/invitations.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { p13n } from '@operato/p13n'\n\nimport { listOutcome, pageFromResponse } from '../../utils/list-result.js'\n\n/**\n * Who was invited into this domain, and what became of it.\n *\n * ── Why this screen is the reason the table exists ──────────────────────────\n * Accepting used to delete the row, so the moment somebody joined, the fact that they had been\n * invited stopped existing. There was nothing to put on a screen — which is why no screen was\n * built, and why nothing called any of the invitation doors at all. The rows stay now and carry\n * a state, and this is what that is for.\n *\n * ── ⚠ The token is not a column and cannot be ───────────────────────────────\n * The token in an invitation mail is what opens the door. This is a screen an administrator\n * reads, so printing it here would hand every pending invitation to whoever can open the list,\n * and to any screenshot of it. It is not a GraphQL field on `Invitation` at all — the same\n * reason `app-bindings` does not ask for a refresh token.\n *\n * ── The state is read as of now ─────────────────────────────────────────────\n * `state` comes from the server's field resolver, which measures a pending row against the\n * clock rather than trusting the stored column. A row whose expiry passed an hour ago says\n * expired here even though nothing has been round to write that down — showing it as live would\n * be the more dangerous of the two ways to be wrong.\n */\n@customElement('invitations-page')\nexport class Invitations extends p13n(localize(i18next)(PageView)) {\n static styles = [\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n [failed] {\n padding: var(--spacing-medium);\n color: var(--md-sys-color-error);\n font: var(--label-font, inherit);\n }\n `\n ]\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n /** The last fetch did not answer. Distinct from answering with nothing. */\n @property({ type: Boolean }) failed: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('label.invitations'),\n help: 'auth/user',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('text.users'),\n action: () => navigate('users'),\n icon: 'group'\n }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n class=\"page-fill\"\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n </ox-grist>\n\n ${this.failed ? html`<div failed>${i18next.t('text.the list could not be loaded')}</div>` : ''}\n `\n }\n\n async pageInitialized() {\n this.config = {\n rows: { appendable: false, selectable: { multiple: false } },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { editable: false },\n /* The title-bar search scans only columns that declare it; the server answers on email. */\n filter: 'search',\n sortable: true,\n width: 240\n },\n {\n type: 'string',\n name: 'state',\n header: i18next.t('field.state'),\n record: { align: 'center', editable: false },\n width: 120\n },\n {\n type: 'string',\n name: 'roleNames',\n header: i18next.t('field.roles'),\n record: { editable: false },\n width: 200\n },\n {\n type: 'string',\n name: 'creatorName',\n header: i18next.t('field.invited'),\n record: { editable: false },\n width: 160\n },\n {\n type: 'datetime',\n name: 'createdAt',\n header: i18next.t('field.created at'),\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'expiresAt',\n header: i18next.t('field.expires at'),\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'acceptedAt',\n header: i18next.t('field.accepted at'),\n sortable: true,\n width: 180\n },\n /*\n * Both actions only mean anything while an invitation is still open, so they are offered\n * only there. A button that is always present and refuses on half the rows reads as the\n * screen being broken rather than as the row being finished.\n */\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'refresh',\n title: i18next.t('button.resend'),\n handlers: {\n click: (_c: any, _d: any, _col: any, record: any) => record?.state === 'pending' && this.resend(record)\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'block',\n title: i18next.t('button.withdraw'),\n handlers: {\n click: (_c: any, _d: any, _col: any, record: any) => record?.state === 'pending' && this.withdraw(record)\n }\n }\n ]\n }\n }\n\n private async resend(record: any) {\n /*\n * A fresh token each time. Re-mailing the old one would leave two live links for one\n * invitation, and the older mail is the one more likely to have been forwarded.\n */\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n resendInvitation(id: $id)\n }\n `,\n variables: { id: record.id },\n context: gqlContext()\n })\n\n if (!response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'info', message: i18next.t('text.invitation resent') } })\n )\n }\n\n this.grist.fetch()\n }\n\n private async withdraw(record: any) {\n const confirmed = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.withdraw'),\n text: i18next.t('text.are you sure you want to delete x', { x: record.email }),\n confirmButton: { text: i18next.t('button.withdraw') },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n\n if (!confirmed) {\n return\n }\n\n await client.mutate({\n mutation: gql`\n mutation ($id: String!) {\n cancelInvitation(id: $id)\n }\n `,\n variables: { id: record.id },\n context: gqlContext()\n })\n\n this.grist.fetch()\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n invitations(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n email\n state\n createdAt\n expiresAt\n acceptedAt\n roles {\n id\n name\n }\n creator {\n id\n name\n }\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n context: gqlContext()\n })\n\n const { items, total, failed } = listOutcome(pageFromResponse(response, 'invitations'))\n\n this.failed = failed\n\n return {\n total,\n records: items.map((item: any) => ({\n ...item,\n roleNames: (item.roles || []).map((role: any) => role.name).join(', '),\n creatorName: item.creator?.name || ''\n }))\n }\n }\n}\n"]}