Kiosk for Customers

The number of customers at the store has increased, and the staff is having trouble processing orders. Let's create a kiosk for customers to order ice cream. A kiosk is a simple interface that allows customers to order ice cream.

Add Schema

There is some missing information in the current schema to allow customers to order directly from the kiosk. We need to add serve type and contact information. Let's expand the schema to add these features.
apps/angelo/lib/icecreamOrder/icecreamOrder.constant.ts
1import { enumOf, Int } from "@akanjs/base";
2import { isPhoneNumber } from "@akanjs/common";
3import { via } from "@akanjs/constant";
4
5export class ServeType extends enumOf("serveType", ["forHere", "takeOut", "delivery"] as const) {}
6
7// ...existing code...
8
9export class IcecreamOrderInput extends via((field) => ({
10  serveType: field(ServeType, { default: "forHere" }),
11  size: field(Int, { default: 50 }),
12  toppings: field([Topping]),
13  phone: field(String, { validate: isPhoneNumber })
14})) {}
15
16// ...existing code...
apps/angelo/lib/icecreamOrder/icecreamOrder.dictionary.ts
1// ...existing code...
2
3export const dictionary = modelDictionary(["en", "ko"])
4// ...existing code...
5  .model<IcecreamOrder>((t) => ({
6    serveType: t(["Serve Type", "서빙 타입"]).desc(["Serve type of the icecream order", "아이스크림 주문의 서빙 타입"]),
7    size: t(["Size", "사이즈"]).desc(["Size of the icecream order", "아이스크림 주문의 사이즈"]),
8    toppings: t(["Toppings", "토핑"]).desc(["Toppings of the icecream order", "아이스크림 주문의 토핑"]),
9    phone: t(["Phone", "전화번호"]).desc(["Phone number of the icecream order", "아이스크림 주문의 전화번호"]),
10    status: t(["Status", "상태"]).desc(["Status of the icecream order", "아이스크림 주문의 상태"]),
11  }))
12  .enum<ServeType>("serveType", (t) => ({
13    forHere: t(["For Here", "매장 식사"]).desc(["For Here serve type", "매장 식사 서빙 타입"]),
14    takeOut: t(["Take Out", "포장 주문"]).desc(["Take Out serve type", "포장 주문 서빙 타입"]),
15    delivery: t(["Delivery", "배달"]).desc(["Delivery serve type", "배달 서빙 타입"]),
16  }))
17// ...existing code...
apps/angelo/lib/icecreamOrder/IcecreamOrder.Template.tsx
1"use client";
2import { Field, Layout } from "@akanjs/ui";
3import { cnst, st, usePage } from "@koyo/client";
4
5interface IcecreamOrderEditProps {
6  className?: string;
7}
8
9export const General = ({ className }: IcecreamOrderEditProps) => {
10  const icecreamOrderForm = st.use.icecreamOrderForm();
11  const { l } = usePage();
12  return (
13    <Layout.Template className={className}>
14      <Field.ToggleSelect
15        label={l("icecreamOrder.size")}
16        items={[50, 100, 200].map((size) => ({ label: `${size}cc`, value: size }))}
17        value={icecreamOrderForm.size}
18        onChange={st.do.setSizeOnIcecreamOrder}
19      />
20      <Field.MultiToggleSelect
21        label={l("icecreamOrder.toppings")}
22        items={cnst.Topping}
23        value={icecreamOrderForm.toppings}
24        onChange={st.do.setToppingsOnIcecreamOrder}
25      />
26      <Field.Phone
27        label={l("icecreamOrder.phone")}
28        placeholder="010-0000-0000"
29        value={icecreamOrderForm.phone}
30        onChange={st.do.setPhoneOnIcecreamOrder}
31      />
32    </Layout.Template>
33  );
34};
35              

Refreshing Order List

apps/koyo/app/[lang]/customer/icecreamOrder/page.tsx
1import { Link, Load } from "@akanjs/ui";
2import { usePage } from "@koyo/client";
3import { SetLang } from "@koyo/ui";
4
5export default function Page() {
6  const { l } = usePage();
7  return (
8    <Load.Page
9      of={Page}
10      loader={async () => {
11        return await Promise.resolve({} as const);
12      }}
13      render={() => {
14        return (
15          <div className="flex min-h-screen flex-col items-center justify-center bg-linear-to-br from-purple-50 via-pink-50 to-blue-50 p-6">
16            <div className="absolute top-6 right-6 flex gap-2">
17              <Link.Lang
18                lang="en"
19                className="rounded-lg bg-white/70 px-4 py-2 font-semibold text-gray-700 backdrop-blur-sm transition-all duration-200 hover:bg-white hover:shadow-md"
20              >
21                English
22              </Link.Lang>
23              <Link.Lang
24                lang="ko"
25                className="rounded-lg bg-white/70 px-4 py-2 font-semibold text-gray-700 backdrop-blur-sm transition-all duration-200 hover:bg-white hover:shadow-md"
26              >
27                한국어
28              </Link.Lang>
29            </div>
30            <div className="w-full max-w-4xl space-y-8 text-center">
31              <div className="space-y-4">
32                <h1 className="bg-linear-to-r from-purple-600 via-pink-500 to-blue-500 bg-clip-text text-7xl font-bold text-transparent duration-1000 md:text-8xl">
33                  Koyo
34                </h1>
35                <p className="text-2xl font-light text-gray-700 delay-150 duration-1000 md:text-3xl">
36                  {l.trans({ en: "Korean Yogurt Ice Cream", ko: "한국 요거트 아이스크림" })}
37                </p>
38                <div className="flex items-center justify-center gap-2 text-lg text-gray-600 delay-300 duration-1000">
39                  <span className="text-3xl">🍦</span>
40                  <span>{l.trans({ en: "Fresh • Creamy • Delicious", ko: "신선한 • 부드러운 • 맛있는" })}</span>
41                  <span className="text-3xl">🍦</span>
42                </div>
43              </div>
44              <div className="flex flex-col items-center gap-4 pt-8 delay-500 duration-1000 sm:flex-row sm:justify-center">
45                <Link
46                  href="/customer/icecreamOrder/new?serveType=forHere"
47                  className="inline-flex w-full items-center justify-center gap-3 rounded-full bg-linear-to-r from-purple-600 via-pink-500 to-blue-500 px-10 py-6 text-2xl font-semibold text-white shadow-2xl transition-all duration-300 hover:scale-105 hover:from-purple-700 hover:via-pink-600 hover:to-blue-600 hover:shadow-purple-500/50 active:scale-95 sm:w-auto"
48                >
49                  <span className="text-4xl">🍽️</span>
50                  {l.trans({ en: "For Here", ko: "매장 식사" })}
51                </Link>
52                <Link
53                  href="/customer/icecreamOrder/new?serveType=takeout"
54                  className="inline-flex w-full items-center justify-center gap-3 rounded-full bg-linear-to-r from-blue-600 via-indigo-500 to-purple-500 px-10 py-6 text-2xl font-semibold text-white shadow-2xl transition-all duration-300 hover:scale-105 hover:from-blue-700 hover:via-indigo-600 hover:to-purple-600 hover:shadow-blue-500/50 active:scale-95 sm:w-auto"
55                >
56                  <span className="text-4xl">🛍️</span>
57                  {l.trans({ en: "Take Out", ko: "포장 주문" })}
58                </Link>
59              </div>
60            </div>
61          </div>
62        );
63      }}
64    />
65  );
66}
67  
apps/koyo/app/[lang]/customer/icecreamOrder/success/page.tsx
1import { Link, Load } from "@akanjs/ui";
2import { usePage } from "@koyo/client";
3
4export default function Page() {
5  const { l } = usePage();
6  return (
7    <Load.Page
8      of={Page}
9      loader={async () => {
10        return await Promise.resolve({} as const);
11      }}
12      render={() => {
13        return (
14          <div className="flex min-h-screen flex-col items-center justify-center bg-linear-to-br from-purple-50 via-pink-50 to-blue-50 p-6">
15            <div className="w-full max-w-2xl space-y-8 text-center">
16              <div className="flex justify-center">
17                <div className="flex h-32 w-32 items-center justify-center rounded-full bg-linear-to-r from-green-400 to-emerald-500 text-7xl shadow-2xl">
1819                </div>
20              </div>
21              <div className="space-y-4">
22                <h1 className="bg-linear-to-r from-purple-600 via-pink-500 to-blue-500 bg-clip-text text-5xl font-bold text-transparent md:text-6xl">
23                  {l.trans({ en: "Order Placed!", ko: "주문 완료!" })}
24                </h1>
25              </div>
26              <div className="rounded-2xl bg-white/70 p-8 shadow-xl backdrop-blur-sm">
27                <div className="space-y-3">
28                  <div className="flex items-center justify-center gap-2 text-lg text-gray-700">
29                    <span className="text-3xl">🎉</span>
30                    <span className="font-semibold">
31                      {l.trans({ en: "We're preparing your order", ko: "주문을 준비하고 있습니다" })}
32                    </span>
33                  </div>
34                  <p className="text-gray-600">
35                    {l.trans({
36                      en: "Please wait for your order number to be called",
37                      ko: "주문 번호가 호출될 때까지 기다려 주세요",
38                    })}
39                  </p>
40                </div>
41              </div>
42              <div className="pt-4">
43                <Link
44                  href="/customer/icecreamOrder"
45                  className="inline-flex items-center justify-center gap-3 rounded-full bg-linear-to-r from-purple-600 via-pink-500 to-blue-500 px-12 py-6 text-2xl font-semibold text-white shadow-2xl transition-all hover:scale-105 hover:from-purple-700 hover:via-pink-600 hover:to-blue-600 hover:shadow-purple-500/50 active:scale-95"
46                >
47                  <span className="text-4xl">🏠</span>
48                  {l.trans({ en: "Place New Order", ko: "새 주문하기" })}
49                </Link>
50              </div>
51            </div>
52          </div>
53        );
54      }}
55    />
56  );
57}

Refreshing Dashboard

apps/koyo/app/[lang]/customer/icecreamOrder/new/page.tsx
1import { Load } from "@akanjs/ui";
2import { cnst, IcecreamOrder, usePage } from "@koyo/client";
3
4interface PageProps {
5  searchParams: Promise<{
6    serveType?: cnst.ServeType["value"];
7  }>;
8}
9export default function Page({ searchParams }: PageProps) {
10  const { l } = usePage();
11  return (
12    <Load.Page
13      of={Page}
14      loader={async () => {
15        const { serveType } = await searchParams;
16        const icecreamOrderForm: Partial<cnst.IcecreamOrder> = { serveType };
17        return await Promise.resolve({ icecreamOrderForm });
18      }}
19      render={({ icecreamOrderForm }) => (
20        <div className="flex min-h-screen flex-col items-center justify-center bg-linear-to-br from-purple-50 via-pink-50 to-blue-50 p-6">
21          <div className="w-full max-w-2xl space-y-8">
22            <div className="space-y-4 text-center">
23              <div className="flex justify-center">
24                <span className="text-8xl">🍦</span>
25              </div>
26              <h1 className="bg-linear-to-r from-purple-600 via-pink-500 to-blue-500 bg-clip-text text-5xl font-bold text-transparent md:text-6xl">
27                {l("base.createModel", { model: l("icecreamOrder.modelName") })}
28              </h1>
29              <p className="text-xl font-light text-gray-600">
30                {l.trans({ en: "Customize your perfect treat", ko: "나만의 완벽한 디저트를 만들어보세요" })}
31              </p>
32            </div>
33            <Load.Edit
34              className="flex flex-col items-center"
35              sliceName="icecreamOrderInPublic"
36              edit={icecreamOrderForm}
37              type="form"
38              onCancel="back"
39              onSubmit="/customer/icecreamOrder/success"
40            >
41              <IcecreamOrder.Template.General />
42            </Load.Edit>
43          </div>
44        </div>
45      )}
46    />
47  );
48}
apps/angelo/lib/icecreamOrder/IcecreamOrder.Template.tsx
1"use client";
2import { clsx } from "@akanjs/client";
3import { Field, Layout } from "@akanjs/ui";
4import { cnst, st, usePage } from "@koyo/client";
5
6interface IcecreamOrderEditProps {
7  className?: string;
8}
9
10export const General = ({ className }: IcecreamOrderEditProps) => {
11  const icecreamOrderForm = st.use.icecreamOrderForm();
12  const { l } = usePage();
13  return (
14    <Layout.Template className={clsx("w-full space-y-6", className)}>
15      <div className="rounded-2xl bg-white/70 p-8 shadow-xl backdrop-blur-sm">
16        <div className="space-y-6">
17          <div className="flex items-center gap-3">
18            <span className="text-3xl">📏</span>
19            <h2 className="text-2xl font-semibold text-gray-800">{l("icecreamOrder.size")}</h2>
20          </div>
21          <Field.ToggleSelect
22            items={[50, 100, 200].map((size) => ({ label: `${size}cc`, value: size }))}
23            value={icecreamOrderForm.size}
24            onChange={st.do.setSizeOnIcecreamOrder}
25          />
26        </div>
27      </div>
28      <div className="rounded-2xl bg-white/70 p-8 shadow-xl backdrop-blur-sm">
29        <div className="space-y-6">
30          <div className="flex items-center gap-3">
31            <span className="text-3xl">🍓</span>
32            <h2 className="text-2xl font-semibold text-gray-800">{l("icecreamOrder.toppings")}</h2>
33          </div>
34          <Field.MultiToggleSelect
35            items={cnst.Topping}
36            value={icecreamOrderForm.toppings}
37            onChange={st.do.setToppingsOnIcecreamOrder}
38          />
39        </div>
40      </div>
41      <div className="rounded-2xl bg-white/70 p-8 shadow-xl backdrop-blur-sm">
42        <div className="space-y-6">
43          <div className="flex items-center gap-3">
44            <span className="text-3xl">📱</span>
45            <h2 className="text-2xl font-semibold text-gray-800">{l("icecreamOrder.phone")}</h2>
46          </div>
47          <Field.Phone
48            placeholder="010-0000-0000"
49            value={icecreamOrderForm.phone}
50            onChange={st.do.setPhoneOnIcecreamOrder}
51          />
52        </div>
53      </div>
54    </Layout.Template>
55  );
56};

Refreshing Order List

apps/angelo/app/[lang]/akanjs/(docs)/docs/(menus)/tutorials/page/page.tsx
Released under the MIT License
Official Akan.js Consulting onAkansoft
Copyright © 2025 Akan.js. All rights reserved.
System managed bybassman