akan create-application koyo
akan start koyo
1 | Fruit Ring |
---|---|
2 | Oreo |
3 | Strawberry |
4 | Mango |
5 | Cheese Cube |
6 | Corn |
7 | Granola |
8 | Banana |
9 | Fig |
akan create-module icecreamOrder
# then select koyo application
└── 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
// File: apps/koyo/lib/icecreamOrder/icecreamOrder.constant.ts
import { enumOf, Int } from "@akanjs/base";
import { Field, Filter, Model, sortOf, via } from "@akanjs/constant";
export const IcecreamOrderStatus = enumOf(["active", "processing", "served"] as const);
export type IcecreamOrderStatus = enumOf<typeof IcecreamOrderStatus>;
export const Topping = enumOf(["fruitRings", "oreo", "strawberry", "mango", "cheeseCube", "corn", "granola", "banana", "fig"] as const);
export type Topping = enumOf<typeof Topping>;
@Model.Input("IcecreamOrderInput")
export class IcecreamOrderInput {
@Field.Prop(() => Int, { default: 50 })
size: 50 | 100 | 200;
@Field.Prop(() => [String], { enum: Topping })
toppings: Topping[];
}
// ...other code
// File: apps/koyo/lib/icecreamOrder/icecreamOrder.dictionary.ts
// ...other code
const modelDictionary = {
...baseTrans,
modelName: ["IcecreamOrder", "아이스크림 주문"],
modelDesc: [
"IcecreamOrder is an option that customers can choose when ordering icecream at koyo store.",
"아이스크림 주문은 koyo 가게에서 고객이 아이스크림을 주문할 때 커스텀할 수 있는 옵션들을 선택할 수 있습니다.",
],
// * ==================== Model ==================== * //
size: ["Size", "사이즈"],
"desc-size": ["Size", "사이즈"],
toppings: ["Toppings", "토핑"],
"desc-toppings": ["Toppings", "토핑"],
// * ==================== Model ==================== * //
// ...other code
// * ==================== Etc ==================== * //
"enum-status-active": ["Active", "활성"],
"enumdesc-status-active": ["Active status", "활성 상태"],
"enum-size-50": ["Small", "소형"],
"enumdesc-size-50": ["Small size", "소형 사이즈"],
"enum-size-100": ["Medium", "중형"],
"enumdesc-size-100": ["Medium size", "중형 사이즈"],
"enum-size-200": ["Large", "대형"],
"enumdesc-size-200": ["Large size", "큰 사이즈"],
"enum-toppings-fruitRings": ["Fruit Rings", "과일 링"],
"enumdesc-toppings-fruitRings": ["Fruit Rings", "과일 링"],
"enum-toppings-oreo": ["Oreo", "오레오"],
"enumdesc-toppings-oreo": ["Oreo", "오레오"],
"enum-toppings-strawberry": ["Strawberry", "딸기"],
"enumdesc-toppings-strawberry": ["Strawberry", "딸기"],
"enum-toppings-mango": ["Mango", "망고"],
"enumdesc-toppings-mango": ["Mango", "망고"],
"enum-toppings-cheeseCube": ["Cheese Cube", "치즈 큐브"],
"enumdesc-toppings-cheeseCube": ["Cheese Cube", "치즈 큐브"],
"enum-toppings-corn": ["Corn", "옥수수"],
"enumdesc-toppings-corn": ["Corn", "옥수수"],
"enum-toppings-granola": ["Granola", "그래놀라"],
"enumdesc-toppings-granola": ["Granola", "그래놀라"],
"enum-toppings-banana": ["Banana", "바나나"],
"enumdesc-toppings-banana": ["Banana", "바나나"],
"enum-toppings-fig": ["Fig", "무화과"],
"enumdesc-toppings-fig": ["Fig", "무화과"],
// * ==================== Etc ==================== * //
// ...other code
// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.Template.tsx
"use client";
import { cnst, st, usePage } from "@myapp/client";
import { Layout, Field } from "@akanjs/ui";
interface IcecreamOrderEditProps {
className?: string;
}
export const General = ({ className }: IcecreamOrderEditProps) => {
const icecreamOrderForm = st.use.icecreamOrderForm();
const { l } = usePage();
return (
<Layout.Template className={className}>
<Field.ToggleSelect
model="icecreamOrder"
field="size"
label={l.field("icecreamOrder", "size")}
items={[50, 100, 200] as const}
value={icecreamOrderForm.size}
onChange={st.do.setSizeOnIcecreamOrder}
/>
<Field.MultiToggleSelect
model="icecreamOrder"
field="toppings"
label={l.field("icecreamOrder", "toppings")}
items={cnst.Topping}
value={icecreamOrderForm.toppings}
onChange={st.do.setToppingsOnIcecreamOrder}
/>
</Layout.Template>
);
};
// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.Unit.tsx
import { clsx, ModelProps } from "@akanjs/client";
import { cnst, usePage } from "@myapp/client";
export const Card = ({ icecreamOrder }: ModelProps<"icecreamOrder", cnst.LightIcecreamOrder>) => {
const { l } = usePage();
return (
<div className="animate-fadeIn 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">
<div className="flex w-full flex-col justify-center">
<div className="flex items-center gap-2 text-lg font-semibold text-pink-700">
<span className="inline-block rounded bg-pink-200 px-2 py-1 text-xs font-bold tracking-wider uppercase">
{l.field("icecreamOrder", "id")}
</span>
<span className="ml-2 font-mono text-pink-900">{icecreamOrder.id}</span>
</div>
<div className="mt-4 flex items-center gap-2">
<span className="inline-block rounded bg-yellow-200 px-2 py-1 text-xs font-bold tracking-wider text-yellow-800 uppercase">
{l.field("icecreamOrder", "status")}
</span>
<span
className={clsx("ml-2 rounded-full px-3 py-1 text-sm font-semibold", {
"bg-green-100 text-green-700": icecreamOrder.status === "active",
"bg-blue-100 text-blue-700": icecreamOrder.status === "processing",
"bg-red-100 text-red-700": icecreamOrder.status === "served",
})}
>
{l.enum("icecreamOrder", "status", icecreamOrder.status)}
</span>
</div>
</div>
</div>
);
};
// File: apps/koyo/app/[lang]/icecreamOrder/page.tsx
import { Load } from "@akanjs/ui";
import { fetch, IcecreamOrder } from "@myapp/client";
export default function Page() {
return (
<Load.Page
of={Page}
loader={async () => {
const { icecreamOrderInitInPublic } = await fetch.initIcecreamOrderInPublic();
return { icecreamOrderInitInPublic };
}}
render={({ icecreamOrderInitInPublic }) => {
return (
<div>
<div className="flex items-center gap-4 pb-5 text-5xl font-black">
{l("icecreamOrder.modelName")}
<Model.New
className="btn btn-primary"
sliceName="icecreamOrderInPublic"
renderTitle="name"
partial={icecreamOrderForm}
>
<IcecreamOrder.Template.General />
</Model.New>
</div>
<IcecreamOrder.Zone.Card className="space-y-2" init={icecreamOrderInitInPublic} />
</div>
);
}}
/>
);
}
Page-level components that display a full page or section
Card or list item components that display a single instance
Form components for creating or editing models
Layout containers that organize multiple components