feat(admin): consentement aux annonces et désabonnement sans connexion
Les comptes déjà créés ne sont pas inscrits. Le choix est facultatif à l’inscription et dans les paramètres. Un lien public demande confirmation avant d’arrêter les actualités, sans toucher aux messages de compte.
This commit is contained in:
121
memento-note/app/(auth)/unsubscribe/page.tsx
Normal file
121
memento-note/app/(auth)/unsubscribe/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
'use client'
|
||||
|
||||
import { Suspense, useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { CheckCircle2, AlertCircle, Mail } from 'lucide-react'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { confirmMarketingUnsubscribe, previewUnsubscribeToken } from '@/app/actions/marketing-unsubscribe'
|
||||
|
||||
function UnsubscribeContent() {
|
||||
const { t } = useLanguage()
|
||||
const searchParams = useSearchParams()
|
||||
const token = searchParams.get('token') || ''
|
||||
const [status, setStatus] = useState<'idle' | 'saving' | 'ok' | 'already' | 'invalid'>(
|
||||
token ? 'idle' : 'invalid',
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return
|
||||
let cancelled = false
|
||||
void previewUnsubscribeToken(token).then((result) => {
|
||||
if (!cancelled && !result.valid) setStatus('invalid')
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [token])
|
||||
|
||||
const confirm = async () => {
|
||||
if (!token) {
|
||||
setStatus('invalid')
|
||||
return
|
||||
}
|
||||
setStatus('saving')
|
||||
const result = await confirmMarketingUnsubscribe(token)
|
||||
if (result.invalid || !result.ok) setStatus('invalid')
|
||||
else if (result.already) setStatus('already')
|
||||
else setStatus('ok')
|
||||
}
|
||||
|
||||
if (status === 'ok' || status === 'already') {
|
||||
return (
|
||||
<div className="bg-white dark:bg-[var(--background)]/50 border border-[var(--border)] p-8 md:p-10 rounded-[48px] shadow-2xl text-center space-y-6">
|
||||
<div className="w-14 h-14 mx-auto rounded-2xl bg-emerald-500/10 flex items-center justify-center">
|
||||
<CheckCircle2 size={28} className="text-emerald-600" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-serif font-bold">{t('unsubscribe.successTitle')}</h1>
|
||||
<p className="text-sm text-[var(--muted-foreground)] leading-relaxed">
|
||||
{t('unsubscribe.successBody')}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/login" className="block text-sm font-medium text-[var(--color-brand-accent)] hover:underline">
|
||||
{t('unsubscribe.backToSignIn')}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (status === 'invalid') {
|
||||
return (
|
||||
<div className="bg-white dark:bg-[var(--background)]/50 border border-[var(--border)] p-8 md:p-10 rounded-[48px] shadow-2xl text-center space-y-6">
|
||||
<div className="w-14 h-14 mx-auto rounded-2xl bg-red-500/10 flex items-center justify-center">
|
||||
<AlertCircle size={28} className="text-red-500" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-serif font-bold">{t('unsubscribe.invalidTitle')}</h1>
|
||||
<p className="text-sm text-[var(--muted-foreground)] leading-relaxed">
|
||||
{t('unsubscribe.invalidBody')}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/settings/general" className="block text-sm font-medium text-[var(--color-brand-accent)] hover:underline">
|
||||
{t('unsubscribe.openSettings')}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-[var(--background)]/50 border border-[var(--border)] p-8 md:p-10 rounded-[48px] shadow-2xl text-center space-y-6">
|
||||
<div className="w-14 h-14 mx-auto rounded-2xl bg-[var(--color-brand-accent)]/10 flex items-center justify-center">
|
||||
<Mail size={28} className="text-[var(--color-brand-accent)]" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-serif font-bold">{t('unsubscribe.title')}</h1>
|
||||
<p className="text-sm text-[var(--muted-foreground)] leading-relaxed">
|
||||
{t('unsubscribe.body')}
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
void confirm()
|
||||
}}
|
||||
className="space-y-3"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === 'saving'}
|
||||
className="w-full bg-[var(--foreground)] text-[var(--background)] py-4 rounded-2xl font-bold uppercase tracking-[0.2em] text-[10px] transition-all hover:shadow-xl active:scale-[0.98] disabled:opacity-50"
|
||||
>
|
||||
{t('unsubscribe.confirm')}
|
||||
</button>
|
||||
<Link
|
||||
href="/home"
|
||||
className="block text-xs text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
|
||||
>
|
||||
{t('unsubscribe.keep')}
|
||||
</Link>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function UnsubscribePage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<UnsubscribeContent />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user