View on GitHub

サインアップページの作成

Home

サインアップページの作成

定数の定義

src/frontend/src/constants/messages.ts

// ↓追加
/**
 * メッセージの定数
 */
export const MESSAGES = {
  SIGN_UP_SUCCESS: "ユーザ登録が完了しました。サインインしてください。",
} as const;
// ↑追加

/**
 * エラーメッセージの定数
 */
export const ERROR_MESSAGES = {
  // ↓追加
  // 汎用エラーメッセージ生成関数
  ERROR_IN_METHOD: (methodName: string) => `Error in ${methodName}:`,
  // ↑追加

  // API関連
  API_REQUEST_ERROR: "APIリクエスト中にエラーが発生しました",

  // ↓追加
  // 認証関連
  EMAIL_AND_PASSWORD_REQUIRED: "メールアドレスとパスワードを入力してください。",
  SIGN_UP_FAILED:
    "サインアップに失敗しました。時間をおいて再度お試しください。",
  // ↑追加
} as const;

src/frontend/src/constants/styles.ts

/**
 * スタイル関連の定数
 */
export const STYLES = {
  ......
  // ↓追加
  // 認証関連
  SIGN_PAGE: `flex min-h-screen items-center justify-center bg-gray-100`,
  SIGN_PAGE_CARD: `w-full max-w-md rounded-lg border border-gray-300 bg-white p-8 shadow-lg`,
  SIGN_PAGE_TITLE: `mb-6 text-center text-2xl font-bold`,
  SIGN_FORM: `flex flex-col gap-4`,
  SIGN_FORM_LABEL_INPUT: `flex flex-col gap-1`,
  SIGN_FORM_LABEL: `text-sm font-medium`,
  SIGN_FORM_INPUT: `rounded border border-gray-500 px-3 py-2 text-sm outline-none focus:ring-4 focus:ring-indigo-400`,
  SIGN_FORM_BUTTON: `cursor-pointer rounded bg-indigo-400 px-2 py-1 font-semibold text-white hover:bg-indigo-600 focus:ring-4 focus:ring-indigo-400 disabled:cursor-not-allowed disabled:bg-gray-300`,
  SIGN_FORM_ERROR: `text-rose-600`,
  SIGN_FORM_HINT: `mt-2 text-xs text-gray-500`,
  // ↑追加
} as const;

ユーティリティ関数の定義

src/frontend/src/utils/functions.ts

/**
 * 関数名を自動取得するヘルパー関数
 * @returns 現在の関数名
 */
export const getFunctionName = (): string => {
  const stack = new Error().stack;
  if (stack) {
    const lines = stack.split("\n");
    // lines[1] = getFunctionName 自身、lines[2] = 呼び出し元の関数
    if (lines.length > 2) {
      const match = lines[2].match(/at\s+(\w+)/);
      return match ? match[1] : "unknown";
    }
  }
  return "unknown";
};

src/frontend/src/utils/index.ts

export * from "./api";
export * from "./functions"; // <- 追加

サインアップページ

src/frontend/src/app/signup/page.tsx

"use client";

import React, { useState } from "react";

import { MESSAGES, ERROR_MESSAGES, STYLES } from "@/constants";
import { getFunctionName } from "@/utils";

import signUp from "@/api/auth/signUp";

const SignUpPage = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSignUp = async (e: React.SyntheticEvent<HTMLFormElement>) => {
    e.preventDefault();
    setError(null);

    if (!email || !password) {
      setError(ERROR_MESSAGES.EMAIL_AND_PASSWORD_REQUIRED);
      return;
    }

    setLoading(true);
    try {
      await signUp(email, password);
      alert(MESSAGES.SIGN_UP_SUCCESS);
      window.location.href = "/signin";
    } catch (error: any) {
      console.error(ERROR_MESSAGES.ERROR_IN_METHOD(getFunctionName()), error);
      setError(ERROR_MESSAGES.SIGN_UP_FAILED);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className={`${STYLES.SIGN_PAGE}`}>
      <div className={`${STYLES.SIGN_PAGE_CARD}`}>
        <h1 className={`${STYLES.SIGN_PAGE_TITLE}`}>Sign Up</h1>
        <form onSubmit={handleSignUp} className={`${STYLES.SIGN_FORM}`}>
          <div className={`${STYLES.SIGN_FORM_LABEL_INPUT}`}>
            <label className={`${STYLES.SIGN_FORM_LABEL}`} htmlFor="email">
              Email
            </label>
            <input
              id="email"
              type="email"
              autoComplete="email"
              placeholder="you@example.com"
              className={`${STYLES.SIGN_FORM_INPUT}`}
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              disabled={loading}
              required
            />
          </div>

          <div className={`${STYLES.SIGN_FORM_LABEL_INPUT}`}>
            <label className={`${STYLES.SIGN_FORM_LABEL}`} htmlFor="password">
              Password
            </label>
            <input
              id="password"
              type="password"
              autoComplete="new-password"
              placeholder="••••••••"
              className={`${STYLES.SIGN_FORM_INPUT}`}
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              disabled={loading}
              required
            />
          </div>

          {error && <p className={`${STYLES.SIGN_FORM_ERROR}`}>{error}</p>}

          <button
            type="submit"
            className={`${STYLES.SIGN_FORM_BUTTON}`}
            disabled={loading}
          >
            {loading ? "Signing up…" : "Sign Up"}
          </button>
        </form>

        <p className={`${STYLES.SIGN_FORM_HINT}`}>
          既にアカウントをお持ちの方は{" "}
          <a href="/signin" className={`${STYLES.LINK}`}>
            Sign In
          </a>{" "}
          してください。
        </p>
      </div>
    </div>
  );
};

export default SignUpPage;

バックエンドサーバを起動した状態で、ブラウザで下記 URL にアクセスしてください。

例えば下記のデータを入力し、Sign Upボタンをクリックしてください。

「ユーザ登録が完了しました。サインインしてください。」と表示されれば成功です。バックエンド側のデータベースにもユーザが登録されていることを確認してください。なお、サインアップ後はサインインページにリダイレクトされますが、現時点ではサインインページは作成していませんので、404のページが表示されます。

バックエンドサーバが停止している状態でサインアップすると、「サインアップに失敗しました。時間をおいて再度お試しください。」とエラーが表示されることも確認してください。