mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-19 15:27:16 -05:00
Fix email verif not refreshing component
This commit is contained in:
@@ -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 (
|
||||
<Col>
|
||||
<Col className={'gap-2 mx-4'}>
|
||||
<Button
|
||||
color={'gray-outline'}
|
||||
onClick={() => sendVerificationEmail(user, t)}
|
||||
@@ -24,6 +39,15 @@ export function EmailVerificationButton(props: {
|
||||
? t('settings.email.verified', 'Email Verified ✔️')
|
||||
: t('settings.email.send_verification', 'Send verification email')}
|
||||
</Button>
|
||||
<div className={'custom-link'}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={reload}
|
||||
className={'w-fit mx-2'}
|
||||
>
|
||||
{t('settings.email.just_verified', 'I verified my email')}
|
||||
</button>
|
||||
</div>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<Col className={clsx('gap-4 max-w-xl', className)}>
|
||||
<h3>{t('messaging.email_verification_required', "You must verify your email to message people.")}</h3>
|
||||
<EmailVerificationButton user={firebaseUser}/>
|
||||
<EmailVerificationButton/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/> :
|
||||
<EmailVerificationPrompt firebaseUser={firebaseUser} t={t} className='max-w-xl'/>
|
||||
<EmailVerificationPrompt t={t} className='max-w-xl'/>
|
||||
}
|
||||
<span className={'text-red-500'}>{error}</span>
|
||||
</Col>
|
||||
|
||||
32
web/hooks/use-firebase-user.ts
Normal file
32
web/hooks/use-firebase-user.ts
Normal file
@@ -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<User | null>(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;
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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 (
|
||||
<PageBase trackPageView={'messages page'} className={'p-2'}>
|
||||
@@ -42,7 +42,7 @@ export default function MessagesPage() {
|
||||
{currentUser && firebaseUser && (
|
||||
firebaseUser?.emailVerified ?
|
||||
<MessagesContent currentUser={currentUser}/>
|
||||
: <EmailVerificationPrompt firebaseUser={firebaseUser} t={t}/>
|
||||
: <EmailVerificationPrompt t={t}/>
|
||||
)}
|
||||
</PageBase>
|
||||
)
|
||||
|
||||
@@ -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: {
|
||||
<h3>{t('settings.general.account', 'Account')}</h3>
|
||||
<h5>{t('settings.general.email', 'Email')}</h5>
|
||||
|
||||
<EmailVerificationButton user={user}/>
|
||||
<EmailVerificationButton/>
|
||||
|
||||
{!isChangingEmail ? (
|
||||
<Button color={'gray-outline'} onClick={() => setIsChangingEmail(true)} className="w-fit">
|
||||
|
||||
Reference in New Issue
Block a user