diff --git a/web/components/email-verification-button.tsx b/web/components/email-verification-button.tsx
index a21c5f0..bdd6b50 100644
--- a/web/components/email-verification-button.tsx
+++ b/web/components/email-verification-button.tsx
@@ -1,19 +1,34 @@
-import {User} from 'firebase/auth'
import {Button} from 'web/components/buttons/button'
import {sendVerificationEmail} from 'web/lib/firebase/email-verification'
import {useT} from 'web/lib/locale'
import {Col} from "web/components/layout/col"
+import toast from "react-hot-toast";
+import {useFirebaseUser} from "web/hooks/use-firebase-user";
-export function EmailVerificationButton(props: {
- user: User
-}) {
- const {user} = props
+export function EmailVerificationButton() {
+ const user = useFirebaseUser()
const t = useT()
- const isEmailVerified = user.emailVerified
+ const isEmailVerified = user?.emailVerified
+
+ async function reload() {
+ if (!user) return false;
+
+ // Refresh user record from Firebase
+ await user.reload();
+
+ if (user.emailVerified) {
+ // IMPORTANT: force a new ID token with updated claims
+ await user.getIdToken(true)
+ console.log("User email verified")
+ return true
+ } else {
+ toast.error(t('', "Email still not verified..."))
+ }
+ }
return (
-
+
+
+
+
)
}
diff --git a/web/components/messaging/email-verification-prompt.tsx b/web/components/messaging/email-verification-prompt.tsx
index 8675641..3d2d7b8 100644
--- a/web/components/messaging/email-verification-prompt.tsx
+++ b/web/components/messaging/email-verification-prompt.tsx
@@ -3,21 +3,19 @@ import clsx from 'clsx'
import {EmailVerificationButton} from "web/components/email-verification-button";
interface EmailVerificationPromptProps {
- firebaseUser: any
t: any
className?: string
}
export function EmailVerificationPrompt(
{
- firebaseUser,
t,
className
}: EmailVerificationPromptProps) {
return (
{t('messaging.email_verification_required', "You must verify your email to message people.")}
-
+
)
}
diff --git a/web/components/messaging/send-message-button.tsx b/web/components/messaging/send-message-button.tsx
index 1c2bd50..3ef9cf1 100644
--- a/web/components/messaging/send-message-button.tsx
+++ b/web/components/messaging/send-message-button.tsx
@@ -15,10 +15,11 @@ import {MAX_COMMENT_LENGTH} from 'common/comment'
import {CommentInputTextArea} from 'web/components/comments/comment-input'
import {Title} from 'web/components/widgets/title'
import {Row} from 'web/components/layout/row'
-import {auth, firebaseLogin} from 'web/lib/firebase/users'
+import {firebaseLogin} from 'web/lib/firebase/users'
import {useT} from 'web/lib/locale'
import {Tooltip} from "web/components/widgets/tooltip";
import {EmailVerificationPrompt} from 'web/components/messaging/email-verification-prompt'
+import {useFirebaseUser} from "web/hooks/use-firebase-user";
export const SendMessageButton = (props: {
toUser: User
@@ -27,7 +28,7 @@ export const SendMessageButton = (props: {
circleButton?: boolean
}) => {
const {toUser, currentUser, includeLabel, circleButton} = props
- const firebaseUser = auth.currentUser
+ const firebaseUser = useFirebaseUser()
const router = useRouter()
const privateUser = usePrivateUser()
const channelMemberships = useSortedPrivateMessageMemberships(currentUser?.id)
@@ -132,7 +133,7 @@ export const SendMessageButton = (props: {
isSubmitting={!editor || submitting}
submitOnEnter={false}
/> :
-
+
}
{error}
diff --git a/web/hooks/use-firebase-user.ts b/web/hooks/use-firebase-user.ts
new file mode 100644
index 0000000..769f862
--- /dev/null
+++ b/web/hooks/use-firebase-user.ts
@@ -0,0 +1,32 @@
+import {useEffect, useState} from 'react'
+import {onAuthStateChanged, onIdTokenChanged, User} from 'firebase/auth'
+import {auth} from 'web/lib/firebase/users'
+
+/**
+ * Subscribe to Firebase Auth user updates.
+ * Reactively returns the current Firebase `User` and updates when:
+ * - auth state changes (sign in/out)
+ * - ID token changes (after `getIdToken(true)` or `user.reload()`),
+ * which is important for reflecting `emailVerified` changes without a hard refresh.
+ */
+export function useFirebaseUser() {
+ const [, forceRender] = useState(0);
+ const [firebaseUser, setFirebaseUser] = useState(auth.currentUser);
+
+ useEffect(() => {
+ const update = (u: User | null) => {
+ setFirebaseUser(u); // keep the real User instance
+ forceRender(v => v + 1); // force React to re-render
+ };
+
+ const unsubAuth = onAuthStateChanged(auth, update);
+ const unsubToken = onIdTokenChanged(auth, update);
+
+ return () => {
+ unsubAuth();
+ unsubToken();
+ };
+ }, []);
+
+ return firebaseUser;
+}
diff --git a/web/lib/firebase/email-verification.ts b/web/lib/firebase/email-verification.ts
index 82c5b64..ce57e60 100644
--- a/web/lib/firebase/email-verification.ts
+++ b/web/lib/firebase/email-verification.ts
@@ -1,10 +1,9 @@
import toast from "react-hot-toast";
import {sendEmailVerification, User} from "firebase/auth";
-import {auth} from "web/lib/firebase/users";
export const sendVerificationEmail = async (
- user: User,
+ user: User | null,
t: any
) => {
// if (!privateUser?.email) {
@@ -15,6 +14,10 @@ export const sendVerificationEmail = async (
toast.error(t('settings.email.must_sign_in', 'You must be signed in to send a verification email.'))
return
}
+ if (user?.emailVerified) {
+ toast.success(t('settings.email.verified', 'Email Verified ✔️'))
+ return true
+ }
toast
.promise(sendEmailVerification(user), {
loading: t('settings.email.sending', 'Sending verification email...'),
@@ -33,7 +36,6 @@ export const sendVerificationEmail = async (
const start = Date.now();
while (Date.now() - start < timeoutMs) {
- const user = auth.currentUser;
if (!user) return false;
// Refresh user record from Firebase
@@ -41,9 +43,9 @@ export const sendVerificationEmail = async (
if (user.emailVerified) {
// IMPORTANT: force a new ID token with updated claims
- await user.getIdToken(true);
+ await user.getIdToken(true)
toast.success(t('settings.email.verified', 'Email Verified ✔️'))
- return true;
+ return true
}
await new Promise(r => setTimeout(r, intervalMs));
diff --git a/web/pages/messages/index.tsx b/web/pages/messages/index.tsx
index d802e2b..4e690d9 100644
--- a/web/pages/messages/index.tsx
+++ b/web/pages/messages/index.tsx
@@ -22,15 +22,15 @@ import {BannedBadge} from 'web/components/widgets/user-link'
import {PrivateMessageChannel} from 'common/supabase/private-messages'
import {SEO} from "web/components/SEO";
import {useT} from 'web/lib/locale'
-import {auth} from "web/lib/firebase/users";
import {EmailVerificationPrompt} from "web/components/messaging/email-verification-prompt";
+import {useFirebaseUser} from 'web/hooks/use-firebase-user'
export default function MessagesPage() {
useRedirectIfSignedOut()
const currentUser = useUser()
- const firebaseUser = auth.currentUser
+ const firebaseUser = useFirebaseUser()
const t = useT()
return (
@@ -42,7 +42,7 @@ export default function MessagesPage() {
{currentUser && firebaseUser && (
firebaseUser?.emailVerified ?
- :
+ :
)}
)
diff --git a/web/pages/settings.tsx b/web/pages/settings.tsx
index 1b65ccc..0aec2da 100644
--- a/web/pages/settings.tsx
+++ b/web/pages/settings.tsx
@@ -13,7 +13,6 @@ import {deleteAccount} from "web/lib/util/delete";
import router from "next/router";
import {Button} from "web/components/buttons/button";
import {updateEmail} from 'firebase/auth';
-import {auth} from "web/lib/firebase/users";
import {NotificationSettings} from "web/components/notifications";
import ThemeIcon from "web/components/theme-icon";
import {WithPrivateUser} from "web/components/user/with-user";
@@ -26,6 +25,7 @@ import {EmailVerificationButton} from "web/components/email-verification-button"
import {api} from 'web/lib/api'
import {useUser} from "web/hooks/use-user";
import {isNativeMobile} from "web/lib/util/webview";
+import {useFirebaseUser} from "web/hooks/use-firebase-user";
export default function NotificationsPage() {
const t = useT()
@@ -63,7 +63,7 @@ const LoadedGeneralSettings = (props: {
const {register, handleSubmit, formState: {errors}, reset} = useForm<{ newEmail: string }>()
const t = useT()
- const user = auth.currentUser
+ const user = useFirebaseUser()
if (!user) return null
const handleDeleteAccount = async () => {
@@ -131,7 +131,7 @@ const LoadedGeneralSettings = (props: {
{t('settings.general.account', 'Account')}
{t('settings.general.email', 'Email')}
-
+
{!isChangingEmail ? (