Authentication
Verify email
A centered card that asks to open the verification link sent by email, and sends it again.
<div class="grid min-h-dvh place-items-center bg-base-200 p-6">
<div class="flex w-full max-w-sm flex-col gap-6">
<a class="flex items-center gap-2 self-center font-medium" href="#">
<span class="grid size-8 place-items-center rounded-lg bg-primary text-primary-content">
<span class="icon icon-stack-2 size-5"></span>
</span>
Pelatform
</a>
<div class="card">
<div class="card-header text-center">
<span class="mx-auto grid size-10 place-items-center rounded-lg bg-primary/10 text-xl text-primary">
<span class="icon icon-mail"></span>
</span>
<h1 class="card-title text-xl">Verify your email</h1>
<p class="card-description" id="verify-email-message" role="status">We sent a link to <strong>john@example.com</strong>. Open it to activate your account; it expires in 24 hours.</p>
</div>
<div class="card-body" id="verify-email-expired" hidden>
<div class="alert alert-error">
<div class="alert-body">
<div class="alert-content">
<h2 class="alert-title">That link has expired</h2>
<p class="alert-description">Send yourself a new one below.</p>
</div>
</div>
</div>
</div>
<div class="card-actions flex-col">
<button class="btn btn-primary w-full" id="verify-email-resend">
<span class="btn-label" id="verify-email-resend-label">Resend email</span>
<span class="btn-loading">
<span class="btn-icon btn-icon-loading"></span>
Sending…
</span>
</button>
<a class="btn btn-ghost w-full" href="#">Back to sign in</a>
<a class="link link-muted self-center text-sm" href="#">Use a different email</a>
</div>
</div>
</div>
</div>
<script>
const resend = document.getElementById('verify-email-resend');
const label = document.getElementById('verify-email-resend-label');
const message = document.getElementById('verify-email-message');
// A verification link opened after it expired returns here with ?expired.
document.getElementById('verify-email-expired').hidden = !new URLSearchParams(location.search).has('expired');
resend.addEventListener('click', async () => {
if (resend.getAttribute('aria-disabled') === 'true') return;
// Your request; the button shows its loading part until it settles.
const response = await PLF.Button.loading(resend, fetch('/email/verify/resend', { method: 'POST' }));
if (!response.ok) {
message.textContent = 'The email could not be sent. Try again in a moment.';
return;
}
message.textContent = 'A new link is on its way. Open it to activate your account; it expires in 24 hours.';
// A short wait before the next email; the button keeps its focus.
resend.setAttribute('aria-disabled', 'true');
for (let seconds = 30; seconds > 0; seconds--) {
label.textContent = `Resend in ${seconds}s`;
await new Promise(resolve => setTimeout(resolve, 1000));
}
label.textContent = 'Resend email';
resend.removeAttribute('aria-disabled');
});
</script><div class="plf:grid plf:min-h-dvh plf:place-items-center plf:bg-base-200 plf:p-6">
<div class="plf:flex plf:w-full plf:max-w-sm plf:flex-col plf:gap-6">
<a class="plf:flex plf:items-center plf:gap-2 plf:self-center plf:font-medium" href="#">
<span class="plf:grid plf:size-8 plf:place-items-center plf:rounded-lg plf:bg-primary plf:text-primary-content">
<span class="plf:icon plf:icon-stack-2 plf:size-5"></span>
</span>
Pelatform
</a>
<div class="plf:card">
<div class="plf:card-header plf:text-center">
<span class="plf:mx-auto plf:grid plf:size-10 plf:place-items-center plf:rounded-lg plf:bg-primary/10 plf:text-xl plf:text-primary">
<span class="plf:icon plf:icon-mail"></span>
</span>
<h1 class="plf:card-title plf:text-xl">Verify your email</h1>
<p class="plf:card-description" id="verify-email-message" role="status">We sent a link to <strong>john@example.com</strong>. Open it to activate your account; it expires in 24 hours.</p>
</div>
<div class="plf:card-body" id="verify-email-expired" hidden>
<div class="plf:alert plf:alert-error">
<div class="plf:alert-body">
<div class="plf:alert-content">
<h2 class="plf:alert-title">That link has expired</h2>
<p class="plf:alert-description">Send yourself a new one below.</p>
</div>
</div>
</div>
</div>
<div class="plf:card-actions plf:flex-col">
<button class="plf:btn plf:btn-primary plf:w-full" id="verify-email-resend">
<span class="plf:btn-label" id="verify-email-resend-label">Resend email</span>
<span class="plf:btn-loading">
<span class="plf:btn-icon plf:btn-icon-loading"></span>
Sending…
</span>
</button>
<a class="plf:btn plf:btn-ghost plf:w-full" href="#">Back to sign in</a>
<a class="plf:link plf:link-muted plf:self-center plf:text-sm" href="#">Use a different email</a>
</div>
</div>
</div>
</div>
<script>
const resend = document.getElementById('verify-email-resend');
const label = document.getElementById('verify-email-resend-label');
const message = document.getElementById('verify-email-message');
// A verification link opened after it expired returns here with ?expired.
document.getElementById('verify-email-expired').hidden = !new URLSearchParams(location.search).has('expired');
resend.addEventListener('click', async () => {
if (resend.getAttribute('aria-disabled') === 'true') return;
// Your request; the button shows its loading part until it settles.
const response = await PLF.Button.loading(resend, fetch('/email/verify/resend', { method: 'POST' }));
if (!response.ok) {
message.textContent = 'The email could not be sent. Try again in a moment.';
return;
}
message.textContent = 'A new link is on its way. Open it to activate your account; it expires in 24 hours.';
// A short wait before the next email; the button keeps its focus.
resend.setAttribute('aria-disabled', 'true');
for (let seconds = 30; seconds > 0; seconds--) {
label.textContent = `Resend in ${seconds}s`;
await new Promise(resolve => setTimeout(resolve, 1000));
}
label.textContent = 'Resend email';
resend.removeAttribute('aria-disabled');
});
</script>'use client';
import { useEffect, useState } from 'react';
import {
Alert, AlertBody, AlertContent, AlertDescription, AlertTitle, Button, buttonClass, Card, CardActions, CardBody,
CardDescription, CardHeader, Icon, Link,
} from '@pelatform/react';
export function Example() {
const [sending, setSending] = useState(false);
// Seconds before the next email may go.
const [wait, setWait] = useState(0);
const [message, setMessage] = useState<string>();
const [expired, setExpired] = useState(false);
// A verification link opened after it expired returns here with ?expired.
useEffect(() => {
setExpired(new URLSearchParams(location.search).has('expired'));
}, []);
useEffect(() => {
if (!wait) return;
const timer = setTimeout(() => setWait(wait - 1), 1000);
return () => clearTimeout(timer);
}, [wait]);
async function resend() {
if (wait) return;
// Your request; the button shows its loading part until it settles.
setSending(true);
try {
const response = await fetch('/email/verify/resend', { method: 'POST' });
if (!response.ok) {
setMessage('The email could not be sent. Try again in a moment.');
return;
}
setMessage('A new link is on its way. Open it to activate your account; it expires in 24 hours.');
setWait(30);
} finally {
setSending(false);
}
}
return (
<div className="grid min-h-dvh place-items-center bg-base-200 p-6">
<div className="flex w-full max-w-sm flex-col gap-6">
<a className="flex items-center gap-2 self-center font-medium" href="#">
<span className="grid size-8 place-items-center rounded-lg bg-primary text-primary-content">
<Icon name="stack-2" className="size-5" />
</span>
Pelatform
</a>
<Card>
<CardHeader className="text-center">
<span className="mx-auto grid size-10 place-items-center rounded-lg bg-primary/10 text-xl text-primary">
<Icon name="mail" />
</span>
<h1 className="card-title text-xl">Verify your email</h1>
<CardDescription role="status">
{message ?? <>We sent a link to <strong>john@example.com</strong>. Open it to activate your account; it expires in 24 hours.</>}
</CardDescription>
</CardHeader>
<CardBody hidden={!expired}>
<Alert color="error">
<AlertBody>
<AlertContent>
<AlertTitle level={2}>That link has expired</AlertTitle>
<AlertDescription>Send yourself a new one below.</AlertDescription>
</AlertContent>
</AlertBody>
</Alert>
</CardBody>
<CardActions className="flex-col">
<Button color="primary" className="w-full" loading={sending} loadingText="Sending…" aria-disabled={wait ? true : undefined} onClick={resend}>
{wait ? `Resend in ${wait}s` : 'Resend email'}
</Button>
<a className={`${buttonClass({ variant: 'ghost' })} w-full`} href="#">Back to sign in</a>
<Link variant="muted" className="self-center text-sm" href="#">Use a different email</Link>
</CardActions>
</Card>
</div>
</div>
);
}'use client';
import { useEffect, useState } from 'react';
import {
Alert, AlertBody, AlertContent, AlertDescription, AlertTitle, Button, buttonClass, Card, CardActions, CardBody,
CardDescription, CardHeader, Icon, Link,
} from '@pelatform/react/prefixed';
export function Example() {
const [sending, setSending] = useState(false);
// Seconds before the next email may go.
const [wait, setWait] = useState(0);
const [message, setMessage] = useState<string>();
const [expired, setExpired] = useState(false);
// A verification link opened after it expired returns here with ?expired.
useEffect(() => {
setExpired(new URLSearchParams(location.search).has('expired'));
}, []);
useEffect(() => {
if (!wait) return;
const timer = setTimeout(() => setWait(wait - 1), 1000);
return () => clearTimeout(timer);
}, [wait]);
async function resend() {
if (wait) return;
// Your request; the button shows its loading part until it settles.
setSending(true);
try {
const response = await fetch('/email/verify/resend', { method: 'POST' });
if (!response.ok) {
setMessage('The email could not be sent. Try again in a moment.');
return;
}
setMessage('A new link is on its way. Open it to activate your account; it expires in 24 hours.');
setWait(30);
} finally {
setSending(false);
}
}
return (
<div className="plf:grid plf:min-h-dvh plf:place-items-center plf:bg-base-200 plf:p-6">
<div className="plf:flex plf:w-full plf:max-w-sm plf:flex-col plf:gap-6">
<a className="plf:flex plf:items-center plf:gap-2 plf:self-center plf:font-medium" href="#">
<span className="plf:grid plf:size-8 plf:place-items-center plf:rounded-lg plf:bg-primary plf:text-primary-content">
<Icon name="stack-2" className="plf:size-5" />
</span>
Pelatform
</a>
<Card>
<CardHeader className="plf:text-center">
<span className="plf:mx-auto plf:grid plf:size-10 plf:place-items-center plf:rounded-lg plf:bg-primary/10 plf:text-xl plf:text-primary">
<Icon name="mail" />
</span>
<h1 className="plf:card-title plf:text-xl">Verify your email</h1>
<CardDescription role="status">
{message ?? <>We sent a link to <strong>john@example.com</strong>. Open it to activate your account; it expires in 24 hours.</>}
</CardDescription>
</CardHeader>
<CardBody hidden={!expired}>
<Alert color="error">
<AlertBody>
<AlertContent>
<AlertTitle level={2}>That link has expired</AlertTitle>
<AlertDescription>Send yourself a new one below.</AlertDescription>
</AlertContent>
</AlertBody>
</Alert>
</CardBody>
<CardActions className="plf:flex-col">
<Button color="primary" className="plf:w-full" loading={sending} loadingText="Sending…" aria-disabled={wait ? true : undefined} onClick={resend}>
{wait ? `Resend in ${wait}s` : 'Resend email'}
</Button>
<a className={`${buttonClass({ variant: 'ghost' })} plf:w-full`} href="#">Back to sign in</a>
<Link variant="muted" className="plf:self-center plf:text-sm" href="#">Use a different email</Link>
</CardActions>
</Card>
</div>
</div>
);
}<link rel="stylesheet" href="pelatform-ui.min.css">
<link rel="stylesheet" href="icons/icons.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="components/card.min.css">
<link rel="stylesheet" href="components/alert.min.css">
<link rel="stylesheet" href="components/link.min.css">
<link rel="stylesheet" href="icons/icons.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/button.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<link rel="stylesheet" href="icons/icons.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="components/card.prefixed.min.css">
<link rel="stylesheet" href="components/alert.prefixed.min.css">
<link rel="stylesheet" href="components/link.prefixed.min.css">
<link rel="stylesheet" href="icons/icons.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/button.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>