@finesoft/create-app 0.1.33 → 0.1.35

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 (198) hide show
  1. package/dist/index.mjs +22 -26
  2. package/package.json +14 -5
  3. package/templates/react/README.md +35 -0
  4. package/templates/react/index.html +2 -3
  5. package/templates/react/package.json +12 -8
  6. package/templates/react/src/App.tsx +7 -15
  7. package/templates/react/src/app-definition.ts +35 -0
  8. package/templates/react/src/components/Layout.tsx +5 -52
  9. package/templates/react/src/components/Loading.tsx +1 -1
  10. package/templates/react/src/components/Navigation.tsx +10 -37
  11. package/templates/react/src/components/ProductCard.tsx +5 -23
  12. package/templates/react/src/config.ts +2 -0
  13. package/templates/react/src/lib/controllers/about.ts +18 -13
  14. package/templates/react/src/lib/controllers/error.ts +11 -7
  15. package/templates/react/src/lib/controllers/home.ts +36 -41
  16. package/templates/react/src/lib/controllers/product-detail.ts +71 -40
  17. package/templates/react/src/lib/controllers/search.ts +33 -54
  18. package/templates/react/src/lib/data/products.ts +36 -0
  19. package/templates/react/src/lib/mappers/product.ts +3 -3
  20. package/templates/react/src/lib/models/product.ts +11 -1
  21. package/templates/react/src/lib/services/api-client.ts +7 -4
  22. package/templates/react/src/main.ts +34 -0
  23. package/templates/react/src/pages/About.tsx +2 -4
  24. package/templates/react/src/pages/Home.tsx +6 -15
  25. package/templates/react/src/pages/NotFound.tsx +4 -14
  26. package/templates/react/src/pages/ProductDetail.tsx +5 -23
  27. package/templates/react/src/pages/Search.tsx +5 -7
  28. package/templates/react/src/ssr.ts +11 -0
  29. package/templates/react/src/styles.css +71 -0
  30. package/templates/react/src/views.ts +12 -0
  31. package/templates/react/tsconfig.json +14 -9
  32. package/templates/react/vite.config.ts +9 -13
  33. package/templates/react-minimal/README.md +39 -0
  34. package/templates/react-minimal/index.html +2 -3
  35. package/templates/react-minimal/package.json +12 -8
  36. package/templates/react-minimal/src/App.tsx +40 -85
  37. package/templates/react-minimal/src/app-definition.ts +48 -0
  38. package/templates/react-minimal/src/config.ts +2 -0
  39. package/templates/react-minimal/src/lib/controllers/detail.ts +19 -18
  40. package/templates/react-minimal/src/lib/controllers/error.ts +15 -0
  41. package/templates/react-minimal/src/lib/controllers/home.ts +19 -24
  42. package/templates/react-minimal/src/lib/controllers/notes.ts +7 -5
  43. package/templates/react-minimal/src/lib/locale.ts +16 -0
  44. package/templates/react-minimal/src/lib/models/page.ts +27 -0
  45. package/templates/react-minimal/src/lib/navigation.ts +11 -0
  46. package/templates/react-minimal/src/locales/en-US.json +5 -0
  47. package/templates/react-minimal/src/locales/zh-Hans.json +5 -0
  48. package/templates/react-minimal/src/main.ts +43 -0
  49. package/templates/react-minimal/src/pages/Detail.tsx +20 -0
  50. package/templates/react-minimal/src/pages/Home.tsx +38 -0
  51. package/templates/react-minimal/src/pages/NotFound.tsx +11 -0
  52. package/templates/react-minimal/src/pages/Notes.tsx +21 -0
  53. package/templates/react-minimal/src/ssr.ts +11 -0
  54. package/templates/react-minimal/src/styles.css +74 -0
  55. package/templates/react-minimal/src/views.ts +6 -0
  56. package/templates/react-minimal/src/vite-env.d.ts +3 -0
  57. package/templates/react-minimal/tsconfig.json +14 -9
  58. package/templates/react-minimal/vite.config.ts +9 -7
  59. package/templates/svelte/README.md +35 -0
  60. package/templates/svelte/index.html +1 -2
  61. package/templates/svelte/package.json +9 -5
  62. package/templates/svelte/src/App.svelte +8 -80
  63. package/templates/svelte/src/app-definition.ts +35 -0
  64. package/templates/svelte/src/components/Layout.svelte +2 -2
  65. package/templates/svelte/src/components/Loading.svelte +1 -1
  66. package/templates/svelte/src/components/Navigation.svelte +5 -66
  67. package/templates/svelte/src/components/ProductCard.svelte +3 -24
  68. package/templates/svelte/src/config.ts +2 -0
  69. package/templates/svelte/src/lib/controllers/about.ts +18 -13
  70. package/templates/svelte/src/lib/controllers/error.ts +11 -7
  71. package/templates/svelte/src/lib/controllers/home.ts +36 -41
  72. package/templates/svelte/src/lib/controllers/product-detail.ts +71 -40
  73. package/templates/svelte/src/lib/controllers/search.ts +33 -54
  74. package/templates/svelte/src/lib/data/products.ts +36 -0
  75. package/templates/svelte/src/lib/mappers/product.ts +3 -3
  76. package/templates/svelte/src/lib/models/product.ts +12 -1
  77. package/templates/svelte/src/lib/services/api-client.ts +7 -4
  78. package/templates/svelte/src/main.ts +28 -24
  79. package/templates/svelte/src/pages/About.svelte +2 -2
  80. package/templates/svelte/src/pages/Home.svelte +3 -15
  81. package/templates/svelte/src/pages/NotFound.svelte +3 -12
  82. package/templates/svelte/src/pages/ProductDetail.svelte +4 -21
  83. package/templates/svelte/src/pages/Search.svelte +2 -10
  84. package/templates/svelte/src/ssr.ts +10 -9
  85. package/templates/svelte/src/styles.css +71 -0
  86. package/templates/svelte/src/views.ts +12 -0
  87. package/templates/svelte/svelte.config.js +2 -0
  88. package/templates/svelte/tsconfig.json +13 -8
  89. package/templates/svelte/vite.config.ts +9 -13
  90. package/templates/svelte-minimal/README.md +39 -0
  91. package/templates/svelte-minimal/index.html +1 -2
  92. package/templates/svelte-minimal/package.json +9 -5
  93. package/templates/svelte-minimal/src/App.svelte +49 -61
  94. package/templates/svelte-minimal/src/app-definition.ts +48 -0
  95. package/templates/svelte-minimal/src/config.ts +2 -0
  96. package/templates/svelte-minimal/src/lib/controllers/detail.ts +23 -0
  97. package/templates/svelte-minimal/src/lib/controllers/error.ts +15 -0
  98. package/templates/svelte-minimal/src/lib/controllers/home.ts +24 -23
  99. package/templates/svelte-minimal/src/lib/controllers/notes.ts +18 -0
  100. package/templates/svelte-minimal/src/lib/locale.ts +16 -0
  101. package/templates/svelte-minimal/src/lib/models/page.ts +27 -0
  102. package/templates/svelte-minimal/src/lib/navigation.ts +11 -0
  103. package/templates/svelte-minimal/src/locales/en-US.json +1 -3
  104. package/templates/svelte-minimal/src/locales/zh-Hans.json +1 -3
  105. package/templates/svelte-minimal/src/main.ts +37 -19
  106. package/templates/svelte-minimal/src/pages/Detail.svelte +11 -0
  107. package/templates/svelte-minimal/src/pages/Home.svelte +17 -21
  108. package/templates/svelte-minimal/src/pages/NotFound.svelte +10 -0
  109. package/templates/svelte-minimal/src/pages/Notes.svelte +11 -0
  110. package/templates/svelte-minimal/src/ssr.ts +9 -17
  111. package/templates/svelte-minimal/src/styles.css +74 -0
  112. package/templates/svelte-minimal/src/views.ts +6 -0
  113. package/templates/svelte-minimal/src/vite-env.d.ts +3 -0
  114. package/templates/svelte-minimal/svelte.config.js +2 -0
  115. package/templates/svelte-minimal/tsconfig.json +13 -8
  116. package/templates/svelte-minimal/vite.config.ts +9 -10
  117. package/templates/vue/README.md +35 -0
  118. package/templates/vue/index.html +1 -2
  119. package/templates/vue/package.json +9 -5
  120. package/templates/vue/src/App.vue +7 -22
  121. package/templates/vue/src/app-definition.ts +35 -0
  122. package/templates/vue/src/components/Layout.vue +4 -6
  123. package/templates/vue/src/components/Loading.vue +1 -1
  124. package/templates/vue/src/components/Navigation.vue +10 -43
  125. package/templates/vue/src/components/ProductCard.vue +4 -28
  126. package/templates/vue/src/config.ts +2 -0
  127. package/templates/vue/src/lib/controllers/about.ts +18 -13
  128. package/templates/vue/src/lib/controllers/error.ts +11 -7
  129. package/templates/vue/src/lib/controllers/home.ts +36 -41
  130. package/templates/vue/src/lib/controllers/product-detail.ts +71 -40
  131. package/templates/vue/src/lib/controllers/search.ts +33 -54
  132. package/templates/vue/src/lib/data/products.ts +36 -0
  133. package/templates/vue/src/lib/mappers/product.ts +3 -3
  134. package/templates/vue/src/lib/models/product.ts +12 -1
  135. package/templates/vue/src/lib/services/api-client.ts +7 -7
  136. package/templates/vue/src/main.ts +28 -43
  137. package/templates/vue/src/pages/About.vue +3 -5
  138. package/templates/vue/src/pages/Home.vue +5 -23
  139. package/templates/vue/src/pages/NotFound.vue +4 -13
  140. package/templates/vue/src/pages/ProductDetail.vue +5 -23
  141. package/templates/vue/src/pages/Search.vue +4 -18
  142. package/templates/vue/src/ssr.ts +5 -18
  143. package/templates/vue/src/styles.css +71 -0
  144. package/templates/vue/src/views.ts +12 -0
  145. package/templates/vue/tsconfig.json +14 -9
  146. package/templates/vue/vite.config.ts +9 -13
  147. package/templates/vue-minimal/README.md +39 -0
  148. package/templates/vue-minimal/index.html +1 -2
  149. package/templates/vue-minimal/package.json +9 -5
  150. package/templates/vue-minimal/src/App.vue +46 -43
  151. package/templates/vue-minimal/src/app-definition.ts +48 -0
  152. package/templates/vue-minimal/src/config.ts +2 -0
  153. package/templates/vue-minimal/src/lib/controllers/detail.ts +19 -18
  154. package/templates/vue-minimal/src/lib/controllers/error.ts +15 -0
  155. package/templates/vue-minimal/src/lib/controllers/home.ts +19 -24
  156. package/templates/vue-minimal/src/lib/controllers/notes.ts +7 -5
  157. package/templates/vue-minimal/src/lib/locale.ts +16 -0
  158. package/templates/vue-minimal/src/lib/models/page.ts +27 -0
  159. package/templates/vue-minimal/src/lib/navigation.ts +11 -0
  160. package/templates/vue-minimal/src/locales/en-US.json +5 -0
  161. package/templates/vue-minimal/src/locales/zh-Hans.json +5 -0
  162. package/templates/vue-minimal/src/main.ts +35 -70
  163. package/templates/vue-minimal/src/pages/Detail.vue +20 -0
  164. package/templates/vue-minimal/src/pages/Home.vue +34 -0
  165. package/templates/vue-minimal/src/pages/NotFound.vue +13 -0
  166. package/templates/vue-minimal/src/pages/Notes.vue +22 -0
  167. package/templates/vue-minimal/src/ssr.ts +7 -46
  168. package/templates/vue-minimal/src/styles.css +74 -0
  169. package/templates/vue-minimal/src/views.ts +6 -0
  170. package/templates/vue-minimal/src/vite-env.d.ts +3 -0
  171. package/templates/vue-minimal/tsconfig.json +14 -9
  172. package/templates/vue-minimal/vite.config.ts +9 -7
  173. package/templates/react/src/actions.ts +0 -14
  174. package/templates/react/src/bootstrap.ts +0 -36
  175. package/templates/react/src/components/PageRenderer.tsx +0 -41
  176. package/templates/react/src/main.tsx +0 -49
  177. package/templates/react/src/ssr.tsx +0 -23
  178. package/templates/react-minimal/src/bootstrap.ts +0 -39
  179. package/templates/react-minimal/src/main.tsx +0 -99
  180. package/templates/react-minimal/src/ssr.tsx +0 -51
  181. package/templates/react-minimal/src/views/DetailView.tsx +0 -28
  182. package/templates/react-minimal/src/views/HomeView.tsx +0 -27
  183. package/templates/react-minimal/src/views/NotesView.tsx +0 -11
  184. package/templates/svelte/src/actions.ts +0 -14
  185. package/templates/svelte/src/bootstrap.ts +0 -36
  186. package/templates/svelte/src/components/PageRenderer.svelte +0 -22
  187. package/templates/svelte/src/lib/framework-svelte.ts +0 -34
  188. package/templates/svelte/src/lib/render.ts +0 -15
  189. package/templates/svelte-minimal/src/bootstrap.ts +0 -6
  190. package/templates/svelte-minimal/src/lib/framework-svelte.ts +0 -34
  191. package/templates/svelte-minimal/src/lib/render.ts +0 -12
  192. package/templates/vue/src/actions.ts +0 -14
  193. package/templates/vue/src/bootstrap.ts +0 -42
  194. package/templates/vue/src/components/PageRenderer.vue +0 -26
  195. package/templates/vue-minimal/src/bootstrap.ts +0 -39
  196. package/templates/vue-minimal/src/views/DetailView.vue +0 -29
  197. package/templates/vue-minimal/src/views/HomeView.vue +0 -26
  198. package/templates/vue-minimal/src/views/NotesView.vue +0 -12
@@ -1,52 +1,83 @@
1
- import {
2
- BaseController,
3
- type Container,
4
- DEP_KEYS,
5
- type Logger,
6
- type LoggerFactory,
7
- } from "@finesoft/front";
1
+ import { DEP_KEYS, type Logger, type LoggerFactory } from "@finesoft/front";
2
+ import { BaseServerController } from "@finesoft/front";
3
+ import { markPublic } from "@finesoft/front";
8
4
  import type { ProductPage } from "../models/product";
5
+ import type {
6
+ ProductDetailControllerFailure as Failure,
7
+ ProductDetailControllerInput as Input,
8
+ } from "../../../.finesoft/controller-types";
9
9
 
10
- export class ProductDetailController extends BaseController<{ id: number }, ProductPage> {
11
- readonly intentId = "product-detail";
12
-
13
- async execute(params: { id: number }, container: Container): Promise<ProductPage> {
14
- const loggerFactory = container.resolve<LoggerFactory>(DEP_KEYS.LOGGER_FACTORY);
10
+ export class ProductDetailController extends BaseServerController<Input, ProductPage> {
11
+ async execute({ params, context }: Input): Promise<ProductPage> {
12
+ const loggerFactory = await context.get<LoggerFactory>(DEP_KEYS.LOGGER_FACTORY);
15
13
  const log: Logger = loggerFactory.loggerFor("ProductDetailController");
16
14
  log.info(`Loading product ${params.id}`);
17
15
 
16
+ // Application-owned browsing preference, unrelated to authentication.
17
+ // getCookie reads the incoming request; setCookie only writes the response.
18
+ context.responseHeaders.set("cache-control", "private, no-store");
19
+ if (context.getCookie("last_product") !== String(params.id)) {
20
+ context.setCookie("last_product", String(params.id), {
21
+ httpOnly: true,
22
+ sameSite: "Lax",
23
+ secure: new URL(context.request.url).protocol === "https:",
24
+ });
25
+ }
26
+
18
27
  // Simulated API fetch — replace with real HttpClient call
19
- return {
20
- id: `product-${params.id}`,
21
- pageType: "product",
22
- title: `Product ${params.id}`,
23
- description: `Details for product ${params.id}`,
24
- url: `/products/${params.id}`,
25
- product: {
26
- id: String(params.id),
27
- name: `Product ${params.id}`,
28
- price: 29.99,
29
- description:
30
- "This is a demo product showcasing BaseController with typed params and DI container usage.",
31
- imageUrl: `/img/product-${params.id}.svg`,
28
+ return markPublic(
29
+ {
30
+ id: `product-${params.id}`,
31
+ pageType: "product",
32
+ title: `Product ${params.id}`,
33
+ description: `Details for product ${params.id}`,
34
+ url: `/products/${params.id}`,
35
+ product: {
36
+ id: String(params.id),
37
+ name: `Product ${params.id}`,
38
+ price: 29.99,
39
+ description:
40
+ "This is a demo product showcasing route-inferred params and DI container usage.",
41
+ imageUrl: `/img/product-${params.id}.svg`,
42
+ },
32
43
  },
33
- };
44
+ {
45
+ product: {
46
+ id: true,
47
+ name: true,
48
+ price: true,
49
+ description: true,
50
+ imageUrl: true,
51
+ },
52
+ },
53
+ );
34
54
  }
35
55
 
36
- fallback(params: { id: number }, error: Error): ProductPage {
37
- return {
38
- id: `product-${params.id}`,
39
- pageType: "product",
40
- title: "Product Not Found",
41
- description: error.message,
42
- url: `/products/${params.id}`,
43
- product: {
44
- id: String(params.id),
45
- name: "Unknown Product",
46
- price: 0,
47
- description: "Could not load product data.",
48
- imageUrl: "/img/placeholder.svg",
56
+ fallback({ params }: Failure): ProductPage {
57
+ return markPublic(
58
+ {
59
+ id: `product-${params.id}`,
60
+ pageType: "product",
61
+ title: "Product Not Found",
62
+ description: "The product is currently unavailable.",
63
+ url: `/products/${params.id}`,
64
+ product: {
65
+ id: String(params.id),
66
+ name: "Unknown Product",
67
+ price: 0,
68
+ description: "Could not load product data.",
69
+ imageUrl: "/img/placeholder.svg",
70
+ },
71
+ },
72
+ {
73
+ product: {
74
+ id: true,
75
+ name: true,
76
+ price: true,
77
+ description: true,
78
+ imageUrl: true,
79
+ },
49
80
  },
50
- };
81
+ );
51
82
  }
52
83
  }
@@ -1,58 +1,37 @@
1
- import { BaseController, makeFlowAction } from "@finesoft/front";
2
- import type { ProductItem, SearchPage } from "../models/product";
1
+ import { PRODUCTS } from "../data/products";
2
+ import { BaseController } from "@finesoft/front";
3
+ import { markPublic } from "@finesoft/front";
4
+ import type { SearchPage } from "../models/product";
5
+ import type { SearchControllerInput as Input } from "../../../.finesoft/controller-types";
3
6
 
4
- const ALL_PRODUCTS: ProductItem[] = [
5
- {
6
- id: "1",
7
- itemType: "product",
8
- name: "TypeScript Handbook",
9
- price: 29.99,
10
- imageUrl: "/img/ts.svg",
11
- clickAction: makeFlowAction("/products/1"),
12
- },
13
- {
14
- id: "2",
15
- itemType: "product",
16
- name: "Vite Starter Kit",
17
- price: 19.99,
18
- imageUrl: "/img/vite.svg",
19
- clickAction: makeFlowAction("/products/2"),
20
- },
21
- {
22
- id: "3",
23
- itemType: "product",
24
- name: "Hono Framework Guide",
25
- price: 24.99,
26
- imageUrl: "/img/hono.svg",
27
- clickAction: makeFlowAction("/products/3"),
28
- },
29
- {
30
- id: "4",
31
- itemType: "product",
32
- name: "SSR Deep Dive",
33
- price: 34.99,
34
- imageUrl: "/img/ssr.svg",
35
- clickAction: makeFlowAction("/products/4"),
36
- },
37
- ];
7
+ export class SearchController extends BaseController<Input, SearchPage> {
8
+ execute({ query }: Input): SearchPage {
9
+ const term = query.q;
10
+ const results = term
11
+ ? PRODUCTS.filter((p) => p.name.toLowerCase().includes(term.toLowerCase()))
12
+ : PRODUCTS;
38
13
 
39
- export class SearchController extends BaseController<{ q?: string }, SearchPage> {
40
- readonly intentId = "search";
41
-
42
- execute(params: { q?: string }): SearchPage {
43
- const query = params.q ?? "";
44
- const results = query
45
- ? ALL_PRODUCTS.filter((p) => p.name.toLowerCase().includes(query.toLowerCase()))
46
- : ALL_PRODUCTS;
47
-
48
- return {
49
- id: "search",
50
- pageType: "search",
51
- title: query ? `Search: ${query}` : "All Products",
52
- description: `${results.length} result(s)`,
53
- url: "/search",
54
- query,
55
- results,
56
- };
14
+ return markPublic(
15
+ {
16
+ id: "search",
17
+ pageType: "search",
18
+ title: term ? `Search: ${term}` : "All Products",
19
+ description: `${results.length} result(s)`,
20
+ url: "/search",
21
+ query: term,
22
+ results,
23
+ },
24
+ {
25
+ query: true,
26
+ results: {
27
+ id: true,
28
+ itemType: true,
29
+ name: true,
30
+ price: true,
31
+ imageUrl: true,
32
+ clickAction: { kind: true, url: true },
33
+ },
34
+ },
35
+ );
57
36
  }
58
37
  }
@@ -0,0 +1,36 @@
1
+ import type { ProductItem } from "../models/product";
2
+
3
+ export const PRODUCTS: ProductItem[] = [
4
+ {
5
+ id: "1",
6
+ itemType: "product",
7
+ name: "TypeScript Handbook",
8
+ price: 29.99,
9
+ imageUrl: "/img/ts.svg",
10
+ clickAction: { url: "/products/1" },
11
+ },
12
+ {
13
+ id: "2",
14
+ itemType: "product",
15
+ name: "Vite Starter Kit",
16
+ price: 19.99,
17
+ imageUrl: "/img/vite.svg",
18
+ clickAction: { url: "/products/2" },
19
+ },
20
+ {
21
+ id: "3",
22
+ itemType: "product",
23
+ name: "Hono Framework Guide",
24
+ price: 24.99,
25
+ imageUrl: "/img/hono.svg",
26
+ clickAction: { url: "/products/3" },
27
+ },
28
+ {
29
+ id: "4",
30
+ itemType: "product",
31
+ name: "SSR Deep Dive",
32
+ price: 34.99,
33
+ imageUrl: "/img/ssr.svg",
34
+ clickAction: { url: "/products/4" },
35
+ },
36
+ ];
@@ -1,4 +1,4 @@
1
- import { makeFlowAction, mapEach, type Mapper } from "@finesoft/front";
1
+ import { mapEach, type Mapper } from "@finesoft/front";
2
2
  import type { ProductItem } from "../models/product";
3
3
 
4
4
  /** Raw API response shape */
@@ -16,12 +16,12 @@ const toProductItem: Mapper<ApiProduct, ProductItem> = (raw) => ({
16
16
  name: raw.title,
17
17
  price: raw.price,
18
18
  imageUrl: raw.thumbnail ?? "/img/placeholder.svg",
19
- clickAction: makeFlowAction(`/products/${raw.id}`),
19
+ clickAction: { url: `/products/${raw.id}` },
20
20
  });
21
21
 
22
22
  /**
23
23
  * Composed mapper pipeline: ApiProduct[] → ProductItem[]
24
24
  *
25
- * Demonstrates pipe() + mapEach() for data transformation.
25
+ * Demonstrates mapEach() for data transformation.
26
26
  */
27
27
  export const mapProducts: Mapper<ApiProduct[], ProductItem[]> = mapEach(toProductItem);
@@ -1,15 +1,25 @@
1
- import type { BaseItem, BasePage, BaseShelf } from "@finesoft/front";
1
+ import type { BasePage } from "@finesoft/front";
2
+ interface BaseItem {
3
+ readonly id: string;
4
+ }
5
+ interface BaseShelf {
6
+ readonly id: string;
7
+ readonly title: string;
8
+ }
2
9
 
3
10
  export interface ProductItem extends BaseItem {
4
11
  itemType: "product";
5
12
  name: string;
6
13
  price: number;
7
14
  imageUrl: string;
15
+ clickAction?: { readonly url: string };
8
16
  }
9
17
 
10
18
  export interface ProductShelf extends BaseShelf {
11
19
  shelfType: "products";
12
20
  items: ProductItem[];
21
+ isHorizontal?: boolean;
22
+ seeAllAction?: { readonly url: string };
13
23
  }
14
24
 
15
25
  export interface HomePage extends BasePage {
@@ -6,11 +6,14 @@ import { HttpClient, type HttpClientConfig } from "@finesoft/front";
6
6
  * Showcases HttpClient subclass pattern with base URL and custom fetch.
7
7
  */
8
8
  export class ApiClient extends HttpClient {
9
- constructor(config?: Partial<HttpClientConfig>) {
9
+ constructor(config: Partial<HttpClientConfig> & Pick<HttpClientConfig, "fetch">) {
10
10
  super({
11
- baseUrl: config?.baseUrl ?? "/api",
12
- defaultHeaders: { "Content-Type": "application/json", ...config?.defaultHeaders },
13
- fetch: config?.fetch,
11
+ baseUrl: config.baseUrl ?? "/api",
12
+ defaultHeaders: { "Content-Type": "application/json", ...config.defaultHeaders },
13
+ fetch: config.fetch,
14
+ validateDns: config.validateDns,
15
+ lookup: config.lookup,
16
+ allowInternalHosts: config.allowInternalHosts,
14
17
  });
15
18
  }
16
19
 
@@ -0,0 +1,34 @@
1
+ import { createBrowserApp } from "@finesoft/front";
2
+ import { createRoot, hydrateRoot } from "react-dom/client";
3
+ import { createElement } from "react";
4
+ import { app } from "./app-definition";
5
+ import App from "./App";
6
+ import "./styles.css";
7
+
8
+ const target = document.getElementById("app")!;
9
+ export async function mountApplication(
10
+ target: HTMLElement,
11
+ history: "browser" | "memory" = "browser",
12
+ url?: string,
13
+ ) {
14
+ const handle = await createBrowserApp({ definition: app, target, history, url });
15
+ const root = handle.shouldHydrate
16
+ ? hydrateRoot(target, createElement(App, { app: handle }))
17
+ : createRoot(target);
18
+ if (!handle.shouldHydrate) root.render(createElement(App, { app: handle }));
19
+ const originalDispose = handle.dispose.bind(handle);
20
+ let disposal: Promise<void> | undefined;
21
+ Object.assign(handle, {
22
+ dispose: () => (disposal ??= originalDispose().finally(() => root.unmount())),
23
+ });
24
+ await handle.ready;
25
+ return handle;
26
+ }
27
+
28
+ export const started = mountApplication(target);
29
+
30
+ if (import.meta.hot) {
31
+ import.meta.hot.dispose(async () => {
32
+ await (await started).dispose();
33
+ });
34
+ }
@@ -1,16 +1,14 @@
1
- import type { Action } from "@finesoft/front";
2
1
  import type { AboutPage } from "../lib/models/product";
3
2
 
4
3
  interface AboutProps {
5
4
  page: AboutPage;
6
- onAction?: (action: Action) => void;
7
5
  }
8
6
 
9
7
  export default function About({ page }: AboutProps) {
10
8
  return (
11
- <div>
9
+ <section className="page page-about">
12
10
  <h1>{page.title}</h1>
13
11
  <p>{page.content}</p>
14
- </div>
12
+ </section>
15
13
  );
16
14
  }
@@ -1,34 +1,25 @@
1
- import type { Action } from "@finesoft/front";
2
1
  import ProductCard from "../components/ProductCard";
3
2
  import type { HomePage } from "../lib/models/product";
4
3
 
5
4
  interface HomeProps {
6
5
  page: HomePage;
7
- onAction?: (action: Action) => void;
8
6
  }
9
7
 
10
- export default function Home({ page, onAction }: HomeProps) {
8
+ export default function Home({ page }: HomeProps) {
11
9
  return (
12
- <div>
10
+ <section className="page page-home">
13
11
  <h1>{page.title}</h1>
14
12
  <p>{page.description}</p>
15
13
  {page.shelves.map((shelf) => (
16
- <section key={shelf.id}>
14
+ <section className="shelf-section" key={shelf.id}>
17
15
  <h2>{shelf.title}</h2>
18
- <div
19
- style={{
20
- display: "flex",
21
- gap: "1rem",
22
- flexWrap: shelf.isHorizontal ? "nowrap" : "wrap",
23
- overflowX: shelf.isHorizontal ? "auto" : undefined,
24
- }}
25
- >
16
+ <div className={`shelf${shelf.isHorizontal ? " horizontal" : ""}`}>
26
17
  {shelf.items.map((item) => (
27
- <ProductCard key={item.id} item={item} onAction={onAction} />
18
+ <ProductCard key={item.id} item={item} />
28
19
  ))}
29
20
  </div>
30
21
  </section>
31
22
  ))}
32
- </div>
23
+ </section>
33
24
  );
34
25
  }
@@ -1,25 +1,15 @@
1
- import type { Action } from "@finesoft/front";
2
- import { makeFlowAction } from "@finesoft/front";
3
1
  import type { ErrorPage } from "../lib/models/product";
4
2
 
5
3
  interface NotFoundProps {
6
4
  page: ErrorPage;
7
- onAction?: (action: Action) => void;
8
5
  }
9
6
 
10
- export default function NotFound({ page, onAction }: NotFoundProps) {
11
- const handleHome = (e: React.MouseEvent) => {
12
- e.preventDefault();
13
- onAction?.(makeFlowAction("/"));
14
- };
15
-
7
+ export default function NotFound({ page }: NotFoundProps) {
16
8
  return (
17
- <div>
9
+ <section className="page page-error">
18
10
  <h1>{page.title}</h1>
19
11
  <p>{page.description}</p>
20
- <a href="/" onClick={handleHome}>
21
- ← Go Home
22
- </a>
23
- </div>
12
+ <a href="/">← Go Home</a>
13
+ </section>
24
14
  );
25
15
  }
@@ -1,34 +1,16 @@
1
- import type { Action } from "@finesoft/front";
2
- import { makeFlowAction } from "@finesoft/front";
3
1
  import type { ProductPage } from "../lib/models/product";
4
2
 
5
3
  interface ProductDetailProps {
6
4
  page: ProductPage;
7
- onAction?: (action: Action) => void;
8
5
  }
9
6
 
10
- export default function ProductDetail({ page, onAction }: ProductDetailProps) {
11
- const handleBack = (e: React.MouseEvent) => {
12
- e.preventDefault();
13
- onAction?.(makeFlowAction("/"));
14
- };
15
-
7
+ export default function ProductDetail({ page }: ProductDetailProps) {
16
8
  return (
17
- <div>
18
- <a href="/" onClick={handleBack}>
19
- ← Back
20
- </a>
9
+ <section className="page page-product">
10
+ <a href="/">← Back</a>
21
11
  <h1>{page.product.name}</h1>
22
- <p
23
- style={{
24
- fontSize: "1.5rem",
25
- color: "#007bff",
26
- fontWeight: "bold",
27
- }}
28
- >
29
- ${page.product.price.toFixed(2)}
30
- </p>
12
+ <p className="price price-large">${page.product.price.toFixed(2)}</p>
31
13
  <p>{page.product.description}</p>
32
- </div>
14
+ </section>
33
15
  );
34
16
  }
@@ -1,23 +1,21 @@
1
- import type { Action } from "@finesoft/front";
2
1
  import ProductCard from "../components/ProductCard";
3
2
  import type { SearchPage } from "../lib/models/product";
4
3
 
5
4
  interface SearchProps {
6
5
  page: SearchPage;
7
- onAction?: (action: Action) => void;
8
6
  }
9
7
 
10
- export default function Search({ page, onAction }: SearchProps) {
8
+ export default function Search({ page }: SearchProps) {
11
9
  return (
12
- <div>
10
+ <section className="page page-search">
13
11
  <h1>{page.title}</h1>
14
12
  <p>{page.description}</p>
15
- <div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
13
+ <div className="results">
16
14
  {page.results.map((item) => (
17
- <ProductCard key={item.id} item={item} onAction={onAction} />
15
+ <ProductCard key={item.id} item={item} />
18
16
  ))}
19
17
  </div>
20
18
  {page.results.length === 0 && <p>No products found.</p>}
21
- </div>
19
+ </section>
22
20
  );
23
21
  }
@@ -0,0 +1,11 @@
1
+ import { renderToString } from "react-dom/server";
2
+ import { createElement } from "react";
3
+ import { createSSRRender } from "@finesoft/front";
4
+ import { app } from "./app-definition";
5
+ import App from "./App";
6
+
7
+ export const render = createSSRRender({
8
+ definition: app,
9
+ render: (app) => renderToString(createElement(App, { app })),
10
+ });
11
+ export { serializeServerData } from "@finesoft/front";
@@ -0,0 +1,71 @@
1
+ :root {
2
+ color: #333;
3
+ font-family: Arial, sans-serif;
4
+ }
5
+
6
+ body {
7
+ margin: 0;
8
+ }
9
+
10
+ .app-shell {
11
+ min-height: 100vh;
12
+ }
13
+
14
+ .navigation {
15
+ display: flex;
16
+ gap: 1rem;
17
+ padding: 1rem;
18
+ border-bottom: 1px solid #eee;
19
+ }
20
+
21
+ .navigation-link {
22
+ color: #333;
23
+ text-decoration: none;
24
+ }
25
+
26
+ .navigation-link:hover,
27
+ .navigation-link.active {
28
+ color: #007bff;
29
+ }
30
+
31
+ .navigation-link.active {
32
+ font-weight: bold;
33
+ }
34
+
35
+ .app-main {
36
+ padding: 1rem;
37
+ }
38
+
39
+ .loading {
40
+ padding: 2rem;
41
+ color: #999;
42
+ text-align: center;
43
+ }
44
+
45
+ .shelf,
46
+ .results {
47
+ display: flex;
48
+ flex-wrap: wrap;
49
+ gap: 1rem;
50
+ }
51
+
52
+ .shelf.horizontal {
53
+ overflow-x: auto;
54
+ flex-wrap: nowrap;
55
+ }
56
+
57
+ .product-card {
58
+ min-width: 200px;
59
+ padding: 1rem;
60
+ border: 1px solid #eee;
61
+ border-radius: 8px;
62
+ }
63
+
64
+ .price {
65
+ color: #007bff;
66
+ font-weight: bold;
67
+ }
68
+
69
+ .price-large {
70
+ font-size: 1.5rem;
71
+ }
@@ -0,0 +1,12 @@
1
+ import About from "./pages/About";
2
+ import Home from "./pages/Home";
3
+ import NotFound from "./pages/NotFound";
4
+ import ProductDetail from "./pages/ProductDetail";
5
+ import Search from "./pages/Search";
6
+ export const views = {
7
+ home: Home,
8
+ product: ProductDetail,
9
+ search: Search,
10
+ about: About,
11
+ error: NotFound,
12
+ };
@@ -4,20 +4,25 @@
4
4
  "target": "ESNext",
5
5
  "module": "ESNext",
6
6
  "moduleResolution": "bundler",
7
- "jsx": "react-jsx",
7
+ "allowImportingTsExtensions": true,
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
8
11
  "esModuleInterop": true,
9
12
  "forceConsistentCasingInFileNames": true,
10
13
  "skipLibCheck": true,
11
14
  "noEmit": true,
15
+ "rootDir": ".",
16
+ "jsx": "react-jsx",
12
17
  "paths": {
13
- "@/*": ["./src/*"],
14
- "@finesoft/front": ["../../packages/front/src/index.ts"],
15
- "@finesoft/front/browser": ["../../packages/front/src/browser.ts"],
16
- "@finesoft/core": ["../../packages/core/src/index.ts"],
17
- "@finesoft/browser": ["../../packages/browser/src/index.ts"],
18
- "@finesoft/ssr": ["../../packages/ssr/src/index.ts"],
19
- "@finesoft/server": ["../../packages/server/src/index.ts"]
18
+ "@finesoft/front": [
19
+ "./.finesoft/front.d.ts"
20
+ ]
20
21
  }
21
22
  },
22
- "include": ["src/**/*", "env.d.ts", "vite.config.ts"]
23
+ "include": [
24
+ "src/**/*",
25
+ "env.d.ts",
26
+ "vite.config.ts"
27
+ ]
23
28
  }