Icecream business

You are now the owner of a Korean-style yogurt ice cream shop, "Ko-yo". The shop is located in San Francisco and you need to open the shop and start your business. The shop is 20 square meters in size.
The concept of Korean-style yogurt ice cream is that customers can freely add their desired toppings to the yogurt base ice cream. You need to implement a order form to allow customers to select and add toppings freely.
start-business
First, let's create an akan app with the project code "koyo". The koyo project is a service that operates all services under your company as the brand "Ko-yo". Now, let's type the following command in the terminal.
akan create-application koyo
Now, you can run the koyo service locally with the following command. Then, you can access the service at http://localhost:4201.
akan start koyo

Create icecream order module

Now, customers want to order the ice cream. The order is simple. You need to select the size of the yogurt base ice cream and check the desired toppings.
In Akan.js, we organize features into "modules" - think of them as complete packages that handle everything related to one thing. An ice cream order module will contain all the code needed to create, display, and manage ice cream orders.
When you create a module, Akan.js automatically generates all the files you need following a consistent pattern. This makes your code organized and easy to understand.
toppings
1Fruit Ring
2Oreo
3Strawberry
4Mango
5Cheese Cube
6Corn
7Granola
8Banana
9Fig
Now, let's create a domain for the ice cream order. The domain name is icecreamOrder, and it stores information about each ice cream order.

akan create-module icecreamOrder
# then select koyo application
This command will ask you which application to add the module to - select "koyo" since that's our ice cream shop app. The module name "icecreamOrder" describes what this module handles.
After running this command, Akan.js creates a complete folder structure with all the files you need. Let's look at what gets created:

└── apps/          # Application code
    └── koyo/      # Individual application
        └── lib/          # Domain modules
            └── icecreamOrder/  # Icecream order domain module
                ├── icecreamOrder.constant.ts   # Types and schemas
                ├── icecreamOrder.dictionary.ts # Translations
                ├── icecreamOrder.document.ts   # Document
                ├── icecreamOrder.service.ts    # Business logic
                ├── icecreamOrder.signal.ts     # API endpoints
                ├── icecreamOrder.store.ts      # State management
                ├── icecreamOrder.Template.tsx  # Form UI
                ├── icecreamOrder.Unit.tsx      # Overview UI
                ├── icecreamOrder.Util.tsx      # Utility UI
                ├── icecreamOrder.View.tsx      # Detail view UI
                └── icecreamOrder.Zone.tsx      # Integration UI
Each file has a specific purpose in organizing your ice cream order feature:
📋
constant.ts: Defines what an ice cream order looks like (size, toppings, etc.)
🌍
dictionary.ts: Translates technical terms into user-friendly language
🗄️
document.ts: Handles database storage and retrieval
⚙️
service.ts: Contains business logic (creating orders, calculating prices)
🔗
signal.ts: Connects frontend to backend with API calls
📦
store.ts: Manages form state and user interactions
🎨
UI Files (.tsx): Create the visual components customers see and interact with
Don't worry about understanding every file right now! We'll work through them step by step. The important thing is that Akan.js is based on this organized structure.

Define Constant

Constant file is a file that defines the shape of the object in the domain. It stores the shape of the object in the database and allows the client to query and represent data based on this shape.
1
2// File: apps/koyo/lib/icecreamOrder/icecreamOrder.constant.ts
3import { enumOf, Int } from "@akanjs/base";
4import { Field, Filter, Model, sortOf, via } from "@akanjs/constant";
5
6export const IcecreamOrderStatus = enumOf(["active", "processing", "served", "canceled"] as const);
7export type IcecreamOrderStatus = enumOf<typeof IcecreamOrderStatus>;
8
9export const Topping = enumOf(["fruitRings", "oreo", "strawberry", "mango", "cheeseCube", "corn", "granola", "banana", "fig"] as const);
10export type Topping = enumOf<typeof Topping>;
11          
12@Model.Input("IcecreamOrderInput")
13export class IcecreamOrderInput {
14  @Field.Prop(() => Int, { default: 50 })
15  size: 50 | 100 | 200;
16
17  @Field.Prop(() => [String], { enum: Topping })
18  toppings: Topping[];
19}
20// ...other code
The ice cream order can be represented as an object like the above. The IcecreamOrderInput, which is information for creating an order, can create an order by specifying the size and toppings of the ice cream.
Great! Now we have defined what our ice cream order looks like. But how will customers see "size" and "toppings" in their own language? That's where the dictionary comes in next.

Fill dictionary

To display and describe each data of the object to the user, you need to fill the dictionary file. You can set how to display each field to the user in the dictionary, and multi-language display is supported by default.
The dictionary starts with two important entries that describe your entire module:
🏷️modelName
This is what users will see as the title or name of your feature. Instead of the technical "icecreamOrder", users see "Ice cream Order" in English or "아이스크림 주문" in Korean.
📝modelDesc
This provides a longer explanation of what this feature does. It helps users understand the purpose and context of ice cream orders in your shop.
1
2// File: apps/koyo/lib/icecreamOrder/icecreamOrder.dictionary.ts
3// ...other code
4const modelDictionary = {
5  ...baseTrans,
6  modelName: ["IcecreamOrder", "아이스크림 주문"],
7  modelDesc: [
8    "IcecreamOrder is an option that customers can choose when ordering icecream at koyo store.",
9    "아이스크림 주문은 koyo 가게에서 고객이 아이스크림을 주문할 때 커스텀할 수 있는 옵션들을 선택할 수 있습니다.",
10  ],
11
12  // * ==================== Model ==================== * //
13  size: ["Size", "사이즈"],
14  "desc-size": ["Size", "사이즈"],
15
16  toppings: ["Toppings", "토핑"],
17  "desc-toppings": ["Toppings", "토핑"],
18  // * ==================== Model ==================== * //
19  
20  // ...other code
21
22  // * ==================== Etc ==================== * //
23  "enum-status-active": ["Active", "활성"],
24  "enumdesc-status-active": ["Active status", "활성 상태"],
25  "enum-status-processing": ["Processing", "처리중"],
26  "enumdesc-status-processing": ["Processing status", "처리중 상태"],
27  "enum-status-served": ["Served", "서빙완료"],
28  "enumdesc-status-served": ["Served status", "서빙완료 상태"],
29  "enum-status-canceled": ["Canceled", "취소됨"],
30  "enumdesc-status-canceled": ["Canceled status", "취소됨 상태"],
31
32  "enum-size-50": ["Small", "소형"],
33  "enumdesc-size-50": ["Small size", "소형 사이즈"],
34  "enum-size-100": ["Medium", "중형"],
35  "enumdesc-size-100": ["Medium size", "중형 사이즈"],
36  "enum-size-200": ["Large", "대형"],
37  "enumdesc-size-200": ["Large size", "큰 사이즈"],
38
39  "enum-toppings-fruitRings": ["Fruit Rings", "과일 링"],
40  "enumdesc-toppings-fruitRings": ["Fruit Rings", "과일 링"],
41  "enum-toppings-oreo": ["Oreo", "오레오"],
42  "enumdesc-toppings-oreo": ["Oreo", "오레오"],
43  "enum-toppings-strawberry": ["Strawberry", "딸기"],
44  "enumdesc-toppings-strawberry": ["Strawberry", "딸기"],
45  "enum-toppings-mango": ["Mango", "망고"],
46  "enumdesc-toppings-mango": ["Mango", "망고"],
47  "enum-toppings-cheeseCube": ["Cheese Cube", "치즈 큐브"],
48  "enumdesc-toppings-cheeseCube": ["Cheese Cube", "치즈 큐브"],
49  "enum-toppings-corn": ["Corn", "옥수수"],
50  "enumdesc-toppings-corn": ["Corn", "옥수수"],
51  "enum-toppings-granola": ["Granola", "그래놀라"],
52  "enumdesc-toppings-granola": ["Granola", "그래놀라"],
53  "enum-toppings-banana": ["Banana", "바나나"],
54  "enumdesc-toppings-banana": ["Banana", "바나나"],
55  "enum-toppings-fig": ["Fig", "무화과"],
56  "enumdesc-toppings-fig": ["Fig", "무화과"],
57  // * ==================== Etc ==================== * //
58
59  // ...other code
These translations make your app user-friendly! For example, when customers visit your ice cream shop website, they'll see "Ice cream Order" as a page title, not the technical "icecreamOrder". The description helps explain what the page is for.
Now customers will see "Size" instead of "size" and "Small/Medium/Large" instead of "50/100/200". Next, let's create the actual form where customers can place their orders.

Make template file

Now let's create the order form that customers will use. The Template file creates input forms where customers can select ice cream size and toppings.
1
2// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.Template.tsx
3"use client";
4import { cnst, st, usePage } from "@koyo/client";
5import { Layout, Field } from "@akanjs/ui";
6
7interface IcecreamOrderEditProps {
8  className?: string;
9}
10
11export const General = ({ className }: IcecreamOrderEditProps) => {
12  const icecreamOrderForm = st.use.icecreamOrderForm();
13  const { l } = usePage();
14  return (
15    <Layout.Template className={className}>
16      <Field.ToggleSelect
17        model="icecreamOrder"
18        field="size"
19        label={l.field("icecreamOrder", "size")}
20        items={[50, 100, 200] as const}
21        value={icecreamOrderForm.size}
22        onChange={st.do.setSizeOnIcecreamOrder}
23      />
24      <Field.MultiToggleSelect
25        model="icecreamOrder"
26        field="toppings"
27        label={l.field("icecreamOrder", "toppings")}
28        items={cnst.Topping}
29        value={icecreamOrderForm.toppings}
30        onChange={st.do.setToppingsOnIcecreamOrder}
31      />
32    </Layout.Template>
33  );
34};
This code creates two input fields: one for selecting ice cream size (small/medium/large) and another for choosing multiple toppings. The Field.ToggleSelect shows three size options as buttons, while Field.MultiToggleSelect allows customers to pick several toppings at once.
The "st.use.icecreamOrderForm()" gets the current form data, while "st.do.setSizeOnIcecreamOrder" and "st.do.setToppingsOnIcecreamOrder" update the form when customers make selections.
🎉 Now customers can create orders using your form. But how do we show those orders in a nice, visual way? Let's create a card design to display each order beautifully.

Update unit file

The Unit file shows how each order looks in a list or card view. Think of it as the "receipt" or "order summary" that displays the order information nicely.
1
2// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.Unit.tsx
3import { clsx, ModelProps } from "@akanjs/client";
4import { cnst, usePage } from "@koyo/client";
5
6export const Card = ({ icecreamOrder }: ModelProps<"icecreamOrder", cnst.LightIcecreamOrder>) => {
7  const { l } = usePage();
8  return (
9    <div className="group flex h-36 w-full overflow-hidden rounded-xl bg-gradient-to-br from-pink-100 via-yellow-50 to-pink-200 px-8 py-6 shadow-md transition-all duration-300 hover:shadow-xl">
10      <div className="flex w-full flex-col justify-center">
11        <div className="flex items-center gap-2 text-lg font-semibold text-pink-700">
12          <span className="inline-block rounded bg-pink-200 px-2 py-1 text-xs font-bold tracking-wider uppercase">
13            {l.field("icecreamOrder", "id")}
14          </span>
15          <span className="ml-2 font-mono text-pink-900">{icecreamOrder.id}</span>
16        </div>
17        <div className="mt-4 flex items-center gap-2">
18          <span className="inline-block rounded bg-yellow-200 px-2 py-1 text-xs font-bold tracking-wider text-yellow-800 uppercase">
19            {l.field("icecreamOrder", "status")}
20          </span>
21          <span
22            className={clsx("ml-2 rounded-full px-3 py-1 text-sm font-semibold", {
23              "bg-green-100 text-green-700": icecreamOrder.status === "active",
24              "bg-blue-100 text-blue-700": icecreamOrder.status === "processing",
25              "bg-red-100 text-red-700": icecreamOrder.status === "served",
26              "bg-gray-100 text-gray-700": icecreamOrder.status === "canceled",
27            })}
28          >
29            {l.enum("icecreamOrder", "status", icecreamOrder.status)}
30          </span>
31        </div>
32      </div>
33    </div>
34  );
35};
This creates a card design for each ice cream order. The card shows the order ID and status with different colors - green for "active", blue for "processing", and red for "served".
The clsx function changes the card's appearance based on the order status, and l.enum() displays the status text in the user's language.
🚀 We have the form (Template) and the display card (Unit). Now let's put it all together on a webpage so customers can actually visit and use your ice cream ordering system!

Expose to page

Finally, let's create a page where customers can actually place their ice cream orders. This page connects everything together and makes it accessible through a web URL.
1
2// File: apps/koyo/app/[lang]/icecreamOrder/page.tsx
3import { Load, Model } from "@akanjs/ui";
4import { cnst, fetch, IcecreamOrder, usePage } from "@koyo/client";
5
6export default function Page() {
7  const { l } = usePage();
8  return (
9    <Load.Page
10      of={Page}
11      loader={async () => {
12        const { icecreamOrderInitInPublic } = await fetch.initIcecreamOrderInPublic();
13        const icecreamOrderForm: Partial<cnst.IcecreamOrderInput> = {};
14        return { icecreamOrderInitInPublic, icecreamOrderForm };
15      }}
16      render={({ icecreamOrderInitInPublic, icecreamOrderForm }) => {
17        return (
18          <div>
19            <div className="flex items-center gap-4 pb-5 text-5xl font-black">
20              {l("icecreamOrder.modelName")}
21              <Model.New
22                className="btn btn-primary"
23                sliceName="icecreamOrderInPublic"
24                renderTitle="name"
25                partial={icecreamOrderForm}
26              >
27                <IcecreamOrder.Template.General />
28              </Model.New>
29            </div>
30            <IcecreamOrder.Zone.Card className="space-y-2" init={icecreamOrderInitInPublic} />
31          </div>
32        );
33      }}
34    />
35  );
36}
This complete page implementation shows how all the pieces work together! Let's break down what each part does:
🏷️l("icecreamOrder.modelName")
This displays the page title using our dictionary! It shows "Ice cream Order" (or "아이스크림 주문" in Korean) as a big, bold heading.
Model.New Button
This creates a "New Order" button that opens the form (Template.General) when clicked. Customers can use this to place new ice cream orders.
📋Zone.Card
This displays all existing ice cream orders as cards, showing the order details and status.
Now visit http://localhost:4201/icecreamOrder to see your ice cream ordering system!
Released under the MIT License
Official Akan.js Consulting onAkansoft
Copyright © 2025 Akan.js. All rights reserved.
System managed bybassman