diff --git a/src/Components/GoalModal/GoalModal.tsx b/src/Components/GoalModal/GoalModal.tsx index 9f0de99..2f2eadf 100644 --- a/src/Components/GoalModal/GoalModal.tsx +++ b/src/Components/GoalModal/GoalModal.tsx @@ -80,7 +80,7 @@ export default function GoalModal() { color="primary" startDecorator={} onClick={() => setOpen(true)} - disabled={!user || user?.loginType === "Guest"} + disabled={!user || user?.loginType === "Guest" || !user?.isMailVerified} > Create Goal @@ -124,7 +124,11 @@ export default function GoalModal() { type="submit" variant="solid" color="primary" - disabled={!user || user?.loginType === "Guest"} + disabled={ + !user || + user?.loginType === "Guest" || + !user?.isMailVerified + } endDecorator={} > Create Goal diff --git a/src/Components/NameUpdate/NameUpdate.tsx b/src/Components/NameUpdate/NameUpdate.tsx index 99848c6..a916584 100644 --- a/src/Components/NameUpdate/NameUpdate.tsx +++ b/src/Components/NameUpdate/NameUpdate.tsx @@ -1,5 +1,5 @@ "use client"; -import { appCheckToken, functionsEndpoint } from "@/app/firebase"; +import { appCheckToken, auth, functionsEndpoint } from "@/app/firebase"; import { useUser } from "@/utils/UserContext"; import { DialogContent, @@ -10,6 +10,7 @@ import { } from "@mui/joy"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; +import { updateProfile } from "firebase/auth"; import React, { useState } from "react"; export default function NameUpdate({ @@ -23,6 +24,7 @@ export default function NameUpdate({ const { user } = useUser(); const handleNameUpdate = async (event: React.FormEvent) => { event.preventDefault(); + const response = await fetch(`${functionsEndpoint}/user/${user?.uid}`, { method: "PUT", headers: { @@ -31,6 +33,15 @@ export default function NameUpdate({ }, body: JSON.stringify({ name: newName }), }); + + // Firebase Authentication の displayName を更新 + if (auth.currentUser) { + await updateProfile(auth.currentUser, { displayName: newName }); + console.log("Firebase displayName updated successfully"); + } else { + console.error("Failed to update displayName: No authenticated user"); + } + if (!response.ok) { console.error("Failed to update name"); } else { @@ -39,6 +50,21 @@ export default function NameUpdate({ setOpen(false); } }; + + // 以下のJoy UIによるエラーを無効化 + // Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release. Error Component Stack + try { + const consoleError = console.error; + console.error = (...args) => { + if (args[0]?.includes("Accessing element.ref was removed")) { + return; + } + consoleError(...args); + }; + } catch { + console.error("Failed to disable Joy UI error"); + } + return ( setOpen(false)} keepMounted disablePortal> } onClick={() => setOpen(true)} - disabled={!user || user?.loginType === "Guest"} > 写真を撮って完了する @@ -188,7 +187,6 @@ export default function PostModal({ goalId }: { goalId: string }) { type="submit" variant="solid" color="primary" - disabled={!user || user?.loginType === "Guest"} endDecorator={} onClick={handleUpload} > diff --git a/src/Components/Progress/Progress.tsx b/src/Components/Progress/Progress.tsx index acf29c5..a9f9b4b 100644 --- a/src/Components/Progress/Progress.tsx +++ b/src/Components/Progress/Progress.tsx @@ -1,6 +1,7 @@ -import { GoalWithId, SuccessResult } from "@/types/types"; +import { GoalWithId, SuccessResult, UserData } from "@/types/types"; import { fetchUserById } from "@/utils/API/fetchUser"; import { formatStringToDate } from "@/utils/DateFormatter"; +import { useUser } from "@/utils/UserContext"; import AppRegistrationRoundedIcon from "@mui/icons-material/AppRegistrationRounded"; import CheckRoundedIcon from "@mui/icons-material/CheckRounded"; import CloseIcon from "@mui/icons-material/Close"; @@ -46,6 +47,7 @@ export default function Progress({ pendingResults = [], }: ProgressProps) { const [userNames, setUserNames] = useState>({}); // + const { user } = useUser(); const fetchUserName = async (userId: string) => { if (userNames[userId]) return; // 既に取得済みの場合はキャッシュのように再利用 @@ -90,7 +92,7 @@ export default function Progress({ if (result.type === "failed") return failedStep(result as GoalWithId, userName); if (result.type === "pending") - return pendingStep(result as GoalWithId, userName); + return pendingStep(result as GoalWithId, userName, user as UserData); return null; })} @@ -173,7 +175,7 @@ const failedStep = (result: GoalWithId, userName: string) => { ); }; -const pendingStep = (result: GoalWithId, userName: string) => { +const pendingStep = (result: GoalWithId, userName: string, user: UserData) => { return ( { goalText={result.text} resultType="pending" /> - + {/* 自分の作成した目標の場合のみ投稿可能にする */} + {result.userId === user?.uid && } ); diff --git a/src/Components/UserForm/UserForm.tsx b/src/Components/UserForm/UserForm.tsx deleted file mode 100644 index 0b21666..0000000 --- a/src/Components/UserForm/UserForm.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client"; -import { auth } from "@/app/firebase"; -import { createUser } from "@/utils/Auth/createUserAuth"; -import { loginUser } from "@/utils/Auth/loginUserAuth"; -import { signInAsGuest } from "@/utils/Auth/signInAnonymously"; -import { signInWithGoogleAccount } from "@/utils/Auth/signInWithGoogleAccount"; -import { useUser } from "@/utils/UserContext"; -import { signOut } from "firebase/auth"; -import React, { useState } from "react"; - -export default function UserForm() { - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - - const { user } = useUser(); - - const handleRegisterSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - await createUser(email, password, name); - }; - - const handleLoginSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - await loginUser(email, password); - }; - - const handleGoogleLogin = async () => { - await signInWithGoogleAccount(); - }; - - const handleGuestLogin = async () => { - await signInAsGuest(); - }; - - const handleLogout = async () => { - try { - await signOut(auth); - console.log("Signed out"); - } catch (error) { - console.error("errorCode:", (error as Error)?.name); - console.error("errorMessage:", (error as Error)?.message); - } - }; - - return ( - <> -

{user ? `ログイン中: ${user.name}` : "ログインしてください"}

- アカウント作成 -
- - -
- ログイン -
- - -
- - - - - ); -} diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx index 9ff0d8d..726b2b2 100644 --- a/src/app/account/page.tsx +++ b/src/app/account/page.tsx @@ -2,10 +2,10 @@ import { auth } from "@/app/firebase"; import NameUpdate from "@/Components/NameUpdate/NameUpdate"; import Notification from "@/Components/Notification/Notification"; -import { createUser } from "@/utils/Auth/createUserAuth"; -import { loginUser } from "@/utils/Auth/loginUserAuth"; import { signInAsGuest } from "@/utils/Auth/signInAnonymously"; -import { signInWithGoogleAccount } from "@/utils/Auth/signInWithGoogleAccount"; +import { signInWithMail } from "@/utils/Auth/signInWithMail"; +import { signUpWithGoogleAccount } from "@/utils/Auth/signUpWithGoogleAccount"; +import { signUpWithMail } from "@/utils/Auth/signUpWithMail"; import { useUser } from "@/utils/UserContext"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; @@ -59,16 +59,16 @@ export default function Account() { const handleRegisterSubmit = async (event: React.FormEvent) => { event.preventDefault(); - await createUser(email, password, name); + await signUpWithMail(email, password, name); }; const handleLoginSubmit = async (event: React.FormEvent) => { event.preventDefault(); - await loginUser(email, password); + await signInWithMail(email, password); }; const handleGoogleLogin = async () => { - await signInWithGoogleAccount(); + await signUpWithGoogleAccount(); }; const handleGuestLogin = async () => { @@ -124,11 +124,25 @@ export default function Account() { {user ? ( <> ログイン中: {user.name} + + {/* ゲストの場合は通知機能を利用しない */} {user.loginType !== "Guest" && } + + {/* メール認証が完了していない場合に表示 */} + {!user.isMailVerified && ( + + メールに届いた認証リンクを確認してください。 +
+ 認証が完了するまで閲覧以外の機能は制限されます。 +
+ )} + ログアウト - {user?.loginType !== "Guest" && ( + + {/* ゲストの場合は名前を変更しない */} + {user.loginType !== "Guest" && ( <> )} - ) : formMode === "register" ? ( - <> - 新規登録 -
- - setName(e.target.value)} - fullWidth - required - /> - setEmail(e.target.value)} - fullWidth - required - /> - setPassword(e.target.value)} - fullWidth - required - /> - - アカウント作成 - - -
- ) : ( <> - ログイン -
- + 新規登録 + + + setName(e.target.value)} + fullWidth + required + /> + setEmail(e.target.value)} + fullWidth + required + /> + setPassword(e.target.value)} + fullWidth + required + /> + + アカウント作成 + + + + + ) : ( + <> + ログイン +
+ + setEmail(e.target.value)} + fullWidth + required + /> + setPassword(e.target.value)} + fullWidth + required + /> + + ログイン + + +
+ + )} + <> + または + - setEmail(e.target.value)} - fullWidth - required - /> - setPassword(e.target.value)} - fullWidth - required - /> - - ログイン - -
- - - )} - {!user && ( - <> - または - - Googleでログイン - - - ゲストログイン - + Googleでログイン +
+ + ゲストログイン + + )} diff --git a/src/types/types.ts b/src/types/types.ts index a2b7f7b..f79bb22 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -3,6 +3,7 @@ export interface UserData { name: string; streak: number; loginType: LoginType; + isMailVerified: boolean; } export type LoginType = "Mail" | "Google" | "Guest" | "None"; diff --git a/src/utils/API/createUserAPI.ts b/src/utils/API/createUser.ts similarity index 90% rename from src/utils/API/createUserAPI.ts rename to src/utils/API/createUser.ts index 80e1d1c..66acbb5 100644 --- a/src/utils/API/createUserAPI.ts +++ b/src/utils/API/createUser.ts @@ -7,7 +7,7 @@ import { appCheckToken, functionsEndpoint } from "@/app/firebase"; * @param {string} name * @param {string} uid */ -export const createUserAPI = async (name: string, uid: string) => { +export const createUser = async (name: string, uid: string) => { const response = await fetch(`${functionsEndpoint}/user/`, { method: "POST", headers: { diff --git a/src/utils/Auth/loginUserAuth.ts b/src/utils/Auth/signInWithMail.ts similarity index 89% rename from src/utils/Auth/loginUserAuth.ts rename to src/utils/Auth/signInWithMail.ts index 4ceadac..6177a29 100644 --- a/src/utils/Auth/loginUserAuth.ts +++ b/src/utils/Auth/signInWithMail.ts @@ -8,7 +8,7 @@ import { signInWithEmailAndPassword } from "firebase/auth"; * @param {string} password * @return {*} */ -export const loginUser = (email: string, password: string) => { +export const signInWithMail = (email: string, password: string) => { signInWithEmailAndPassword(auth, email, password) .then(async (userCredential) => { // Signed in diff --git a/src/utils/Auth/signInWithGoogleAccount.ts b/src/utils/Auth/signUpWithGoogleAccount.ts similarity index 78% rename from src/utils/Auth/signInWithGoogleAccount.ts rename to src/utils/Auth/signUpWithGoogleAccount.ts index 4ba8738..707597c 100644 --- a/src/utils/Auth/signInWithGoogleAccount.ts +++ b/src/utils/Auth/signUpWithGoogleAccount.ts @@ -1,5 +1,5 @@ import { auth, googleProvider } from "@/app/firebase"; -import { createUserAPI } from "@/utils/API/createUserAPI"; +import { createUser } from "@/utils/API/createUser"; import { updateUser } from "@/utils/UserContext"; import { getAdditionalUserInfo, signInWithPopup } from "firebase/auth"; @@ -7,22 +7,20 @@ import { getAdditionalUserInfo, signInWithPopup } from "firebase/auth"; * Googleアカウントでログインする * */ -export const signInWithGoogleAccount = async () => { +export const signUpWithGoogleAccount = async () => { try { const result = await signInWithPopup(auth, googleProvider); // 初めての時だけユーザー情報を登録する if (getAdditionalUserInfo(result)?.isNewUser) { // uidとdocument IDを一致させる - await createUserAPI( - result.user.displayName ?? "no name", - result.user.uid - ); + await createUser(result.user.displayName ?? "no name", result.user.uid); updateUser({ uid: result.user.uid, name: result.user.displayName ?? "no name", streak: 0, loginType: "Google", + isMailVerified: result.user.emailVerified, }); } diff --git a/src/utils/Auth/createUserAuth.ts b/src/utils/Auth/signUpWithMail.ts similarity index 50% rename from src/utils/Auth/createUserAuth.ts rename to src/utils/Auth/signUpWithMail.ts index c2782e9..b0b58f9 100644 --- a/src/utils/Auth/createUserAuth.ts +++ b/src/utils/Auth/signUpWithMail.ts @@ -1,7 +1,11 @@ import { auth } from "@/app/firebase"; -import { createUserAPI } from "@/utils/API/createUserAPI"; +import { createUser } from "@/utils/API/createUser"; import { updateUser } from "@/utils/UserContext"; -import { createUserWithEmailAndPassword } from "firebase/auth"; +import { + createUserWithEmailAndPassword, + sendEmailVerification, + updateProfile, +} from "firebase/auth"; /** * Firebase Authenticationでユーザーを作成し、生成されたuidをドキュメントIDとしてFirestoreにユーザー情報を登録する @@ -10,21 +14,42 @@ import { createUserWithEmailAndPassword } from "firebase/auth"; * @param {string} password * @param {string} name */ -export const createUser = (email: string, password: string, name: string) => { +export const signUpWithMail = ( + email: string, + password: string, + name: string +) => { // メールは初回ログインの時のみ成功する、2回目以降はエラーになるので、ログインを使う createUserWithEmailAndPassword(auth, email, password) .then(async (userCredential) => { // Signed up const user = userCredential.user; + // Firebase AuthのdisplayNameを設定 + try { + await updateProfile(user, { displayName: name }); + console.log("ユーザー名を設定しました:", name); + } catch (profileUpdateError) { + console.error("プロファイル更新に失敗しました:", profileUpdateError); + } + // uidとdocument IDを一致させる - await createUserAPI(name, user.uid); + await createUser(name, user.uid); updateUser({ uid: user.uid, name: name, streak: 0, loginType: "Mail", + isMailVerified: user.emailVerified, }); + + // メール確認リンクを送信 + try { + await sendEmailVerification(user); + console.log("確認メールを送信しました。"); + } catch (verificationError) { + console.error("確認メールの送信に失敗しました:", verificationError); + } console.log(user); }) .catch((error) => { diff --git a/src/utils/UserContext.tsx b/src/utils/UserContext.tsx index 5b915d7..45e5ab4 100644 --- a/src/utils/UserContext.tsx +++ b/src/utils/UserContext.tsx @@ -79,6 +79,7 @@ export const UserProvider = ({ children }: Props) => { name: "Guest", streak: 0, loginType: "Guest", + isMailVerified: true, }; setUser(guestData); return; @@ -89,7 +90,11 @@ export const UserProvider = ({ children }: Props) => { // ユーザーデータを作成する前にfetchしようとして"User not found"になるので、postした場所でsetさせている // "User not found"ではない(= 初回ログイン直後ではない)場合のみsetする if (userData.uid) { - setUser({ ...userData, loginType }); + setUser({ + ...userData, + loginType, + isMailVerified: firebaseUser.emailVerified, + }); } } catch (error) { console.error("ユーザーデータの取得に失敗しました:", error);