📱

学園祭にモバイルオーダーを導入した #1

2024/12/19に公開

これは team411アドベントカレンダー 16日目の記事です。

前回はかずたつさんの「謎のプログラミング言語『DNCL』を書いてみたい」でした!defineを使って日本語で表現していてとても面白いです!

今回の記事では、2024年に東京都立大学で行われた第20回みやこ祭と、電気通信大学で行われた第74回調布祭において私たちteam411が導入したモバイルオーダーについて、自分の担当したフロントエンドの良かった点と改善点を紹介し、記事として記録しようと思います!

なお、12月下旬には同じくteam411アドベントカレンダーにて、モバイルオーダー中編・後編が投稿されるので、そちらも合わせてお読みください!そちらではバックエンドの解説やER図が見れると思います。

はじめに

まず、今回主に使用した技術スタックを紹介します。

カテゴリ 名称
フロントエンド Next.js, React
UI MUI, tailwind
APIの実装 SWR, OpenAPI, Swagger
バックエンド NestJS, AWS, Prisma
認証 LINE Liff, Firebase
決済 Stripe

この内、自分はフロントエンドやUIを担当しました。もともとReactを遊びで書いていたこともあり、MOプロジェクトに参加したのですが、新しい知識を得ることができ良い経験となりました。

作成したもの

まず、完成した画面をいくつか紹介します。(これらは開発環境であり、実際に使用したものとは異なります)

・ホーム画面

・商品の詳細画面

とくにスマートフォンで見やすく表示されるようレイアウトを作成しました。また、読み込み時には以下のようにバーが出るなど、ローディングのUIなども工夫しました。

それでは、注目してほしい点を紹介していきます。

良かったところ

・操作のしやすさ

これはカートの画面です。

この画面からでも商品の個数変更や削除を行えるようにしたことで、余計な画面遷移が一つでも少なくなるように設計しました。また、画面下のナビゲーションでは、カート内に商品がある際にバッジが付くように実装しました。さらに、サムネイルのアスペクト比はすべて1:1で統一していたのですが、わざわざ利用者にその縦横比で用意させるのは大変なので、店舗の管理画面において以下のような画像クロッパーを用意することで利用者の負担を減らしました。

・調布祭のWEBページとの連携を実装した

こちらは注文履歴の画面です。

「お呼び出し場所を確認」のボタンを押すと、なんと調布祭の地図のページに飛び、しかも自動でピンが立つようになっています!これで初めて大学に来た人でも迷うことがありません!(実は地図の実装にも関わったのはまた別の話)

・見た目をアプリっぽくした

今回のモバイルオーダーシステムはLINE内ブラウザを使用したためブラウザ上での運用となりましたが、利用者からしてみればアプリのようなものなので、見た目がブラウザのようにならないように極力工夫しました。例えば、ナビゲーションもそうですが、「さがす」ページでのタブを作ったり、

実際に運用されているモバイルオーダーアプリを参考にして誤って長押しで文字を選択されないようにしたりなど細かい工夫も行いました。

・カスタムフックの導入

フロントエンドからデータベースにアクセスするためにはバック側で用意されたエンドポイントを叩く必要があり、基本的にはURLを指定してPOSTやGETなどを行うと思いますが、今回は以下のようにカスタムフックが整備されたことによりリクエストがとても楽になりました。

sample.ts
  const [, setCart] = useCart();
  const { data: item, isLoading, error } = useItem(cartItem.itemId);
  const { data: optionCategories } = useAllOptionCategories();
hook.ts
export function useItem(itemId: number | null) {
  const { user } = useUser();
  const find = useSWR(user && itemId ? { url: `/items/${itemId}`, uid: user.uid } : null, ({ url }) =>
    client
      .get<paths["/api/items/{id}"]["get"]["responses"]["200"]["content"]["application/json"]>(url)
      .then((res) => res.data)
      .catch((err) => {
        if (err.response?.status === 404) {
          return Promise.reject({ status: 404, message: "Not Found" });
        }
        throw err;
      }),
  );

  中略

  return { ...find, data: sortedData };
}

しかも、Swaggerによってエンドポイントも見やすかったのでバックエンドにはとても感謝しています。

・細かいネタ

・オーバーフロー対策
最初はデバッグ中にとても大きい数字で遊んでいたため発見したバックエンドの仕様で、int型のためオーバーフローしてしまうのを防ぎます。

      const MAX_VALUE = (1 << 31) - 1;
      if (totalQuantity > MAX_VALUE) {
        toast.error("注文の合計商品数が多すぎます");
        return;
      }
      if (totalPrice < 50) {
        toast.error("注文の合計金額は50円以上である必要があります");
        return;
      } else if (totalPrice > MAX_VALUE) {
        toast.error("注文の合計金額が大きすぎます");
        return;
      }

ちなみに、調布祭直前には大量のコミットが...

その他、利用者アンケートでは様々なお声をいただきました。ありがとうございました。

改善点

色々と良かった点もありましたが、改善点ももちろんあります。

・導入店舗での操作が快適ではなかった

まずは以下の画像をご覧ください。「呼び出す」のボタンを押すと、次のような確認画面が表示されます。

・確認画面

このように、注文の状態を更新するボタンには確認モーダルを設けていたのですが、実際に調理を行いながら操作するには手順が多かった、という貴重な意見を利用者から頂きました。この点に関しては、実際に料理をしながらのデバッグを行わなかったため、気づくことが出来ませんでした。次回以降の実装ではより洗練できる点であると思います。

さいごに

ここまで様々な要素を紹介しましたが、一番良かった点としては失敗が起きずに本番を終えられたことだと思っています。チームメンバーのみなさんにも感謝しかありません。

さて、明日の記事はのぶさんの「2段階認証を楽に突破しよう」です。何やら怪しげなタイトルですね...
ここまでご覧いただきありがとうございました!!

Discussion