mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-18 00:37:27 +02:00
✨ Allow users to log in with magic link (#553)
This commit is contained in:
parent
5b78093c6f
commit
2cf9ad467c
17 changed files with 425 additions and 186 deletions
|
@ -27,6 +27,7 @@ export const EmailLayout = ({
|
|||
children,
|
||||
footNote,
|
||||
}: React.PropsWithChildren<EmailLayoutProps>) => {
|
||||
const firstName = recipientName.split(" ")[0];
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
|
@ -127,12 +128,12 @@ export const EmailLayout = ({
|
|||
}}
|
||||
>
|
||||
<Body className="bg-white px-3 py-6">
|
||||
<Container className="max-w-lg">
|
||||
<Section className="mb-4">
|
||||
<Container className="max-w-xl">
|
||||
<Section className="my-4">
|
||||
<Img src={absoluteUrl("/logo.png")} alt="Rallly" width={128} />
|
||||
</Section>
|
||||
<Section>
|
||||
<Text>Hi {recipientName},</Text>
|
||||
<Text>Hi {firstName},</Text>
|
||||
{children}
|
||||
{footNote ? (
|
||||
<>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { EmailLayout } from "./email-layout";
|
||||
import { Link, Section } from "./styled-components";
|
||||
import { Button, Card, Link, Text } from "./styled-components";
|
||||
import { getDomain } from "./utils";
|
||||
|
||||
export interface NotificationBaseProps {
|
||||
name: string;
|
||||
|
@ -14,7 +15,6 @@ export interface NotificationEmailProps extends NotificationBaseProps {
|
|||
|
||||
export const NotificationEmail = ({
|
||||
name,
|
||||
title,
|
||||
pollUrl,
|
||||
unsubscribeUrl,
|
||||
preview,
|
||||
|
@ -25,21 +25,18 @@ export const NotificationEmail = ({
|
|||
recipientName={name}
|
||||
footNote={
|
||||
<>
|
||||
You're receiving this email because notifications are enabled for{" "}
|
||||
<strong>{title}</strong>. If you want to stop receiving emails about
|
||||
this event you can{" "}
|
||||
If you would like to stop receiving updates you can{" "}
|
||||
<Link className="whitespace-nowrap" href={unsubscribeUrl}>
|
||||
turn notifications off
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
}
|
||||
preview={preview}
|
||||
>
|
||||
{children}
|
||||
<Section>
|
||||
<Link href={pollUrl}>Go to poll →</Link>
|
||||
</Section>
|
||||
<Text>
|
||||
<Button href={pollUrl}>View on {getDomain()}</Button>
|
||||
</Text>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { absoluteUrl } from "@rallly/utils";
|
||||
import {
|
||||
Button as UnstyledButton,
|
||||
ButtonProps,
|
||||
|
@ -11,18 +12,29 @@ import {
|
|||
} from "@react-email/components";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const Text = (props: TextProps) => {
|
||||
import { getDomain } from "./utils";
|
||||
|
||||
export const Text = (
|
||||
props: TextProps & { light?: boolean; small?: boolean },
|
||||
) => {
|
||||
const { light, small, className, ...forwardProps } = props;
|
||||
return (
|
||||
<UnstyledText
|
||||
{...props}
|
||||
{...forwardProps}
|
||||
className={clsx(
|
||||
"my-4 font-sans text-base text-slate-800",
|
||||
props.className,
|
||||
"my-4 font-sans ",
|
||||
{ "text-base": !small, "text-sm": small },
|
||||
{ "text-slate-800": !light, "text-slate-500": light },
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Domain = () => {
|
||||
return <Link href={absoluteUrl()}>{getDomain()}</Link>;
|
||||
};
|
||||
|
||||
export const Button = (props: ButtonProps) => {
|
||||
return (
|
||||
<UnstyledButton
|
||||
|
@ -47,18 +59,36 @@ export const Link = (props: LinkProps) => {
|
|||
export const Heading = (
|
||||
props: React.ComponentProps<typeof UnstyledHeading>,
|
||||
) => {
|
||||
const { as = "h3" } = props;
|
||||
return (
|
||||
<UnstyledHeading
|
||||
{...props}
|
||||
as={props.as || "h3"}
|
||||
className={clsx("my-4 font-sans text-slate-800", props.className)}
|
||||
as={as}
|
||||
className={clsx(
|
||||
"mt-4 mb-2 font-sans font-semibold text-slate-800",
|
||||
props.className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubHeadingText = (props: TextProps) => {
|
||||
const { className, ...forwardProps } = props;
|
||||
return (
|
||||
<UnstyledText
|
||||
{...forwardProps}
|
||||
className={clsx(
|
||||
"mb-4 mt-2 font-sans text-base text-slate-800",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Section = (props: SectionProps) => {
|
||||
const { className, ...forwardProps } = props;
|
||||
return (
|
||||
<UnstyledSection {...props} className={clsx("my-4", props.className)} />
|
||||
<UnstyledSection {...forwardProps} className={clsx("my-4", className)} />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -70,3 +100,12 @@ export const SmallText = (props: TextProps) => {
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Card = (props: SectionProps) => {
|
||||
return (
|
||||
<Section
|
||||
{...props}
|
||||
className={clsx("rounded bg-gray-50 px-4", props.className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { absoluteUrl } from "@rallly/utils";
|
||||
|
||||
export const removeProtocalFromUrl = (url: string) => {
|
||||
return url.replace(/(^\w+:|^)\/\//, "");
|
||||
};
|
||||
|
||||
export const getDomain = () => removeProtocalFromUrl(absoluteUrl());
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
import { absoluteUrl } from "@rallly/utils";
|
||||
import { Hr } from "@react-email/components";
|
||||
|
||||
import { EmailLayout } from "./components/email-layout";
|
||||
import { Heading, Link, Text } from "./components/styled-components";
|
||||
import { removeProtocalFromUrl } from "./components/utils";
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
Link,
|
||||
Section,
|
||||
Text,
|
||||
} from "./components/styled-components";
|
||||
import { getDomain, removeProtocalFromUrl } from "./components/utils";
|
||||
|
||||
interface LoginEmailProps {
|
||||
name: string;
|
||||
code: string;
|
||||
// magicLink: string;
|
||||
magicLink: string;
|
||||
}
|
||||
|
||||
export const LoginEmail = ({
|
||||
name = "Guest",
|
||||
code = "123456",
|
||||
}: // magicLink = "https://rallly.co",
|
||||
LoginEmailProps) => {
|
||||
magicLink = "https://rallly.co",
|
||||
}: LoginEmailProps) => {
|
||||
return (
|
||||
<EmailLayout
|
||||
footNote={
|
||||
|
@ -30,24 +37,19 @@ LoginEmailProps) => {
|
|||
recipientName={name}
|
||||
preview={`Your 6-digit code: ${code}`}
|
||||
>
|
||||
<Text>Your 6-digit code is:</Text>
|
||||
<Heading as="h1" className="font-sans tracking-widest" id="code">
|
||||
{code}
|
||||
</Heading>
|
||||
<Text>
|
||||
Use this code to complete the verification process on{" "}
|
||||
<Link href={absoluteUrl()}>{removeProtocalFromUrl(absoluteUrl())}</Link>
|
||||
</Text>
|
||||
<Text>
|
||||
<span className="text-slate-500">
|
||||
This code is valid for 15 minutes
|
||||
</span>
|
||||
</Text>
|
||||
{/* <Heading>Magic link</Heading>
|
||||
<Text>
|
||||
Alternatively, you can login by using this{" "}
|
||||
<Link href={magicLink}>magic link ✨</Link>
|
||||
</Text> */}
|
||||
<Text>Use this link to log in on this device.</Text>
|
||||
<Button href={magicLink} id="magicLink">
|
||||
Log in to {getDomain()}
|
||||
</Button>
|
||||
<Text light={true}>This link is valid for 15 minutes</Text>
|
||||
<Section>
|
||||
<Text>
|
||||
Alternatively, you can enter this 6-digit verification code directly.
|
||||
</Text>
|
||||
<Heading as="h1" className="tracking-widest" id="code">
|
||||
{code}
|
||||
</Heading>
|
||||
</Section>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { EmailLayout } from "./components/email-layout";
|
||||
import { Button, Section, Text } from "./components/styled-components";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Domain,
|
||||
Heading,
|
||||
Section,
|
||||
Text,
|
||||
} from "./components/styled-components";
|
||||
import { getDomain } from "./components/utils";
|
||||
|
||||
interface NewParticipantConfirmationEmailProps {
|
||||
name: string;
|
||||
|
@ -8,31 +16,32 @@ interface NewParticipantConfirmationEmailProps {
|
|||
}
|
||||
export const NewParticipantConfirmationEmail = ({
|
||||
title = "Untitled Poll",
|
||||
name = "Guest",
|
||||
name = "John",
|
||||
editSubmissionUrl = "https://rallly.co",
|
||||
}: NewParticipantConfirmationEmailProps) => {
|
||||
return (
|
||||
<EmailLayout
|
||||
footNote={
|
||||
<>You are receiving this email because a response was submitting </>
|
||||
<>
|
||||
You are receiving this email because a response was submitted on{" "}
|
||||
<Domain />. If this wasn't you, please ignore this email.
|
||||
</>
|
||||
}
|
||||
recipientName={name}
|
||||
preview="To edit your response use the link below"
|
||||
>
|
||||
<Text>
|
||||
Thank you for submitting your availability for <strong>{title}</strong>.
|
||||
Thank you for responding to <strong>{title}</strong>.
|
||||
</Text>
|
||||
<Text>
|
||||
While the poll is still open you can change your response using the link
|
||||
below.
|
||||
</Text>
|
||||
<Text>To review your response, use the link below:</Text>
|
||||
<Section>
|
||||
<Button id="editSubmissionUrl" href={editSubmissionUrl}>
|
||||
Review response →
|
||||
Review response on {getDomain()}
|
||||
</Button>
|
||||
</Section>
|
||||
<Text>
|
||||
<em className="text-slate-500">
|
||||
Keep this link safe and do not share it with others.
|
||||
</em>
|
||||
</Text>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ export interface NewParticipantEmailProps extends NotificationBaseProps {
|
|||
}
|
||||
|
||||
export const NewParticipantEmail = ({
|
||||
name = "Guest",
|
||||
name = "John",
|
||||
title = "Untitled Poll",
|
||||
participantName = "Someone",
|
||||
pollUrl = "https://rallly.co",
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
import { absoluteUrl } from "@rallly/utils";
|
||||
import { absoluteUrl, preventWidows } from "@rallly/utils";
|
||||
|
||||
import { EmailLayout } from "./components/email-layout";
|
||||
import { Heading, Link, Section, Text } from "./components/styled-components";
|
||||
import { removeProtocalFromUrl } from "./components/utils";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Heading,
|
||||
Link,
|
||||
Section,
|
||||
SubHeadingText,
|
||||
Text,
|
||||
} from "./components/styled-components";
|
||||
import { getDomain } from "./components/utils";
|
||||
|
||||
export interface NewPollEmailProps {
|
||||
title: string;
|
||||
|
@ -22,7 +30,7 @@ const ShareLink = ({
|
|||
participantLink: string;
|
||||
}>) => {
|
||||
return (
|
||||
<Link
|
||||
<Button
|
||||
href={`mailto:?subject=${encodeURIComponent(
|
||||
`Availability for ${title}`,
|
||||
)}&body=${encodeURIComponent(
|
||||
|
@ -30,23 +38,13 @@ const ShareLink = ({
|
|||
)}`}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const LinkContainer = (props: { link: string }) => {
|
||||
return (
|
||||
<Section className="rounded bg-gray-50 p-4">
|
||||
<Link href={props.link} className="font-mono">
|
||||
{props.link}
|
||||
</Link>
|
||||
</Section>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewPollEmail = ({
|
||||
title = "Untitled Poll",
|
||||
name = "Guest",
|
||||
name = "John",
|
||||
adminLink = "https://rallly.co/admin/abcdefg123",
|
||||
participantLink = "https://rallly.co/p/wxyz9876",
|
||||
}: NewPollEmailProps) => {
|
||||
|
@ -55,39 +53,51 @@ export const NewPollEmail = ({
|
|||
footNote={
|
||||
<>
|
||||
You are receiving this email because a new poll was created with this
|
||||
email address on{" "}
|
||||
<Link href={absoluteUrl()}>
|
||||
{removeProtocalFromUrl(absoluteUrl())}
|
||||
</Link>
|
||||
. If this wasn't you, please ignore this email.
|
||||
email address on <Link href={absoluteUrl()}>{getDomain()}</Link>. If
|
||||
this wasn't you, please ignore this email.
|
||||
</>
|
||||
}
|
||||
recipientName={name}
|
||||
preview="Share your participant link to start collecting responses."
|
||||
>
|
||||
<Text>
|
||||
Your new poll is ready! Now lets find a date for{" "}
|
||||
<strong>{title}</strong>.
|
||||
</Text>
|
||||
<Text>
|
||||
Copy this link and share it with your participants to start collecting
|
||||
responses.
|
||||
</Text>
|
||||
<LinkContainer link={participantLink} />
|
||||
<Text>
|
||||
<ShareLink title={title} name={name} participantLink={participantLink}>
|
||||
Share via email →
|
||||
</ShareLink>
|
||||
</Text>
|
||||
<Heading>Your secret link</Heading>
|
||||
<Text>
|
||||
Use this link to access the admin page where you can view and edit your
|
||||
poll.
|
||||
</Text>
|
||||
<LinkContainer link={adminLink} />
|
||||
<Text>
|
||||
<Link href={adminLink}>Go to admin page →</Link>
|
||||
Your poll is live! Here are two links you will need to manage your poll.
|
||||
</Text>
|
||||
<Card>
|
||||
<Heading>Admin link</Heading>
|
||||
<SubHeadingText>
|
||||
Use this link to view results and make changes to your poll.
|
||||
</SubHeadingText>
|
||||
<Text className="rounded bg-white px-4 py-3">
|
||||
<Link href={adminLink} className="font-mono">
|
||||
{adminLink}
|
||||
</Link>
|
||||
</Text>
|
||||
<Text>
|
||||
<Button href={adminLink}>Go to admin page</Button>
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Heading>Participant link</Heading>
|
||||
<SubHeadingText>
|
||||
Copy this link and share it with your participants to start collecting
|
||||
responses.
|
||||
</SubHeadingText>
|
||||
<Text className="rounded bg-white px-4 py-3">
|
||||
<Link href={participantLink} className="font-mono">
|
||||
{participantLink}
|
||||
</Link>
|
||||
</Text>
|
||||
<Text>
|
||||
<ShareLink
|
||||
title={title}
|
||||
name={name}
|
||||
participantLink={participantLink}
|
||||
>
|
||||
Share via email
|
||||
</ShareLink>
|
||||
</Text>
|
||||
</Card>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { absoluteUrl } from "@rallly/utils";
|
||||
import { Heading } from "@react-email/heading";
|
||||
|
||||
import { EmailLayout } from "./components/email-layout";
|
||||
import { Link, Text } from "./components/styled-components";
|
||||
import { removeProtocalFromUrl } from "./components/utils";
|
||||
import { Domain, Heading, Link, Text } from "./components/styled-components";
|
||||
import { getDomain } from "./components/utils";
|
||||
|
||||
interface RegisterEmailProps {
|
||||
name: string;
|
||||
|
@ -11,7 +10,7 @@ interface RegisterEmailProps {
|
|||
}
|
||||
|
||||
export const RegisterEmail = ({
|
||||
name = "Guest",
|
||||
name = "John",
|
||||
code = "123456",
|
||||
}: RegisterEmailProps) => {
|
||||
return (
|
||||
|
@ -21,25 +20,22 @@ export const RegisterEmail = ({
|
|||
You're receiving this email because a request was made to
|
||||
register an account on{" "}
|
||||
<Link className="text-primary-500" href={absoluteUrl()}>
|
||||
{removeProtocalFromUrl(absoluteUrl())}
|
||||
{getDomain()}
|
||||
</Link>
|
||||
.
|
||||
. If this wasn't you, please ignore this email.
|
||||
</>
|
||||
}
|
||||
recipientName={name}
|
||||
preview={`Your 6-digit code is: ${code}`}
|
||||
>
|
||||
<Text>Your 6-digit code is:</Text>
|
||||
<Heading className="font-sans tracking-widest" id="code">
|
||||
<Text>
|
||||
Use this code to complete the verification process on <Domain />
|
||||
</Text>
|
||||
<Heading>Your 6-digit code is:</Heading>
|
||||
<Heading as="h1" className="font-sans tracking-widest" id="code">
|
||||
{code}
|
||||
</Heading>
|
||||
<Text>
|
||||
Use this code to complete the verification process on{" "}
|
||||
<Link href={absoluteUrl()}>{removeProtocalFromUrl(absoluteUrl())}</Link>
|
||||
</Text>
|
||||
<Text>
|
||||
<span className="text-gray-500">This code is valid for 15 minutes</span>
|
||||
</Text>
|
||||
<Text light={true}>This code is valid for 15 minutes</Text>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { EmailLayout } from "./components/email-layout";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Heading,
|
||||
Link,
|
||||
Section,
|
||||
SmallText,
|
||||
|
@ -16,14 +18,14 @@ type EnableNotificationsEmailProps = {
|
|||
|
||||
export const EnableNotificationsEmail = ({
|
||||
title = "Untitled Poll",
|
||||
name = "Guest",
|
||||
name = "John",
|
||||
verificationLink = "https://rallly.co",
|
||||
adminLink = "https://rallly.co",
|
||||
}: EnableNotificationsEmailProps) => {
|
||||
return (
|
||||
<EmailLayout
|
||||
recipientName={name}
|
||||
preview="Before we can send you notifications we need to verify your email"
|
||||
preview="We need to verify your email address"
|
||||
footNote={
|
||||
<>
|
||||
You are receiving this email because a request was made to enable
|
||||
|
@ -32,18 +34,19 @@ export const EnableNotificationsEmail = ({
|
|||
}
|
||||
>
|
||||
<Text>
|
||||
Before we can send you notifications we need to verify your email.
|
||||
Would you like to get notified when participants respond to{" "}
|
||||
<strong>{title}</strong>?
|
||||
</Text>
|
||||
<Text>
|
||||
Click the button below to complete the email verification and enable
|
||||
notifications for <strong>{title}</strong>.
|
||||
</Text>
|
||||
<Section>
|
||||
<Button href={verificationLink} id="verifyEmailUrl">
|
||||
Enable notifications →
|
||||
</Button>
|
||||
</Section>
|
||||
<SmallText>The link will expire in 15 minutes.</SmallText>
|
||||
<Card>
|
||||
<Heading>Enable notifications</Heading>
|
||||
<Text>You will get an email when someone responds to the poll.</Text>
|
||||
<Section>
|
||||
<Button href={verificationLink} id="verifyEmailUrl">
|
||||
Yes, enable notifications
|
||||
</Button>
|
||||
</Section>
|
||||
<SmallText>The link will expire in 15 minutes.</SmallText>
|
||||
</Card>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue