feat: user activation and emails

This commit is contained in:
Riccardo
2023-12-04 16:18:35 +01:00
parent 527fc25c08
commit 37f5692f61
40 changed files with 2093 additions and 310 deletions

View File

@@ -1,10 +0,0 @@
type ButtonProps = {
label: string;
onClick: () => void;
};
export const Button = ({ label, onClick }: ButtonProps) => (
<button onClick={onClick} key={1} className="overflow-hidden rounded-md">
<h1>{label}</h1>
</button>
);

View File

@@ -1,14 +0,0 @@
export const VerticalLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "16px",
}}
>
{children}
</div>
);
};

View File

@@ -0,0 +1,14 @@
import Link from 'next/link';
type CustomLinkProps = {
path: string;
text: string;
};
export function CustomLink({ path, text }: CustomLinkProps) {
return (
<Link href={path} className="overflow-hidden rounded-md">
<h1>{text}</h1>
</Link>
);
}

View File

@@ -0,0 +1,10 @@
import { HomeLink } from './homeLink';
export default function ErrorComponent() {
return (
<div>
<h1>Oops. Something went wrong. Please try later :(</h1>
<HomeLink />
</div>
);
}

View File

@@ -0,0 +1,5 @@
import { CustomLink } from './customLink';
export function HomeLink() {
return <CustomLink path={`/`} text={`Home`} />;
}

View File

@@ -0,0 +1,11 @@
import { HomeLink } from './homeLink';
export function SuccessComponent(message: string) {
return (
<div>
<h1>Success!</h1>
<h3>{message}</h3>
<HomeLink />
</div>
);
}

View File

@@ -0,0 +1,19 @@
import { Container } from '@react-email/container';
import { Html } from '@react-email/html';
import { Section } from '@react-email/section';
import { Text } from '@react-email/text';
import { container, main, paragraph } from './utils/styling';
export default function NewsletterEmail(ids: number[]) {
return (
<Html>
<Section style={main}>
<Container style={container}>
<Text style={paragraph}>
These were the ids retrieved: {ids.join(', ')}
</Text>
</Container>
</Section>
</Html>
);
}

View File

@@ -0,0 +1,23 @@
import { Container } from '@react-email/container';
import { Html } from '@react-email/html';
import { Section } from '@react-email/section';
import { Text } from '@react-email/text';
import { container, main, paragraph } from './utils/styling';
export default function SubscribeEmail(code: string) {
return (
<Html>
<Section style={main}>
<Container style={container}>
<Text style={paragraph}>
To confirm the subscription, please click{' '}
<a href={`${process.env.HOME_URL}/confirmation?code=${code}`}>
here
</a>
.
</Text>
</Container>
</Section>
</Html>
);
}

View File

@@ -0,0 +1,19 @@
import { Container } from '@react-email/container';
import { Html } from '@react-email/html';
import { Section } from '@react-email/section';
import { Text } from '@react-email/text';
import { container, main, paragraph } from './utils/styling';
export default function UnsubscribeEmail() {
return (
<Html>
<Section style={main}>
<Container style={container}>
<Text style={paragraph}>
You have unsubscribed from the newsletter.
</Text>
</Container>
</Section>
</Html>
);
}

View File

@@ -0,0 +1,15 @@
export const main = {
backgroundColor: '#ffffff'
};
export const container = {
margin: '0 auto',
padding: '20px 0 48px',
width: '580px'
};
export const paragraph = {
fontSize: '18px',
lineHeight: '1.4',
color: '#484848'
};

View File

@@ -0,0 +1,58 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { z } from 'zod';
import { ResponseSchema } from '../../utils/types';
import { HomeLink } from '../elements/homeLink';
export const ConfirmationPage = () => {
const router = useRouter();
const searchParams = useSearchParams();
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState('');
const code = searchParams.get('code');
useEffect(() => {
if (!code) {
router.push('/');
}
fetch('/api/confirmation', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: code,
}),
})
.then(async (res) => {
if (!res.ok) {
router.push('/');
}
const response: z.infer<typeof ResponseSchema> = await res.json();
return response;
})
.then((response) => {
setMessage(response.message);
setLoading(false);
});
}, [code, router]);
if (!loading) {
return (
<div>
<h1>{message}</h1>
<HomeLink />
</div>
);
}
return (
<div>
<h1>Verifying...</h1>
<HomeLink />
</div>
);
};

View File

@@ -0,0 +1,87 @@
'use client';
import React, { useEffect, useRef, useState } from 'react';
import { z } from 'zod';
import { ResponseSchema } from '../../utils/types';
import { CustomLink } from '../elements/customLink';
import ErrorComponent from '../elements/error';
import { HomeLink } from '../elements/homeLink';
import { SuccessComponent } from '../elements/success';
export const SubscribeForm = () => {
const [completed, setCompleted] = useState(false);
const [message, setMessage] = useState('');
const [error, setError] = useState(false);
const honeypotRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (honeypotRef.current) {
honeypotRef.current.style.display = 'none';
}
}, []);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const data = new FormData(e.currentTarget);
if (data.get('name')) {
return;
}
try {
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: data.get('email'),
}),
});
if (!response?.ok) {
throw new Error(`Invalid response: ${response.status}`);
}
const formResponse: z.infer<typeof ResponseSchema> =
await response.json();
setMessage(formResponse.message);
setCompleted(true);
} catch (error) {
console.log('Subscribe error', error);
setError(true);
}
}
if (error) {
return ErrorComponent();
}
if (completed) {
return SuccessComponent(message);
}
return (
<div>
<form className="container" onSubmit={handleSubmit}>
<h1>Subscribe to newsletter</h1>
<div className="email block">
<label htmlFor="frm-email">Email</label>
<input
placeholder="example@email.com"
id="email"
type="email"
name="email"
required
/>
</div>
<input type="text" name="name" ref={honeypotRef} />
<div className="button block">
<button type="submit">Subscribe</button>
</div>
</form>
<HomeLink />
<CustomLink path={`/unsubscribe`} text="Unsubscribe" />
</div>
);
};

View File

@@ -0,0 +1,87 @@
'use client';
import React, { useEffect, useRef, useState } from 'react';
import { z } from 'zod';
import { ResponseSchema } from '../../utils/types';
import { CustomLink } from '../elements/customLink';
import ErrorComponent from '../elements/error';
import { HomeLink } from '../elements/homeLink';
import { SuccessComponent } from '../elements/success';
export const UnsubscribeForm = () => {
const [completed, setCompleted] = useState(false);
const [message, setMessage] = useState('');
const [error, setError] = useState(false);
const honeypotRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (honeypotRef.current) {
honeypotRef.current.style.display = 'none';
}
}, []);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const data = new FormData(e.currentTarget);
if (data.get('name')) {
return;
}
try {
const response = await fetch('/api/unsubscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: data.get('email'),
}),
});
if (!response?.ok) {
throw new Error(`Invalid response: ${response.status}`);
}
const formResponse: z.infer<typeof ResponseSchema> =
await response.json();
setMessage(formResponse.message);
setCompleted(true);
} catch (error) {
console.log('Unsubscribe error', error);
setError(true);
}
}
if (error) {
return ErrorComponent();
}
if (completed) {
return SuccessComponent(message);
}
return (
<div>
<form className="container" onSubmit={handleSubmit}>
<h1>Unsubscribe from newsletter</h1>
<div className="email block">
<label htmlFor="frm-email">Email</label>
<input
placeholder="example@email.com"
id="email"
type="email"
name="email"
required
/>
</div>
<input type="text" name="name" ref={honeypotRef} />
<div className="button block">
<button type="submit">Unsubscribe</button>
</div>
</form>
<HomeLink />
<CustomLink path={`/subscribe`} text="Subscribe" />
</div>
);
};