@gopherium/react-auth 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AuthGate.js +3 -1
- package/dist/admin/index.d.ts +24 -0
- package/dist/admin/index.js +72 -2
- package/dist/api.d.ts +1 -0
- package/dist/api.js +1 -0
- package/dist/domain.d.ts +2 -0
- package/dist/domain.js +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/languages/es-ES.json +1 -0
- package/dist/languages/languages/es-ES.json +1 -0
- package/dist/languages.d.ts +8 -0
- package/dist/languages.js +14 -0
- package/dist/session.d.ts +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.js +27 -0
- package/dist/transport.d.ts +1 -0
- package/dist/wpds/AccountPanel.js +3 -1
- package/dist/wpds/LoginScreen.js +6 -4
- package/dist/wpds/NewUserScreen.js +7 -5
- package/dist/wpds/UsersScreen.js +8 -4
- package/dist/wpds/nav.js +10 -1
- package/package.json +10 -2
package/dist/AuthGate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import { DOMAIN } from './domain.js';
|
|
4
6
|
import { sessionQueryKey, useSession } from './session.js';
|
|
5
7
|
/**
|
|
6
8
|
* Guards its children behind a login session, rendering the given login
|
|
@@ -11,7 +13,7 @@ import { sessionQueryKey, useSession } from './session.js';
|
|
|
11
13
|
* @param error - Shown when the session cannot be loaded.
|
|
12
14
|
* @returns The children, the login screen, or a status element.
|
|
13
15
|
*/
|
|
14
|
-
export function AuthGate({ children, loginScreen, loading = _jsx("p", { role: "status", children:
|
|
16
|
+
export function AuthGate({ children, loginScreen, loading = _jsx("p", { role: "status", children: __('Loading…', DOMAIN) }), error = _jsx("p", { role: "alert", children: __('Something went wrong.', DOMAIN) }), }) {
|
|
15
17
|
const queryClient = useQueryClient();
|
|
16
18
|
const session = useSession();
|
|
17
19
|
if (session.data === undefined) {
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare const userSchema: z.ZodObject<{
|
|
|
5
5
|
email: z.ZodString;
|
|
6
6
|
name: z.ZodString;
|
|
7
7
|
disabled: z.ZodBoolean;
|
|
8
|
+
rank: z.ZodDefault<z.ZodString>;
|
|
8
9
|
created_at: z.ZodCoercedDate<unknown>;
|
|
9
10
|
}, z.core.$strip>;
|
|
10
11
|
export type User = z.infer<typeof userSchema>;
|
|
@@ -12,6 +13,7 @@ export interface NewUser {
|
|
|
12
13
|
email: string;
|
|
13
14
|
name: string;
|
|
14
15
|
password: string;
|
|
16
|
+
rank?: string;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* EmailTakenError is thrown when a new user's email is already in use.
|
|
@@ -24,6 +26,22 @@ export declare class EmailTakenError extends Error {
|
|
|
24
26
|
*/
|
|
25
27
|
export declare class ValidationError extends Error {
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* RankRefusedError is thrown when the actor holds no rank the server admits.
|
|
31
|
+
*/
|
|
32
|
+
export declare class RankRefusedError extends Error {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SelfRankError is thrown when an account changes its own rank.
|
|
36
|
+
*/
|
|
37
|
+
export declare class SelfRankError extends Error {
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* LastPrivilegedError is thrown when a write would leave no enabled
|
|
41
|
+
* account holding a privileged rank.
|
|
42
|
+
*/
|
|
43
|
+
export declare class LastPrivilegedError extends Error {
|
|
44
|
+
}
|
|
27
45
|
/**
|
|
28
46
|
* Returns every user account.
|
|
29
47
|
* @param signal - Aborts the in-flight request.
|
|
@@ -42,4 +60,10 @@ export declare function createUser(input: NewUser): Promise<User>;
|
|
|
42
60
|
* @param disabled - Whether the account should be disabled.
|
|
43
61
|
*/
|
|
44
62
|
export declare function setUserDisabled(id: string, disabled: boolean): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Sets the rank an account holds.
|
|
65
|
+
* @param id - The identifier of the account to update.
|
|
66
|
+
* @param rank - The rank the account is to hold.
|
|
67
|
+
*/
|
|
68
|
+
export declare function setUserRank(id: string, rank: string): Promise<void>;
|
|
45
69
|
export {};
|
package/dist/admin/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { UnauthorizedError } from '../api.js';
|
|
5
|
+
import { DOMAIN } from '../domain.js';
|
|
4
6
|
import { resolveTransport } from '../transport.js';
|
|
5
7
|
export const usersQueryKey = ['users'];
|
|
6
8
|
const userSchema = z.object({
|
|
@@ -8,6 +10,7 @@ const userSchema = z.object({
|
|
|
8
10
|
email: z.string(),
|
|
9
11
|
name: z.string(),
|
|
10
12
|
disabled: z.boolean(),
|
|
13
|
+
rank: z.string().default(''),
|
|
11
14
|
created_at: z.coerce.date(),
|
|
12
15
|
});
|
|
13
16
|
/**
|
|
@@ -21,7 +24,23 @@ export class EmailTakenError extends Error {
|
|
|
21
24
|
*/
|
|
22
25
|
export class ValidationError extends Error {
|
|
23
26
|
}
|
|
24
|
-
|
|
27
|
+
/**
|
|
28
|
+
* RankRefusedError is thrown when the actor holds no rank the server admits.
|
|
29
|
+
*/
|
|
30
|
+
export class RankRefusedError extends Error {
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* SelfRankError is thrown when an account changes its own rank.
|
|
34
|
+
*/
|
|
35
|
+
export class SelfRankError extends Error {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* LastPrivilegedError is thrown when a write would leave no enabled
|
|
39
|
+
* account holding a privileged rank.
|
|
40
|
+
*/
|
|
41
|
+
export class LastPrivilegedError extends Error {
|
|
42
|
+
}
|
|
43
|
+
const errorSchema = z.object({ error: z.string(), code: z.string().optional() });
|
|
25
44
|
/**
|
|
26
45
|
* Extracts the backend's explanation from a failed response body.
|
|
27
46
|
* @param response - The failed HTTP response.
|
|
@@ -69,7 +88,7 @@ async function restCreateUser(input) {
|
|
|
69
88
|
throw new EmailTakenError('email already in use');
|
|
70
89
|
}
|
|
71
90
|
if (response.status === 422) {
|
|
72
|
-
throw new ValidationError(await errorMessage(response, 'invalid user details'));
|
|
91
|
+
throw new ValidationError(await errorMessage(response, __('invalid user details', DOMAIN)));
|
|
73
92
|
}
|
|
74
93
|
if (!response.ok) {
|
|
75
94
|
throw new Error(`creating user failed with status ${response.status}`);
|
|
@@ -94,6 +113,49 @@ async function restSetUserDisabled(id, disabled) {
|
|
|
94
113
|
throw new Error(`updating user failed with status ${response.status}`);
|
|
95
114
|
}
|
|
96
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Writes the rank an account holds over REST.
|
|
118
|
+
* @param id - The identifier of the account to update.
|
|
119
|
+
* @param rank - The rank the account is to hold.
|
|
120
|
+
*/
|
|
121
|
+
async function restSetUserRank(id, rank) {
|
|
122
|
+
const response = await fetch(`/api/users/${id}/rank`, {
|
|
123
|
+
method: 'PUT',
|
|
124
|
+
headers: { 'Content-Type': 'application/json' },
|
|
125
|
+
body: JSON.stringify({ rank }),
|
|
126
|
+
});
|
|
127
|
+
if (response.ok) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (response.status === 401) {
|
|
131
|
+
throw new UnauthorizedError('session expired');
|
|
132
|
+
}
|
|
133
|
+
const refusal = await refusalOf(response);
|
|
134
|
+
if (refusal.code === 'rank_insufficient') {
|
|
135
|
+
throw new RankRefusedError(refusal.message);
|
|
136
|
+
}
|
|
137
|
+
if (refusal.code === 'self_rank_refused') {
|
|
138
|
+
throw new SelfRankError(refusal.message);
|
|
139
|
+
}
|
|
140
|
+
if (refusal.code === 'last_privileged_refused') {
|
|
141
|
+
throw new LastPrivilegedError(refusal.message);
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`updating rank failed with status ${response.status}`);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns the code and message a refused response carries.
|
|
147
|
+
* @param response - The refused HTTP response.
|
|
148
|
+
* @returns The refusal code, if any, and its message.
|
|
149
|
+
*/
|
|
150
|
+
async function refusalOf(response) {
|
|
151
|
+
try {
|
|
152
|
+
const held = errorSchema.parse(await response.json());
|
|
153
|
+
return { code: held.code, message: held.error };
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return { message: __('the account could not be updated', DOMAIN) };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
97
159
|
/**
|
|
98
160
|
* Returns every user account.
|
|
99
161
|
* @param signal - Aborts the in-flight request.
|
|
@@ -118,3 +180,11 @@ export async function createUser(input) {
|
|
|
118
180
|
export async function setUserDisabled(id, disabled) {
|
|
119
181
|
await resolveTransport('setUserDisabled', restSetUserDisabled)(id, disabled);
|
|
120
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Sets the rank an account holds.
|
|
185
|
+
* @param id - The identifier of the account to update.
|
|
186
|
+
* @param rank - The rank the account is to hold.
|
|
187
|
+
*/
|
|
188
|
+
export async function setUserRank(id, rank) {
|
|
189
|
+
await resolveTransport('setUserRank', restSetUserRank)(id, rank);
|
|
190
|
+
}
|
package/dist/api.d.ts
CHANGED
package/dist/api.js
CHANGED
package/dist/domain.d.ts
ADDED
package/dist/domain.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { InvalidCredentialsError, RateLimitedError, UnauthorizedError, fetchSession, login, logout, } from './api.js';
|
|
2
2
|
export type { User } from './api.js';
|
|
3
3
|
export { AuthGate } from './AuthGate.js';
|
|
4
|
+
export { DOMAIN } from './domain.js';
|
|
5
|
+
export { catalogFor } from './languages.js';
|
|
6
|
+
export type { Catalog } from './languages.js';
|
|
4
7
|
export { isSessionRevoked } from './probe.js';
|
|
5
8
|
export { createAuthQueryClient } from './queryClient.js';
|
|
6
9
|
export { sessionQueryKey, useLogout, useSession } from './session.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
export { InvalidCredentialsError, RateLimitedError, UnauthorizedError, fetchSession, login, logout, } from './api.js';
|
|
3
3
|
export { AuthGate } from './AuthGate.js';
|
|
4
|
+
export { DOMAIN } from './domain.js';
|
|
5
|
+
export { catalogFor } from './languages.js';
|
|
4
6
|
export { isSessionRevoked } from './probe.js';
|
|
5
7
|
export { createAuthQueryClient } from './queryClient.js';
|
|
6
8
|
export { sessionQueryKey, useLogout, useSession } from './session.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "": { "lang": "es-ES", "plural-forms": "nplurals=2; plural=(n != 1);" }, "Actions": ["Acciones"], "Active": ["Activo"], "Create user": ["Crear usuario"], "Disable": ["Desactivar"], "Disable %s": ["Desactivar a %s"], "Disabled": ["Desactivado"], "Enable": ["Activar"], "Enable %s": ["Activar a %s"], "Invalid email or password.": ["Correo o contraseña no válidos."], "Loading users…": ["Cargando usuarios…"], "Loading…": ["Cargando…"], "Log in": ["Iniciar sesión"], "Log out": ["Cerrar sesión"], "Login failed, please try again.": ["No se pudo iniciar sesión, inténtalo de nuevo."], "Logout failed, please try again.": ["No se pudo cerrar sesión, inténtalo de nuevo."], "Password": ["Contraseña"], "Something went wrong.": ["Algo ha ido mal."], "That email is already in use.": ["Ese correo ya está en uso."], "The user could not be created.": ["No se pudo crear el usuario."], "Too many attempts. Please wait a minute and try again.": ["Demasiados intentos. Espera un minuto e inténtalo de nuevo."], "Update failed.": ["No se pudo actualizar."], "Users could not be loaded.": ["No se pudieron cargar los usuarios."], "admin section\u0004Users": ["Usuarios"], "button\u0004New user": ["Nuevo usuario"], "column\u0004Email": ["Correo"], "column\u0004Name": ["Nombre"], "column\u0004Status": ["Estado"], "field label\u0004Email": ["Correo"], "field label\u0004Name": ["Nombre"], "invalid user details": ["datos de usuario no válidos"], "page heading\u0004New user": ["Nuevo usuario"], "page heading\u0004Users": ["Usuarios"], "the account could not be updated": ["no se ha podido actualizar la cuenta"] }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"":{"lang":"es-ES","plural-forms":"nplurals=2; plural=(n != 1);"},"Actions":["Acciones"],"Active":["Activo"],"Create user":["Crear usuario"],"Disable":["Desactivar"],"Disable %s":["Desactivar a %s"],"Disabled":["Desactivado"],"Enable":["Activar"],"Enable %s":["Activar a %s"],"Invalid email or password.":["Correo o contraseña no válidos."],"Loading users…":["Cargando usuarios…"],"Loading…":["Cargando…"],"Log in":["Iniciar sesión"],"Log out":["Cerrar sesión"],"Login failed, please try again.":["No se pudo iniciar sesión, inténtalo de nuevo."],"Logout failed, please try again.":["No se pudo cerrar sesión, inténtalo de nuevo."],"Password":["Contraseña"],"Something went wrong.":["Algo ha ido mal."],"That email is already in use.":["Ese correo ya está en uso."],"The user could not be created.":["No se pudo crear el usuario."],"Too many attempts. Please wait a minute and try again.":["Demasiados intentos. Espera un minuto e inténtalo de nuevo."],"Update failed.":["No se pudo actualizar."],"Users could not be loaded.":["No se pudieron cargar los usuarios."],"admin section\u0004Users":["Usuarios"],"button\u0004New user":["Nuevo usuario"],"column\u0004Email":["Correo"],"column\u0004Name":["Nombre"],"column\u0004Status":["Estado"],"field label\u0004Email":["Correo"],"field label\u0004Name":["Nombre"],"invalid user details":["datos de usuario no válidos"],"page heading\u0004New user":["Nuevo usuario"],"page heading\u0004Users":["Usuarios"],"the account could not be updated":["no se ha podido actualizar la cuenta"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A compiled catalogue, keyed by message with the metadata entry first. */
|
|
2
|
+
export type Catalog = Record<string, string[] | Record<string, string>>;
|
|
3
|
+
/**
|
|
4
|
+
* Returns the catalogue this package ships for a language.
|
|
5
|
+
* @param locale - The language the reader settled on.
|
|
6
|
+
* @returns The catalogue, or nothing when the package ships none for it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function catalogFor(locale: string): Promise<Catalog | undefined>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/** The catalogue this package ships for each language it has one for. */
|
|
3
|
+
const SHIPPED = {
|
|
4
|
+
'es-ES': () => import('./languages/es-ES.json', { with: { type: 'json' } }),
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Returns the catalogue this package ships for a language.
|
|
8
|
+
* @param locale - The language the reader settled on.
|
|
9
|
+
* @returns The catalogue, or nothing when the package ships none for it.
|
|
10
|
+
*/
|
|
11
|
+
export async function catalogFor(locale) {
|
|
12
|
+
const load = SHIPPED[locale];
|
|
13
|
+
return load === undefined ? undefined : (await load()).default;
|
|
14
|
+
}
|
package/dist/session.d.ts
CHANGED
package/dist/testing/index.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export declare function installTestEnvironment(): void;
|
|
|
11
11
|
* defaultUser is the canned account tests sign in as.
|
|
12
12
|
*/
|
|
13
13
|
export declare const defaultUser: User;
|
|
14
|
+
/**
|
|
15
|
+
* Returns a canned account holding the given rank, one identifier per rank.
|
|
16
|
+
* @param rank - The rank the account holds.
|
|
17
|
+
* @returns The account.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rankedUser(rank: string): User;
|
|
20
|
+
/**
|
|
21
|
+
* Accepts a rank change.
|
|
22
|
+
* @returns The msw handler.
|
|
23
|
+
*/
|
|
24
|
+
export declare function rankOk(): import("msw").HttpHandler;
|
|
14
25
|
/**
|
|
15
26
|
* Seeds the cached session so components render signed in without a fetch.
|
|
16
27
|
* @param client - The query client under test.
|
package/dist/testing/index.js
CHANGED
|
@@ -25,7 +25,34 @@ export const defaultUser = {
|
|
|
25
25
|
id: '0198b2f0-0000-7000-8000-000000000001',
|
|
26
26
|
email: 'grace@example.com',
|
|
27
27
|
name: 'Grace Hopper',
|
|
28
|
+
rank: '',
|
|
28
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Returns a canned account holding the given rank, one identifier per rank.
|
|
32
|
+
* @param rank - The rank the account holds.
|
|
33
|
+
* @returns The account.
|
|
34
|
+
*/
|
|
35
|
+
export function rankedUser(rank) {
|
|
36
|
+
let held = 2166136261;
|
|
37
|
+
for (const letter of rank) {
|
|
38
|
+
held ^= letter.charCodeAt(0);
|
|
39
|
+
held = Math.imul(held, 16777619);
|
|
40
|
+
}
|
|
41
|
+
held >>>= 0;
|
|
42
|
+
return {
|
|
43
|
+
id: `0198b2f0-0000-7000-8000-${String(held).padStart(12, '0')}`,
|
|
44
|
+
email: `${rank}@example.com`,
|
|
45
|
+
name: `Maria Perez the ${rank}`,
|
|
46
|
+
rank,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Accepts a rank change.
|
|
51
|
+
* @returns The msw handler.
|
|
52
|
+
*/
|
|
53
|
+
export function rankOk() {
|
|
54
|
+
return http.put('/api/users/:id/rank', () => new HttpResponse(null, { status: 204 }));
|
|
55
|
+
}
|
|
29
56
|
/**
|
|
30
57
|
* Seeds the cached session so components render signed in without a fetch.
|
|
31
58
|
* @param client - The query client under test.
|
package/dist/transport.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface AuthTransport {
|
|
|
14
14
|
fetchUsers(signal?: AbortSignal): Promise<Account[]>;
|
|
15
15
|
createUser(input: NewUser): Promise<Account>;
|
|
16
16
|
setUserDisabled(id: string, disabled: boolean): Promise<void>;
|
|
17
|
+
setUserRank(id: string, rank: string): Promise<void>;
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Replaces backend calls with the given implementations, keeping the REST
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
3
4
|
import { Button, Stack, Text } from '@wordpress/ui';
|
|
5
|
+
import { DOMAIN } from '../domain.js';
|
|
4
6
|
import { useLogout, useSession } from '../session.js';
|
|
5
7
|
/**
|
|
6
8
|
* Renders the signed-in user's identity with a logout control, or nothing
|
|
@@ -14,5 +16,5 @@ export function AccountPanel({ className }) {
|
|
|
14
16
|
if (!user) {
|
|
15
17
|
return null;
|
|
16
18
|
}
|
|
17
|
-
return (_jsxs(Stack, { direction: "column", gap: "sm", className: className, children: [_jsx(Text, { children: user.name }), _jsx(Button, { variant: "outline", loading: signOut.isPending, onClick: () => signOut.mutate(), children:
|
|
19
|
+
return (_jsxs(Stack, { direction: "column", gap: "sm", className: className, children: [_jsx(Text, { children: user.name }), _jsx(Button, { variant: "outline", loading: signOut.isPending, onClick: () => signOut.mutate(), children: __('Log out', DOMAIN) }), signOut.isError ? (_jsx(Text, { role: "alert", children: __('Logout failed, please try again.', DOMAIN) })) : null] }));
|
|
18
20
|
}
|
package/dist/wpds/LoginScreen.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { useMutation } from '@tanstack/react-query';
|
|
4
|
+
import { __, _x } from '@wordpress/i18n';
|
|
4
5
|
import { Button, Card, InputControl, Stack, Text } from '@wordpress/ui';
|
|
5
6
|
import { useState } from 'react';
|
|
6
7
|
import { InvalidCredentialsError, RateLimitedError, login } from '../api.js';
|
|
8
|
+
import { DOMAIN } from '../domain.js';
|
|
7
9
|
/**
|
|
8
10
|
* Maps a login attempt error to the message shown to the user.
|
|
9
11
|
* @param error - The error thrown by the login attempt.
|
|
@@ -11,12 +13,12 @@ import { InvalidCredentialsError, RateLimitedError, login } from '../api.js';
|
|
|
11
13
|
*/
|
|
12
14
|
function loginErrorMessage(error) {
|
|
13
15
|
if (error instanceof InvalidCredentialsError) {
|
|
14
|
-
return 'Invalid email or password.';
|
|
16
|
+
return __('Invalid email or password.', DOMAIN);
|
|
15
17
|
}
|
|
16
18
|
if (error instanceof RateLimitedError) {
|
|
17
|
-
return 'Too many attempts. Please wait a minute and try again.';
|
|
19
|
+
return __('Too many attempts. Please wait a minute and try again.', DOMAIN);
|
|
18
20
|
}
|
|
19
|
-
return 'Login failed, please try again.';
|
|
21
|
+
return __('Login failed, please try again.', DOMAIN);
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* Renders the login form and reports the authenticated user upward.
|
|
@@ -34,5 +36,5 @@ export function LoginScreen({ brand, onLogin, }) {
|
|
|
34
36
|
return (_jsx("div", { className: "gopherium-login", children: _jsx(Card.Root, { className: "gopherium-login__card", children: _jsx(Card.Content, { children: _jsx("form", { onSubmit: (event) => {
|
|
35
37
|
event.preventDefault();
|
|
36
38
|
attempt.mutate();
|
|
37
|
-
}, children: _jsxs(Stack, { direction: "column", gap: "lg", children: [_jsx(Text, { variant: "heading-lg", render: _jsx("h1", {}), children: brand }), _jsx(InputControl, { label:
|
|
39
|
+
}, children: _jsxs(Stack, { direction: "column", gap: "lg", children: [_jsx(Text, { variant: "heading-lg", render: _jsx("h1", {}), children: brand }), _jsx(InputControl, { label: _x('Email', 'field label', DOMAIN), type: "email", value: email, onChange: (event) => setEmail(event.target.value) }), _jsx(InputControl, { label: __('Password', DOMAIN), type: "password", value: password, onChange: (event) => setPassword(event.target.value) }), _jsx(Button, { type: "submit", disabled: email.trim() === '' || password === '' || attempt.isPending, loading: attempt.isPending, children: __('Log in', DOMAIN) }), attempt.isError ? (_jsx(Text, { role: "alert", children: loginErrorMessage(attempt.error) })) : null] }) }) }) }) }));
|
|
38
40
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { __, _x } from '@wordpress/i18n';
|
|
4
5
|
import { Button, InputControl, Stack, Text } from '@wordpress/ui';
|
|
5
6
|
import { useState } from 'react';
|
|
6
7
|
import { EmailTakenError, ValidationError, createUser, usersQueryKey } from '../admin/index.js';
|
|
8
|
+
import { DOMAIN } from '../domain.js';
|
|
7
9
|
/**
|
|
8
10
|
* Maps a creation failure to the message shown under the form, surfacing
|
|
9
11
|
* the backend's explanation for rejected input.
|
|
@@ -12,12 +14,12 @@ import { EmailTakenError, ValidationError, createUser, usersQueryKey } from '../
|
|
|
12
14
|
*/
|
|
13
15
|
function createErrorMessage(error) {
|
|
14
16
|
if (error instanceof EmailTakenError) {
|
|
15
|
-
return 'That email is already in use.';
|
|
17
|
+
return __('That email is already in use.', DOMAIN);
|
|
16
18
|
}
|
|
17
19
|
if (error instanceof ValidationError) {
|
|
18
20
|
return error.message;
|
|
19
21
|
}
|
|
20
|
-
return 'The user could not be created.';
|
|
22
|
+
return __('The user could not be created.', DOMAIN);
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Renders the new user form, creating an account and reporting success
|
|
@@ -37,11 +39,11 @@ export function NewUserScreen({ onCreated, }) {
|
|
|
37
39
|
await onCreated?.();
|
|
38
40
|
},
|
|
39
41
|
});
|
|
40
|
-
return (_jsxs(Stack, { direction: "column", gap: "lg", children: [_jsx(Text, { variant: "heading-lg", render: _jsx("h1", {}), children:
|
|
42
|
+
return (_jsxs(Stack, { direction: "column", gap: "lg", children: [_jsx(Text, { variant: "heading-lg", render: _jsx("h1", {}), children: _x('New user', 'page heading', DOMAIN) }), _jsx("form", { onSubmit: (event) => {
|
|
41
43
|
event.preventDefault();
|
|
42
44
|
create.mutate();
|
|
43
|
-
}, children: _jsxs(Stack, { direction: "column", gap: "md", children: [_jsx(InputControl, { label:
|
|
45
|
+
}, children: _jsxs(Stack, { direction: "column", gap: "md", children: [_jsx(InputControl, { label: _x('Email', 'field label', DOMAIN), type: "email", autoComplete: "off", value: email, onChange: (event) => setEmail(event.target.value) }), _jsx(InputControl, { label: _x('Name', 'field label', DOMAIN), autoComplete: "off", value: name, onChange: (event) => setName(event.target.value) }), _jsx(InputControl, { label: __('Password', DOMAIN), type: "password", autoComplete: "new-password", value: password, onChange: (event) => setPassword(event.target.value) }), _jsx(Button, { type: "submit", disabled: email.trim() === '' ||
|
|
44
46
|
name.trim() === '' ||
|
|
45
47
|
password === '' ||
|
|
46
|
-
create.isPending, children:
|
|
48
|
+
create.isPending, children: __('Create user', DOMAIN) }), create.isError ? (_jsx(Text, { role: "alert", children: createErrorMessage(create.error) })) : null] }) })] }));
|
|
47
49
|
}
|
package/dist/wpds/UsersScreen.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { __, _x, sprintf } from '@wordpress/i18n';
|
|
4
5
|
import { Badge, Button, Stack, Text, VisuallyHidden } from '@wordpress/ui';
|
|
5
6
|
import { fetchUsers, setUserDisabled, usersQueryKey } from '../admin/index.js';
|
|
7
|
+
import { DOMAIN } from '../domain.js';
|
|
6
8
|
import { useSession } from '../session.js';
|
|
7
9
|
/**
|
|
8
10
|
* Renders one user row with status and a disable toggle, hidden for the
|
|
@@ -17,7 +19,9 @@ function UserRow({ user, isSelf }) {
|
|
|
17
19
|
mutationFn: () => setUserDisabled(user.id, !user.disabled),
|
|
18
20
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: usersQueryKey }),
|
|
19
21
|
});
|
|
20
|
-
return (_jsxs("tr", { children: [_jsx("td", { children: user.name }), _jsx("td", { children: user.email }), _jsx("td", { children: _jsx(Badge, { intent: user.disabled ? 'draft' : 'stable', children: user.disabled ? 'Disabled' : 'Active' }) }), _jsx("td", { children: isSelf ? null : (_jsxs(Stack, { direction: "column", gap: "xs", children: [_jsx(Button, { variant: "outline", "aria-label":
|
|
22
|
+
return (_jsxs("tr", { children: [_jsx("td", { children: user.name }), _jsx("td", { children: user.email }), _jsx("td", { children: _jsx(Badge, { intent: user.disabled ? 'draft' : 'stable', children: user.disabled ? __('Disabled', DOMAIN) : __('Active', DOMAIN) }) }), _jsx("td", { children: isSelf ? null : (_jsxs(Stack, { direction: "column", gap: "xs", children: [_jsx(Button, { variant: "outline", "aria-label": user.disabled
|
|
23
|
+
? sprintf(__('Enable %s', DOMAIN), user.name)
|
|
24
|
+
: sprintf(__('Disable %s', DOMAIN), user.name), disabled: toggle.isPending, onClick: () => toggle.mutate(), children: user.disabled ? __('Enable', DOMAIN) : __('Disable', DOMAIN) }), toggle.isError ? _jsx(Text, { role: "alert", children: __('Update failed.', DOMAIN) }) : null] })) })] }));
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
27
|
* Renders the user administration screen: the account list with status and
|
|
@@ -33,10 +37,10 @@ export function UsersScreen({ newUserRender, }) {
|
|
|
33
37
|
queryFn: ({ signal }) => fetchUsers(signal),
|
|
34
38
|
});
|
|
35
39
|
if (users.isPending) {
|
|
36
|
-
return _jsx(Text, { role: "status", children:
|
|
40
|
+
return _jsx(Text, { role: "status", children: __('Loading users…', DOMAIN) });
|
|
37
41
|
}
|
|
38
42
|
if (users.isError) {
|
|
39
|
-
return _jsx(Text, { role: "alert", children:
|
|
43
|
+
return _jsx(Text, { role: "alert", children: __('Users could not be loaded.', DOMAIN) });
|
|
40
44
|
}
|
|
41
|
-
return (_jsxs(Stack, { direction: "column", gap: "lg", children: [_jsxs(Stack, { direction: "row", align: "center", gap: "md", children: [_jsx(Text, { variant: "heading-lg", render: _jsx("h1", {}), children:
|
|
45
|
+
return (_jsxs(Stack, { direction: "column", gap: "lg", children: [_jsxs(Stack, { direction: "row", align: "center", gap: "md", children: [_jsx(Text, { variant: "heading-lg", render: _jsx("h1", {}), children: _x('Users', 'page heading', DOMAIN) }), newUserRender ? (_jsx(Button, { render: newUserRender, children: _x('New user', 'button', DOMAIN) })) : null] }), _jsxs("table", { className: "gopherium-users", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: _x('Name', 'column', DOMAIN) }), _jsx("th", { scope: "col", children: _x('Email', 'column', DOMAIN) }), _jsx("th", { scope: "col", children: _x('Status', 'column', DOMAIN) }), _jsx("th", { scope: "col", children: _jsx(VisuallyHidden, { children: __('Actions', DOMAIN) }) })] }) }), _jsx("tbody", { children: users.data.map((user) => (_jsx(UserRow, { user: user, isSelf: user.id === currentUserId }, user.id))) })] })] }));
|
|
42
46
|
}
|
package/dist/wpds/nav.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { _x } from '@wordpress/i18n';
|
|
4
|
+
import { DOMAIN } from '../domain.js';
|
|
2
5
|
const usersIcon = (_jsx("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "currentColor", children: _jsx("path", { d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }) }));
|
|
3
6
|
/**
|
|
4
7
|
* usersNavItem is the ready-made navigation entry for the user
|
|
5
8
|
* administration screen.
|
|
6
9
|
*/
|
|
7
|
-
export const usersNavItem = {
|
|
10
|
+
export const usersNavItem = {
|
|
11
|
+
get label() {
|
|
12
|
+
return _x('Users', 'admin section', DOMAIN);
|
|
13
|
+
},
|
|
14
|
+
to: '/users',
|
|
15
|
+
icon: usersIcon,
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gopherium/react-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Session authentication for React apps backed by gouncer's authkit.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -26,14 +26,17 @@
|
|
|
26
26
|
"default": "./dist/wpds/index.js"
|
|
27
27
|
},
|
|
28
28
|
"./wpds/style.css": "./dist/wpds/style.css",
|
|
29
|
+
"./languages/*": "./dist/languages/*",
|
|
29
30
|
"./testing": {
|
|
30
31
|
"types": "./dist/testing/index.d.ts",
|
|
31
32
|
"default": "./dist/testing/index.js"
|
|
32
33
|
}
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
|
-
"build": "tsc -p tsconfig.build.json && cp src/wpds/style.css dist/wpds/style.css",
|
|
36
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp src/wpds/style.css dist/wpds/style.css && cp -R src/languages dist/languages",
|
|
37
|
+
"catalogs": "node scripts/write-catalogs.ts",
|
|
36
38
|
"cover": "vitest run --coverage",
|
|
39
|
+
"pot": "node scripts/write-pot.ts",
|
|
37
40
|
"lint": "oxlint",
|
|
38
41
|
"prepublishOnly": "pnpm run build"
|
|
39
42
|
},
|
|
@@ -47,6 +50,7 @@
|
|
|
47
50
|
"@tanstack/react-query": "^5.101.2",
|
|
48
51
|
"@testing-library/jest-dom": "^6.9.1",
|
|
49
52
|
"@testing-library/react": "^16.3.2",
|
|
53
|
+
"@wordpress/i18n": ">=6.26.0 <7.0.0",
|
|
50
54
|
"@wordpress/theme": ">=1.0.0 <2.0.0",
|
|
51
55
|
"@wordpress/ui": ">=0.19.0 <0.20.0",
|
|
52
56
|
"msw": "^2.15.0",
|
|
@@ -78,15 +82,19 @@
|
|
|
78
82
|
"@testing-library/jest-dom": "^6.9.1",
|
|
79
83
|
"@testing-library/react": "^16.3.2",
|
|
80
84
|
"@testing-library/user-event": "^14.6.1",
|
|
85
|
+
"@types/gettext-parser": "9.0.0",
|
|
81
86
|
"@types/react": "^19.2.17",
|
|
82
87
|
"@vitejs/plugin-react": "^6.0.3",
|
|
83
88
|
"@vitest/coverage-v8": "^4.1.10",
|
|
89
|
+
"@wordpress/i18n": "6.26.0",
|
|
84
90
|
"@wordpress/theme": "1.1.0",
|
|
85
91
|
"@wordpress/ui": "0.19.0",
|
|
86
92
|
"eslint": "^9.39.1",
|
|
87
93
|
"eslint-plugin-jsdoc": "^63.0.13",
|
|
88
94
|
"eslint-plugin-sonarjs": "^4.2.0",
|
|
89
95
|
"eslint-plugin-tsdoc": "^0.4.0",
|
|
96
|
+
"gettext-extractor": "4.0.6",
|
|
97
|
+
"gettext-parser": "9.1.1",
|
|
90
98
|
"jsdom": "^29.1.1",
|
|
91
99
|
"msw": "^2.15.0",
|
|
92
100
|
"oxlint": "^1.71.0",
|