Skip to content

Commit 68a4a24

Browse files
committed
send notice email to old email after change
1 parent 088bfbd commit 68a4a24

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

app/routes/settings+/profile.change-email.index/index.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
prepareVerification,
1717
type VerifyFunctionArgs,
1818
} from '../../resources+/verify.tsx'
19-
import { EmailChangeEmail } from './email.server.tsx'
19+
import { EmailChangeEmail, EmailChangeNoticeEmail } from './email.server.tsx'
2020

2121
export const newEmailAddressSessionKey = 'new-email-address'
2222

@@ -107,14 +107,24 @@ export async function handleVerification({
107107
]
108108
return json({ status: 'error', submission } as const, { status: 400 })
109109
}
110-
await prisma.user.update({
110+
const preUpdateUser = await prisma.user.findFirstOrThrow({
111+
select: { email: true },
112+
where: { id: submission.value.target },
113+
})
114+
const user = await prisma.user.update({
111115
where: { id: submission.value.target },
112-
select: { email: true, username: true },
116+
select: { id: true, email: true, username: true },
113117
data: { email: newEmail },
114118
})
115119

116120
cookieSession.unset(newEmailAddressSessionKey)
117121

122+
void sendEmail({
123+
to: preUpdateUser.email,
124+
subject: 'Epic Stack email changed',
125+
react: <EmailChangeNoticeEmail userId={user.id} />,
126+
})
127+
118128
return redirectWithToast(
119129
'/settings/profile',
120130
{ title: 'Success', description: 'Your email has been changed.' },

tests/e2e/settings-profile.test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { faker } from '@faker-js/faker'
2-
import { expect, insertNewUser, test } from '../playwright-utils.ts'
2+
import { expect, insertNewUser, test, waitFor } from '../playwright-utils.ts'
33
import { createUser } from '../../tests/db-utils.ts'
44
import { verifyUserPassword } from '~/utils/auth.server.ts'
55
import { readEmail } from 'tests/mocks/utils.ts'
@@ -86,15 +86,17 @@ test('Users can update their profile photo', async ({ login, page }) => {
8686
})
8787

8888
test('Users can change their email address', async ({ page, login }) => {
89-
const user = await login()
89+
const preUpdateUser = await login()
9090
const newEmailAddress = faker.internet.email().toLowerCase()
91-
expect(user.email).not.toEqual(newEmailAddress)
91+
expect(preUpdateUser.email).not.toEqual(newEmailAddress)
9292
await page.goto('/settings/profile')
9393
await page.getByRole('link', { name: /change email/i }).click()
9494
await page.getByRole('textbox', { name: /new email/i }).fill(newEmailAddress)
9595
await page.getByRole('button', { name: /send confirmation/i }).click()
96-
await expect(page.getByText(/check your email/i)).toBeVisible()
97-
const email = await readEmail(newEmailAddress)
96+
// await expect(page.getByText(/check your email/i)).toBeVisible()
97+
const email = await waitFor(() => readEmail(newEmailAddress), {
98+
errorMessage: 'Confirmation email was not sent',
99+
})
98100
invariant(email, 'Email was not sent')
99101
const codeMatch = email.text.match(
100102
/Here's your verification code: (?<code>\d+)/,
@@ -106,9 +108,13 @@ test('Users can change their email address', async ({ page, login }) => {
106108
await expect(page.getByText(newEmailAddress)).toBeVisible()
107109

108110
const updatedUser = await prisma.user.findUnique({
109-
where: { id: user.id },
111+
where: { id: preUpdateUser.id },
110112
select: { email: true },
111113
})
112114
invariant(updatedUser, 'Updated user not found')
113115
expect(updatedUser.email).toBe(newEmailAddress)
116+
const noticeEmail = await waitFor(() => readEmail(preUpdateUser.email), {
117+
errorMessage: 'Notice email was not sent',
118+
})
119+
expect(noticeEmail.subject).toContain('changed')
114120
})

tests/playwright-utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,34 @@ export async function loginPage({
109109
return user
110110
}
111111

112+
/**
113+
* This allows you to wait for something (like an email to be available).
114+
*
115+
* It calls the callback every 50ms until it returns a value (and does not throw
116+
* an error). After the timeout, it will throw the last error that was thrown or
117+
* throw the error message provided as a fallback
118+
*/
119+
export async function waitFor<ReturnValue>(
120+
cb: () => ReturnValue | Promise<ReturnValue>,
121+
{
122+
errorMessage,
123+
timeout = 5000,
124+
}: { errorMessage?: string; timeout?: number } = {},
125+
) {
126+
const endTime = Date.now() + timeout
127+
let lastError: unknown = new Error(errorMessage)
128+
while (Date.now() < endTime) {
129+
try {
130+
const response = await cb()
131+
if (response) return response
132+
} catch (e: unknown) {
133+
lastError = e
134+
}
135+
await new Promise(r => setTimeout(r, 100))
136+
}
137+
throw lastError
138+
}
139+
112140
test.afterEach(async () => {
113141
type Delegate = {
114142
deleteMany: (opts: {

0 commit comments

Comments
 (0)