feat(backend): Implement email change flow
This commit is contained in:
parent
6a3a14a6bd
commit
4c3d3a5019
14 changed files with 202 additions and 47 deletions
|
@ -432,10 +432,12 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
const isUnitTest = this.isUnitTest(body)
|
const isUnitTest = this.isUnitTest(body)
|
||||||
if (typeof body.email === 'string' && this.clear.email !== clean(body.email)) {
|
if (typeof body.email === 'string' && this.clear.email !== clean(body.email)) {
|
||||||
// Email change (requires confirmation)
|
// Email change (requires confirmation)
|
||||||
|
const check = randomString()
|
||||||
this.confirmation = await this.Confirmation.create({
|
this.confirmation = await this.Confirmation.create({
|
||||||
type: 'emailchange',
|
type: 'emailchange',
|
||||||
data: {
|
data: {
|
||||||
language: this.record.language,
|
language: this.record.language,
|
||||||
|
check,
|
||||||
email: {
|
email: {
|
||||||
current: this.clear.email,
|
current: this.clear.email,
|
||||||
new: body.email,
|
new: body.email,
|
||||||
|
@ -451,13 +453,20 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
to: body.email,
|
to: body.email,
|
||||||
cc: this.clear.email,
|
cc: this.clear.email,
|
||||||
replacements: {
|
replacements: {
|
||||||
actionUrl: i18nUrl(this.language, `/confirm/emailchange/${this.Confirmation.record.id}`),
|
actionUrl: i18nUrl(
|
||||||
whyUrl: i18nUrl(this.language, `/docs/faq/email/why-emailchange`),
|
this.record.language,
|
||||||
supportUrl: i18nUrl(this.language, `/patrons/join`),
|
`/confirm/emailchange/${this.Confirmation.record.id}/${check}`
|
||||||
|
),
|
||||||
|
whyUrl: i18nUrl(this.record.language, `/docs/faq/email/why-emailchange`),
|
||||||
|
supportUrl: i18nUrl(this.record.language, `/patrons/join`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (typeof body.confirmation === 'string' && body.confirm === 'emailchange') {
|
} else if (
|
||||||
|
typeof body.confirmation === 'string' &&
|
||||||
|
body.confirm === 'emailchange' &&
|
||||||
|
typeof body.check === 'string'
|
||||||
|
) {
|
||||||
// Handle email change confirmation
|
// Handle email change confirmation
|
||||||
await this.Confirmation.read({ id: body.confirmation })
|
await this.Confirmation.read({ id: body.confirmation })
|
||||||
|
|
||||||
|
@ -472,7 +481,11 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.Confirmation.clear.data
|
const data = this.Confirmation.clear.data
|
||||||
if (data.email.current === this.clear.email && typeof data.email.new === 'string') {
|
if (
|
||||||
|
data.check === body.check &&
|
||||||
|
data.email.current === this.clear.email &&
|
||||||
|
typeof data.email.new === 'string'
|
||||||
|
) {
|
||||||
await this.unguardedUpdate({
|
await this.unguardedUpdate({
|
||||||
email: this.encrypt(data.email.new),
|
email: this.encrypt(data.email.new),
|
||||||
ehash: hash(clean(data.email.new)),
|
ehash: hash(clean(data.email.new)),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
subject: Email change subject here FIXME
|
subject: "[FreeSewing] Confirm your new E-mail address"
|
||||||
heading: Does this new E-mail address work?
|
heading: Does this new E-mail address work?
|
||||||
lead: 'To confirm your new E-mail address, click the big black rectangle below:'
|
lead: 'To confirm your new E-mail address, click the big black rectangle below:'
|
||||||
text-lead: 'To confirm your new E-mail address, click the link below:'
|
text-lead: 'To confirm your new E-mail address, click the link below:'
|
||||||
|
|
|
@ -6,7 +6,7 @@ import es from '../../../../public/locales/es/emailchange.json' assert { type: '
|
||||||
import fr from '../../../../public/locales/fr/emailchange.json' assert { type: 'json' }
|
import fr from '../../../../public/locales/fr/emailchange.json' assert { type: 'json' }
|
||||||
import nl from '../../../../public/locales/nl/emailchange.json' assert { type: 'json' }
|
import nl from '../../../../public/locales/nl/emailchange.json' assert { type: 'json' }
|
||||||
|
|
||||||
export const emailChange = {
|
export const emailchange = {
|
||||||
html: wrap.html(`
|
html: wrap.html(`
|
||||||
${headingRow.html}
|
${headingRow.html}
|
||||||
${lead1Row.html}
|
${lead1Row.html}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { emailChange, translations as emailChangeTranslations } from './emailchange/index.mjs'
|
import { emailchange, translations as emailchangeTranslations } from './emailchange/index.mjs'
|
||||||
import { goodbye, translations as goodbyeTranslations } from './goodbye/index.mjs'
|
import { goodbye, translations as goodbyeTranslations } from './goodbye/index.mjs'
|
||||||
import { loginLink, translations as loginLinkTranslations } from './loginlink/index.mjs'
|
import { loginlink, translations as loginlinkTranslations } from './loginlink/index.mjs'
|
||||||
import { newsletterSub, translations as newsletterSubTranslations } from './newslettersub/index.mjs'
|
import { newslettersub, translations as newslettersubTranslations } from './newslettersub/index.mjs'
|
||||||
import { passwordReset, translations as passwordResetTranslations } from './passwordreset/index.mjs'
|
import { passwordreset, translations as passwordresetTranslations } from './passwordreset/index.mjs'
|
||||||
import { signup, translations as signupTranslations } from './signup/index.mjs'
|
import { signup, translations as signupTranslations } from './signup/index.mjs'
|
||||||
import { signupAea, translations as signupAeaTranslations } from './signup-aea/index.mjs'
|
import { signupaea, translations as signupaeaTranslations } from './signup-aea/index.mjs'
|
||||||
import { signupAed, translations as signupAedTranslations } from './signup-aed/index.mjs'
|
import { signupaed, translations as signupaedTranslations } from './signup-aed/index.mjs'
|
||||||
// Shared translations
|
// Shared translations
|
||||||
import en from '../../../public/locales/en/shared.json' assert { type: 'json' }
|
import en from '../../../public/locales/en/shared.json' assert { type: 'json' }
|
||||||
import de from '../../../public/locales/de/shared.json' assert { type: 'json' }
|
import de from '../../../public/locales/de/shared.json' assert { type: 'json' }
|
||||||
|
@ -13,25 +13,28 @@ import es from '../../../public/locales/es/shared.json' assert { type: 'json' }
|
||||||
import fr from '../../../public/locales/fr/shared.json' assert { type: 'json' }
|
import fr from '../../../public/locales/fr/shared.json' assert { type: 'json' }
|
||||||
import nl from '../../../public/locales/nl/shared.json' assert { type: 'json' }
|
import nl from '../../../public/locales/nl/shared.json' assert { type: 'json' }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Everything is kept lowercase here because these key names are used in URLS
|
||||||
|
*/
|
||||||
export const templates = {
|
export const templates = {
|
||||||
emailChange,
|
emailchange,
|
||||||
goodbye,
|
goodbye,
|
||||||
loginLink,
|
loginlink,
|
||||||
newsletterSub,
|
newslettersub,
|
||||||
passwordReset,
|
passwordreset,
|
||||||
signup,
|
signup,
|
||||||
'signup-aea': signupAea,
|
'signup-aea': signupaea,
|
||||||
'signup-aed': signupAed,
|
'signup-aed': signupaed,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const translations = {
|
export const translations = {
|
||||||
emailChange: emailChangeTranslations,
|
emailchange: emailchangeTranslations,
|
||||||
goodbye: goodbyeTranslations,
|
goodbye: goodbyeTranslations,
|
||||||
loginLink: loginLinkTranslations,
|
loginlink: loginlinkTranslations,
|
||||||
newsletterSub: newsletterSubTranslations,
|
newslettersub: newslettersubTranslations,
|
||||||
passwordReset: passwordResetTranslations,
|
passwordreset: passwordresetTranslations,
|
||||||
signup: signupTranslations,
|
signup: signupTranslations,
|
||||||
'signup-aea': signupAeaTranslations,
|
'signup-aea': signupaeaTranslations,
|
||||||
'signup-aed': signupAedTranslations,
|
'signup-aed': signupaedTranslations,
|
||||||
shared: { en, de, es, fr, nl },
|
shared: { en, de, es, fr, nl },
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import es from '../../../../public/locales/es/loginlink.json' assert { type: 'js
|
||||||
import fr from '../../../../public/locales/fr/loginlink.json' assert { type: 'json' }
|
import fr from '../../../../public/locales/fr/loginlink.json' assert { type: 'json' }
|
||||||
import nl from '../../../../public/locales/nl/loginlink.json' assert { type: 'json' }
|
import nl from '../../../../public/locales/nl/loginlink.json' assert { type: 'json' }
|
||||||
|
|
||||||
export const loginLink = {
|
export const loginlink = {
|
||||||
html: wrap.html(`
|
html: wrap.html(`
|
||||||
${headingRow}
|
${headingRow}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import es from '../../../../public/locales/es/newslettersub.json' assert { type:
|
||||||
import fr from '../../../../public/locales/fr/newslettersub.json' assert { type: 'json' }
|
import fr from '../../../../public/locales/fr/newslettersub.json' assert { type: 'json' }
|
||||||
import nl from '../../../../public/locales/nl/newslettersub.json' assert { type: 'json' }
|
import nl from '../../../../public/locales/nl/newslettersub.json' assert { type: 'json' }
|
||||||
|
|
||||||
export const newsletterSub = {
|
export const newslettersub = {
|
||||||
html: wrap.html(`
|
html: wrap.html(`
|
||||||
${headingRow.html}
|
${headingRow.html}
|
||||||
${lead1Row.html}
|
${lead1Row.html}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import es from '../../../../public/locales/es/passwordreset.json' assert { type:
|
||||||
import fr from '../../../../public/locales/fr/passwordreset.json' assert { type: 'json' }
|
import fr from '../../../../public/locales/fr/passwordreset.json' assert { type: 'json' }
|
||||||
import nl from '../../../../public/locales/nl/passwordreset.json' assert { type: 'json' }
|
import nl from '../../../../public/locales/nl/passwordreset.json' assert { type: 'json' }
|
||||||
|
|
||||||
export const passwordReset = {
|
export const passwordreset = {
|
||||||
html: wrap.html(`
|
html: wrap.html(`
|
||||||
${headingRow.html}
|
${headingRow.html}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import fr from '../../../../public/locales/fr/signup-aea.json' assert { type: 'j
|
||||||
import nl from '../../../../public/locales/nl/signup-aea.json' assert { type: 'json' }
|
import nl from '../../../../public/locales/nl/signup-aea.json' assert { type: 'json' }
|
||||||
|
|
||||||
// aea = Account Exists and is Active
|
// aea = Account Exists and is Active
|
||||||
export const signupAea = {
|
export const signupaea = {
|
||||||
html: wrap.html(`
|
html: wrap.html(`
|
||||||
${headingRow.html}
|
${headingRow.html}
|
||||||
${preLeadRow.html}
|
${preLeadRow.html}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import fr from '../../../../public/locales/fr/signup-aed.json' assert { type: 'j
|
||||||
import nl from '../../../../public/locales/nl/signup-aed.json' assert { type: 'json' }
|
import nl from '../../../../public/locales/nl/signup-aed.json' assert { type: 'json' }
|
||||||
|
|
||||||
// aed = Account Exists but is Disabled
|
// aed = Account Exists but is Disabled
|
||||||
export const signupAed = {
|
export const signupaed = {
|
||||||
html: wrap.html(`
|
html: wrap.html(`
|
||||||
${headingRow.html}
|
${headingRow.html}
|
||||||
${preLeadRow.html}
|
${preLeadRow.html}
|
||||||
|
|
|
@ -19,13 +19,21 @@ export const mailer = (config) => ({
|
||||||
* If you want to use another way to send email, change the mailer
|
* If you want to use another way to send email, change the mailer
|
||||||
* assignment above to point to another method to deliver email
|
* assignment above to point to another method to deliver email
|
||||||
*/
|
*/
|
||||||
async function sendEmailViaAwsSes(config, { template, to, language = 'en', replacements = {} }) {
|
async function sendEmailViaAwsSes(
|
||||||
|
config,
|
||||||
|
{ template, to, cc = false, language = 'en', replacements = {} }
|
||||||
|
) {
|
||||||
// Make sure we have what it takes
|
// Make sure we have what it takes
|
||||||
if (!template || !to || typeof templates[template] === 'undefined') {
|
if (!template || !to || typeof templates[template] === 'undefined') {
|
||||||
log.warn(`Tried to email invalid template: ${template}`)
|
log.warn(`Tried to email invalid template: ${template}`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cc) cc = []
|
||||||
|
if (typeof cc === 'string') cc = [cc]
|
||||||
|
if (Array.isArray(config.aws.ses.cc) && config.aws.ses.cc.length > 0)
|
||||||
|
cc = [...new Set([...cc, ...config.aws.ses.cc])]
|
||||||
|
|
||||||
log.info(`Emailing template ${template} to ${to}`)
|
log.info(`Emailing template ${template} to ${to}`)
|
||||||
|
|
||||||
// Load template
|
// Load template
|
||||||
|
@ -62,7 +70,7 @@ async function sendEmailViaAwsSes(config, { template, to, language = 'en', repla
|
||||||
},
|
},
|
||||||
Destination: {
|
Destination: {
|
||||||
ToAddresses: [to],
|
ToAddresses: [to],
|
||||||
CcAddresses: config.aws.ses.cc || [],
|
CcAddresses: cc,
|
||||||
BccAddresses: config.aws.ses.bcc || [],
|
BccAddresses: config.aws.ses.bcc || [],
|
||||||
},
|
},
|
||||||
FeedbackForwardingEmailAddress: config.aws.ses.feedback,
|
FeedbackForwardingEmailAddress: config.aws.ses.feedback,
|
||||||
|
|
|
@ -13,9 +13,9 @@ yourProfile: Your profile
|
||||||
yourPatterns: Your patterns
|
yourPatterns: Your patterns
|
||||||
yourSets: Your measurement sets
|
yourSets: Your measurement sets
|
||||||
logout: Log out
|
logout: Log out
|
||||||
|
politeOhCrap: Oh fiddlesticks
|
||||||
bio: Bio
|
bio: Bio
|
||||||
email: Email address
|
email: E-mail address
|
||||||
github: Github username
|
github: Github username
|
||||||
img: Profile image
|
img: Profile image
|
||||||
username: Username
|
username: Username
|
||||||
|
@ -124,6 +124,10 @@ usernameNotAvailable: Username is not available
|
||||||
|
|
||||||
# email
|
# email
|
||||||
emailTitle: Where can we reach you in case we have a good reason for it (like when you forgot your password)?
|
emailTitle: Where can we reach you in case we have a good reason for it (like when you forgot your password)?
|
||||||
|
oneMoreThing: One more thing
|
||||||
|
oneMomentPlease: One moment please
|
||||||
|
emailChangeConfirmation: We have sent an E-mail to your new address to confirm this change.
|
||||||
|
vagueError: Something went wrong, and we're not certain how to handle it. Please try again, or involve a human being for assistance.
|
||||||
|
|
||||||
# github
|
# github
|
||||||
githubTitle: Enter your github username here to receive updates when you report a problem through this website.
|
githubTitle: Enter your github username here to receive updates when you report a problem through this website.
|
||||||
|
|
|
@ -7,19 +7,22 @@ import { useToast } from 'site/hooks/useToast.mjs'
|
||||||
import { validateEmail, validateTld } from 'site/utils.mjs'
|
import { validateEmail, validateTld } from 'site/utils.mjs'
|
||||||
// Components
|
// Components
|
||||||
import { BackToAccountButton } from './shared.mjs'
|
import { BackToAccountButton } from './shared.mjs'
|
||||||
|
import { Popout } from 'shared/components/popout.mjs'
|
||||||
|
|
||||||
export const ns = ['account']
|
export const ns = ['account', 'toast']
|
||||||
|
|
||||||
export const EmailSettings = ({ app, title = false }) => {
|
export const EmailSettings = ({ app, title = false }) => {
|
||||||
const backend = useBackend(app)
|
const backend = useBackend(app)
|
||||||
const { t } = useTranslation(ns)
|
const { t } = useTranslation(ns)
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const [email, setEmail] = useState(app.account.email)
|
const [email, setEmail] = useState(app.account.email)
|
||||||
|
const [changed, setChanged] = useState(false)
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
const result = await backend.updateAccount({ email })
|
const result = await backend.updateAccount({ email })
|
||||||
if (result) toast.for.settingsSaved()
|
if (result) toast.for.settingsSaved()
|
||||||
else toast.for.backendError()
|
else toast.for.backendError()
|
||||||
|
setChanged(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = (validateEmail(email) && validateTld(email)) || false
|
const valid = (validateEmail(email) && validateTld(email)) || false
|
||||||
|
@ -27,17 +30,30 @@ export const EmailSettings = ({ app, title = false }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{title ? <h2 className="text-4xl">{t('emailTitle')}</h2> : null}
|
{title ? <h2 className="text-4xl">{t('emailTitle')}</h2> : null}
|
||||||
<div className="flex flex-row items-center mt-4">
|
{changed ? (
|
||||||
<input
|
<Popout note>
|
||||||
value={email}
|
<h3>{t('oneMoreThing')}</h3>
|
||||||
onChange={(evt) => setEmail(evt.target.value)}
|
<p>{t('emailChangeConfirmation')}</p>
|
||||||
className="input w-full input-bordered flex flex-row"
|
</Popout>
|
||||||
type="text"
|
) : (
|
||||||
/>
|
<>
|
||||||
</div>
|
<div className="flex flex-row items-center mt-4">
|
||||||
<button className="btn mt-4 btn-primary w-full" onClick={save} disabled={!valid}>
|
<input
|
||||||
{t('save')}
|
value={email}
|
||||||
</button>
|
onChange={(evt) => setEmail(evt.target.value)}
|
||||||
|
className="input w-full input-bordered flex flex-row"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn mt-4 btn-primary w-full"
|
||||||
|
onClick={save}
|
||||||
|
disabled={!valid || email.toLowerCase() === app.account.email}
|
||||||
|
>
|
||||||
|
{t('save')}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<BackToAccountButton loading={app.loading} />
|
<BackToAccountButton loading={app.loading} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -69,7 +69,7 @@ export function useBackend(app) {
|
||||||
*/
|
*/
|
||||||
backend.loadConfirmation = async ({ id, check }) => {
|
backend.loadConfirmation = async ({ id, check }) => {
|
||||||
const result = await api.get(`/confirmations/${id}/${check}`)
|
const result = await api.get(`/confirmations/${id}/${check}`)
|
||||||
if (result && result.status === 201 && result.data) return result.data
|
if (result && result.status === 200 && result.data) return result.data
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
111
sites/org/pages/confirm/emailchange/[...confirmation].mjs
Normal file
111
sites/org/pages/confirm/emailchange/[...confirmation].mjs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// Hooks
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useApp } from 'site/hooks/useApp.mjs'
|
||||||
|
import { useBackend } from 'site/hooks/useBackend.mjs'
|
||||||
|
import { useToast } from 'site/hooks/useToast.mjs'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import Link from 'next/link'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'site/components/wrappers/page.mjs'
|
||||||
|
import { BareLayout } from 'site/components/layouts/bare.mjs'
|
||||||
|
import { Spinner } from 'shared/components/spinner.mjs'
|
||||||
|
import { Robot } from 'shared/components/robot/index.mjs'
|
||||||
|
import { BackToAccountButton } from 'site/components/account/shared.mjs'
|
||||||
|
import { HelpIcon } from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const ns = Array.from(new Set([...pageNs, 'account']))
|
||||||
|
|
||||||
|
const ConfirmSignUpPage = (props) => {
|
||||||
|
const app = useApp(props)
|
||||||
|
const backend = useBackend(app)
|
||||||
|
const toast = useToast()
|
||||||
|
const { t } = useTranslation(ns)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
|
||||||
|
// Get confirmation ID and check from url
|
||||||
|
const [id, check] = router.asPath.slice(1).split('/').slice(2)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Async inside useEffect requires this approach
|
||||||
|
const confirmEmail = async () => {
|
||||||
|
app.startLoading()
|
||||||
|
const result = await backend.loadConfirmation({ id, check })
|
||||||
|
if (result?.result === 'success' && result.confirmation) {
|
||||||
|
const changed = await backend.updateAccount({
|
||||||
|
confirm: 'emailchange',
|
||||||
|
confirmation: result.confirmation.id,
|
||||||
|
check: result.confirmation.check,
|
||||||
|
})
|
||||||
|
if (changed) {
|
||||||
|
app.stopLoading()
|
||||||
|
setReady(true)
|
||||||
|
setError(false)
|
||||||
|
toast.for.settingsSaved()
|
||||||
|
router.push('/account')
|
||||||
|
} else {
|
||||||
|
app.stopLoading()
|
||||||
|
setReady(true)
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.stopLoading()
|
||||||
|
setReady(true)
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Call async methods
|
||||||
|
if (app.token) confirmEmail()
|
||||||
|
}, [id, check, app.token])
|
||||||
|
|
||||||
|
// Short-circuit errors
|
||||||
|
if (error)
|
||||||
|
return (
|
||||||
|
<PageWrapper app={app} title={t('account:politeOhCrap')} layout={BareLayout} footer={false}>
|
||||||
|
<div className="max-w-md flex flex-col items-center m-auto justify-center h-screen text-center">
|
||||||
|
<h1 className="text-center">{t('account:politeOhCrap')}</h1>
|
||||||
|
<Robot pose="ohno" className="w-48" embed />
|
||||||
|
<p className="mt-4">{t('account:vagueError')}</p>
|
||||||
|
<div className="flex flex-row gap-4 items-center mt-4">
|
||||||
|
<BackToAccountButton />
|
||||||
|
<Link className="btn btn-primary mt-4 pr-6" href="/support">
|
||||||
|
<HelpIcon className="w-6 h-6 mr-4" /> {t('contactSupport')}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper app={app} title={t('account:oneMomentPlease')} layout={BareLayout} footer={false}>
|
||||||
|
<div className="max-w-md flex flex-col items-center m-auto justify-center h-screen text-center">
|
||||||
|
<h1 className="text-center">{t('account:oneMomentPlease')}</h1>
|
||||||
|
<p className="text-center">
|
||||||
|
<Spinner />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfirmSignUpPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, ns)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return {
|
||||||
|
paths: [],
|
||||||
|
fallback: true,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue