-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8f884ee
commit bbf262a
Showing
7 changed files
with
207 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import ConfirmEmailForm from "@/containers/auth/confirm-email/form"; | ||
|
||
export default function ConfirmEmailPage() { | ||
return <ConfirmEmailForm />; | ||
} |
139 changes: 139 additions & 0 deletions
139
client/src/containers/auth/confirm-email/form/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
"use client"; | ||
|
||
import { FC, FormEvent, useCallback, useRef } from "react"; | ||
|
||
import { useForm } from "react-hook-form"; | ||
|
||
import { useParams, useRouter, useSearchParams } from "next/navigation"; | ||
|
||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { TOKEN_TYPE_ENUM } from "@shared/schemas/auth/token-type.schema"; | ||
import { RequestEmailUpdateSchema } from "@shared/schemas/users/request-email-update.schema"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { z } from "zod"; | ||
|
||
import { client } from "@/lib/query-client"; | ||
import { queryKeys } from "@/lib/query-keys"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormMessage, | ||
} from "@/components/ui/form"; | ||
import { Input } from "@/components/ui/input"; | ||
import { useApiResponseToast } from "@/components/ui/toast/use-api-response-toast"; | ||
|
||
const NewPasswordForm: FC = () => { | ||
const router = useRouter(); | ||
const params = useParams<{ token: string }>(); | ||
const searchParams = useSearchParams(); | ||
const newEmail = searchParams.get("newEmail"); | ||
|
||
const formRef = useRef<HTMLFormElement>(null); | ||
const form = useForm<z.infer<typeof RequestEmailUpdateSchema>>({ | ||
resolver: zodResolver(RequestEmailUpdateSchema), | ||
defaultValues: { | ||
newEmail: newEmail as NonNullable<typeof newEmail>, | ||
}, | ||
}); | ||
const { apiResponseToast, toast } = useApiResponseToast(); | ||
|
||
const { | ||
data: isValidToken, | ||
isFetching, | ||
isError, | ||
} = useQuery({ | ||
queryKey: queryKeys.auth.confirmEmailToken(params.token).queryKey, | ||
queryFn: () => { | ||
return client.auth.validateToken.query({ | ||
headers: { | ||
authorization: `Bearer ${params.token}`, | ||
}, | ||
query: { | ||
tokenType: TOKEN_TYPE_ENUM.EMAIL_CONFIRMATION, | ||
}, | ||
}); | ||
}, | ||
select: (data) => data.status === 200, | ||
}); | ||
|
||
const handleEmailConfirmation = useCallback( | ||
(evt: FormEvent<HTMLFormElement>) => { | ||
evt.preventDefault(); | ||
|
||
form.handleSubmit(async (formValues) => { | ||
try { | ||
const { status, body } = await client.auth.confirmEmail.mutation({ | ||
body: formValues, | ||
extraHeaders: { | ||
authorization: `Bearer ${params.token}`, | ||
}, | ||
}); | ||
apiResponseToast( | ||
{ status, body }, | ||
{ | ||
successMessage: "Email updated successfully.", | ||
}, | ||
); | ||
router.push("/auth/signin"); | ||
} catch (err) { | ||
toast({ | ||
variant: "destructive", | ||
description: "Something went wrong", | ||
}); | ||
} | ||
})(evt); | ||
}, | ||
[form, apiResponseToast, toast, params.token, router], | ||
); | ||
|
||
const isDisabled = isFetching || isError || !isValidToken; | ||
|
||
return ( | ||
<div className="space-y-8 rounded-2xl py-6"> | ||
<div className="space-y-4 px-6"> | ||
<h2 className="text-xl font-semibold">Confirm email</h2> | ||
{!isValidToken && ( | ||
<p className="text-sm text-destructive"> | ||
The token is invalid or has expired. | ||
</p> | ||
)} | ||
</div> | ||
<Form {...form}> | ||
<form | ||
ref={formRef} | ||
className="w-full space-y-8" | ||
onSubmit={handleEmailConfirmation} | ||
> | ||
<FormField | ||
control={form.control} | ||
name="newEmail" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormControl> | ||
<Input type="hidden" {...field} /> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<div className="space-y-2 px-6"> | ||
<Button | ||
variant="secondary" | ||
type="submit" | ||
className="w-full" | ||
disabled={isDisabled} | ||
> | ||
Confirm email | ||
</Button> | ||
</div> | ||
</form> | ||
</Form> | ||
</div> | ||
); | ||
}; | ||
|
||
export default NewPasswordForm; |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { expect, Page, test } from "@playwright/test"; | ||
import { E2eTestManager } from "@shared/lib/e2e-test-manager"; | ||
import { User } from "@shared/entities/users/user.entity"; | ||
|
||
let testManager: E2eTestManager; | ||
let page: Page; | ||
|
||
test.describe.configure({ mode: "serial" }); | ||
|
||
test.describe("Auth - Update Password process", () => { | ||
test.beforeAll(async ({ browser }) => { | ||
page = await browser.newPage(); | ||
testManager = await E2eTestManager.load(page); | ||
}); | ||
|
||
test.beforeEach(async () => { | ||
await testManager.clearDatabase(); | ||
}); | ||
|
||
test.afterEach(async () => { | ||
// await testManager.clearDatabase(); | ||
}); | ||
|
||
test.afterAll(async () => { | ||
await testManager.close(); | ||
}); | ||
|
||
test("an user changes their password successfully", async () => { | ||
const user: Pick<User, "email" | "password" | "partnerName"> = { | ||
email: "[email protected]", | ||
password: "12345678", | ||
partnerName: "admin", | ||
}; | ||
const newPassword = "987654321987654321"; | ||
|
||
await testManager.mocks().createUser(user); | ||
await testManager.login(user as User); | ||
|
||
await page.waitForURL('/profile'); | ||
|
||
await page.getByPlaceholder('Type your current password').fill(user.password); | ||
await page.getByPlaceholder('Create new password').fill(newPassword); | ||
await page.getByPlaceholder('Repeat new password').fill(newPassword); | ||
|
||
await page.getByRole("button", { name: /update password/i }).click(); | ||
|
||
// expect to see toast message | ||
await page.waitForSelector("text=Your password has been updated successfully."); | ||
|
||
await page.getByRole("button", { name: /sign out/i }).click(); | ||
|
||
await testManager.login({ email: user.email, password: newPassword } as User); | ||
|
||
await expect(page).toHaveURL("/profile"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters