1
0
Fork 0
freesewing/sites/shared/components/wrappers/auth/index.mjs
joostdecock 9208fb9a3c fix: Handle newsletter unsubscribe
There were a few issues with the newsletter unsubscribe links that we
sent out in the newsletter. They were pointing to the backend for one
thing.

Also updated the frontend pages to handle unsubscribe from both users
and subscribers.
2024-01-02 16:59:14 +01:00

211 lines
6 KiB
JavaScript

// __SDEFILE__ - This file is a dependency for the stand-alone environment
import Link from 'next/link'
import { useTranslation } from 'next-i18next'
import { useAccount } from 'shared/hooks/use-account.mjs'
import { useBackend } from 'shared/hooks/use-backend.mjs'
import { roles } from 'config/roles.mjs'
import { useEffect, useState } from 'react'
import { Loading } from 'shared/components/spinner.mjs'
import { horFlexClasses } from 'shared/utils.mjs'
import { LockIcon, PlusIcon } from 'shared/components/icons.mjs'
import { ConsentForm, ns as gdprNs } from 'shared/components/gdpr/form.mjs'
export const ns = ['auth', gdprNs]
const Wrap = ({ children }) => (
<div className="m-auto max-w-xl text-center mt-8 p-8">{children}</div>
)
const ContactSupport = ({ t }) => (
<div className="flex flex-row items-center justify-center gap-4 mt-8">
<Link href="/support" className="btn btn-success w-full">
{t('contactSupport')}
</Link>
</div>
)
const AuthRequired = ({ t, banner }) => (
<Wrap>
{banner}
<h2>{t('authRequired')}</h2>
<p>{t('membersOnly')}</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mt-8">
<Link href="/signup" className={`${horFlexClasses} btn btn-secondary w-full`}>
<PlusIcon />
{t('signUp')}
</Link>
<Link href="/signin" className={`${horFlexClasses} btn btn-secondary btn-outline w-full`}>
<LockIcon />
{t('signIn')}
</Link>
</div>
</Wrap>
)
const AccountInactive = ({ t, banner }) => (
<Wrap>
{banner}
<h1>{t('accountInactive')}</h1>
<p>{t('accountInactiveMsg')}</p>
<p>{t('signupAgain')}</p>
<div className="flex flex-row items-center justify-center gap-4 mt-8">
<Link href="/signup" className="btn btn-primary w-full">
{t('signUp')}
</Link>
</div>
</Wrap>
)
const AccountDisabled = ({ t, banner }) => (
<Wrap>
{banner}
<h1>{t('accountDisabled')}</h1>
<p>{t('accountDisabledMsg')}</p>
<ContactSupport t={t} />
</Wrap>
)
const AccountProhibited = ({ t, banner }) => (
<Wrap>
{banner}
<h1>{t('accountProhibited')}</h1>
<p>{t('accountProhibitedMsg')}</p>
<ContactSupport t={t} />
</Wrap>
)
const AccountStatusUnknown = ({ t, banner }) => (
<Wrap>
{banner}
<h1>{t('statusUnknown')}</h1>
<p>{t('statusUnknownMsg')}</p>
<ContactSupport t={t} />
</Wrap>
)
const RoleLacking = ({ t, requiredRole, role, banner }) => (
<Wrap>
{banner}
<h1>{t('roleLacking')}</h1>
<p dangerouslySetInnerHTML={{ __html: t('roleLackingMsg', { requiredRole, role }) }} />
<ContactSupport t={t} />
</Wrap>
)
const ConsentLacking = ({ banner, refresh }) => {
const { setAccount, setToken, setSeenUser } = useAccount()
const backend = useBackend()
const updateConsent = async ({ consent1, consent2 }) => {
let consent = 0
if (consent1) consent = 1
if (consent1 && consent2) consent = 2
if (consent > 0) {
const result = await backend.updateConsent(consent)
console.log({ result })
if (result.success) {
setToken(result.data.token)
setAccount(result.data.account)
setSeenUser(result.data.account.username)
refresh()
} else {
console.log('something went wrong', result)
refresh()
}
}
}
return (
<Wrap>
<div className="text-left">
{banner}
<ConsentForm submit={updateConsent} />
</div>
</Wrap>
)
}
export const AuthWrapper = ({ children, requiredRole = 'user' }) => {
const { t } = useTranslation(ns)
const { account, setAccount, token, admin, stopImpersonating, signOut } = useAccount()
const backend = useBackend()
const [ready, setReady] = useState(false)
const [impersonating, setImpersonating] = useState(false)
const [error, setError] = useState(false)
const [refreshCount, setRefreshCount] = useState(0)
/*
* Avoid hydration errors
*/
useEffect(() => {
const verifyAdmin = async () => {
const result = await backend.adminPing(admin.token)
if (result.success && result.data.account.role === 'admin') {
setImpersonating({
admin: result.data.account.username,
user: account.username,
})
}
setReady(true)
}
const verifyUser = async () => {
const result = await backend.ping()
if (result.success) {
// Refresh account in local storage
setAccount({
...account,
...result.data.account,
})
} else {
if (result.data?.error?.error) setError(result.data.error.error)
else signOut()
}
setReady(true)
}
if (admin && admin.token) verifyAdmin()
if (token) verifyUser()
else setReady(true)
}, [admin, token, refreshCount])
const refresh = () => {
setRefreshCount(refreshCount + 1)
setError(false)
}
if (!ready)
return (
<>
<p>not ready</p>
<Loading />
</>
)
const banner = impersonating ? (
<div className="bg-warning rounded-lg shadow py-4 px-6 flex flex-row items-center gap-4 justify-between">
<span className="text-base-100 text-left">
Hi <b>{impersonating.admin}</b>, you are currently impersonating <b>{impersonating.user}</b>
</span>
<button className="btn btn-neutral" onClick={stopImpersonating}>
Stop Impersonating
</button>
</div>
) : null
const childProps = { t, banner }
if (!token || !account.username) return <AuthRequired {...childProps} />
if (error) {
if (error === 'accountInactive') return <AccountInactive {...childProps} />
if (error === 'accountDisabled') return <AccountDisabled {...childProps} />
if (error === 'accountBlocked') return <AccountProhibited {...childProps} />
if (error === 'consentLacking') return <ConsentLacking {...childProps} refresh={refresh} />
return <AccountStatusUnknown {...childProps} />
}
if (!roles.levels[account.role] || roles.levels[account.role] < roles.levels[requiredRole]) {
return <RoleLacking {...childProps} role={account.role} requiredRole={requiredRole} />
}
return children
}