24日目にLINEミニアプリが完成する初心者 ―― Day16. 顧客管理機能の実装


# はじめに

前回は「24 日目に LINE ミニアプリが完成する初心者 ―― Day15. スプレッドシートへの出力」と題して、問い合わせ内容を Google スプレッドシートに出力する機能を実装しました。

https://qiita.com/non_watanabe/items/3212d61e9b2d4e1ce69f

今回は、新機能として顧客情報をスプレッドシートへ出力して管理できる機能を実装していきます。

# 顧客管理スプレッドシートの準備

まず、顧客情報を保存するためのスプレッドシートを作成します。

今回は「LINE 顧客管理」という名前のスプレッドシートを作成し、以下のヘッダーを設定しました。

- **A1: ユーザ ID**
- **B1: 名前**
- **C1: 登録日**

以下のようなスプレッドシートが出来上がります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/f3dcce1f-0a8c-2b78-1256-42018adb952c.png)

# Google Apps Script の作成

次に、スプレッドシートに顧客情報を登録するための Google Apps Script（GAS）を作成します。

スプレッドシートのメニューから「拡張機能」>「Apps Script」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/0324d10b-fe3f-92c6-613c-520885a7b3f4.png)

プロジェクト名を「LINE 顧客管理」とします。

コードは以下の内容です。

```js
function doPost(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const data = JSON.parse(e.postData.contents);

  const { userId, name, email } = data;
  const registrationDate = new Date();

  // シート内のデータを取得して重複チェック
  const userIds = sheet
    .getRange("A2:A" + sheet.getLastRow())
    .getValues()
    .flat();

  if (!userIds.includes(userId)) {
    // 重複がない場合のみ追加
    sheet.appendRow([userId, name, email, registrationDate]);
    return ContentService.createTextOutput(
      JSON.stringify({ status: "success", message: "User added" })
    ).setMimeType(ContentService.MimeType.JSON);
  } else {
    // 重複がある場合の処理
    return ContentService.createTextOutput(
      JSON.stringify({ status: "duplicate", message: "User already exists" })
    ).setMimeType(ContentService.MimeType.JSON);
  }
}
```

いつもと違う内容としては、1 度登録した利用者を再度登録することが無いよう、重複チェックを挟んでいます。

# GAS のデプロイ

GAS をデプロイしてウェブアプリの URL を取得していきます。

<br/>
1. 「デプロイ」>「新しいデプロイ」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/0ec18f18-36b5-83b2-36ce-401e320f9166.png)

<br/>
2. 種類の選択で「ウェブアプリ」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/eec39550-4a8a-8e05-8dde-e8629433ec43.png)

<br/>
3. デプロイの説明を記載して「デプロイ」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/bc7e2d2e-b6aa-cfe8-e6df-1e5478648186.png)

<br/>
4.「アクセスを承認」を押してグーグルアカウントから承認を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/c56fbe96-0b45-4246-912b-e346d78c61fc.png)

<br/>
5. デプロイ完了後に表示されるウェブアプリの URL をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/3e3ab185-ccc5-de0e-9d56-d1972e8e12ae.png)

# Next.js 側での実装

GAS の準備が整ったら、Next.js 側に顧客情報を送信する機能を実装します。

## 環境変数の設定

`.env.local`ファイルに以下を追加します。

```
NEXT_PUBLIC_GOOGLE_SCRIPT_URL_CUSTOMER=your-script-url
```

Netlify 環境にも同じ変数を設定しましょう。

```terminal
$ netlify env:set NEXT_PUBLIC_GOOGLE_SCRIPT_URL_CUSTOMER "your-script-url"
```

## 顧客情報送信関数の実装

新しく`pages/api`配下に customer.ts を新しく作成します。

```ts
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { method, body } = req;
  if (method === "POST") {
    try {
      const scriptUrl = process.env.NEXT_PUBLIC_GOOGLE_SCRIPT_URL_CUSTOMER;

      const response = await fetch(scriptUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body,
      });

      const result = await response.json();
      res.status(200).json(result);
    } catch (err) {
      res.status(500).json({ error: "Internal Server Error" });
    }
  } else {
    res.setHeader("Allow", ["POST"]);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}
```

## API の呼び出し

作った API を今回は\_api.tsx から SWR を使って呼び出していきます。

はじめに 以下の通り`useSWRMutation`の`trigger`を呼び出します。

```tsx:src/nextjs/pages/_app.tsx
import useSWRMutation from "swr/mutation";

async function sendData<T>(url: string, { arg }: { arg: T }) {
  await fetch(url, {
    method: "POST",
    body: JSON.stringify(arg),
  });
}

function MyApp({ Component, pageProps }) {
  const { trigger } = useSWRMutation("/api/customer", sendData);

  // （中略）
}
```

これを init を呼び出している useEffect の中で呼び出していきます。

```tsx:src/nextjs/pages/_app.tsx
  useEffect(() => {
    console.log("start liff.init()...");
    liff
      .init({ liffId: process.env.LIFF_ID })
      .then(() => {
        console.log("liff.init() done");
        setLiffObject(liff);

        // プロフィール情報を取得して送信
        liff.getProfile().then((profile) => {
          trigger({ userId: profile.userId, name: profile.displayName });
        });
      })
      .catch((error) => {
        console.log(`liff.init() failed: ${error}`);
        if (!process.env.LIFF_ID) {
          console.info(
            "LIFF Starter: Please make sure that you provided `LIFF_ID` as an environmental variable."
          );
        }
        setLiffError(error.toString());
      });
  }, []);
```

# デプロイ

コードの実装が完了したらいつも通りデプロイを行い、変更を反映させましょう。

```terminal
$ netlify deploy --build --prod
```

# 動作確認

LINE ミニアプリを開き LIFF アプリを開く必要があるため、以前作った資料請求のページを開いてみましょう。

するとユーザー情報がスプレッドシートに反映されているのが確認できました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2743174/6a4f58de-78ab-1bee-99b2-bf790ca4c2b0.png)

# まとめ

今回は、顧客管理ができるよう、ユーザーの基本情報をスプレッドシートに出力する仕組みを構築しました。

次回からは、新機能として Google カレンダーを使った来店予約の機能を実装していきたいと思います。

残りは 8 日！
気になる方は是非フォローやカレンダー購読をお願いします:star: