mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-15 08:21:15 -05:00
People must verify their email to send messages
This commit is contained in:
@@ -1,13 +1,24 @@
|
||||
import { APIError, APIHandler } from 'api/helpers/endpoint'
|
||||
import { filterDefined } from 'common/util/array'
|
||||
import { uniq } from 'lodash'
|
||||
import { createSupabaseDirectClient } from 'shared/supabase/init'
|
||||
import { addUsersToPrivateMessageChannel } from 'api/helpers/private-messages'
|
||||
import { getPrivateUser, getUser } from 'shared/utils'
|
||||
import {APIError, APIHandler} from 'api/helpers/endpoint'
|
||||
import {filterDefined} from 'common/util/array'
|
||||
import {uniq} from 'lodash'
|
||||
import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
import {addUsersToPrivateMessageChannel} from 'api/helpers/private-messages'
|
||||
import {getPrivateUser, getUser} from 'shared/utils'
|
||||
import * as admin from 'firebase-admin'
|
||||
|
||||
export const createPrivateUserMessageChannel: APIHandler<
|
||||
'create-private-user-message-channel'
|
||||
> = async (body, auth) => {
|
||||
// Do not use auth.creds.data as its info can be staled. It comes from a client token, which refreshes hourly or so
|
||||
const user = await admin.auth().getUser(auth.uid)
|
||||
// console.log(JSON.stringify(user, null, 2))
|
||||
if (!user?.emailVerified) {
|
||||
throw new APIError(
|
||||
403,
|
||||
'You must verify your email to contact people.'
|
||||
)
|
||||
}
|
||||
|
||||
const userIds = uniq(body.userIds.concat(auth.uid))
|
||||
|
||||
const pg = createSupabaseDirectClient()
|
||||
@@ -41,11 +52,12 @@ export const createPrivateUserMessageChannel: APIHandler<
|
||||
|
||||
const currentChannel = await pg.oneOrNone(
|
||||
`
|
||||
select channel_id from private_user_message_channel_members
|
||||
group by channel_id
|
||||
having array_agg(user_id::text) @> array[$1]::text[]
|
||||
and array_agg(user_id::text) <@ array[$1]::text[]
|
||||
`,
|
||||
select channel_id
|
||||
from private_user_message_channel_members
|
||||
group by channel_id
|
||||
having array_agg(user_id::text) @> array [$1]::text[]
|
||||
and array_agg(user_id::text) <@ array [$1]::text[]
|
||||
`,
|
||||
[userIds]
|
||||
)
|
||||
if (currentChannel)
|
||||
@@ -55,17 +67,19 @@ export const createPrivateUserMessageChannel: APIHandler<
|
||||
}
|
||||
|
||||
const channel = await pg.one(
|
||||
`insert into private_user_message_channels default values returning id`
|
||||
`insert into private_user_message_channels default
|
||||
values
|
||||
returning id`
|
||||
)
|
||||
|
||||
await pg.none(
|
||||
`insert into private_user_message_channel_members (channel_id, user_id, role, status)
|
||||
values ($1, $2, 'creator', 'joined')
|
||||
`,
|
||||
values ($1, $2, 'creator', 'joined')
|
||||
`,
|
||||
[channel.id, creatorId]
|
||||
)
|
||||
|
||||
const memberIds = userIds.filter((id) => id !== creatorId)
|
||||
await addUsersToPrivateMessageChannel(memberIds, channel.id, pg)
|
||||
return { status: 'success', channelId: Number(channel.id) }
|
||||
return {status: 'success', channelId: Number(channel.id)}
|
||||
}
|
||||
|
||||
26
web/components/messaging/email-verification-prompt.tsx
Normal file
26
web/components/messaging/email-verification-prompt.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import {Button} from 'web/components/buttons/button'
|
||||
import {Col} from 'web/components/layout/col'
|
||||
import {sendVerificationEmail} from 'web/lib/firebase/email-verification'
|
||||
import clsx from 'clsx'
|
||||
|
||||
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>
|
||||
<Button color={'gray-outline'} onClick={() => sendVerificationEmail(firebaseUser!, t)} disabled={false}>
|
||||
{t('settings.email.send_verification', 'Send verification email')}
|
||||
</Button>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
@@ -15,9 +15,10 @@ 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 {firebaseLogin} from 'web/lib/firebase/users'
|
||||
import {auth, 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'
|
||||
|
||||
export const SendMessageButton = (props: {
|
||||
toUser: User
|
||||
@@ -25,11 +26,12 @@ export const SendMessageButton = (props: {
|
||||
includeLabel?: boolean
|
||||
circleButton?: boolean
|
||||
}) => {
|
||||
const { toUser, currentUser, includeLabel, circleButton } = props
|
||||
const {toUser, currentUser, includeLabel, circleButton} = props
|
||||
const firebaseUser = auth.currentUser
|
||||
const router = useRouter()
|
||||
const privateUser = usePrivateUser()
|
||||
const channelMemberships = useSortedPrivateMessageMemberships(currentUser?.id)
|
||||
const { memberIdsByChannelId } = channelMemberships
|
||||
const {memberIdsByChannelId} = channelMemberships
|
||||
const t = useT()
|
||||
|
||||
const [openComposeModal, setOpenComposeModal] = useState(false)
|
||||
@@ -123,13 +125,15 @@ export const SendMessageButton = (props: {
|
||||
<Row className={'w-full'}>
|
||||
<Title className={'!mb-2'}>{t('send_message.title', 'Message')} {toUser.name}</Title>
|
||||
</Row>
|
||||
<CommentInputTextArea
|
||||
editor={editor}
|
||||
user={currentUser}
|
||||
submit={sendMessage}
|
||||
isSubmitting={!editor || submitting}
|
||||
submitOnEnter={false}
|
||||
/>
|
||||
{firebaseUser?.emailVerified ? <CommentInputTextArea
|
||||
editor={editor}
|
||||
user={currentUser}
|
||||
submit={sendMessage}
|
||||
isSubmitting={!editor || submitting}
|
||||
submitOnEnter={false}
|
||||
/> :
|
||||
<EmailVerificationPrompt firebaseUser={firebaseUser} t={t} className='max-w-xl'/>
|
||||
}
|
||||
<span className={'text-red-500'}>{error}</span>
|
||||
</Col>
|
||||
</Modal>
|
||||
|
||||
26
web/lib/firebase/email-verification.ts
Normal file
26
web/lib/firebase/email-verification.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import toast from "react-hot-toast";
|
||||
import {sendEmailVerification, User} from "firebase/auth";
|
||||
|
||||
|
||||
export const sendVerificationEmail = async (
|
||||
user: User,
|
||||
t: any
|
||||
) => {
|
||||
// if (!privateUser?.email) {
|
||||
// toast.error(t('settings.email.no_email', 'No email found on your account.'))
|
||||
// return
|
||||
// }
|
||||
if (!user) {
|
||||
toast.error(t('settings.email.must_sign_in', 'You must be signed in to send a verification email.'))
|
||||
return
|
||||
}
|
||||
toast
|
||||
.promise(sendEmailVerification(user), {
|
||||
loading: t('settings.email.sending', 'Sending verification email...'),
|
||||
success: t('settings.email.verification_sent', 'Verification email sent — check your inbox and spam.'),
|
||||
error: t('settings.email.verification_failed', 'Failed to send verification email.'),
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("Failed to send verification email")
|
||||
})
|
||||
}
|
||||
@@ -822,6 +822,7 @@
|
||||
"settings.email.verification_failed": "Senden der Bestätigungs-E-Mail fehlgeschlagen.",
|
||||
"settings.email.verification_sent": "Bestätigungs-E-Mail gesendet – prüfen Sie Ihren Posteingang und Spam-Ordner.",
|
||||
"settings.email.verified": "E-Mail bestätigt ✔️",
|
||||
"messaging.email_verification_required": "Sie müssen Ihre E-Mail überprüfen, um Personen zu schreiben.",
|
||||
"settings.general.account": "Konto",
|
||||
"settings.general.email": "E-Mail",
|
||||
"settings.general.language": "Sprache",
|
||||
|
||||
@@ -822,6 +822,7 @@
|
||||
"settings.email.verification_failed": "Échec de l'envoi de l'e-mail de vérification.",
|
||||
"settings.email.verification_sent": "E-mail de vérification envoyé — vérifiez votre boîte de réception et les spams.",
|
||||
"settings.email.verified": "E-mail vérifié ✔️",
|
||||
"messaging.email_verification_required": "Vous devez vérifier votre e-mail pour pouvoir envoyer des messages.",
|
||||
"settings.general.account": "Compte",
|
||||
"settings.general.email": "E-mail",
|
||||
"settings.general.language": "Langue",
|
||||
|
||||
@@ -22,12 +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";
|
||||
|
||||
|
||||
export default function MessagesPage() {
|
||||
useRedirectIfSignedOut()
|
||||
|
||||
const currentUser = useUser()
|
||||
const firebaseUser = auth.currentUser
|
||||
const t = useT()
|
||||
return (
|
||||
<PageBase trackPageView={'messages page'} className={'p-2'}>
|
||||
@@ -36,7 +39,11 @@ export default function MessagesPage() {
|
||||
description={t('messages.seo.description', 'Your Messages')}
|
||||
url={`/messages`}
|
||||
/>
|
||||
{currentUser && <MessagesContent currentUser={currentUser}/>}
|
||||
{currentUser && (
|
||||
firebaseUser?.emailVerified ?
|
||||
<MessagesContent currentUser={currentUser}/>
|
||||
: <EmailVerificationPrompt firebaseUser={firebaseUser} t={t}/>
|
||||
)}
|
||||
</PageBase>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {useRedirectIfSignedOut} from "web/hooks/use-redirect-if-signed-out";
|
||||
import {deleteAccount} from "web/lib/util/delete";
|
||||
import router from "next/router";
|
||||
import {Button} from "web/components/buttons/button";
|
||||
import {sendEmailVerification, updateEmail} from 'firebase/auth';
|
||||
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";
|
||||
@@ -22,6 +22,7 @@ import {AboutSettings} from "web/components/about-settings";
|
||||
import {LanguagePicker} from "web/components/language/language-picker";
|
||||
import {useT} from "web/lib/locale";
|
||||
import HiddenProfilesModal from 'web/components/settings/hidden-profiles-modal'
|
||||
import {sendVerificationEmail} from "web/lib/firebase/email-verification";
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const t = useT()
|
||||
@@ -107,27 +108,6 @@ const LoadedGeneralSettings = (props: {
|
||||
changeUserEmail(data.newEmail)
|
||||
}
|
||||
|
||||
|
||||
const sendVerificationEmail = async () => {
|
||||
if (!privateUser?.email) {
|
||||
toast.error(t('settings.email.no_email', 'No email found on your account.'))
|
||||
return
|
||||
}
|
||||
if (!user) {
|
||||
toast.error(t('settings.email.must_sign_in', 'You must be signed in to send a verification email.'))
|
||||
return
|
||||
}
|
||||
toast
|
||||
.promise(sendEmailVerification(user), {
|
||||
loading: t('settings.email.sending', 'Sending verification email...'),
|
||||
success: t('settings.email.verification_sent', 'Verification email sent — check your inbox and spam.'),
|
||||
error: t('settings.email.verification_failed', 'Failed to send verification email.'),
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Failed to send verification email")
|
||||
})
|
||||
}
|
||||
|
||||
const isEmailVerified = user.emailVerified
|
||||
|
||||
return <>
|
||||
@@ -147,7 +127,8 @@ const LoadedGeneralSettings = (props: {
|
||||
<h3>{t('settings.general.account', 'Account')}</h3>
|
||||
<h5>{t('settings.general.email', 'Email')}</h5>
|
||||
|
||||
<Button color={'gray-outline'} onClick={sendVerificationEmail} disabled={!privateUser?.email || isEmailVerified}>
|
||||
<Button color={'gray-outline'} onClick={() => sendVerificationEmail(user, t)}
|
||||
disabled={!privateUser?.email || isEmailVerified}>
|
||||
{isEmailVerified ? t('settings.email.verified', 'Email Verified ✔️') : t('settings.email.send_verification', 'Send verification email')}
|
||||
</Button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user