Skip to content

Commit

Permalink
🚸 Improve magic link sign in experience
Browse files Browse the repository at this point in the history
New email and sign in feedback
  • Loading branch information
baptisteArno committed Mar 13, 2023
1 parent 4ae9ea3 commit 48db171
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 37 deletions.
80 changes: 46 additions & 34 deletions apps/builder/src/features/auth/components/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
HStack,
Text,
Spinner,
Tooltip,
Alert,
Flex,
AlertIcon,
SlideFade,
} from '@chakra-ui/react'
import React, { ChangeEvent, FormEvent, useEffect } from 'react'
import { useState } from 'react'
Expand Down Expand Up @@ -78,11 +81,6 @@ export const SignInForm = ({
})
} else {
setIsMagicLinkSent(true)
showToast({
status: 'success',
title: 'Success!',
description: 'Check your inbox to sign in',
})
}
setAuthLoading(false)
}
Expand All @@ -103,40 +101,54 @@ export const SignInForm = ({
)
return (
<Stack spacing="4" w="330px">
<SocialLoginButtons providers={providers} />
{providers?.email && (
{!isMagicLinkSent && (
<>
<DividerWithText mt="6">Or with your email</DividerWithText>
<HStack as="form" onSubmit={handleEmailSubmit}>
<Input
name="email"
type="email"
autoComplete="email"
placeholder="email@company.com"
required
value={emailValue}
onChange={handleEmailChange}
/>
<Tooltip
label="A sign in email was sent. Make sure to check your SPAM folder."
isDisabled={!isMagicLinkSent}
>
<Button
type="submit"
isLoading={
['loading', 'authenticated'].includes(status) || authLoading
}
isDisabled={isMagicLinkSent}
>
Submit
</Button>
</Tooltip>
</HStack>
<SocialLoginButtons providers={providers} />
{providers?.email && (
<>
<DividerWithText mt="6">Or with your email</DividerWithText>
<HStack as="form" onSubmit={handleEmailSubmit}>
<Input
name="email"
type="email"
autoComplete="email"
placeholder="email@company.com"
required
value={emailValue}
onChange={handleEmailChange}
/>
<Button
type="submit"
isLoading={
['loading', 'authenticated'].includes(status) || authLoading
}
isDisabled={isMagicLinkSent}
>
Submit
</Button>
</HStack>
</>
)}
</>
)}
{router.query.error && (
<SignInError error={router.query.error.toString()} />
)}
<SlideFade offsetY="20px" in={isMagicLinkSent} unmountOnExit>
<Flex>
<Alert status="success" w="100%">
<HStack>
<AlertIcon />
<Stack spacing={1}>
<Text fontWeight="semibold">
A magic link email was sent. 🪄
</Text>
<Text fontSize="sm">Make sure to check your SPAM folder.</Text>
</Stack>
</HStack>
</Alert>
</Flex>
</SlideFade>
</Stack>
)
}
2 changes: 2 additions & 0 deletions apps/builder/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { User } from 'db'
import { env, getAtPath, isDefined, isNotEmpty } from 'utils'
import { mockedUser } from '@/features/auth'
import { getNewUserInvitations } from '@/features/auth/api'
import { sendVerificationRequest } from './sendVerificationRequest'

const providers: Provider[] = []

Expand Down Expand Up @@ -42,6 +43,7 @@ if (isNotEmpty(env('SMTP_FROM')) && process.env.SMTP_AUTH_DISABLED !== 'true')
},
},
from: env('SMTP_FROM'),
sendVerificationRequest,
})
)

Expand Down
16 changes: 16 additions & 0 deletions apps/builder/src/pages/api/auth/sendVerificationRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { EmailConfig } from 'next-auth/providers/email'
import { sendMagicLinkEmail } from 'emails'

type Props = {
identifier: string
url: string
provider: Partial<Omit<EmailConfig, 'options'>>
}

export const sendVerificationRequest = async ({ identifier, url }: Props) => {
try {
await sendMagicLinkEmail({ url, to: identifier })
} catch (err) {
throw new Error(`Email(s) could not be sent`)
}
}
7 changes: 4 additions & 3 deletions packages/emails/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react'
import { MjmlButton } from '@faire/mjml-react'
import { IMjmlButtonProps, MjmlButton } from '@faire/mjml-react'
import { blue, grayLight } from '../theme'
import { leadingTight, textBase, borderBase } from '../theme'

type ButtonProps = {
link: string
children: React.ReactNode
}
} & IMjmlButtonProps

export const Button = ({ link, children }: ButtonProps) => (
export const Button = ({ link, children, ...props }: ButtonProps) => (
<MjmlButton
lineHeight={leadingTight}
fontSize={textBase}
Expand All @@ -20,6 +20,7 @@ export const Button = ({ link, children }: ButtonProps) => (
backgroundColor={blue}
color={grayLight}
borderRadius={borderBase}
{...props}
>
{children}
</MjmlButton>
Expand Down
55 changes: 55 additions & 0 deletions packages/emails/src/emails/MagicLinkEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { ComponentProps } from 'react'
import {
Mjml,
MjmlBody,
MjmlSection,
MjmlColumn,
MjmlSpacer,
} from '@faire/mjml-react'
import { render } from '@faire/mjml-react/utils/render'
import { HeroImage, Text, Button, Head } from '../components'
import { SendMailOptions } from 'nodemailer'
import { sendEmail } from '../sendEmail'

type Props = {
url: string
}

export const MagicLinkEmail = ({ url }: Props) => (
<Mjml>
<Head />
<MjmlBody width={600}>
<MjmlSection padding="0">
<MjmlColumn>
<HeroImage src="https://s3.fr-par.scw.cloud/typebot/public/typebots/rxp84mn10va5iqek63enrg99/blocks/yfazs53p6coxe4u3tbbvkl0m" />
</MjmlColumn>
</MjmlSection>
<MjmlSection padding="0 24px" cssClass="smooth">
<MjmlColumn>
<Text>Here is your magic link 👇</Text>
<MjmlSpacer />
<Button link={url} align="center">
Click here to sign in
</Button>
<Text>
If you didn&apos;t request this, please ignore this email.
</Text>
<Text>
Best,
<br />- Typebot Team.
</Text>
</MjmlColumn>
</MjmlSection>
</MjmlBody>
</Mjml>
)

export const sendMagicLinkEmail = ({
to,
...props
}: Pick<SendMailOptions, 'to'> & ComponentProps<typeof MagicLinkEmail>) =>
sendEmail({
to,
subject: 'Sign in to Typebot',
html: render(<MagicLinkEmail {...props} />).html,
})
1 change: 1 addition & 0 deletions packages/emails/src/emails/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './GuestInvitationEmail'
export * from './ReachedChatsLimitEmail'
export * from './ReachedStorageLimitEmail'
export * from './WorkspaceMemberInvitationEmail'
export * from './MagicLinkEmail'
5 changes: 5 additions & 0 deletions packages/emails/src/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ReachedStorageLimitEmail,
WorkspaceMemberInvitation,
} from './emails'
import { MagicLinkEmail } from './emails/MagicLinkEmail'

const createDistFolder = () => {
const dist = path.resolve(__dirname, 'dist')
Expand Down Expand Up @@ -91,6 +92,10 @@ const createHtmlFile = () => {
/>
).html
)
fs.writeFileSync(
path.resolve(__dirname, 'dist', 'magicLink.html'),
render(<MagicLinkEmail url={'https://app.typebot.io'} />).html
)
}

createDistFolder()
Expand Down

0 comments on commit 48db171

Please sign in to comment.