@finesoft/create-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/index.mjs +129 -0
  2. package/package.json +26 -0
  3. package/templates/react/env.d.ts +1 -0
  4. package/templates/react/index.html +13 -0
  5. package/templates/react/package.json +21 -0
  6. package/templates/react/src/App.tsx +20 -0
  7. package/templates/react/src/actions.ts +14 -0
  8. package/templates/react/src/bootstrap.ts +36 -0
  9. package/templates/react/src/components/Layout.tsx +63 -0
  10. package/templates/react/src/components/Loading.tsx +3 -0
  11. package/templates/react/src/components/Navigation.tsx +51 -0
  12. package/templates/react/src/components/PageRenderer.tsx +41 -0
  13. package/templates/react/src/components/ProductCard.tsx +35 -0
  14. package/templates/react/src/lib/controllers/about.ts +18 -0
  15. package/templates/react/src/lib/controllers/error.ts +11 -0
  16. package/templates/react/src/lib/controllers/home.ts +53 -0
  17. package/templates/react/src/lib/controllers/product-detail.ts +52 -0
  18. package/templates/react/src/lib/controllers/search.ts +58 -0
  19. package/templates/react/src/lib/guards/auth.ts +17 -0
  20. package/templates/react/src/lib/guards/seo.ts +15 -0
  21. package/templates/react/src/lib/mappers/product.ts +27 -0
  22. package/templates/react/src/lib/models/product.ts +48 -0
  23. package/templates/react/src/lib/services/api-client.ts +28 -0
  24. package/templates/react/src/main.tsx +49 -0
  25. package/templates/react/src/pages/About.tsx +16 -0
  26. package/templates/react/src/pages/Home.tsx +34 -0
  27. package/templates/react/src/pages/NotFound.tsx +25 -0
  28. package/templates/react/src/pages/ProductDetail.tsx +34 -0
  29. package/templates/react/src/pages/Search.tsx +23 -0
  30. package/templates/react/src/ssr.tsx +23 -0
  31. package/templates/react/tsconfig.json +17 -0
  32. package/templates/react/vite.config.ts +20 -0
  33. package/templates/react-minimal/env.d.ts +1 -0
  34. package/templates/react-minimal/index.html +13 -0
  35. package/templates/react-minimal/package.json +21 -0
  36. package/templates/react-minimal/src/App.tsx +22 -0
  37. package/templates/react-minimal/src/bootstrap.ts +6 -0
  38. package/templates/react-minimal/src/lib/controllers/home.ts +14 -0
  39. package/templates/react-minimal/src/main.tsx +44 -0
  40. package/templates/react-minimal/src/ssr.tsx +25 -0
  41. package/templates/react-minimal/tsconfig.json +17 -0
  42. package/templates/react-minimal/vite.config.ts +12 -0
  43. package/templates/svelte/env.d.ts +9 -0
  44. package/templates/svelte/index.html +13 -0
  45. package/templates/svelte/package.json +18 -0
  46. package/templates/svelte/src/App.svelte +85 -0
  47. package/templates/svelte/src/actions.ts +14 -0
  48. package/templates/svelte/src/bootstrap.ts +36 -0
  49. package/templates/svelte/src/components/Layout.svelte +19 -0
  50. package/templates/svelte/src/components/Loading.svelte +1 -0
  51. package/templates/svelte/src/components/Navigation.svelte +75 -0
  52. package/templates/svelte/src/components/PageRenderer.svelte +22 -0
  53. package/templates/svelte/src/components/ProductCard.svelte +37 -0
  54. package/templates/svelte/src/lib/controllers/about.ts +18 -0
  55. package/templates/svelte/src/lib/controllers/error.ts +11 -0
  56. package/templates/svelte/src/lib/controllers/home.ts +53 -0
  57. package/templates/svelte/src/lib/controllers/product-detail.ts +52 -0
  58. package/templates/svelte/src/lib/controllers/search.ts +58 -0
  59. package/templates/svelte/src/lib/framework-svelte.ts +34 -0
  60. package/templates/svelte/src/lib/guards/auth.ts +17 -0
  61. package/templates/svelte/src/lib/guards/seo.ts +15 -0
  62. package/templates/svelte/src/lib/mappers/product.ts +27 -0
  63. package/templates/svelte/src/lib/models/product.ts +47 -0
  64. package/templates/svelte/src/lib/render.ts +15 -0
  65. package/templates/svelte/src/lib/services/api-client.ts +28 -0
  66. package/templates/svelte/src/main.ts +27 -0
  67. package/templates/svelte/src/pages/About.svelte +10 -0
  68. package/templates/svelte/src/pages/Home.svelte +34 -0
  69. package/templates/svelte/src/pages/NotFound.svelte +23 -0
  70. package/templates/svelte/src/pages/ProductDetail.svelte +32 -0
  71. package/templates/svelte/src/pages/Search.svelte +29 -0
  72. package/templates/svelte/src/ssr.ts +12 -0
  73. package/templates/svelte/tsconfig.json +16 -0
  74. package/templates/svelte/vite.config.ts +21 -0
  75. package/templates/svelte-minimal/env.d.ts +9 -0
  76. package/templates/svelte-minimal/index.html +13 -0
  77. package/templates/svelte-minimal/package.json +18 -0
  78. package/templates/svelte-minimal/src/App.svelte +63 -0
  79. package/templates/svelte-minimal/src/bootstrap.ts +6 -0
  80. package/templates/svelte-minimal/src/lib/controllers/home.ts +15 -0
  81. package/templates/svelte-minimal/src/lib/framework-svelte.ts +34 -0
  82. package/templates/svelte-minimal/src/lib/render.ts +12 -0
  83. package/templates/svelte-minimal/src/main.ts +22 -0
  84. package/templates/svelte-minimal/src/pages/Home.svelte +10 -0
  85. package/templates/svelte-minimal/src/ssr.ts +18 -0
  86. package/templates/svelte-minimal/tsconfig.json +16 -0
  87. package/templates/svelte-minimal/vite.config.ts +12 -0
  88. package/templates/vue/env.d.ts +7 -0
  89. package/templates/vue/index.html +13 -0
  90. package/templates/vue/package.json +18 -0
  91. package/templates/vue/src/App.vue +29 -0
  92. package/templates/vue/src/actions.ts +14 -0
  93. package/templates/vue/src/bootstrap.ts +42 -0
  94. package/templates/vue/src/components/Layout.vue +18 -0
  95. package/templates/vue/src/components/Loading.vue +3 -0
  96. package/templates/vue/src/components/Navigation.vue +58 -0
  97. package/templates/vue/src/components/PageRenderer.vue +26 -0
  98. package/templates/vue/src/components/ProductCard.vue +39 -0
  99. package/templates/vue/src/lib/controllers/about.ts +18 -0
  100. package/templates/vue/src/lib/controllers/error.ts +11 -0
  101. package/templates/vue/src/lib/controllers/home.ts +53 -0
  102. package/templates/vue/src/lib/controllers/product-detail.ts +52 -0
  103. package/templates/vue/src/lib/controllers/search.ts +58 -0
  104. package/templates/vue/src/lib/guards/auth.ts +17 -0
  105. package/templates/vue/src/lib/guards/seo.ts +15 -0
  106. package/templates/vue/src/lib/mappers/product.ts +27 -0
  107. package/templates/vue/src/lib/models/product.ts +47 -0
  108. package/templates/vue/src/lib/services/api-client.ts +31 -0
  109. package/templates/vue/src/main.ts +46 -0
  110. package/templates/vue/src/pages/About.vue +16 -0
  111. package/templates/vue/src/pages/Home.vue +38 -0
  112. package/templates/vue/src/pages/NotFound.vue +22 -0
  113. package/templates/vue/src/pages/ProductDetail.vue +34 -0
  114. package/templates/vue/src/pages/Search.vue +33 -0
  115. package/templates/vue/src/ssr.ts +24 -0
  116. package/templates/vue/tsconfig.json +17 -0
  117. package/templates/vue/vite.config.ts +20 -0
  118. package/templates/vue-minimal/env.d.ts +7 -0
  119. package/templates/vue-minimal/index.html +13 -0
  120. package/templates/vue-minimal/package.json +18 -0
  121. package/templates/vue-minimal/src/App.vue +21 -0
  122. package/templates/vue-minimal/src/bootstrap.ts +6 -0
  123. package/templates/vue-minimal/src/lib/controllers/home.ts +15 -0
  124. package/templates/vue-minimal/src/main.ts +41 -0
  125. package/templates/vue-minimal/src/pages/Home.vue +12 -0
  126. package/templates/vue-minimal/src/ssr.ts +28 -0
  127. package/templates/vue-minimal/tsconfig.json +17 -0
  128. package/templates/vue-minimal/vite.config.ts +12 -0
@@ -0,0 +1,58 @@
1
+ <script setup lang="ts">
2
+ import { makeExternalUrlAction, makeFlowAction, type Action } from "@finesoft/front";
3
+
4
+ const { currentPath = "/", onAction } = defineProps<{
5
+ currentPath?: string;
6
+ onAction?: (action: Action) => void;
7
+ }>();
8
+
9
+ const links = [
10
+ { label: "Home", action: makeFlowAction("/"), path: "/" },
11
+ { label: "Search", action: makeFlowAction("/search"), path: "/search" },
12
+ { label: "About", action: makeFlowAction("/about"), path: "/about" },
13
+ { label: "GitHub", action: makeExternalUrlAction("https://github.com"), path: null as null },
14
+ ];
15
+
16
+ function handleClick(action: Action) {
17
+ return (e: MouseEvent) => {
18
+ if (onAction) {
19
+ e.preventDefault();
20
+ onAction(action);
21
+ }
22
+ };
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+ <nav>
28
+ <a
29
+ v-for="link in links"
30
+ :key="link.label"
31
+ :href="link.action.url"
32
+ :class="{ active: link.path !== null && currentPath === link.path }"
33
+ @click="handleClick(link.action)"
34
+ >
35
+ {{ link.label }}
36
+ </a>
37
+ </nav>
38
+ </template>
39
+
40
+ <style scoped>
41
+ nav {
42
+ display: flex;
43
+ gap: 1rem;
44
+ padding: 1rem;
45
+ border-bottom: 1px solid #eee;
46
+ }
47
+ a {
48
+ text-decoration: none;
49
+ color: #333;
50
+ }
51
+ a:hover {
52
+ color: #007bff;
53
+ }
54
+ a.active {
55
+ color: #007bff;
56
+ font-weight: bold;
57
+ }
58
+ </style>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import type { AppPage } from "../lib/models/product";
4
+ import About from "../pages/About.vue";
5
+ import Home from "../pages/Home.vue";
6
+ import NotFound from "../pages/NotFound.vue";
7
+ import ProductDetail from "../pages/ProductDetail.vue";
8
+ import Search from "../pages/Search.vue";
9
+
10
+ const { page, onAction } = defineProps<{
11
+ page: AppPage;
12
+ onAction?: (action: Action) => void;
13
+ }>();
14
+
15
+ const pageComponents: Record<string, unknown> = {
16
+ home: Home,
17
+ product: ProductDetail,
18
+ search: Search,
19
+ about: About,
20
+ error: NotFound,
21
+ };
22
+ </script>
23
+
24
+ <template>
25
+ <component :is="pageComponents[page.pageType] ?? NotFound" :page="page" :on-action="onAction" />
26
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import type { ProductItem } from "../lib/models/product";
4
+
5
+ const { item, onAction } = defineProps<{
6
+ item: ProductItem;
7
+ onAction?: (action: Action) => void;
8
+ }>();
9
+
10
+ function handleClick(e: MouseEvent) {
11
+ if (onAction && item.clickAction) {
12
+ e.preventDefault();
13
+ onAction(item.clickAction);
14
+ }
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <div class="product-card">
20
+ <h3>{{ item.name }}</h3>
21
+ <p class="price">${{ item.price.toFixed(2) }}</p>
22
+ <a v-if="item.clickAction" :href="item.clickAction.url" @click="handleClick">
23
+ View Details →
24
+ </a>
25
+ </div>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .product-card {
30
+ border: 1px solid #eee;
31
+ border-radius: 8px;
32
+ padding: 1rem;
33
+ min-width: 200px;
34
+ }
35
+ .price {
36
+ color: #007bff;
37
+ font-weight: bold;
38
+ }
39
+ </style>
@@ -0,0 +1,18 @@
1
+ import { BaseController } from "@finesoft/front";
2
+ import type { AboutPage } from "../models/product";
3
+
4
+ export class AboutController extends BaseController<Record<string, string>, AboutPage> {
5
+ readonly intentId = "about";
6
+
7
+ execute(): AboutPage {
8
+ return {
9
+ id: "about",
10
+ pageType: "about",
11
+ url: "/about",
12
+ title: "About",
13
+ description: "About Finesoft Front",
14
+ content:
15
+ "Finesoft Front is a full-stack TypeScript framework with router, DI, middleware, SSR, and multi-platform deployment.",
16
+ };
17
+ }
18
+ }
@@ -0,0 +1,11 @@
1
+ import type { ErrorPage } from "../models/product";
2
+
3
+ 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
+ };
11
+ }
@@ -0,0 +1,53 @@
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";
33
+
34
+ execute(): HomePage {
35
+ const shelf: ProductShelf = {
36
+ id: "featured",
37
+ shelfType: "products",
38
+ title: "Featured Products",
39
+ isHorizontal: true,
40
+ seeAllAction: makeFlowAction("/search"),
41
+ items: MOCK_PRODUCTS,
42
+ };
43
+
44
+ return {
45
+ id: "home",
46
+ pageType: "home",
47
+ url: "/",
48
+ title: "Home",
49
+ description: "Welcome to Finesoft Front Demo",
50
+ shelves: [shelf],
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,52 @@
1
+ import {
2
+ BaseController,
3
+ type Container,
4
+ DEP_KEYS,
5
+ type Logger,
6
+ type LoggerFactory,
7
+ } from "@finesoft/front";
8
+ import type { ProductPage } from "../models/product";
9
+
10
+ export class ProductDetailController extends BaseController<{ id: string }, ProductPage> {
11
+ readonly intentId = "product-detail";
12
+
13
+ async execute(params: { id: string }, container: Container): Promise<ProductPage> {
14
+ const loggerFactory = container.resolve<LoggerFactory>(DEP_KEYS.LOGGER_FACTORY);
15
+ const log: Logger = loggerFactory.loggerFor("ProductDetailController");
16
+ log.info(`Loading product ${params.id}`);
17
+
18
+ // Simulated API fetch — replace with real HttpClient call
19
+ return {
20
+ id: `product-${params.id}`,
21
+ pageType: "product",
22
+ url: `/products/${params.id}`,
23
+ title: `Product ${params.id}`,
24
+ description: `Details for product ${params.id}`,
25
+ product: {
26
+ id: 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`,
32
+ },
33
+ };
34
+ }
35
+
36
+ fallback(params: { id: string }, error: Error): ProductPage {
37
+ return {
38
+ id: `product-${params.id}`,
39
+ pageType: "product",
40
+ url: `/products/${params.id}`,
41
+ title: "Product Not Found",
42
+ description: error.message,
43
+ product: {
44
+ id: params.id,
45
+ name: "Unknown Product",
46
+ price: 0,
47
+ description: "Could not load product data.",
48
+ imageUrl: "/img/placeholder.svg",
49
+ },
50
+ };
51
+ }
52
+ }
@@ -0,0 +1,58 @@
1
+ import { BaseController, makeFlowAction } from "@finesoft/front";
2
+ import type { ProductItem, SearchPage } from "../models/product";
3
+
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
+ ];
38
+
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
+ url: "/search",
52
+ title: query ? `Search: ${query}` : "All Products",
53
+ description: `${results.length} result(s)`,
54
+ query,
55
+ results,
56
+ };
57
+ }
58
+ }
@@ -0,0 +1,17 @@
1
+ import { type BeforeLoadGuard, next, redirect } from "@finesoft/front";
2
+
3
+ /**
4
+ * Auth guard — redirects unauthenticated users to login.
5
+ *
6
+ * Demonstrates beforeLoad middleware with `redirect()` and `next()`.
7
+ * In a real app, check a session cookie or token via `ctx.getCookie()`.
8
+ */
9
+ export const authGuard: BeforeLoadGuard = (ctx) => {
10
+ const token = ctx.getCookie("auth_token");
11
+
12
+ if (!token) {
13
+ return redirect(`/login?from=${encodeURIComponent(ctx.url)}`);
14
+ }
15
+
16
+ return next();
17
+ };
@@ -0,0 +1,15 @@
1
+ import { type AfterLoadGuard, next, rewrite } from "@finesoft/front";
2
+
3
+ /**
4
+ * SEO guard — normalizes URLs after data load.
5
+ *
6
+ * Demonstrates afterLoad middleware with `rewrite()`.
7
+ * Strips trailing slashes for canonical URL consistency.
8
+ */
9
+ export const seoGuard: AfterLoadGuard = (ctx) => {
10
+ if (ctx.path !== "/" && ctx.path.endsWith("/")) {
11
+ return rewrite(ctx.path.slice(0, -1));
12
+ }
13
+
14
+ return next();
15
+ };
@@ -0,0 +1,27 @@
1
+ import { makeFlowAction, mapEach, type Mapper } from "@finesoft/front";
2
+ import type { ProductItem } from "../models/product";
3
+
4
+ /** Raw API response shape */
5
+ interface ApiProduct {
6
+ id: number;
7
+ title: string;
8
+ price: number;
9
+ thumbnail?: string;
10
+ }
11
+
12
+ /** Transform raw API product → domain ProductItem */
13
+ const toProductItem: Mapper<ApiProduct, ProductItem> = (raw) => ({
14
+ id: String(raw.id),
15
+ itemType: "product",
16
+ name: raw.title,
17
+ price: raw.price,
18
+ imageUrl: raw.thumbnail ?? "/img/placeholder.svg",
19
+ clickAction: makeFlowAction(`/products/${raw.id}`),
20
+ });
21
+
22
+ /**
23
+ * Composed mapper pipeline: ApiProduct[] → ProductItem[]
24
+ *
25
+ * Demonstrates pipe() + mapEach() for data transformation.
26
+ */
27
+ export const mapProducts: Mapper<ApiProduct[], ProductItem[]> = mapEach(toProductItem);
@@ -0,0 +1,47 @@
1
+ import type { BaseItem, BasePage, BaseShelf } from "@finesoft/front";
2
+
3
+ export interface ProductItem extends BaseItem {
4
+ itemType: "product";
5
+ name: string;
6
+ price: number;
7
+ imageUrl: string;
8
+ }
9
+
10
+ export interface ProductShelf extends BaseShelf {
11
+ shelfType: "products";
12
+ items: ProductItem[];
13
+ }
14
+
15
+ export interface HomePage extends BasePage {
16
+ pageType: "home";
17
+ shelves: ProductShelf[];
18
+ }
19
+
20
+ export interface ProductPage extends BasePage {
21
+ pageType: "product";
22
+ product: {
23
+ id: string;
24
+ name: string;
25
+ price: number;
26
+ description: string;
27
+ imageUrl: string;
28
+ };
29
+ }
30
+
31
+ export interface SearchPage extends BasePage {
32
+ pageType: "search";
33
+ query: string;
34
+ results: ProductItem[];
35
+ }
36
+
37
+ export interface AboutPage extends BasePage {
38
+ pageType: "about";
39
+ content: string;
40
+ }
41
+
42
+ export interface ErrorPage extends BasePage {
43
+ pageType: "error";
44
+ status: number;
45
+ }
46
+
47
+ export type AppPage = HomePage | ProductPage | SearchPage | AboutPage | ErrorPage;
@@ -0,0 +1,31 @@
1
+ import { HttpClient, type HttpClientConfig } from "@finesoft/front";
2
+
3
+ /**
4
+ * Demo API client — extends HttpClient for typed endpoint access.
5
+ *
6
+ * Showcases HttpClient subclass pattern with base URL and custom fetch.
7
+ */
8
+ export class ApiClient extends HttpClient {
9
+ constructor(config?: Partial<HttpClientConfig>) {
10
+ super({
11
+ baseUrl: config?.baseUrl ?? "/api",
12
+ defaultHeaders: {
13
+ "Content-Type": "application/json",
14
+ ...config?.defaultHeaders,
15
+ },
16
+ fetch: config?.fetch,
17
+ });
18
+ }
19
+
20
+ async getProducts(): Promise<unknown[]> {
21
+ return this.get<unknown[]>("/products");
22
+ }
23
+
24
+ async getProduct(id: string): Promise<unknown> {
25
+ return this.get<unknown>(`/products/${id}`);
26
+ }
27
+
28
+ async searchProducts(query: string): Promise<unknown[]> {
29
+ return this.get<unknown[]>("/products", { q: query });
30
+ }
31
+ }
@@ -0,0 +1,46 @@
1
+ import { startBrowserApp, type Action, type BasePage, type Framework } from "@finesoft/front";
2
+ import { createApp, reactive } from "vue";
3
+ import App from "./App.vue";
4
+ import { bootstrap } from "./bootstrap";
5
+ import type { AppPage } from "./lib/models/product";
6
+
7
+ void startBrowserApp({
8
+ bootstrap,
9
+ mount(target: HTMLElement, { framework }: { framework: Framework; locale: string }) {
10
+ const state = reactive<{ page: AppPage | null; loading: boolean }>({
11
+ page: null,
12
+ loading: false,
13
+ });
14
+ const handleAction = (action: Action) => {
15
+ void framework.perform(action);
16
+ };
17
+ createApp(App, { state, onAction: handleAction }).mount(target);
18
+
19
+ return ({
20
+ page,
21
+ isFirstPage,
22
+ }: {
23
+ page: Promise<BasePage> | BasePage;
24
+ isFirstPage?: boolean;
25
+ }) => {
26
+ if (page instanceof Promise) {
27
+ if (!isFirstPage) state.loading = true;
28
+ void page.then((p) => {
29
+ state.page = p as AppPage;
30
+ state.loading = false;
31
+ });
32
+ } else {
33
+ state.page = page as AppPage;
34
+ state.loading = false;
35
+ }
36
+ };
37
+ },
38
+ callbacks: {
39
+ onNavigate(pathname) {
40
+ console.log("[navigate]", pathname);
41
+ },
42
+ onModal(page) {
43
+ console.log("[modal]", page.title);
44
+ },
45
+ },
46
+ });
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import type { AboutPage } from "../lib/models/product";
4
+
5
+ const { page, onAction: _onAction } = defineProps<{
6
+ page: AboutPage;
7
+ onAction?: (action: Action) => void;
8
+ }>();
9
+ </script>
10
+
11
+ <template>
12
+ <div>
13
+ <h1>{{ page.title }}</h1>
14
+ <p>{{ page.content }}</p>
15
+ </div>
16
+ </template>
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import ProductCard from "../components/ProductCard.vue";
4
+ import type { HomePage } from "../lib/models/product";
5
+
6
+ const { page, onAction } = defineProps<{ page: HomePage; onAction?: (action: Action) => void }>();
7
+ </script>
8
+
9
+ <template>
10
+ <div>
11
+ <h1>{{ page.title }}</h1>
12
+ <p>{{ page.description }}</p>
13
+
14
+ <section v-for="shelf in page.shelves" :key="shelf.id">
15
+ <h2>{{ shelf.title }}</h2>
16
+ <div class="shelf" :class="{ horizontal: shelf.isHorizontal }">
17
+ <ProductCard
18
+ v-for="item in shelf.items"
19
+ :key="item.id"
20
+ :item="item"
21
+ :on-action="onAction"
22
+ />
23
+ </div>
24
+ </section>
25
+ </div>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .shelf {
30
+ display: flex;
31
+ gap: 1rem;
32
+ flex-wrap: wrap;
33
+ }
34
+ .shelf.horizontal {
35
+ flex-wrap: nowrap;
36
+ overflow-x: auto;
37
+ }
38
+ </style>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import { NAV_ACTIONS } from "../actions";
4
+ import type { ErrorPage } from "../lib/models/product";
5
+
6
+ const { page, onAction } = defineProps<{ page: ErrorPage; onAction?: (action: Action) => void }>();
7
+
8
+ function handleHome(e: MouseEvent) {
9
+ if (onAction) {
10
+ e.preventDefault();
11
+ onAction(NAV_ACTIONS.home);
12
+ }
13
+ }
14
+ </script>
15
+
16
+ <template>
17
+ <div>
18
+ <h1>{{ page.title }}</h1>
19
+ <p>{{ page.description }}</p>
20
+ <a href="/" @click="handleHome">← Go Home</a>
21
+ </div>
22
+ </template>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import { NAV_ACTIONS } from "../actions";
4
+ import type { ProductPage } from "../lib/models/product";
5
+
6
+ const { page, onAction } = defineProps<{
7
+ page: ProductPage;
8
+ onAction?: (action: Action) => void;
9
+ }>();
10
+
11
+ function handleBack(e: MouseEvent) {
12
+ if (onAction) {
13
+ e.preventDefault();
14
+ onAction(NAV_ACTIONS.home);
15
+ }
16
+ }
17
+ </script>
18
+
19
+ <template>
20
+ <div>
21
+ <a href="/" @click="handleBack">← Back</a>
22
+ <h1>{{ page.product.name }}</h1>
23
+ <p class="price">${{ page.product.price.toFixed(2) }}</p>
24
+ <p>{{ page.product.description }}</p>
25
+ </div>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .price {
30
+ font-size: 1.5rem;
31
+ color: #007bff;
32
+ font-weight: bold;
33
+ }
34
+ </style>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import type { Action } from "@finesoft/front";
3
+ import ProductCard from "../components/ProductCard.vue";
4
+ import type { SearchPage } from "../lib/models/product";
5
+
6
+ const { page, onAction } = defineProps<{ page: SearchPage; onAction?: (action: Action) => void }>();
7
+ </script>
8
+
9
+ <template>
10
+ <div>
11
+ <h1>{{ page.title }}</h1>
12
+ <p>{{ page.description }}</p>
13
+
14
+ <div class="results">
15
+ <ProductCard
16
+ v-for="item in page.results"
17
+ :key="item.id"
18
+ :item="item"
19
+ :on-action="onAction"
20
+ />
21
+ </div>
22
+
23
+ <p v-if="page.results.length === 0">No products found.</p>
24
+ </div>
25
+ </template>
26
+
27
+ <style scoped>
28
+ .results {
29
+ display: flex;
30
+ gap: 1rem;
31
+ flex-wrap: wrap;
32
+ }
33
+ </style>