created email signup component, can be used via shortcodes
This commit is contained in:
parent
f92def3cd3
commit
f7ca282152
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { useContext } from 'react';
|
||||
import React, { LegacyRef, useContext } from 'react';
|
||||
import { ScreenContext } from '../contexts/screen';
|
||||
|
||||
export interface Props {
|
||||
|
@ -9,6 +9,8 @@ export interface Props {
|
|||
|
||||
disabled?: boolean;
|
||||
selected?: boolean;
|
||||
buttonType?: 'submit';
|
||||
reference?: LegacyRef<HTMLButtonElement>;
|
||||
onClick?(): any;
|
||||
children?: string;
|
||||
className?: string;
|
||||
|
@ -27,6 +29,8 @@ export function Button(props: Props) {
|
|||
type = 'solid',
|
||||
disabled = false,
|
||||
selected = false,
|
||||
buttonType,
|
||||
reference,
|
||||
onClick,
|
||||
children,
|
||||
className,
|
||||
|
@ -90,7 +94,7 @@ export function Button(props: Props) {
|
|||
'';
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
className={classNames(
|
||||
'flex',
|
||||
'justify-center',
|
||||
|
@ -111,8 +115,9 @@ export function Button(props: Props) {
|
|||
type !== 'text' && ['border-2', 'border-solid', `border-${color}`],
|
||||
className,
|
||||
)}
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
type={buttonType}
|
||||
ref={reference}
|
||||
onClick={onClickFn}
|
||||
>
|
||||
{prefix && (
|
||||
|
@ -126,6 +131,6 @@ export function Button(props: Props) {
|
|||
{suffix}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ import React, { ReactNode } from 'react';
|
|||
import { UI } from '../constants';
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
backgroundColor?: 'primary' | 'secondary' | 'secondary-1';
|
||||
classes?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function Contained(props: Props) {
|
||||
const { backgroundColor, children } = props;
|
||||
const { id, backgroundColor, classes, children } = props;
|
||||
|
||||
const containerStyle = {
|
||||
paddingLeft: `${UI.PAGE_CONTAINED_PADDING_VW}vw`,
|
||||
|
@ -20,9 +22,11 @@ export function Contained(props: Props) {
|
|||
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
className={classNames(
|
||||
'w-full',
|
||||
backgroundColor && `bg-${backgroundColor}`,
|
||||
classes,
|
||||
)}
|
||||
>
|
||||
<div className="relative" style={containerStyle}>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { ReactElement, useState, useRef, FormEventHandler } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Contained } from './Contained';
|
||||
import { Input } from './Input';
|
||||
import { Button } from './Button';
|
||||
|
||||
export default function EmailSignup(): ReactElement {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const setButtonText = (value: string) => {
|
||||
if (null !== buttonRef.current) {
|
||||
buttonRef.current.innerText = value;
|
||||
}
|
||||
};
|
||||
const [email, setEmail] = useState('');
|
||||
const handleSubscription: FormEventHandler = async event => {
|
||||
event.preventDefault();
|
||||
setButtonText('Subscribing...');
|
||||
let response;
|
||||
try {
|
||||
response = await fetch('/api/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
} catch (error) {
|
||||
response = error;
|
||||
}
|
||||
switch (response?.status) {
|
||||
case 201:
|
||||
setEmail('');
|
||||
setButtonText('Signed up ✓');
|
||||
break;
|
||||
case 400:
|
||||
default:
|
||||
setButtonText('Signup failed ✗');
|
||||
break;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Contained
|
||||
id="signup"
|
||||
classes={classNames(
|
||||
'border-2 border-solid border-primary py-6 px-2 mt-6 mb-10',
|
||||
'tablet:w-4/5 tablet:mx-auto tablet:py-4 tablet:mt-6 tablet:mb-8',
|
||||
'desktop:py-6',
|
||||
)}
|
||||
>
|
||||
<h3
|
||||
className={classNames(
|
||||
'text-2xl font-semibold leading-none mb-2',
|
||||
'tablet:text-3xl',
|
||||
'desktop:text-4xl desktop:mb-3',
|
||||
)}
|
||||
>
|
||||
You've got mail!
|
||||
</h3>
|
||||
<p
|
||||
className={classNames(
|
||||
'leading-none mb-6',
|
||||
'tablet:mb-3 tablet:leading-tight',
|
||||
'desktop:mb-6 desktop:text-xl',
|
||||
)}
|
||||
>
|
||||
Sign up to our newsletter to keep up to date with everything Oxen.
|
||||
</p>
|
||||
<form onSubmit={handleSubscription}>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Your Email"
|
||||
value={email}
|
||||
onValueChange={value => setEmail(value)}
|
||||
size={'large'}
|
||||
border={'primary'}
|
||||
inputMode={'text'}
|
||||
className={classNames(
|
||||
'mb-6 rounded-sm',
|
||||
'tablet:mb-4',
|
||||
'desktop:mb-6',
|
||||
)}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
className={classNames('tablet:w-40 tablet:mx-auto')}
|
||||
buttonType={'submit'}
|
||||
reference={buttonRef}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</form>
|
||||
</Contained>
|
||||
);
|
||||
}
|
|
@ -54,8 +54,8 @@ export interface InputProps {
|
|||
|
||||
// HTMLInputElement Props
|
||||
|
||||
autofocus?: boolean;
|
||||
// required?: boolean;
|
||||
// autofocus?: boolean;
|
||||
required?: boolean;
|
||||
// validity?: ValidityState;
|
||||
// validationMessage?: string;
|
||||
// willValidate?: boolean;
|
||||
|
@ -83,7 +83,7 @@ export function Input(props: InputProps) {
|
|||
prefix,
|
||||
duration = true,
|
||||
suffix,
|
||||
autofocus,
|
||||
required,
|
||||
disabled,
|
||||
min,
|
||||
max,
|
||||
|
@ -171,8 +171,6 @@ export function Input(props: InputProps) {
|
|||
// 'bg-white',
|
||||
'text-gray-700',
|
||||
'leading-tight',
|
||||
'outline-black',
|
||||
'outline-secondary',
|
||||
'focus:outline-black',
|
||||
border !== 'none' && 'border-2',
|
||||
size === 'small' ? 'px-2' : 'px-4',
|
||||
|
@ -211,6 +209,7 @@ export function Input(props: InputProps) {
|
|||
ref={inputRef}
|
||||
spellCheck={false}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
value={props.value ?? value}
|
||||
step={step}
|
||||
|
|
|
@ -31,6 +31,7 @@ const CMS = {
|
|||
TRADE_LINKS: /^{{[\s*]trade_links[\s*]}}$/,
|
||||
CTA_WHO_USES_OXEN: /^\{\{[\s]*who_uses_oxen[\s]*\}\}$/,
|
||||
CTA_SESSION_LOKINET: /^\{\{[\s]*session_lokinet[\s]*\}\}$/,
|
||||
CTA_EMAIL_SIGNUP: /^\{\{[\s]*email_signup[\s]*\}\}$/,
|
||||
},
|
||||
BLOG_RESULTS_PER_PAGE: 13,
|
||||
BLOG_RESULTS_PER_PAGE_TAGGED: 12,
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"D": "^1.0.0",
|
||||
"babel-plugin-tailwind": "^0.1.10",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"classnames": "^2.2.6",
|
||||
"base-64": "^1.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"contentful": "^8.4.2",
|
||||
"date-fns": "^2.23.0",
|
||||
"dotenv": "^8.2.0",
|
||||
|
@ -64,6 +65,8 @@
|
|||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@contentful/rich-text-types": "^15.0.0",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@types/base-64": "^1.0.0",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.set": "^4.3.6",
|
||||
"@types/node": "^14.14.28",
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import base64 from 'base-64';
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
if (req.method !== 'POST') {
|
||||
res.status(400).json({
|
||||
message: 'Email API: Invalid http method. | Only POST is accepted.',
|
||||
});
|
||||
}
|
||||
|
||||
const email = req.body.email;
|
||||
const response = await fetch(
|
||||
`https://api.createsend.com/api/v3.2/subscribers/${process.env.CAMPAIGN_MONITOR_LIST_API_ID}.json`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
EmailAddress: email,
|
||||
ConsentToTrack: 'Unchanged',
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Basic ${base64.encode(
|
||||
`${process.env.CAMPAIGN_MONITOR_API_KEY}:x`,
|
||||
)}`,
|
||||
},
|
||||
method: 'POST',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status === 201) {
|
||||
// console.log(`Email API: ${email} subscribed!`);
|
||||
res.status(201).json({ email });
|
||||
} else {
|
||||
// const result = await response.json();
|
||||
// console.warn(
|
||||
// `Email API: | Code: ${result.Code} | Email: ${email} | ${result.Message}`
|
||||
// );
|
||||
res.status(400).json({ email });
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import DiscordSVG from '../assets/svgs/socials/brand-discord.svg';
|
|||
import RedditSVG from '../assets/svgs/socials/brand-reddit.svg';
|
||||
import TelegramSVG from '../assets/svgs/socials/brand-telegram.svg';
|
||||
import { Button } from '../components/Button';
|
||||
import EmailSignup from '../components/EmailSignup';
|
||||
import { CMS } from '../constants';
|
||||
import { SideMenuItem, TPages } from '../state/navigation';
|
||||
import {
|
||||
|
@ -187,10 +188,12 @@ export class CmsApi {
|
|||
|
||||
public async fetchPageById(id: SideMenuItem): Promise<ISplitPage> {
|
||||
return this.client
|
||||
.getEntries({
|
||||
content_type: 'splitPage',
|
||||
'fields.id[in]': id,
|
||||
})
|
||||
.getEntries(
|
||||
loadOptions({
|
||||
content_type: 'splitPage',
|
||||
'fields.id[in]': id,
|
||||
}),
|
||||
)
|
||||
.then(entries => {
|
||||
if (entries && entries.items && entries.items.length > 0) {
|
||||
return this.convertPage(entries.items[0]);
|
||||
|
@ -478,6 +481,11 @@ export const renderShortcode = (shortcode: string) => {
|
|||
);
|
||||
}
|
||||
|
||||
// Call to Action -> Email Signup
|
||||
if (CMS.SHORTCODES.CTA_EMAIL_SIGNUP.test(shortcode)) {
|
||||
return <EmailSignup />;
|
||||
}
|
||||
|
||||
// All shortcode buttons with simple hrefs
|
||||
const shortcodeButton = Object.values(CMS.SHORTCODE_BUTTONS).find(item =>
|
||||
item.regex.test(shortcode),
|
||||
|
|
|
@ -72,12 +72,10 @@ function EmbeddedMedia(node: any, isInline = false): ReactElement {
|
|||
const imageWidth = node.width ?? media.file.details.image.width;
|
||||
const imageHeight = node.height ?? media.file.details.image.height;
|
||||
const figureClasses = [
|
||||
isInline && node.position && 'mx-auto mb-5',
|
||||
isInline && node.position && 'text-center mx-auto mb-5',
|
||||
isInline && !node.position && 'inline-block align-middle mx-1',
|
||||
isInline && node.position === 'left' && 'tablet:float-left tablet:mr-4',
|
||||
isInline &&
|
||||
node.position === 'right' &&
|
||||
'tablet:float-right tablet:ml-4',
|
||||
isInline && node.position === 'right' && 'tablet:float-right tablet:ml-4',
|
||||
!isInline && 'text-center mb-5',
|
||||
];
|
||||
const captionClasses = [
|
||||
|
|
27
yarn.lock
27
yarn.lock
|
@ -2105,6 +2105,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.2.0.tgz#bebd32b7d0756b695294d4db1ae658796ff72a2c"
|
||||
integrity sha512-v5LyHkwXj/4lI74B06zUrmWEdmSqS43+jw717pkt3fAXqb7ALwu77A8t7j+Bej+ZbdlIIqNMYheGN7wSGV1A6w==
|
||||
|
||||
"@tailwindcss/forms@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.3.3.tgz#a29d22668804f3dae293dcadbef1aa6315c45b64"
|
||||
integrity sha512-U8Fi/gq4mSuaLyLtFISwuDYzPB73YzgozjxOIHsK6NXgg/IWD1FLaHbFlWmurAMyy98O+ao74ksdQefsquBV1Q==
|
||||
dependencies:
|
||||
mini-svg-data-uri "^1.2.3"
|
||||
|
||||
"@types/anymatch@*":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
|
||||
|
@ -2143,6 +2150,11 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/base-64@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/base-64/-/base-64-1.0.0.tgz#de9c6070ea457fbd65a1b5ebf13976b3ac0bdad0"
|
||||
integrity sha512-AvCJx/HrfYHmOQRFdVvgKMplXfzTUizmh0tz9GFTpDePWgCY4uoKll84zKlaRoeiYiCr7c9ZnqSTzkl0BUVD6g==
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
|
@ -3891,6 +3903,11 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base-64@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a"
|
||||
integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==
|
||||
|
||||
base64-js@^1.0.2, base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
|
@ -4679,6 +4696,11 @@ classnames@2.2.6, classnames@^2.2.6:
|
|||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||
|
||||
classnames@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||
|
||||
clean-css@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
|
||||
|
@ -10654,6 +10676,11 @@ mini-css-extract-plugin@0.11.3:
|
|||
schema-utils "^1.0.0"
|
||||
webpack-sources "^1.1.0"
|
||||
|
||||
mini-svg-data-uri@^1.2.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz#91d2c09f45e056e5e1043340b8b37ba7b50f4fac"
|
||||
integrity sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
|
|
Loading…
Reference in New Issue