@alepha/react 0.13.2 → 0.13.4

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.
@@ -4,20 +4,24 @@ export default function NotFoundPage(props: { style?: CSSProperties }) {
4
4
  return (
5
5
  <div
6
6
  style={{
7
- height: "100vh",
7
+ width: "100%",
8
+ minHeight: "90vh",
9
+ boxSizing: "border-box",
8
10
  display: "flex",
9
11
  flexDirection: "column",
10
12
  justifyContent: "center",
11
13
  alignItems: "center",
12
14
  textAlign: "center",
13
- fontFamily: "sans-serif",
14
- padding: "1rem",
15
+ fontFamily:
16
+ 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
17
+ padding: "2rem",
15
18
  ...props.style,
16
19
  }}
17
20
  >
18
- <h1 style={{ fontSize: "1rem", marginBottom: "0.5rem" }}>
19
- 404 - This page does not exist
20
- </h1>
21
+ <div style={{ fontSize: "6rem", fontWeight: 200, lineHeight: 1 }}>404</div>
22
+ <div style={{ fontSize: "0.875rem", marginTop: "1rem", opacity: 0.6 }}>
23
+ Page not found
24
+ </div>
21
25
  </div>
22
26
  );
23
27
  }
@@ -77,6 +77,7 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
77
77
  }
78
78
  }
79
79
 
80
+ state.name = route?.page.name;
80
81
  state.query = query;
81
82
  state.params = params ?? {};
82
83
 
@@ -694,6 +694,9 @@ export interface ReactRouterState {
694
694
  * Optional meta information associated with the current page.
695
695
  */
696
696
  meta: Record<string, any>;
697
+
698
+ //
699
+ name?: string;
697
700
  }
698
701
 
699
702
  export interface RouterStackItem {
@@ -184,19 +184,17 @@ export class ReactServerProvider {
184
184
  }
185
185
 
186
186
  for (const page of this.pageApi.getPages()) {
187
- if (page.children?.length) {
188
- continue;
187
+ if (page.component || page.lazy) {
188
+ this.log.debug(`+ ${page.match} -> ${page.name}`);
189
+
190
+ this.serverRouterProvider.createRoute({
191
+ ...page,
192
+ schema: undefined, // schema is handled by the page primitive provider for now (shared by browser and server)
193
+ method: "GET",
194
+ path: page.match,
195
+ handler: this.createHandler(page, templateLoader),
196
+ });
189
197
  }
190
-
191
- this.log.debug(`+ ${page.match} -> ${page.name}`);
192
-
193
- this.serverRouterProvider.createRoute({
194
- ...page,
195
- schema: undefined, // schema is handled by the page primitive provider for now (shared by browser and server)
196
- method: "GET",
197
- path: page.match,
198
- handler: this.createHandler(page, templateLoader),
199
- });
200
198
  }
201
199
  }
202
200
 
@@ -339,6 +337,8 @@ export class ReactServerProvider {
339
337
 
340
338
  const state = entry as ReactRouterState;
341
339
 
340
+ state.name = route.name;
341
+
342
342
  if (this.alepha.has(ServerLinksProvider)) {
343
343
  this.alepha.store.set(
344
344
  "alepha.server.request.apiLinks",
@@ -352,6 +352,9 @@ export class ReactServerProvider {
352
352
  let target: PageRoute | undefined = route; // TODO: move to PagePrimitiveProvider
353
353
  while (target) {
354
354
  if (route.can && !route.can()) {
355
+ this.log.warn(
356
+ `Access to page '${route.name}' is forbidden by can() check`,
357
+ )
355
358
  // if the page is not accessible, return 403
356
359
  reply.status = 403;
357
360
  reply.headers["content-type"] = "text/plain";
@@ -383,6 +386,9 @@ export class ReactServerProvider {
383
386
  this.serverTimingProvider.endTiming("createLayers");
384
387
 
385
388
  if (redirect) {
389
+ this.log.debug("Resolver resulted in redirection", {
390
+ redirect,
391
+ });
386
392
  return reply.redirect(redirect);
387
393
  }
388
394
 
@@ -402,9 +408,14 @@ export class ReactServerProvider {
402
408
  ? html.redirect
403
409
  : this.pageApi.href(html.redirect),
404
410
  );
411
+ this.log.debug("Rendering resulted in redirection", {
412
+ redirect: html.redirect,
413
+ });
405
414
  return;
406
415
  }
407
416
 
417
+ this.log.trace("Page rendered to HTML successfully");
418
+
408
419
  const event = {
409
420
  request: serverRequest,
410
421
  state,
@@ -51,6 +51,22 @@ export class ReactRouter<T extends object> {
51
51
  return isActive;
52
52
  }
53
53
 
54
+ public node(
55
+ name: keyof VirtualRouter<T> | string,
56
+ config: {
57
+ params?: Record<string, any>;
58
+ query?: Record<string, any>;
59
+ } = {},
60
+ ) {
61
+ const page = this.pageApi.page(name as string);
62
+ return {
63
+ ...page,
64
+ label: page.label ?? page.name,
65
+ href: this.path(name, config),
66
+ children: undefined,
67
+ }
68
+ }
69
+
54
70
  public path(
55
71
  name: keyof VirtualRouter<T> | string,
56
72
  config: {
@@ -43,4 +43,5 @@ export interface SimpleHead {
43
43
  htmlAttributes?: Record<string, string>;
44
44
  bodyAttributes?: Record<string, string>;
45
45
  meta?: Array<{ name: string; content: string }>;
46
+ link?: Array<{ rel: string; href: string }>;
46
47
  }
@@ -18,7 +18,10 @@ export type HeadPrimitiveOptions = Head | (() => Head);
18
18
  export class HeadPrimitive extends Primitive<HeadPrimitiveOptions> {
19
19
  protected readonly provider = $inject(HeadProvider);
20
20
  protected onInit() {
21
- this.provider.global = this.options;
21
+ this.provider.global = [
22
+ ...(this.provider.global ?? []),
23
+ this.options,
24
+ ];
22
25
  }
23
26
  }
24
27
 
@@ -101,5 +101,18 @@ export class BrowserHeadProvider {
101
101
  }
102
102
  }
103
103
  }
104
+
105
+ if (head.link) {
106
+ for (const it of head.link) {
107
+ const { rel, href } = it;
108
+ let link = document.querySelector(`link[rel="${rel}"][href="${href}"]`);
109
+ if (!link) {
110
+ link = document.createElement("link");
111
+ link.setAttribute("rel", rel);
112
+ link.setAttribute("href", href);
113
+ document.head.appendChild(link);
114
+ }
115
+ }
116
+ }
104
117
  }
105
118
  }
@@ -2,21 +2,23 @@ import type { PageRoute, ReactRouterState } from "@alepha/react";
2
2
  import type { Head } from "../interfaces/Head.ts";
3
3
 
4
4
  export class HeadProvider {
5
- public global?: Head | (() => Head);
6
-
7
- protected getGlobalHead(): Head | undefined {
8
- if (typeof this.global === "function") {
9
- return this.global();
10
- }
11
- return this.global;
12
- }
5
+ public global?: Array<Head | (() => Head)> = [];
13
6
 
14
7
  public fillHead(state: ReactRouterState) {
15
8
  state.head = {
16
9
  ...state.head,
17
- ...this.getGlobalHead(),
18
10
  };
19
11
 
12
+ for (const h of this.global ?? []) {
13
+ const head =
14
+ typeof h === "function" ? h() : h;
15
+ state.head = {
16
+ ...state.head,
17
+ ...head,
18
+ meta: [...(state.head.meta ?? []), ...(head.meta ?? [])],
19
+ };
20
+ }
21
+
20
22
  for (const layer of state.layers) {
21
23
  if (layer.route?.head && !layer.error) {
22
24
  this.fillHeadByPage(layer.route, state, layer.props ?? {});
@@ -62,6 +62,12 @@ export class ServerHeadProvider {
62
62
  }
63
63
  }
64
64
 
65
+ if (head.link) {
66
+ for (const link of head.link) {
67
+ headContent += `<link rel="${this.escapeHtml(link.rel)}" href="${this.escapeHtml(link.href)}">\n`;
68
+ }
69
+ }
70
+
65
71
  // Inject into <head>...</head>
66
72
  result = result.replace(
67
73
  /<head([^>]*)>(.*?)<\/head>/is,