@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
package/dist/index.mjs CHANGED
@@ -4,44 +4,36 @@ import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  //#region src/index.ts
7
+ const VARIANTS = [{
8
+ suffix: "",
9
+ display: "Full (products, search, guards)"
10
+ }, {
11
+ suffix: "-minimal",
12
+ display: "Minimal (tabs, drafts, session restore)"
13
+ }];
7
14
  const FRAMEWORKS = [
8
15
  {
9
16
  name: "vue",
10
17
  display: "Vue",
11
- color: green,
12
- variants: [{
13
- name: "vue",
14
- display: "Full (SSR + routing + DI + guards)"
15
- }, {
16
- name: "vue-minimal",
17
- display: "Minimal"
18
- }]
18
+ color: green
19
19
  },
20
20
  {
21
21
  name: "react",
22
22
  display: "React",
23
- color: cyan,
24
- variants: [{
25
- name: "react",
26
- display: "Full (SSR + routing + DI + guards)"
27
- }, {
28
- name: "react-minimal",
29
- display: "Minimal"
30
- }]
23
+ color: cyan
31
24
  },
32
25
  {
33
26
  name: "svelte",
34
27
  display: "Svelte",
35
- color: red,
36
- variants: [{
37
- name: "svelte",
38
- display: "Full (SSR + routing + DI + guards)"
39
- }, {
40
- name: "svelte-minimal",
41
- display: "Minimal"
42
- }]
28
+ color: red
43
29
  }
44
- ];
30
+ ].map((framework) => ({
31
+ ...framework,
32
+ variants: VARIANTS.map(({ suffix, display }) => ({
33
+ name: framework.name + suffix,
34
+ display
35
+ }))
36
+ }));
45
37
  const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
46
38
  function validateProjectName(value) {
47
39
  if (!value) return "Project name is required";
@@ -136,7 +128,11 @@ function copyDir(src, dest) {
136
128
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
137
129
  const srcPath = path.join(src, entry.name);
138
130
  const destPath = path.join(dest, entry.name);
139
- if (entry.name === "node_modules" || entry.name === "dist") continue;
131
+ if ([
132
+ "node_modules",
133
+ "dist",
134
+ ".finesoft"
135
+ ].includes(entry.name)) continue;
140
136
  if (entry.isDirectory()) copyDir(srcPath, destPath);
141
137
  else fs.copyFileSync(srcPath, destPath);
142
138
  }
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@finesoft/create-app",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "Scaffold a new Finesoft Front project",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/GaKireiTsuki/finesoft-front.git",
9
+ "directory": "packages/create-app"
10
+ },
6
11
  "bin": {
7
12
  "create-finesoft-app": "bin/create-finesoft-app.js"
8
13
  },
@@ -12,14 +17,18 @@
12
17
  "templates"
13
18
  ],
14
19
  "type": "module",
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "registry": "https://registry.npmjs.org/"
23
+ },
15
24
  "dependencies": {
16
- "@clack/prompts": "^0.10.0",
25
+ "@clack/prompts": "^1.7.0",
17
26
  "kolorist": "^1.8.0"
18
27
  },
19
28
  "devDependencies": {
20
- "@types/node": "^22",
21
- "vite": "npm:@voidzero-dev/vite-plus-core@0.1.16",
22
- "vite-plus": "0.1.17"
29
+ "@types/node": "^22.20.1",
30
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
31
+ "vite-plus": "0.2.8"
23
32
  },
24
33
  "scripts": {
25
34
  "build": "vp pack",
@@ -0,0 +1,35 @@
1
+ # Finesoft Front full application
2
+
3
+ Home, product detail, search and About demonstrate SSR, CSR, URL actions, typed route parameters, DI, data mapping and guards. All three native frameworks have the same example.
4
+
5
+ ## Run
6
+
7
+ Node must satisfy `^22.18.0 || >=24.11.0`. Use Vite+ for tooling.
8
+
9
+ ```sh
10
+ vp install
11
+ vp run dev
12
+ vp run build
13
+ vp run preview
14
+ ```
15
+
16
+ Visit `/`, open a product, try `/search?q=Vite`, and navigate to `/about` (CSR). `/admin` demonstrates an authentication guard: without an `auth_token` cookie it redirects to `/login?from=...`. Add your real login page and authentication service when adopting this example.
17
+
18
+ ## Server controller example
19
+
20
+ `src/lib/controllers/product-detail.ts` extends `BaseServerController` from `@finesoft/front`. Open `/products/1`, then navigate to another product. The controller reads the incoming `last_product` cookie and explicitly writes a new HttpOnly cookie when the product changes. It also sets `Cache-Control: private, no-store`. This is an application-owned browsing preference; the framework provides request/response tools and does not manage login state or automatically forward credentials.
21
+
22
+ SSR executes the controller directly. Browser navigation invokes it on the server, and hydration reuses the SSR result. Its implementation stays out of the browser bundle; `markPublic` selects the page data sent to the browser. Home, search and About keep ordinary `BaseController` examples. Route types still come from `src/app-definition.ts`; the Vite plugin maintains the generated `Input` / `Failure` types and provides the server context automatically.
23
+
24
+ Keep the configured SSR entry and deploy to a request host such as Node or Worker. Serving only `dist/client` cannot execute this controller. The template runs locally without external API credentials.
25
+
26
+ ## Edit
27
+
28
+ - `src/config.ts`: application identity and persistence namespace.
29
+ - `src/app-definition.ts`: pages, routes, guards and navigation.
30
+ - `src/lib/controllers/` and `src/lib/models/`: page loading and typed public data.
31
+ - `src/pages/`, `src/App.*`, `src/styles.css`: native UI and presentation.
32
+ - `src/views.ts`: the view registry shared by browser and server.
33
+ - `src/main.ts` and `src/ssr.ts`: standard browser and SSR startup.
34
+
35
+ All framework APIs import from `@finesoft/front`. Select the native component with `Outlet("react")`, `Outlet("vue")`, or `Outlet("svelte")`, and pass the same renderer to `useSnapshot`. The plugin maintains `.finesoft/front.d.ts` and the exact TypeScript import mapping; install only the chosen renderer. For standalone TypeScript checks, first run `vp exec finesoft-types`. The project is standalone and requires no sibling templates or private runtime packages.
@@ -6,8 +6,7 @@
6
6
  <!--ssr-head-->
7
7
  </head>
8
8
  <body>
9
- <div id="app"><!--ssr-body--></div>
10
- <!--ssr-data-->
11
- <script type="module" src="/src/main.tsx"></script>
9
+ <div id="app"><!--ssr-body--><!--ssr-data--></div>
10
+ <script type="module" src="/src/main.ts"></script>
12
11
  </body>
13
12
  </html>
@@ -8,15 +8,19 @@
8
8
  "preview": "vp preview"
9
9
  },
10
10
  "dependencies": {
11
- "@finesoft/front": "^0.5.0",
12
- "react": "^19.0.0",
13
- "react-dom": "^19.0.0"
11
+ "@finesoft/front": "^0.5.2",
12
+ "react": "^19.2.8",
13
+ "react-dom": "^19.2.8"
14
14
  },
15
15
  "devDependencies": {
16
- "@types/react": "^19.0.0",
17
- "@types/react-dom": "^19.0.0",
18
- "@vitejs/plugin-react": "^4.0.0",
19
- "vite": "npm:@voidzero-dev/vite-plus-core@0.1.16",
20
- "vite-plus": "0.1.17"
16
+ "@hono/node-server": "^2.1.0",
17
+ "@types/node": "^22.20.1",
18
+ "@types/react": "^19.2.18",
19
+ "@types/react-dom": "^19.2.4",
20
+ "@vitejs/plugin-react": "^6.0.5",
21
+ "hono": "^4.13.1",
22
+ "typescript": "^6.0.3",
23
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
24
+ "vite-plus": "0.2.8"
21
25
  }
22
26
  }
@@ -1,20 +1,12 @@
1
- import type { Action } from "@finesoft/front";
1
+ import { Outlet as selectOutlet, useSnapshot, type WebAppView } from "@finesoft/front";
2
2
  import { Layout } from "./components/Layout";
3
- import { Loading } from "./components/Loading";
4
- import { PageRenderer } from "./components/PageRenderer";
5
- import type { AppPage } from "./lib/models/product";
6
-
7
- interface AppProps {
8
- page?: AppPage | null;
9
- loading?: boolean;
10
- onAction?: (action: Action) => void;
11
- }
12
-
13
- export default function App({ page, loading = false, onAction }: AppProps) {
3
+ import { views } from "./views";
4
+ const Outlet = selectOutlet("react");
5
+ export default function App({ app }: { readonly app: WebAppView }) {
6
+ const snapshot = useSnapshot("react", app);
14
7
  return (
15
- <Layout currentPath={page?.url ?? "/"} onAction={onAction}>
16
- {loading && <Loading />}
17
- {page && <PageRenderer page={page} onAction={onAction} />}
8
+ <Layout currentPath={snapshot.entries.find((entry) => entry.visible)?.page.url ?? "/"}>
9
+ <Outlet app={app} views={views} />
18
10
  </Layout>
19
11
  );
20
12
  }
@@ -0,0 +1,35 @@
1
+ import { appId } from "./config";
2
+ import { definePage, defineWebApp, int, str, withDefault } from "@finesoft/front";
3
+ import { AboutController } from "./lib/controllers/about";
4
+ import { HomeController } from "./lib/controllers/home";
5
+ import { ProductDetailController } from "./lib/controllers/product-detail";
6
+ import { SearchController } from "./lib/controllers/search";
7
+ import { authGuard } from "./lib/guards/auth";
8
+ import { seoGuard } from "./lib/guards/seo";
9
+ import { getErrorPage } from "./lib/controllers/error";
10
+ export const homePage = definePage({
11
+ id: "home",
12
+ create: () => new HomeController(),
13
+ routes: ["/", { path: "/admin", beforeLoad: [authGuard] }],
14
+ });
15
+ export const productDetailPage = definePage({
16
+ id: "product-detail",
17
+ routes: [{ path: "/products/:id", params: { id: int() } }],
18
+ create: () => new ProductDetailController(),
19
+ });
20
+ export const searchPage = definePage({
21
+ id: "search",
22
+ create: () => new SearchController(),
23
+ routes: [{ path: "/search", query: { q: withDefault(str(), "") } }],
24
+ });
25
+ export const aboutPage = definePage({
26
+ id: "about",
27
+ create: () => new AboutController(),
28
+ routes: [{ path: "/about", renderMode: "csr" }],
29
+ });
30
+ export const app = defineWebApp({
31
+ id: appId,
32
+ pages: [homePage, productDetailPage, searchPage, aboutPage],
33
+ getErrorPage,
34
+ afterLoad: [seoGuard],
35
+ });
@@ -1,63 +1,16 @@
1
- import type { Action } from "@finesoft/front";
2
- import { NAV_ACTIONS } from "../actions";
1
+ import Navigation from "./Navigation";
3
2
 
4
3
  interface LayoutProps {
5
4
  children: React.ReactNode;
6
5
  currentPath?: string;
7
- onAction?: (action: Action) => void;
8
6
  }
9
7
 
10
8
  /** 公共布局 — 顶部导航栏 + 内容区 */
11
- export function Layout({ children, currentPath = "/", onAction }: LayoutProps) {
12
- const handleNav = (action: Action) => (e: React.MouseEvent) => {
13
- e.preventDefault();
14
- onAction?.(action);
15
- };
16
-
17
- const navItems = [
18
- { label: "Home", action: NAV_ACTIONS.home, path: "/" },
19
- { label: "Search", action: NAV_ACTIONS.search, path: "/search" },
20
- { label: "About", action: NAV_ACTIONS.about, path: "/about" },
21
- ];
22
-
9
+ export function Layout({ children, currentPath = "/" }: LayoutProps) {
23
10
  return (
24
- <div>
25
- <nav
26
- style={{
27
- display: "flex",
28
- gap: "1rem",
29
- padding: "1rem",
30
- borderBottom: "1px solid #eee",
31
- alignItems: "center",
32
- }}
33
- >
34
- {navItems.map((item) => (
35
- <a
36
- key={item.path}
37
- href={item.path}
38
- style={{
39
- textDecoration: "none",
40
- color: currentPath === item.path ? "#007bff" : "#333",
41
- fontWeight: currentPath === item.path ? "bold" : "normal",
42
- }}
43
- onClick={handleNav(item.action)}
44
- >
45
- {item.label}
46
- </a>
47
- ))}
48
- <a
49
- href={NAV_ACTIONS.github.url}
50
- style={{
51
- marginLeft: "auto",
52
- textDecoration: "none",
53
- color: "#333",
54
- }}
55
- onClick={handleNav(NAV_ACTIONS.github)}
56
- >
57
- GitHub ↗
58
- </a>
59
- </nav>
60
- <main style={{ padding: "1rem" }}>{children}</main>
11
+ <div className="app-shell">
12
+ <Navigation currentPath={currentPath} />
13
+ <main className="app-main">{children}</main>
61
14
  </div>
62
15
  );
63
16
  }
@@ -1,3 +1,3 @@
1
1
  export function Loading() {
2
- return <div style={{ padding: "2rem", textAlign: "center", color: "#999" }}>Loading…</div>;
2
+ return <div className="loading">Loading…</div>;
3
3
  }
@@ -1,47 +1,20 @@
1
- import type { Action } from "@finesoft/front";
2
- import { makeExternalUrlAction, makeFlowAction } from "@finesoft/front";
3
-
4
- const links = [
5
- { label: "Home", action: makeFlowAction("/"), path: "/" },
6
- { label: "Search", action: makeFlowAction("/search"), path: "/search" },
7
- { label: "About", action: makeFlowAction("/about"), path: "/about" },
8
- {
9
- label: "GitHub",
10
- action: makeExternalUrlAction("https://github.com/nicepkg/finesoft"),
11
- path: null,
12
- },
13
- ];
14
-
15
1
  interface NavigationProps {
16
2
  currentPath?: string;
17
- onAction?: (action: Action) => void;
18
3
  }
19
-
20
- export default function Navigation({ currentPath = "/", onAction }: NavigationProps) {
21
- const handleNav = (action: Action) => (e: React.MouseEvent) => {
22
- e.preventDefault();
23
- onAction?.(action);
24
- };
25
-
4
+ const links = [
5
+ { label: "Home", href: "/" },
6
+ { label: "Search", href: "/search" },
7
+ { label: "About", href: "/about" },
8
+ { label: "GitHub", href: "https://github.com/nicepkg/finesoft" },
9
+ ] as const;
10
+ export default function Navigation({ currentPath = "/" }: NavigationProps) {
26
11
  return (
27
- <nav
28
- style={{
29
- display: "flex",
30
- gap: "1rem",
31
- padding: "1rem",
32
- borderBottom: "1px solid #eee",
33
- }}
34
- >
12
+ <nav className="navigation">
35
13
  {links.map((link) => (
36
14
  <a
37
15
  key={link.label}
38
- href={link.action.url}
39
- style={{
40
- textDecoration: "none",
41
- color: link.path === currentPath ? "#007bff" : "#333",
42
- fontWeight: link.path === currentPath ? "bold" : "normal",
43
- }}
44
- onClick={handleNav(link.action)}
16
+ href={link.href}
17
+ className={`navigation-link${currentPath === link.href ? " active" : ""}`}
45
18
  >
46
19
  {link.label}
47
20
  </a>
@@ -1,35 +1,17 @@
1
- import type { Action } from "@finesoft/front";
2
1
  import type { ProductItem } from "../lib/models/product";
3
2
 
4
3
  interface ProductCardProps {
5
4
  item: ProductItem;
6
- onAction?: (action: Action) => void;
7
5
  }
8
6
 
9
- export default function ProductCard({ item, onAction }: ProductCardProps) {
10
- const handleClick = item.clickAction
11
- ? (e: React.MouseEvent) => {
12
- e.preventDefault();
13
- onAction?.(item.clickAction!);
14
- }
15
- : undefined;
16
-
7
+ export default function ProductCard({ item }: ProductCardProps) {
17
8
  return (
18
- <div
19
- style={{
20
- border: "1px solid #eee",
21
- borderRadius: "8px",
22
- padding: "1rem",
23
- minWidth: "200px",
24
- }}
25
- >
9
+ <article className="product-card">
26
10
  <h3>{item.name}</h3>
27
- <p style={{ color: "#007bff", fontWeight: "bold" }}>${item.price.toFixed(2)}</p>
11
+ <p className="price">${item.price.toFixed(2)}</p>
28
12
  {item.clickAction && "url" in item.clickAction && (
29
- <a href={item.clickAction.url} onClick={handleClick}>
30
- View Details &rarr;
31
- </a>
13
+ <a href={item.clickAction.url}>View Details &rarr;</a>
32
14
  )}
33
- </div>
15
+ </article>
34
16
  );
35
17
  }
@@ -0,0 +1,2 @@
1
+ /** App identity also namespaces persisted browser sessions. */
2
+ export const appId = "react";
@@ -1,18 +1,23 @@
1
- import { BaseController } from "@finesoft/front";
1
+ import { BaseController, type ControllerInput } from "@finesoft/front";
2
+ import { markPublic } from "@finesoft/front";
2
3
  import type { AboutPage } from "../models/product";
3
4
 
4
- export class AboutController extends BaseController<Record<string, string>, AboutPage> {
5
- readonly intentId = "about";
6
-
5
+ export class AboutController extends BaseController<
6
+ ControllerInput<Record<string, string>>,
7
+ AboutPage
8
+ > {
7
9
  execute(): AboutPage {
8
- return {
9
- id: "about",
10
- pageType: "about",
11
- title: "About",
12
- description: "About Finesoft Front",
13
- url: "/about",
14
- content:
15
- "Finesoft Front is a full-stack TypeScript framework with router, DI, middleware, SSR, and multi-platform deployment.",
16
- };
10
+ return markPublic(
11
+ {
12
+ id: "about",
13
+ pageType: "about",
14
+ title: "About",
15
+ description: "About Finesoft Front",
16
+ url: "/about",
17
+ content:
18
+ "Finesoft Front is a full-stack TypeScript framework with router, DI, middleware, SSR, and multi-platform deployment.",
19
+ },
20
+ ["content"],
21
+ );
17
22
  }
18
23
  }
@@ -1,11 +1,15 @@
1
+ import { markPublic } from "@finesoft/front";
1
2
  import type { ErrorPage } from "../models/product";
2
3
 
3
4
  export function getErrorPage(status: number, message: string): ErrorPage {
4
- return {
5
- id: "error",
6
- pageType: "error",
7
- title: `Error ${status}`,
8
- description: message,
9
- status,
10
- };
5
+ return markPublic(
6
+ {
7
+ id: "error",
8
+ pageType: "error",
9
+ title: `Error ${status}`,
10
+ description: message,
11
+ status,
12
+ },
13
+ ["status"],
14
+ );
11
15
  }
@@ -1,36 +1,12 @@
1
- import { BaseController, makeFlowAction } from "@finesoft/front";
2
- import type { HomePage, ProductItem, ProductShelf } from "../models/product";
3
-
4
- const MOCK_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
-
31
- export class HomeController extends BaseController<Record<string, string>, HomePage> {
32
- readonly intentId = "home";
1
+ import { PRODUCTS } from "../data/products";
2
+ import { BaseController, type ControllerInput } from "@finesoft/front";
3
+ import { markPublic, makeFlowAction } from "@finesoft/front";
4
+ import type { HomePage, ProductShelf } from "../models/product";
33
5
 
6
+ export class HomeController extends BaseController<
7
+ ControllerInput<Record<string, string>>,
8
+ HomePage
9
+ > {
34
10
  execute(): HomePage {
35
11
  const shelf: ProductShelf = {
36
12
  id: "featured",
@@ -38,16 +14,35 @@ export class HomeController extends BaseController<Record<string, string>, HomeP
38
14
  title: "Featured Products",
39
15
  isHorizontal: true,
40
16
  seeAllAction: makeFlowAction("/search"),
41
- items: MOCK_PRODUCTS,
17
+ items: PRODUCTS.slice(0, 3),
42
18
  };
43
19
 
44
- return {
45
- id: "home",
46
- pageType: "home",
47
- title: "Home",
48
- description: "Welcome to Finesoft Front Demo",
49
- url: "/",
50
- shelves: [shelf],
51
- };
20
+ return markPublic(
21
+ {
22
+ id: "home",
23
+ pageType: "home",
24
+ title: "Home",
25
+ description: "Welcome to Finesoft Front Demo",
26
+ url: "/",
27
+ shelves: [shelf],
28
+ },
29
+ {
30
+ shelves: {
31
+ id: true,
32
+ shelfType: true,
33
+ title: true,
34
+ isHorizontal: true,
35
+ seeAllAction: { kind: true, url: true },
36
+ items: {
37
+ id: true,
38
+ itemType: true,
39
+ name: true,
40
+ price: true,
41
+ imageUrl: true,
42
+ clickAction: { kind: true, url: true },
43
+ },
44
+ },
45
+ },
46
+ );
52
47
  }
53
48
  }