@dr.pogodin/react-utils 1.41.17 → 1.42.1
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/babel.config.js +3 -0
- package/bin/build.js +5 -8
- package/bin/setup.js +2 -1
- package/build/development/client/getInj.js +7 -2
- package/build/development/client/getInj.js.map +1 -1
- package/build/development/client/index.js +1 -2
- package/build/development/client/index.js.map +1 -1
- package/build/development/client/init.js +16 -13
- package/build/development/client/init.js.map +1 -1
- package/build/development/index.js +4 -1
- package/build/development/index.js.map +1 -1
- package/build/development/server/Cache.js +5 -9
- package/build/development/server/Cache.js.map +1 -1
- package/build/development/server/index.js +14 -11
- package/build/development/server/index.js.map +1 -1
- package/build/development/server/renderer.js +36 -41
- package/build/development/server/renderer.js.map +1 -1
- package/build/development/server/server.js +22 -13
- package/build/development/server/server.js.map +1 -1
- package/build/development/server/utils/errors.js.map +1 -1
- package/build/development/shared/components/Button/index.js +2 -3
- package/build/development/shared/components/Button/index.js.map +1 -1
- package/build/development/shared/components/Checkbox/index.js +3 -1
- package/build/development/shared/components/Checkbox/index.js.map +1 -1
- package/build/development/shared/components/GenericLink/index.js +13 -6
- package/build/development/shared/components/GenericLink/index.js.map +1 -1
- package/build/development/shared/components/Input/index.js +5 -1
- package/build/development/shared/components/Input/index.js.map +1 -1
- package/build/development/shared/components/Link.js +5 -6
- package/build/development/shared/components/Link.js.map +1 -1
- package/build/development/shared/components/MetaTags.js +31 -24
- package/build/development/shared/components/MetaTags.js.map +1 -1
- package/build/development/shared/components/Modal/index.js +13 -6
- package/build/development/shared/components/Modal/index.js.map +1 -1
- package/build/development/shared/components/NavLink.js +6 -6
- package/build/development/shared/components/NavLink.js.map +1 -1
- package/build/development/shared/components/TextArea/index.js +6 -3
- package/build/development/shared/components/TextArea/index.js.map +1 -1
- package/build/development/shared/components/WithTooltip/Tooltip.js +35 -39
- package/build/development/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/development/shared/components/WithTooltip/index.js +27 -21
- package/build/development/shared/components/WithTooltip/index.js.map +1 -1
- package/build/development/shared/components/YouTubeVideo/index.js +4 -3
- package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/Options/index.js +4 -5
- package/build/development/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/index.js +7 -9
- package/build/development/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/NativeDropdown/index.js +3 -5
- package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/Switch/index.js +21 -20
- package/build/development/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/development/shared/utils/config.js +6 -3
- package/build/development/shared/utils/config.js.map +1 -1
- package/build/development/shared/utils/globalState.js +6 -0
- package/build/development/shared/utils/globalState.js.map +1 -1
- package/build/development/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/development/shared/utils/isomorphy/environment-check.js +6 -1
- package/build/development/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/development/shared/utils/isomorphy/index.js +1 -3
- package/build/development/shared/utils/isomorphy/index.js.map +1 -1
- package/build/development/shared/utils/jest/E2eSsrEnv.js +26 -17
- package/build/development/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/development/shared/utils/jest/global.js +0 -7
- package/build/development/shared/utils/jest/global.js.map +1 -1
- package/build/development/shared/utils/jest/index.js +15 -4
- package/build/development/shared/utils/jest/index.js.map +1 -1
- package/build/development/shared/utils/splitComponent.js +37 -19
- package/build/development/shared/utils/splitComponent.js.map +1 -1
- package/build/development/shared/utils/time.js +3 -3
- package/build/development/shared/utils/time.js.map +1 -1
- package/build/development/shared/utils/webpack.js +11 -11
- package/build/development/shared/utils/webpack.js.map +1 -1
- package/build/development/web.bundle.js +31 -31
- package/build/production/client/getInj.js +4 -2
- package/build/production/client/getInj.js.map +1 -1
- package/build/production/client/index.js +2 -2
- package/build/production/client/index.js.map +1 -1
- package/build/production/client/init.js +4 -2
- package/build/production/client/init.js.map +1 -1
- package/build/production/index.js +2 -1
- package/build/production/index.js.map +1 -1
- package/build/production/server/Cache.js +1 -2
- package/build/production/server/Cache.js.map +1 -1
- package/build/production/server/index.js +9 -5
- package/build/production/server/index.js.map +1 -1
- package/build/production/server/renderer.js +24 -21
- package/build/production/server/renderer.js.map +1 -1
- package/build/production/server/server.js +14 -9
- package/build/production/server/server.js.map +1 -1
- package/build/production/server/utils/errors.js.map +1 -1
- package/build/production/shared/components/Button/index.js +1 -1
- package/build/production/shared/components/Button/index.js.map +1 -1
- package/build/production/shared/components/Checkbox/index.js +1 -1
- package/build/production/shared/components/Checkbox/index.js.map +1 -1
- package/build/production/shared/components/GenericLink/index.js +6 -4
- package/build/production/shared/components/GenericLink/index.js.map +1 -1
- package/build/production/shared/components/Input/index.js +3 -1
- package/build/production/shared/components/Input/index.js.map +1 -1
- package/build/production/shared/components/Link.js +3 -1
- package/build/production/shared/components/Link.js.map +1 -1
- package/build/production/shared/components/MetaTags.js +5 -2
- package/build/production/shared/components/MetaTags.js.map +1 -1
- package/build/production/shared/components/Modal/index.js +6 -2
- package/build/production/shared/components/Modal/index.js.map +1 -1
- package/build/production/shared/components/NavLink.js +4 -1
- package/build/production/shared/components/NavLink.js.map +1 -1
- package/build/production/shared/components/TextArea/index.js +4 -4
- package/build/production/shared/components/TextArea/index.js.map +1 -1
- package/build/production/shared/components/WithTooltip/Tooltip.js +37 -38
- package/build/production/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/production/shared/components/WithTooltip/index.js +4 -4
- package/build/production/shared/components/WithTooltip/index.js.map +1 -1
- package/build/production/shared/components/YouTubeVideo/index.js +3 -2
- package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/Options/index.js +2 -2
- package/build/production/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/index.js +2 -2
- package/build/production/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/NativeDropdown/index.js +2 -2
- package/build/production/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/Switch/index.js +1 -1
- package/build/production/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/production/shared/utils/config.js +6 -4
- package/build/production/shared/utils/config.js.map +1 -1
- package/build/production/shared/utils/globalState.js +4 -1
- package/build/production/shared/utils/globalState.js.map +1 -1
- package/build/production/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/production/shared/utils/isomorphy/environment-check.js +5 -1
- package/build/production/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/production/shared/utils/isomorphy/index.js +1 -3
- package/build/production/shared/utils/isomorphy/index.js.map +1 -1
- package/build/production/shared/utils/jest/E2eSsrEnv.js +15 -8
- package/build/production/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/production/shared/utils/jest/global.js +1 -1
- package/build/production/shared/utils/jest/global.js.map +1 -1
- package/build/production/shared/utils/jest/index.js +13 -5
- package/build/production/shared/utils/jest/index.js.map +1 -1
- package/build/production/shared/utils/splitComponent.js +16 -8
- package/build/production/shared/utils/splitComponent.js.map +1 -1
- package/build/production/shared/utils/time.js +2 -2
- package/build/production/shared/utils/time.js.map +1 -1
- package/build/production/shared/utils/webpack.js +5 -2
- package/build/production/shared/utils/webpack.js.map +1 -1
- package/build/production/web.bundle.js +1 -1
- package/build/production/web.bundle.js.map +1 -1
- package/build/types-code/client/getInj.d.ts +1 -1
- package/build/types-code/client/index.d.ts +2 -2
- package/build/types-code/client/init.d.ts +1 -1
- package/build/types-code/index.d.ts +6 -5
- package/build/types-code/server/Cache.d.ts +1 -2
- package/build/types-code/server/index.d.ts +4 -5
- package/build/types-code/server/renderer.d.ts +9 -11
- package/build/types-code/server/server.d.ts +3 -5
- package/build/types-code/server/utils/errors.d.ts +1 -1
- package/build/types-code/shared/components/Button/index.d.ts +2 -2
- package/build/types-code/shared/components/TextArea/index.d.ts +3 -3
- package/build/types-code/shared/components/WithTooltip/Tooltip.d.ts +7 -1
- package/build/types-code/shared/components/index.d.ts +12 -12
- package/build/types-code/shared/utils/config.d.ts +1 -1
- package/build/types-code/shared/utils/globalState.d.ts +3 -6
- package/build/types-code/shared/utils/isomorphy/index.d.ts +1 -3
- package/build/types-code/shared/utils/jest/E2eSsrEnv.d.ts +1 -1
- package/build/types-code/shared/utils/jest/global.d.ts +4 -4
- package/build/types-code/shared/utils/jest/index.d.ts +2 -2
- package/build/types-code/shared/utils/webpack.d.ts +1 -1
- package/config/babel/node-ssr.d.ts +3 -2
- package/config/babel/node-ssr.js +5 -7
- package/config/babel/webpack.d.ts +3 -11
- package/config/babel/webpack.js +15 -15
- package/config/eslint/default.mjs +32 -0
- package/config/jest/default.js +10 -6
- package/config/jest/resolver.js +2 -0
- package/config/jest/setup.js +2 -2
- package/config/stylelint/default.js +3 -0
- package/config/webpack/app-base.d.ts +0 -6
- package/config/webpack/app-base.js +64 -70
- package/config/webpack/app-development.d.ts +2 -2
- package/config/webpack/app-development.js +8 -12
- package/config/webpack/app-production.js +1 -0
- package/config/webpack/lib-base.js +20 -18
- package/config/webpack/lib-development.js +1 -0
- package/config/webpack/lib-production.js +1 -0
- package/config/workbox/default.js +2 -5
- package/dev-styles.js +1 -0
- package/eslint.config.mjs +13 -0
- package/node-entry.js +7 -2
- package/null.js +1 -0
- package/package.json +27 -35
- package/prod-styles.js +1 -0
- package/src/client/getInj.ts +8 -3
- package/src/client/index.tsx +4 -4
- package/src/client/init.ts +18 -15
- package/src/index.ts +8 -3
- package/src/server/Cache.ts +7 -15
- package/src/server/index.ts +30 -21
- package/src/server/renderer.tsx +77 -67
- package/src/server/server.ts +41 -21
- package/src/server/utils/errors.ts +6 -3
- package/src/shared/components/Button/index.tsx +4 -5
- package/src/shared/components/Checkbox/index.tsx +10 -7
- package/src/shared/components/GenericLink/index.tsx +14 -7
- package/src/shared/components/Input/index.tsx +4 -1
- package/src/shared/components/Link.tsx +9 -5
- package/src/shared/components/MetaTags.tsx +21 -15
- package/src/shared/components/Modal/index.tsx +12 -11
- package/src/shared/components/NavLink.tsx +10 -5
- package/src/shared/components/TextArea/index.tsx +17 -9
- package/src/shared/components/WithTooltip/{Tooltip.tsx → Tooltip.ts} +35 -39
- package/src/shared/components/WithTooltip/index.tsx +36 -30
- package/src/shared/components/YouTubeVideo/index.tsx +10 -6
- package/src/shared/components/selectors/CustomDropdown/Options/index.tsx +5 -6
- package/src/shared/components/selectors/CustomDropdown/index.tsx +10 -14
- package/src/shared/components/selectors/NativeDropdown/index.tsx +5 -7
- package/src/shared/components/selectors/Switch/index.tsx +19 -17
- package/src/shared/utils/config.ts +12 -5
- package/src/shared/utils/globalState.ts +8 -5
- package/src/shared/utils/isomorphy/buildInfo.ts +1 -1
- package/src/shared/utils/isomorphy/environment-check.ts +5 -1
- package/src/shared/utils/isomorphy/index.ts +4 -6
- package/src/shared/utils/jest/E2eSsrEnv.ts +64 -39
- package/src/shared/utils/jest/global.ts +6 -8
- package/src/shared/utils/jest/{index.tsx → index.ts} +25 -12
- package/src/shared/utils/splitComponent.tsx +44 -25
- package/src/shared/utils/time.ts +16 -9
- package/src/shared/utils/webpack.ts +19 -14
- package/webpack.config.ts +36 -10
- package/config/eslint/default.json +0 -30
- package/config/eslint/jest.json +0 -19
- package/config/eslint/typescript.js +0 -46
package/src/server/server.ts
CHANGED
|
@@ -18,6 +18,7 @@ import csrf from '@dr.pogodin/csurf';
|
|
|
18
18
|
import express, {
|
|
19
19
|
type Express,
|
|
20
20
|
type NextFunction,
|
|
21
|
+
type RequestHandler,
|
|
21
22
|
type Request,
|
|
22
23
|
type Response,
|
|
23
24
|
} from 'express';
|
|
@@ -27,7 +28,8 @@ import helmet, { type HelmetOptions } from 'helmet';
|
|
|
27
28
|
import loggerMiddleware from 'morgan';
|
|
28
29
|
import requestIp from 'request-ip';
|
|
29
30
|
import { v4 as uuid } from 'uuid';
|
|
30
|
-
|
|
31
|
+
|
|
32
|
+
import type { Compiler, Configuration } from 'webpack';
|
|
31
33
|
|
|
32
34
|
import rendererFactory, {
|
|
33
35
|
type LoggerI,
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
export type CspOptionsT =
|
|
45
47
|
Exclude<HelmetOptions['contentSecurityPolicy'], boolean | undefined>;
|
|
46
48
|
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
47
50
|
interface RequestT extends Request {
|
|
48
51
|
cspNonce: string;
|
|
49
52
|
nonce: string;
|
|
@@ -98,7 +101,9 @@ delete defaultCspSettings.directives['upgrade-insecure-requests'];
|
|
|
98
101
|
* with the exception of `nonce-xxx` clause in `script-src` directive,
|
|
99
102
|
* which is added dynamically for each request.
|
|
100
103
|
*/
|
|
101
|
-
export function getDefaultCspSettings() {
|
|
104
|
+
export function getDefaultCspSettings(): {
|
|
105
|
+
directives: Record<string, string[]>;
|
|
106
|
+
} {
|
|
102
107
|
return cloneDeep(defaultCspSettings);
|
|
103
108
|
}
|
|
104
109
|
|
|
@@ -107,7 +112,9 @@ export type ServerT = Express & {
|
|
|
107
112
|
};
|
|
108
113
|
|
|
109
114
|
export type OptionsT = RendererOptionsT & {
|
|
110
|
-
beforeExpressJsError?:
|
|
115
|
+
beforeExpressJsError?:
|
|
116
|
+
(server: ServerT) => boolean | Promise<boolean>;
|
|
117
|
+
|
|
111
118
|
beforeExpressJsSetup?: (server: ServerT) => Promise<void> | void;
|
|
112
119
|
cspSettingsHook?: (
|
|
113
120
|
defaultOptions: CspOptionsT,
|
|
@@ -121,7 +128,7 @@ export type OptionsT = RendererOptionsT & {
|
|
|
121
128
|
export default async function factory(
|
|
122
129
|
webpackConfig: Configuration,
|
|
123
130
|
options: OptionsT,
|
|
124
|
-
) {
|
|
131
|
+
): Promise<ServerT> {
|
|
125
132
|
const rendererOps: RendererOptionsT = pick(options, [
|
|
126
133
|
'Application',
|
|
127
134
|
'beforeRender',
|
|
@@ -150,9 +157,10 @@ export default async function factory(
|
|
|
150
157
|
if (schema === 'http') {
|
|
151
158
|
let url = `https://${req.headers.host}`;
|
|
152
159
|
if (req.originalUrl !== '/') url += req.originalUrl;
|
|
153
|
-
|
|
160
|
+
res.redirect(url);
|
|
161
|
+
return;
|
|
154
162
|
}
|
|
155
|
-
|
|
163
|
+
next();
|
|
156
164
|
});
|
|
157
165
|
}
|
|
158
166
|
|
|
@@ -193,7 +201,9 @@ export default async function factory(
|
|
|
193
201
|
server.use(favicon(options.favicon));
|
|
194
202
|
}
|
|
195
203
|
|
|
196
|
-
server.use('/robots.txt', (req, res) =>
|
|
204
|
+
server.use('/robots.txt', (req, res) => {
|
|
205
|
+
res.send('User-agent: *\nDisallow:');
|
|
206
|
+
});
|
|
197
207
|
|
|
198
208
|
server.use(express.json({ limit: '300kb' }));
|
|
199
209
|
server.use(express.urlencoded({ extended: false }));
|
|
@@ -220,7 +230,7 @@ export default async function factory(
|
|
|
220
230
|
// Thus, this setup to serve it. Probably, need some more configuration
|
|
221
231
|
// for special cases, but this will do for now.
|
|
222
232
|
server.get('/__service-worker.js', express.static(
|
|
223
|
-
webpackConfig.output?.path
|
|
233
|
+
webpackConfig.output?.path ?? '',
|
|
224
234
|
{
|
|
225
235
|
setHeaders: (res) => res.set('Cache-Control', 'no-cache'),
|
|
226
236
|
},
|
|
@@ -229,23 +239,31 @@ export default async function factory(
|
|
|
229
239
|
/* Setup of Hot Module Reloading for development environment.
|
|
230
240
|
* These dependencies are not used, nor installed for production use,
|
|
231
241
|
* hence we should violate some import-related lint rules. */
|
|
232
|
-
/* eslint-disable global-require */
|
|
233
242
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
234
|
-
/* eslint-disable import/no-unresolved */
|
|
235
243
|
if (options.devMode) {
|
|
236
244
|
// This is a workaround for SASS bug:
|
|
237
245
|
// https://github.com/dart-lang/sdk/issues/27979
|
|
238
246
|
// which manifests itself sometimes when webpack dev middleware is used
|
|
239
247
|
// (in dev mode), and app modules are imported in some unfortunate ways.
|
|
248
|
+
// TODO: Double-check, what is going on here.
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
240
250
|
if (!global.location) {
|
|
241
251
|
global.location = {
|
|
242
252
|
href: `${pathToFileURL(process.cwd()).href}${sep}`,
|
|
243
253
|
} as Location;
|
|
244
254
|
}
|
|
245
255
|
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
256
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
257
|
+
const webpack = require('webpack') as (ops: Configuration) => Compiler;
|
|
258
|
+
|
|
259
|
+
// TODO: Figure out the exact type for options, don't wanna waste time on it
|
|
260
|
+
// right now.
|
|
261
|
+
const webpackDevMiddleware = require('webpack-dev-middleware') as
|
|
262
|
+
(c: Compiler, ops: unknown) => RequestHandler;
|
|
263
|
+
|
|
264
|
+
const webpackHotMiddleware = require('webpack-hot-middleware') as
|
|
265
|
+
(c: Compiler) => RequestHandler;
|
|
266
|
+
|
|
249
267
|
const compiler = webpack(webpackConfig);
|
|
250
268
|
server.use(webpackDevMiddleware(compiler, {
|
|
251
269
|
publicPath,
|
|
@@ -253,9 +271,7 @@ export default async function factory(
|
|
|
253
271
|
}));
|
|
254
272
|
server.use(webpackHotMiddleware(compiler));
|
|
255
273
|
}
|
|
256
|
-
/* eslint-enable global-require */
|
|
257
274
|
/* eslint-enable import/no-extraneous-dependencies */
|
|
258
|
-
/* eslint-enable import/no-unresolved */
|
|
259
275
|
|
|
260
276
|
server.use(publicPath as string, express.static(webpackConfig.output!.path!));
|
|
261
277
|
|
|
@@ -285,20 +301,25 @@ export default async function factory(
|
|
|
285
301
|
// prevents to do it without some extra refactoring. Should be done sometime
|
|
286
302
|
// though.
|
|
287
303
|
server.use((
|
|
288
|
-
error:
|
|
304
|
+
error: Error & {
|
|
305
|
+
status?: number;
|
|
306
|
+
},
|
|
289
307
|
req: Request,
|
|
290
308
|
res: Response,
|
|
291
309
|
next: NextFunction,
|
|
292
310
|
) => {
|
|
293
311
|
// TODO: This is needed to correctly handled any errors thrown after
|
|
294
312
|
// sending initial response to the client.
|
|
295
|
-
if (res.headersSent)
|
|
313
|
+
if (res.headersSent) {
|
|
314
|
+
next(error);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
296
317
|
|
|
297
|
-
const status = error.status
|
|
298
|
-
const serverSide = status >= CODES.INTERNAL_SERVER_ERROR;
|
|
318
|
+
const status = error.status ?? CODES.INTERNAL_SERVER_ERROR;
|
|
319
|
+
const serverSide = status >= (CODES.INTERNAL_SERVER_ERROR as number);
|
|
299
320
|
|
|
300
321
|
// Log server-side errors always, client-side at debug level only.
|
|
301
|
-
options.logger!.log(serverSide ? 'error' : 'debug', error);
|
|
322
|
+
options.logger!.log(serverSide ? 'error' : 'debug', error.toString());
|
|
302
323
|
|
|
303
324
|
let message = error.message || getErrorForCode(status);
|
|
304
325
|
if (serverSide && process.env.NODE_ENV === 'production') {
|
|
@@ -306,7 +327,6 @@ export default async function factory(
|
|
|
306
327
|
}
|
|
307
328
|
|
|
308
329
|
res.status(status).send(message);
|
|
309
|
-
return undefined;
|
|
310
330
|
});
|
|
311
331
|
}
|
|
312
332
|
|
|
@@ -89,7 +89,10 @@ class ErrorWithStatus extends Error {
|
|
|
89
89
|
* Server Error).
|
|
90
90
|
* @return {Error}
|
|
91
91
|
*/
|
|
92
|
-
export function newError(
|
|
92
|
+
export function newError(
|
|
93
|
+
message: string,
|
|
94
|
+
statusCode = CODES.INTERNAL_SERVER_ERROR,
|
|
95
|
+
): ErrorWithStatus {
|
|
93
96
|
const error = new ErrorWithStatus(message);
|
|
94
97
|
error.status = statusCode;
|
|
95
98
|
return error;
|
|
@@ -119,11 +122,11 @@ export function fail(
|
|
|
119
122
|
* Request).
|
|
120
123
|
*/
|
|
121
124
|
export function assert(
|
|
122
|
-
value:
|
|
125
|
+
value: unknown,
|
|
123
126
|
schema: joi.AnySchema,
|
|
124
127
|
message = '',
|
|
125
128
|
statusCode = CODES.BAD_REQUEST,
|
|
126
|
-
) {
|
|
129
|
+
): void {
|
|
127
130
|
const { error } = schema.validate(value, { abortEarly: false });
|
|
128
131
|
if (error) {
|
|
129
132
|
fail(message.concat(message ? '\n' : '', error.message), statusCode);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The <Button> component implements a standard button / button-like link.
|
|
2
2
|
|
|
3
|
-
import type { PointerEventHandler, ReactNode } from 'react';
|
|
3
|
+
import type { FunctionComponent, PointerEventHandler, ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
import Link from 'components/Link';
|
|
6
6
|
|
|
@@ -24,8 +24,7 @@ type PropsT = {
|
|
|
24
24
|
to?: object | string;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
export const BaseButton: React.FunctionComponent<PropsT> = ({
|
|
27
|
+
export const BaseButton: FunctionComponent<PropsT> = ({
|
|
29
28
|
active,
|
|
30
29
|
children,
|
|
31
30
|
disabled,
|
|
@@ -75,9 +74,9 @@ export const BaseButton: React.FunctionComponent<PropsT> = ({
|
|
|
75
74
|
className={className}
|
|
76
75
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
77
76
|
onClick={onClick}
|
|
78
|
-
onKeyDown={onClick
|
|
77
|
+
onKeyDown={onClick ? (e) => {
|
|
79
78
|
if (e.key === 'Enter') onClick(e);
|
|
80
|
-
}
|
|
79
|
+
} : undefined}
|
|
81
80
|
onMouseDown={onMouseDown}
|
|
82
81
|
onPointerDown={onPointerDown}
|
|
83
82
|
role="button"
|
|
@@ -9,11 +9,11 @@ type PropT<ValueT> = {
|
|
|
9
9
|
onChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
10
10
|
testId?: string;
|
|
11
11
|
theme: Theme<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
| 'checkbox'
|
|
13
|
+
| 'container'
|
|
14
|
+
| 'disabled'
|
|
15
|
+
| 'indeterminate'
|
|
16
|
+
| 'label'
|
|
17
17
|
>;
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -33,14 +33,17 @@ const Checkbox = <ValueT extends boolean | 'indeterminate' = boolean>({
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
<div className={containerClassName}>
|
|
36
|
-
{ label === undefined
|
|
36
|
+
{ label === undefined
|
|
37
|
+
? null : <div className={theme.label}>{label}</div> }
|
|
37
38
|
<input
|
|
38
39
|
checked={checked === undefined ? undefined : checked === true}
|
|
39
40
|
className={checkboxClassName}
|
|
40
41
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
41
42
|
disabled={disabled}
|
|
42
43
|
onChange={onChange}
|
|
43
|
-
onClick={(e) =>
|
|
44
|
+
onClick={(e) => {
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
}}
|
|
44
47
|
type="checkbox"
|
|
45
48
|
/>
|
|
46
49
|
</div>
|
|
@@ -87,7 +87,8 @@ const GenericLink = ({
|
|
|
87
87
|
* - It should be opened in a new tab;
|
|
88
88
|
* - It is an absolte URL (starts with http:// or https://);
|
|
89
89
|
* - It is anchor link (starts with #). */
|
|
90
|
-
if (disabled || enforceA || openNewTab
|
|
90
|
+
if (disabled || enforceA || openNewTab
|
|
91
|
+
|| (to as string).match(/^(#|(https?|mailto):)/)) {
|
|
91
92
|
return (
|
|
92
93
|
<a
|
|
93
94
|
className={className}
|
|
@@ -96,8 +97,12 @@ const GenericLink = ({
|
|
|
96
97
|
// styled as a link.
|
|
97
98
|
// disabled={disabled}
|
|
98
99
|
href={to as string}
|
|
99
|
-
onClick={disabled ? (e) =>
|
|
100
|
-
|
|
100
|
+
onClick={disabled ? (e) => {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
} : onClick}
|
|
103
|
+
onMouseDown={disabled ? (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
} : onMouseDown}
|
|
101
106
|
rel="noopener noreferrer"
|
|
102
107
|
styleName="link"
|
|
103
108
|
target={openNewTab ? '_blank' : ''}
|
|
@@ -114,9 +119,6 @@ const GenericLink = ({
|
|
|
114
119
|
className={className}
|
|
115
120
|
discover="none"
|
|
116
121
|
// disabled
|
|
117
|
-
onMouseDown={onMouseDown}
|
|
118
|
-
replace={replace}
|
|
119
|
-
to={to!}
|
|
120
122
|
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
121
123
|
// Executes the user-provided event handler, if any.
|
|
122
124
|
if (onClick) onClick(e);
|
|
@@ -124,7 +126,12 @@ const GenericLink = ({
|
|
|
124
126
|
// By default, clicking the link scrolls the page to beginning.
|
|
125
127
|
if (!keepScrollPosition) window.scroll(0, 0);
|
|
126
128
|
}}
|
|
127
|
-
{
|
|
129
|
+
onMouseDown={onMouseDown}
|
|
130
|
+
replace={replace}
|
|
131
|
+
to={to}
|
|
132
|
+
// TODO: Refactor it later.
|
|
133
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
134
|
+
{...rest}
|
|
128
135
|
>
|
|
129
136
|
{children}
|
|
130
137
|
</L>
|
|
@@ -37,7 +37,10 @@ const Input: FunctionComponent<PropsT> = ({
|
|
|
37
37
|
className={theme.input}
|
|
38
38
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
39
39
|
ref={ref}
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
// TODO: Avoid the spreading later.
|
|
42
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
43
|
+
{...rest}
|
|
41
44
|
/>
|
|
42
45
|
</span>
|
|
43
46
|
);
|
|
@@ -12,10 +12,14 @@ import GenericLink, { type PropsT as GenericLinkPropsT } from './GenericLink';
|
|
|
12
12
|
|
|
13
13
|
type PropsT = Omit<GenericLinkPropsT, 'routerLinkType'> & LinkProps;
|
|
14
14
|
|
|
15
|
-
const Link: React.FunctionComponent<PropsT>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const Link: React.FunctionComponent<PropsT>
|
|
16
|
+
= (props) => (
|
|
17
|
+
<GenericLink
|
|
18
|
+
// TODO: Avoid the spreading later.
|
|
19
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
20
|
+
{...props}
|
|
21
|
+
routerLinkType={RrLink}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
20
24
|
|
|
21
25
|
export default Link;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// TODO: Move this component to React Helmet library.
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
type Context as ContextT,
|
|
3
5
|
type FunctionComponent,
|
|
@@ -46,8 +48,12 @@ const MetaTags: FunctionComponent<PropsT> & {
|
|
|
46
48
|
title,
|
|
47
49
|
url,
|
|
48
50
|
}) => {
|
|
51
|
+
// NOTE: I guess, in this very case, we should prefer title and description
|
|
52
|
+
// also to empty social title and decscription?
|
|
53
|
+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
|
49
54
|
const socTitle = socialTitle || title;
|
|
50
55
|
const socDesc = socialDescription || description;
|
|
56
|
+
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
|
|
51
57
|
|
|
52
58
|
const context = useMemo(() => ({
|
|
53
59
|
description,
|
|
@@ -74,8 +80,8 @@ const MetaTags: FunctionComponent<PropsT> & {
|
|
|
74
80
|
extra.push(
|
|
75
81
|
<meta
|
|
76
82
|
content={content}
|
|
77
|
-
name={name}
|
|
78
83
|
key={`extra-meta-tag-${i}`}
|
|
84
|
+
name={name}
|
|
79
85
|
/>,
|
|
80
86
|
);
|
|
81
87
|
}
|
|
@@ -88,28 +94,28 @@ const MetaTags: FunctionComponent<PropsT> & {
|
|
|
88
94
|
<title>
|
|
89
95
|
{title}
|
|
90
96
|
</title>
|
|
91
|
-
<meta name="description"
|
|
97
|
+
<meta content={description} name="description" />
|
|
92
98
|
|
|
93
99
|
{/* Twitter cards. */}
|
|
94
|
-
<meta name="twitter:card"
|
|
95
|
-
<meta name="twitter:title"
|
|
96
|
-
<meta name="twitter:description"
|
|
97
|
-
{ image ? <meta name="twitter:image"
|
|
100
|
+
<meta content="summary_large_image" name="twitter:card" />
|
|
101
|
+
<meta content={socTitle} name="twitter:title" />
|
|
102
|
+
<meta content={socDesc} name="twitter:description" />
|
|
103
|
+
{ image ? <meta content={image} name="twitter:image" /> : null }
|
|
98
104
|
{
|
|
99
|
-
siteName
|
|
100
|
-
<meta
|
|
101
|
-
|
|
105
|
+
siteName
|
|
106
|
+
? <meta content={`@${siteName}`} name="twitter:site" />
|
|
107
|
+
: null
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
{/* Open Graph data. */}
|
|
105
|
-
<meta name="og:title"
|
|
106
|
-
{ image ? <meta name="og:image"
|
|
107
|
-
{ image ? <meta name="og:image:alt"
|
|
108
|
-
<meta name="og:description"
|
|
111
|
+
<meta content={socTitle} name="og:title" />
|
|
112
|
+
{ image ? <meta content={image} name="og:image" /> : null }
|
|
113
|
+
{ image ? <meta content={socTitle} name="og:image:alt" /> : null }
|
|
114
|
+
<meta content={socDesc} name="og:description" />
|
|
109
115
|
{
|
|
110
|
-
siteName ?
|
|
116
|
+
siteName ? <meta content={siteName} name="og:sitename" /> : null
|
|
111
117
|
}
|
|
112
|
-
{ url ?
|
|
118
|
+
{ url ? <meta content={url} name="og:url" /> : null }
|
|
113
119
|
{extra}
|
|
114
120
|
</Helmet>
|
|
115
121
|
{
|
|
@@ -94,13 +94,14 @@ const BaseModal: FunctionComponent<PropsT> = ({
|
|
|
94
94
|
const focusLast = useMemo(() => (
|
|
95
95
|
<div
|
|
96
96
|
onFocus={() => {
|
|
97
|
-
const elems = containerRef.current
|
|
97
|
+
const elems = containerRef.current!.querySelectorAll('*');
|
|
98
98
|
for (let i = elems.length - 1; i >= 0; --i) {
|
|
99
|
-
elems[i]
|
|
99
|
+
(elems[i] as HTMLElement).focus();
|
|
100
100
|
if (document.activeElement === elems[i]) return;
|
|
101
101
|
}
|
|
102
102
|
overlayRef.current?.focus();
|
|
103
103
|
}}
|
|
104
|
+
// TODO: Have a look at this later.
|
|
104
105
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
105
106
|
tabIndex={0}
|
|
106
107
|
/>
|
|
@@ -149,30 +150,30 @@ const BaseModal: FunctionComponent<PropsT> = ({
|
|
|
149
150
|
// (because visually and logically the modal dialog does not belong
|
|
150
151
|
// to its parent container, where it technically belongs from
|
|
151
152
|
// the HTML mark-up perpective).
|
|
152
|
-
/* eslint-disable jsx-a11y/click-events-have-key-events,
|
|
153
|
-
jsx-a11y/no-noninteractive-element-interactions */
|
|
154
153
|
}
|
|
155
|
-
<div
|
|
154
|
+
<div // eslint-disable-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
|
156
155
|
aria-modal="true"
|
|
157
156
|
className={theme.container}
|
|
158
157
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
159
|
-
onClick={(e) =>
|
|
160
|
-
|
|
158
|
+
onClick={(e) => {
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
}}
|
|
161
|
+
onWheel={(event) => {
|
|
162
|
+
event.stopPropagation();
|
|
163
|
+
}}
|
|
161
164
|
ref={containerRef}
|
|
162
165
|
role="dialog"
|
|
163
166
|
style={style ?? containerStyle}
|
|
164
167
|
>
|
|
165
168
|
{children}
|
|
166
169
|
</div>
|
|
167
|
-
{/* eslint-enable jsx-a11y/click-events-have-key-events,
|
|
168
|
-
jsx-a11y/no-noninteractive-element-interactions */}
|
|
169
170
|
<div
|
|
170
171
|
onFocus={() => {
|
|
171
172
|
overlayRef.current?.focus();
|
|
172
173
|
}}
|
|
173
|
-
|
|
174
|
+
// TODO: Have a look at this later.
|
|
175
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
174
176
|
tabIndex={0}
|
|
175
|
-
/* eslint-enable jsx-a11y/no-noninteractive-tabindex */
|
|
176
177
|
/>
|
|
177
178
|
{focusLast}
|
|
178
179
|
</>
|
|
@@ -4,10 +4,15 @@ import GenericLink, { type PropsT as GenericLinkPropsT } from './GenericLink';
|
|
|
4
4
|
|
|
5
5
|
type PropsT = Omit<GenericLinkPropsT, 'routerLinkType'> & NavLinkProps;
|
|
6
6
|
|
|
7
|
-
const NavLink: React.FunctionComponent<PropsT>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const NavLink: React.FunctionComponent<PropsT>
|
|
8
|
+
= (props) => (
|
|
9
|
+
<GenericLink
|
|
10
|
+
// TODO: I guess, we better re-write it to avoid the props spreading,
|
|
11
|
+
// but no need to spend time on it right now.
|
|
12
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
13
|
+
{...props}
|
|
14
|
+
routerLinkType={RrNavLink}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
12
17
|
|
|
13
18
|
export default NavLink;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ChangeEventHandler,
|
|
2
3
|
type FocusEventHandler,
|
|
4
|
+
type FunctionComponent,
|
|
5
|
+
type KeyboardEventHandler,
|
|
3
6
|
useEffect,
|
|
4
7
|
useRef,
|
|
5
8
|
useState,
|
|
@@ -17,14 +20,14 @@ type ThemeKeyT =
|
|
|
17
20
|
type Props = {
|
|
18
21
|
disabled?: boolean;
|
|
19
22
|
onBlur?: FocusEventHandler<HTMLTextAreaElement>;
|
|
20
|
-
onChange?:
|
|
21
|
-
onKeyDown?:
|
|
23
|
+
onChange?: ChangeEventHandler<HTMLTextAreaElement>;
|
|
24
|
+
onKeyDown?: KeyboardEventHandler<HTMLTextAreaElement>;
|
|
22
25
|
placeholder?: string;
|
|
23
26
|
theme: Theme<ThemeKeyT>;
|
|
24
27
|
value?: string;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
|
-
const TextArea:
|
|
30
|
+
const TextArea: FunctionComponent<Props> = ({
|
|
28
31
|
disabled,
|
|
29
32
|
onBlur,
|
|
30
33
|
onChange,
|
|
@@ -36,7 +39,7 @@ const TextArea: React.FunctionComponent<Props> = ({
|
|
|
36
39
|
const hiddenAreaRef = useRef<HTMLTextAreaElement>(null);
|
|
37
40
|
const [height, setHeight] = useState<number | undefined>();
|
|
38
41
|
|
|
39
|
-
const [localValue, setLocalValue] = useState(value
|
|
42
|
+
const [localValue, setLocalValue] = useState(value ?? '');
|
|
40
43
|
if (value !== undefined && localValue !== value) setLocalValue(value);
|
|
41
44
|
|
|
42
45
|
// This resizes text area's height when its width is changed for any reason.
|
|
@@ -64,27 +67,32 @@ const TextArea: React.FunctionComponent<Props> = ({
|
|
|
64
67
|
return (
|
|
65
68
|
<div className={theme.container}>
|
|
66
69
|
<textarea
|
|
70
|
+
className={`${theme.textarea} ${theme.hidden}`}
|
|
71
|
+
|
|
67
72
|
// This text area is hidden underneath the primary one below,
|
|
68
73
|
// and it is used for text measurements, to implement auto-scaling
|
|
69
74
|
// of the primary textarea's height.
|
|
70
75
|
readOnly
|
|
71
76
|
ref={hiddenAreaRef}
|
|
72
|
-
className={`${theme.textarea} ${theme.hidden}`}
|
|
73
77
|
value={localValue}
|
|
74
78
|
/>
|
|
75
79
|
<textarea
|
|
80
|
+
className={theme.textarea}
|
|
76
81
|
disabled={disabled}
|
|
77
82
|
onBlur={onBlur}
|
|
83
|
+
|
|
78
84
|
// When value is "undefined" the text area is not-managed, and we should
|
|
79
85
|
// manage it internally for the measurement / resizing functionality
|
|
80
86
|
// to work.
|
|
81
|
-
onChange={
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
onChange={
|
|
88
|
+
value === undefined
|
|
89
|
+
? (e) => {
|
|
90
|
+
setLocalValue(e.target.value);
|
|
91
|
+
} : onChange
|
|
92
|
+
}
|
|
84
93
|
onKeyDown={onKeyDown}
|
|
85
94
|
placeholder={placeholder}
|
|
86
95
|
style={{ height }}
|
|
87
|
-
className={theme.textarea}
|
|
88
96
|
value={localValue}
|
|
89
97
|
/>
|
|
90
98
|
</div>
|