From 1ce3e56feffc78b3214abc6b13f14d7a27a25c4b Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Wed, 15 Apr 2026 11:52:10 -0400 Subject: [PATCH 1/3] email verification page --- messages/en.json | 10 ++ messages/fr.json | 10 ++ .../EmailVerificationContent.tsx | 101 ++++++++++++++++++ src/app/[locale]/email-verification/page.tsx | 48 +++++++++ 4 files changed, 169 insertions(+) create mode 100644 src/app/[locale]/email-verification/EmailVerificationContent.tsx create mode 100644 src/app/[locale]/email-verification/page.tsx diff --git a/messages/en.json b/messages/en.json index e1fd815..0df2d1a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -116,6 +116,16 @@ "transitFeedsRedirectTitle": "You've been redirected from TransitFeeds", "transitFeedsRedirectBody": "This page now lives on MobilityDatabase.org, where you'll find the most up-to-date transit data." }, + "emailVerification": { + "loadingTitle": "Verifying your email", + "loadingDescription": "Please wait while we confirm your email address.", + "successTitle": "Email verified", + "successDescription": "Your email address has been verified successfully. You can return to Mobility Database and continue using your account.", + "errorTitle": "Unable to verify email", + "errorDescription": "This verification link is invalid, incomplete, or has already been used.", + "invalidLink": "The verification link is missing required information.", + "verificationFailed": "We could not verify your email with this link." + }, "feeds": { "feeds": "Feeds", "dataType": "Data Format", diff --git a/messages/fr.json b/messages/fr.json index b65bfd9..0450aa9 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -116,6 +116,16 @@ "transitFeedsRedirectTitle": "Vous avez été redirigé depuis TransitFeeds", "transitFeedsRedirectBody": "Cette page se trouve désormais sur MobilityDatabase.org, où vous trouverez les données de transit les plus récentes." }, + "emailVerification": { + "loadingTitle": "Vérification de votre e-mail", + "loadingDescription": "Veuillez patienter pendant que nous confirmons votre adresse e-mail.", + "successTitle": "E-mail vérifié", + "successDescription": "Votre adresse e-mail a bien été vérifiée. Vous pouvez retourner sur Mobility Database et continuer à utiliser votre compte.", + "errorTitle": "Impossible de vérifier l'e-mail", + "errorDescription": "Ce lien de vérification est invalide, incomplet ou a déjà été utilisé.", + "invalidLink": "Le lien de vérification ne contient pas les informations requises.", + "verificationFailed": "Nous n'avons pas pu vérifier votre e-mail avec ce lien." + }, "feeds": { "feeds": "Feeds", "dataType": "Data Format", diff --git a/src/app/[locale]/email-verification/EmailVerificationContent.tsx b/src/app/[locale]/email-verification/EmailVerificationContent.tsx new file mode 100644 index 0000000..d84bbdf --- /dev/null +++ b/src/app/[locale]/email-verification/EmailVerificationContent.tsx @@ -0,0 +1,101 @@ +'use client'; + +import * as React from 'react'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { + Alert, + CircularProgress, + Stack, + Typography, + useTheme, +} from '@mui/material'; +import { useTranslations } from 'next-intl'; +import { app } from '../../../firebase'; +import { ContentBox } from '../../components/ContentBox'; + +type VerificationStatus = 'loading' | 'success' | 'error'; + +interface EmailVerificationContentProps { + mode?: string; + oobCode?: string; +} + +export default function EmailVerificationContent({ + mode, + oobCode, +}: EmailVerificationContentProps): React.ReactElement { + const t = useTranslations('emailVerification'); + const theme = useTheme(); + const [status, setStatus] = React.useState('loading'); + const [errorMessage, setErrorMessage] = React.useState(null); + + React.useEffect(() => { + const verifyEmail = async (): Promise => { + if (mode !== 'verifyEmail' || oobCode == null || oobCode.length === 0) { + setStatus('error'); + setErrorMessage(t('invalidLink')); + return; + } + + try { + await app.auth().applyActionCode(oobCode); + + setStatus('success'); + setErrorMessage(null); + } catch { + setStatus('error'); + setErrorMessage(t('verificationFailed')); + } + }; + + void verifyEmail(); + }, [mode, oobCode]); + + return ( + + + {status === 'loading' ? ( + + ) : status === 'success' ? ( + + ) : ( + + )} + + + + {status === 'loading' + ? t('loadingTitle') + : status === 'success' + ? t('successTitle') + : t('errorTitle')} + + + {status === 'loading' + ? t('loadingDescription') + : status === 'success' + ? t('successDescription') + : t('errorDescription')} + + + + {errorMessage != null && ( + + {errorMessage} + + )} + + + ); +} diff --git a/src/app/[locale]/email-verification/page.tsx b/src/app/[locale]/email-verification/page.tsx new file mode 100644 index 0000000..00b1889 --- /dev/null +++ b/src/app/[locale]/email-verification/page.tsx @@ -0,0 +1,48 @@ +import { type ReactElement } from 'react'; +import { setRequestLocale } from 'next-intl/server'; +import { type Metadata } from 'next'; +import { type Locale, routing } from '../../../i18n/routing'; +import EmailVerificationContent from './EmailVerificationContent'; + +export const metadata: Metadata = { + title: 'Email Verification | MobilityDatabase', + description: + 'Verify your Mobility Database account email address through Firebase authentication.', + robots: { + index: false, + follow: false, + googleBot: { + index: false, + follow: false, + 'max-image-preview': 'none', + 'max-snippet': -1, + 'max-video-preview': -1, + }, + }, +}; + +export function generateStaticParams(): Array<{ + locale: Locale; +}> { + return routing.locales.map((locale) => ({ locale })); +} + +interface PageProps { + params: Promise<{ locale: string }>; + searchParams: Promise<{ + mode?: string; + oobCode?: string; + }>; +} + +export default async function EmailVerificationPage({ + params, + searchParams, +}: PageProps): Promise { + const { locale } = await params; + const { mode, oobCode } = await searchParams; + + setRequestLocale(locale as Locale); + + return ; +} From 2c6e438bde4c2d4082264a677e920438dae7cf93 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Wed, 15 Apr 2026 14:28:49 -0400 Subject: [PATCH 2/3] Update messages/en.json Co-authored-by: David Gamez <1192523+davidgamez@users.noreply.github.com> --- messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en.json b/messages/en.json index 0df2d1a..77b3d33 100644 --- a/messages/en.json +++ b/messages/en.json @@ -120,7 +120,7 @@ "loadingTitle": "Verifying your email", "loadingDescription": "Please wait while we confirm your email address.", "successTitle": "Email verified", - "successDescription": "Your email address has been verified successfully. You can return to Mobility Database and continue using your account.", + "successDescription": "Your email address has been verified successfully.You can now continue using your Mobility Database account.", "errorTitle": "Unable to verify email", "errorDescription": "This verification link is invalid, incomplete, or has already been used.", "invalidLink": "The verification link is missing required information.", From 134ddc59dc6e81d8506fa79bd22f5578cd27f4a8 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Wed, 15 Apr 2026 14:30:05 -0400 Subject: [PATCH 3/3] removed uneeded type --- src/app/[locale]/email-verification/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[locale]/email-verification/page.tsx b/src/app/[locale]/email-verification/page.tsx index 00b1889..3b63fff 100644 --- a/src/app/[locale]/email-verification/page.tsx +++ b/src/app/[locale]/email-verification/page.tsx @@ -42,7 +42,7 @@ export default async function EmailVerificationPage({ const { locale } = await params; const { mode, oobCode } = await searchParams; - setRequestLocale(locale as Locale); + setRequestLocale(locale); return ; }