From de4ed818eb5bf94aa2c8dc7234642715d957ada7 Mon Sep 17 00:00:00 2001 From: Ryohei Kamei <231205751@ccmailg.meijo-u.ac.jp> Date: Fri, 11 Oct 2024 18:15:17 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=92=E9=81=A9=E5=88=87=E3=81=AB?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88?= =?UTF-8?q?=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/user/login/components/LoginForm.tsx | 119 ++++++++++++++++++++++++ app/user/login/page.tsx | 117 +---------------------- 2 files changed, 121 insertions(+), 115 deletions(-) create mode 100644 app/user/login/components/LoginForm.tsx diff --git a/app/user/login/components/LoginForm.tsx b/app/user/login/components/LoginForm.tsx new file mode 100644 index 0000000..6767209 --- /dev/null +++ b/app/user/login/components/LoginForm.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useState } from "react"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; +import useUser from "app/hooks/useUser"; +import toast from "react-hot-toast"; +import { Input } from "@/components/ui/input"; + +const LoginForm = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm<{ email: string; password: string }>({ + defaultValues: { + email: "", + password: "", + }, + }); + const { signIn } = useUser(); + const router = useRouter(); + const [loading, setLoading] = useState(false); + + const doLogin: SubmitHandler<{ email: string; password: string }> = async ( + formData + ) => { + try { + setLoading(true); + const { error } = await signIn({ + email: formData.email, + password: formData.password, + }); + if (error) { + // エラー時の処理 + toast.error("メールアドレス、もしくはパスワードが違います"); + setLoading(false); + } else { + // 成功時の処理 + toast.success("ログインしました"); + router.push("/"); + setLoading(false); + } + } catch (error) { + toast.error("予期せぬエラーが発生しました"); + setLoading(false); + } + }; + + return ( + <form onSubmit={handleSubmit(doLogin)} className="w-full max-w-md"> + <div className="mb-4"> + <Input + id="email" + placeholder="メールアドレス" + disabled={loading} + {...register("email", { + required: { + value: true, + message: "メールアドレスを入力してください", + }, + pattern: { + value: + /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]+.[A-Za-z0-9]+$/, + message: "有効なメールアドレスを入力してください", + }, + })} + className="border border-gray-300 rounded-md px-3 py-2 w-full" + /> + {errors.email && ( + <div className="text-red-500 text-sm">{errors.email.message}</div> + )} + </div> + + <div className="mb-4"> + <Input + id="password" + type="password" + disabled={loading} + placeholder="パスワード" + {...register("password", { + required: { + value: true, + message: "パスワードを入力してください", + }, + minLength: { + value: 6, + message: "パスワードは6文字以上にしてください", + }, + })} + className="border border-gray-300 rounded-md px-3 py-2 w-full" + /> + {errors.password && ( + <div className="text-red-500 text-sm">{errors.password.message}</div> + )} + </div> + + <button + type="submit" + className={`text-white px-4 py-2 rounded-md focus:outline-none ${ + loading + ? "cursor-not-allowed bg-gray-400" + : "bg-blue-500 hover:bg-blue-600 transition-colors duration-300 focus:border-blue-500 focus:ring" + }`} + > + {loading ? ( + <div className="flex justify-center items-center"> + <p>ログイン中・・・</p> + <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" /> + </div> + ) : ( + "ログイン" + )} + </button> + </form> + ); +}; + +export default LoginForm; diff --git a/app/user/login/page.tsx b/app/user/login/page.tsx index 7b7a26e..446bf0e 100644 --- a/app/user/login/page.tsx +++ b/app/user/login/page.tsx @@ -1,125 +1,12 @@ -"use client"; -import { useState } from "react"; -import { SubmitHandler, useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; -import useUser from "app/hooks/useUser"; -import toast from "react-hot-toast"; -import { Input } from "@/components/ui/input"; +import LoginForm from "./components/LoginForm"; const LoginPage = () => { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm<{ email: string; password: string }>({ - defaultValues: { - email: "", - password: "", - }, - }); - const { signIn } = useUser(); - const router = useRouter(); - const [loading, setLoading] = useState(false); - - const doLogin: SubmitHandler<{ email: string; password: string }> = async ( - formData - ) => { - try { - setLoading(true); - const { error } = await signIn({ - email: formData.email, - password: formData.password, - }); - if (error) { - // エラー時の処理 - toast.error("メールアドレス、もしくはパスワードが違います"); - setLoading(false); - } else { - // 成功時の処理 - toast.success("ログインしました"); - router.push("/"); - setLoading(false); - } - } catch (error) { - toast.error("予期せぬエラーが発生しました"); - setLoading(false); - } - }; - return ( <div className="mx-auto max-w-3xl lg:max-w-2xl px-4 sm:px-6 lg:px-8 pb-16 pt-40 text-center lg:pt-32"> <div className="bg-slate-50 rounded-xl px-4 py-4 shadow-lg ring-1 ring-gray-400"> <p className="text-2xl text-center text-gray-900">ログイン画面</p> <div className="flex justify-center pt-8"> - <form onSubmit={handleSubmit(doLogin)} className="w-full max-w-md"> - <div className="mb-4"> - <Input - id="email" - placeholder="メールアドレス" - disabled={loading} - {...register("email", { - required: { - value: true, - message: "メールアドレスを入力してください", - }, - pattern: { - value: - /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]+.[A-Za-z0-9]+$/, - message: "有効なメールアドレスを入力してください", - }, - })} - className="border border-gray-300 rounded-md px-3 py-2 w-full" - /> - {errors.email && ( - <div className="text-red-500 text-sm"> - {errors.email.message} - </div> - )} - </div> - - <div className="mb-4"> - <Input - id="password" - type="password" - disabled={loading} - placeholder="パスワード" - {...register("password", { - required: { - value: true, - message: "パスワードを入力してください", - }, - minLength: { - value: 6, - message: "パスワードは6文字以上にしてください", - }, - })} - className="border border-gray-300 rounded-md px-3 py-2 w-full" - /> - {errors.password && ( - <div className="text-red-500 text-sm"> - {errors.password.message} - </div> - )} - </div> - - <button - type="submit" - className={`text-white px-4 py-2 rounded-md focus:outline-none ${ - loading - ? "cursor-not-allowed bg-gray-400" - : "bg-blue-500 hover:bg-blue-600 transition-colors duration-300 focus:border-blue-500 focus:ring" - }`} - > - {loading ? ( - <div className="flex justify-center items-center"> - <p>ログイン中・・・</p> - <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" /> - </div> - ) : ( - "ログイン" - )} - </button> - </form> + <LoginForm /> </div> </div> </div> From 27c1bd2331f8292e82ff5941b0d81ce5ba9cc2e5 Mon Sep 17 00:00:00 2001 From: Ryohei Kamei <231205751@ccmailg.meijo-u.ac.jp> Date: Fri, 11 Oct 2024 18:15:46 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E7=94=BB=E9=9D=A2=E3=82=92=E9=81=A9=E5=88=87?= =?UTF-8?q?=E3=81=AB=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/user/register/components/RegisterForm.tsx | 108 ++++++++++++++++++ app/user/register/page.tsx | 107 +---------------- 2 files changed, 110 insertions(+), 105 deletions(-) create mode 100644 app/user/register/components/RegisterForm.tsx diff --git a/app/user/register/components/RegisterForm.tsx b/app/user/register/components/RegisterForm.tsx new file mode 100644 index 0000000..6c7a21e --- /dev/null +++ b/app/user/register/components/RegisterForm.tsx @@ -0,0 +1,108 @@ +"use client"; +import { useRouter } from "next/navigation"; +import { SubmitHandler, useForm } from "react-hook-form"; +import useUser from "app/hooks/useUser"; +import { Input } from "@/components/ui/input"; +import toast from "react-hot-toast"; +import { useState } from "react"; + +const RegisterForm = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm<{ email: string; password: string }>({ + defaultValues: { + email: "", + password: "", + }, + }); + + const { signUp } = useUser(); + const router = useRouter(); + const [loading, setLoading] = useState(false); + + const doRegister: SubmitHandler<{ email: string; password: string }> = async ( + formData + ) => { + try { + setLoading(true); + await signUp({ email: formData.email, password: formData.password }); + alert( + "登録いただいたメールアドレスに確認メールを送信しましたのでご確認ください!(メールの送信は数分かかる場合がありますのでご了承ください)" + ); + router.push("/"); + } catch (error) { + toast.error("エラーが発生しました"); + setLoading(false); + } + }; + + return ( + <form onSubmit={handleSubmit(doRegister)} className="w-full max-w-md"> + <div className="mb-4"> + <Input + id="email" + placeholder="メールアドレス" + {...register("email", { + required: { + value: true, + message: "メールアドレスを入力してください", + }, + pattern: { + value: + /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]+.[A-Za-z0-9]+$/, + message: "有効なメールアドレスを入力してください", + }, + })} + className="border border-gray-300 rounded-md px-3 py-2 w-full" + /> + {errors.email && ( + <div className="text-red-500 text-sm">{errors.email.message}</div> + )} + </div> + + <div className="mb-4"> + <Input + id="password" + type="password" + placeholder="パスワード" + {...register("password", { + required: { + value: true, + message: "パスワードを入力してください", + }, + minLength: { + value: 6, + message: "パスワードは6文字以上にしてください", + }, + })} + className="border border-gray-300 rounded-md px-3 py-2 w-full" + /> + {errors.password && ( + <div className="text-red-500 text-sm">{errors.password.message}</div> + )} + </div> + + <button + type="submit" + className={`text-white px-4 py-2 rounded-md focus:outline-none ${ + loading + ? "cursor-not-allowed bg-gray-400" + : "bg-blue-500 hover:bg-blue-600 transition-colors duration-300 focus:border-blue-500 focus:ring" + }`} + > + {loading ? ( + <div className="flex justify-center items-center"> + <p>読み込み中・・・</p> + <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" /> + </div> + ) : ( + "新規登録" + )} + </button> + </form> + ); +}; + +export default RegisterForm; diff --git a/app/user/register/page.tsx b/app/user/register/page.tsx index 60bb9d0..d85c212 100644 --- a/app/user/register/page.tsx +++ b/app/user/register/page.tsx @@ -1,115 +1,12 @@ -"use client"; -import { useRouter } from "next/navigation"; -import { SubmitHandler, useForm } from "react-hook-form"; -import useUser from "app/hooks/useUser"; -import { Input } from "@/components/ui/input"; -import toast from "react-hot-toast"; -import { useState } from "react"; +import RegisterForm from "./components/RegisterForm"; const RegisterPage = () => { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm<{ email: string; password: string }>({ - defaultValues: { - email: "", - password: "", - }, - }); - - const { signUp } = useUser(); - const router = useRouter(); - const [loading, setLoading] = useState(false); - - const doRegister: SubmitHandler<{ email: string; password: string }> = async ( - formData - ) => { - try { - setLoading(true); - await signUp({ email: formData.email, password: formData.password }); - alert( - "登録いただいたメールアドレスに確認メールを送信しましたのでご確認ください!(メールの送信は数分かかる場合がありますのでご了承ください)" - ); - router.push("/"); - } catch (error) { - toast.error("エラーが発生しました"); - setLoading(false); - } - }; - return ( <div className="mx-auto max-w-3xl lg:max-w-2xl px-4 sm:px-6 lg:px-8 pb-16 pt-40 text-center lg:pt-32"> <div className="bg-slate-50 rounded-xl px-4 py-4 shadow-lg ring-1 ring-gray-400"> <p className="text-2xl text-center text-gray-900">新規登録画面</p> <div className="flex justify-center pt-8"> - <form onSubmit={handleSubmit(doRegister)} className="w-full max-w-md"> - <div className="mb-4"> - <Input - id="email" - placeholder="メールアドレス" - {...register("email", { - required: { - value: true, - message: "メールアドレスを入力してください", - }, - pattern: { - value: - /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]+.[A-Za-z0-9]+$/, - message: "有効なメールアドレスを入力してください", - }, - })} - className="border border-gray-300 rounded-md px-3 py-2 w-full" - /> - {errors.email && ( - <div className="text-red-500 text-sm"> - {errors.email.message} - </div> - )} - </div> - - <div className="mb-4"> - <Input - id="password" - type="password" - placeholder="パスワード" - {...register("password", { - required: { - value: true, - message: "パスワードを入力してください", - }, - minLength: { - value: 6, - message: "パスワードは6文字以上にしてください", - }, - })} - className="border border-gray-300 rounded-md px-3 py-2 w-full" - /> - {errors.password && ( - <div className="text-red-500 text-sm"> - {errors.password.message} - </div> - )} - </div> - - <button - type="submit" - className={`text-white px-4 py-2 rounded-md focus:outline-none ${ - loading - ? "cursor-not-allowed bg-gray-400" - : "bg-blue-500 hover:bg-blue-600 transition-colors duration-300 focus:border-blue-500 focus:ring" - }`} - > - {loading ? ( - <div className="flex justify-center items-center"> - <p>読み込み中・・・</p> - <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" /> - </div> - ) : ( - "新規登録" - )} - </button> - </form> + <RegisterForm /> </div> </div> </div> From 96868b29008f5a316c2af6360d2cea498081783d Mon Sep 17 00:00:00 2001 From: Ryohei Kamei <231205751@ccmailg.meijo-u.ac.jp> Date: Fri, 11 Oct 2024 18:16:03 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E7=99=BB=E9=8C=B2=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __test__/RegisterPage.test.tsx | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 __test__/RegisterPage.test.tsx diff --git a/__test__/RegisterPage.test.tsx b/__test__/RegisterPage.test.tsx new file mode 100644 index 0000000..af2e039 --- /dev/null +++ b/__test__/RegisterPage.test.tsx @@ -0,0 +1,60 @@ +import { act, render, screen } from "@testing-library/react"; +import user from "@testing-library/user-event"; +import RegisterPage from "app/user/register/page"; + +// モック化したuseRouterをインポート +jest.mock("next/navigation", () => ({ + useRouter: jest.fn(), +})); + +describe("<Page />", () => { + test("RegisterPageコンポーネントが正しく表示されるかのテスト", async () => { + render(<RegisterPage />); + + await act(async () => {}); + + expect(screen.getByText("新規登録画面")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("メールアドレス")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("パスワード")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + test("メールアドレスとパスワード欄が未入力の場合、エラーメッセージが表示されるかのテスト", async () => { + render(<RegisterPage />); + + const submitButton = screen.getByRole("button"); + await user.click(submitButton); + + await act(async () => {}); + + expect( + screen.getByText("メールアドレスを入力してください") + ).toBeInTheDocument(); + + expect( + screen.getByText("パスワードを入力してください") + ).toBeInTheDocument(); + }); + + test("適切なメールアドレス,適切なパスワードを入力していない場合、エラーメッセージが表示されるテスト", async () => { + render(<RegisterPage />); + + const emailInput = screen.getByPlaceholderText("メールアドレス"); + const passwordInput = screen.getByPlaceholderText("パスワード"); + const submitButton = screen.getByRole("button"); + + await user.type(emailInput, "test"); + await user.type(passwordInput, "pass"); + await user.click(submitButton); + + await act(async () => {}); + + expect( + screen.getByText("有効なメールアドレスを入力してください") + ).toBeInTheDocument(); + + expect( + screen.getByText("パスワードは6文字以上にしてください") + ).toBeInTheDocument(); + }); +});