11"use client" ;
22
3- import { type FormEvent , useState } from "react" ;
3+ import { SyntheticEvent , useReducer } from "react" ;
44import { useRouter } from "next/navigation" ;
55
66import { Button } from "@/components/ui/Button" ;
@@ -9,42 +9,84 @@ import { Link } from "@/components/ui/Link";
99import { TextField } from "@/components/ui/TextField" ;
1010import { createClient } from "@/lib/supabase/client" ;
1111
12+ type State = {
13+ email : string ;
14+ password : string ;
15+ repeatPassword : string ;
16+ isPending : boolean ;
17+ errorMessage : string | null ;
18+ } ;
19+
20+ type Action =
21+ | { type : "SET_EMAIL" ; payload : string }
22+ | { type : "SET_PASSWORD" ; payload : string }
23+ | { type : "SET_REPEAT_PASSWORD" ; payload : string }
24+ | { type : "SET_PENDING" ; payload : boolean }
25+ | { type : "SET_ERROR" ; payload : string | null } ;
26+
27+ const initialState : State = {
28+ email : "" ,
29+ password : "" ,
30+ repeatPassword : "" ,
31+ isPending : false ,
32+ errorMessage : null ,
33+ } ;
34+
35+ function reducer ( state : State , action : Action ) : State {
36+ switch ( action . type ) {
37+ case "SET_EMAIL" :
38+ return { ...state , email : action . payload } ;
39+ case "SET_PASSWORD" :
40+ return { ...state , password : action . payload } ;
41+ case "SET_REPEAT_PASSWORD" :
42+ return { ...state , repeatPassword : action . payload } ;
43+ case "SET_PENDING" :
44+ return { ...state , isPending : action . payload } ;
45+ case "SET_ERROR" :
46+ return { ...state , errorMessage : action . payload } ;
47+ default :
48+ return state ;
49+ }
50+ }
51+
1252export function SignUpForm ( ) {
1353 const router = useRouter ( ) ;
14- const [ email , setEmail ] = useState ( "" ) ;
15- const [ password , setPassword ] = useState ( "" ) ;
16- const [ repeatPassword , setRepeatPassword ] = useState ( "" ) ;
17- const [ isPending , setIsPending ] = useState ( false ) ;
18- const [ errorMessage , setErrorMessage ] = useState < string | null > ( null ) ;
54+ const [ state , dispatch ] = useReducer ( reducer , initialState ) ;
1955
20- async function handleSubmit ( event : FormEvent < HTMLFormElement > ) {
56+ async function handleSubmit ( event : SyntheticEvent < HTMLFormElement > ) {
2157 event . preventDefault ( ) ;
22- const normalizedEmail = email . trim ( ) ;
58+ const normalizedEmail = state . email . trim ( ) ;
2359
24- if ( password !== repeatPassword ) {
25- setErrorMessage ( " パスワードが一致しません") ;
60+ if ( state . password !== state . repeatPassword ) {
61+ dispatch ( { type : "SET_ERROR" , payload : " パスワードが一致しません" } ) ;
2662 return ;
2763 }
2864
29- if ( ! normalizedEmail || ! password ) {
30- setErrorMessage ( " アカウント登録に失敗しました") ;
65+ if ( ! normalizedEmail || ! state . password ) {
66+ dispatch ( { type : "SET_ERROR" , payload : " アカウント登録に失敗しました" } ) ;
3167 return ;
3268 }
3369
3470 const supabase = createClient ( ) ;
35- setIsPending ( true ) ;
36- setErrorMessage ( null ) ;
71+ dispatch ( { type : "SET_PENDING" , payload : true } ) ;
72+ dispatch ( { type : "SET_ERROR" , payload : null } ) ;
3773
3874 try {
39- const { error } = await supabase . auth . signUp ( { email : normalizedEmail , password } ) ;
75+ const { error } = await supabase . auth . signUp ( {
76+ email : normalizedEmail ,
77+ password : state . password ,
78+ } ) ;
4079 if ( error ) {
4180 throw error ;
4281 }
4382 router . push ( "/auth/sign-up-success" ) ;
4483 } catch ( error : unknown ) {
45- setErrorMessage ( error instanceof Error ? error . message : "アカウント登録に失敗しました" ) ;
84+ dispatch ( {
85+ type : "SET_ERROR" ,
86+ payload : error instanceof Error ? error . message : "アカウント登録に失敗しました" ,
87+ } ) ;
4688 } finally {
47- setIsPending ( false ) ;
89+ dispatch ( { type : "SET_PENDING" , payload : false } ) ;
4890 }
4991 }
5092
@@ -70,40 +112,40 @@ export function SignUpForm() {
70112 label = "メールアドレス"
71113 placeholder = "admin@example.com"
72114 autoComplete = "email"
73- value = { email }
74- onChange = { setEmail }
115+ value = { state . email }
116+ onChange = { ( value ) => dispatch ( { type : "SET_EMAIL" , payload : value } ) }
75117 isRequired
76118 />
77119 < TextField
78120 name = "password"
79121 type = "password"
80122 label = "パスワード"
81123 autoComplete = "new-password"
82- value = { password }
83- onChange = { setPassword }
124+ value = { state . password }
125+ onChange = { ( value ) => dispatch ( { type : "SET_PASSWORD" , payload : value } ) }
84126 isRequired
85127 />
86128 < TextField
87129 name = "repeatPassword"
88130 type = "password"
89131 label = "パスワード(確認)"
90132 autoComplete = "new-password"
91- value = { repeatPassword }
92- onChange = { setRepeatPassword }
133+ value = { state . repeatPassword }
134+ onChange = { ( value ) => dispatch ( { type : "SET_REPEAT_PASSWORD" , payload : value } ) }
93135 isRequired
94136 />
95137 < p
96138 role = "alert"
97139 aria-live = "polite"
98140 className = {
99- errorMessage
141+ state . errorMessage
100142 ? "rounded-md border border-red-500/40 bg-red-500/15 px-3 py-2 text-sm text-red-200"
101143 : "min-h-6 text-sm leading-6 text-transparent"
102144 }
103145 >
104- { errorMessage ?? "\u00a0" }
146+ { state . errorMessage ?? "\u00a0" }
105147 </ p >
106- < Button type = "submit" className = "h-11 w-full font-medium" isPending = { isPending } >
148+ < Button type = "submit" className = "h-11 w-full font-medium" isPending = { state . isPending } >
107149 新規登録
108150 </ Button >
109151 < p className = "text-center text-sm leading-relaxed text-zinc-300" >
0 commit comments