Skip to content

562: reset password feature#771

Merged
need4deed merged 8 commits into
developfrom
darrell/feat/forgot-password
Jul 13, 2026
Merged

562: reset password feature#771
need4deed merged 8 commits into
developfrom
darrell/feat/forgot-password

Conversation

@DarrellRoberts

@DarrellRoberts DarrellRoberts commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds a feature to reset password which follows these steps:

  • User clicks "forgot password" on /login
  • User taken to /forgotten-password where they are prompted to enter their email
  • User inputs email and clicks submit which sends an email to their address with following path: /reset-password?token=[token]
  • User navigates to this link and inputs their new password and confirms it.
  • After submit, it changes the password and takes user back to /login

Related Issues

Closes #562

Changes

How to manually test

  1. Start be local dev
  2. Create a user
  3. On fe go to /login and click "forgot password?". Ensure you're sending data to be localhost
  4. Enter email of the user you created and submit
  5. In the terminal logs of be you'll see the email link
  6. Copy the path of the email link e.g. /reset-password?token=[token] and paste it into the fe so that you have: localhost:3000/en/reset-password?token=[token]
  7. Enter new password and submit
  8. On success you'll be taken to /login where you can test your new password which should work

Screenshots / Demos

/forgotten-password

image

On success

image

/reset-password?token=[token]

image

Checklist

  • WITHIN THE SCOPE OF AN ISSUE; No unnecessary files included
  • Tests added/updated
  • Documentation updated
  • CI passes

@DarrellRoberts DarrellRoberts self-assigned this Jul 7, 2026
display: flex;
justify-content: center;
`;

interface LoginFormProps {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored LoginForm to put styles in one shared file

</PageLayout>
<LoginLayout heading={t("dashboard.login.login")}>
<LoginController />
</LoginLayout>
);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored Login to use a shared layout as I use this also for /forgotten-password and /reset-password pages


interface ResetPasswordData {
newPassword: string;
confirmPassword: string;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confirmPassword not needed in the be (it does nothing). Still wanted another password value so that the user could essentially double-check what they're changing their password to.
This is so in the form it triggers a fe error if newPassword and confirmPassword do not match

onChange: ({ value }) => (!value ? t("dashboard.login.emailMissing") : undefined),
onChangeAsyncDebounceMs: 500,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 500));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this onChangeAsync with synthetic wait?
IT feels like it's easier to add to onChange, or just use some email validator

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I just copied the email field from the Login without questioning this so have tidied it up

},
});
const schema = createResetPasswordSchema(t);
console.log("s", schema);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better to remove

name="confirmPassword"
validators={{
onChange: ({ value, fieldApi }) => {
const resultValue = schema.shape.newPassword.safeParse(value);

@kiborrdis kiborrdis Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About all validation in this file:

I think it's better to try form level validation
https://tanstack.com/form/v1/docs/framework/react/guides/validation
As far as i understand it's the docs for a newer version, but still worth a try to do it like that.

Otherwise I would propose to wrap it in a function. It will remove code duplication with other validators, and will make reading this file easier. For confrimPassword matching with newPassword, I feel, if form level validation wont work, it's easier and more readable to make a check in place

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean and good suggestion. I find it easier to stick to the zod Schema pattern particularly as we're using it for other validators for user-defined inputs (e.g. on the Profile edits).

I've wrapped the validation logic in one function and it does look a lot cleaner

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant using form level validation with zod. Based on that I believe, it's probably possible to do something like that

const form = useForm({
  defaultValues: {
    newPassword: "",
    confirmPassword: "",
    token: urlTokenParam || "",
  },
  onSubmit: async ({ value }) => {
    resetPassword(value);
  },
  validators: {
      onChange: ({ value }) => {
        const result = schema.safeParse(value);

        if (!result.success) {
          const errors = toErrors(result.error.issues);
          // where toErrors transforms zod format to { 'key1': 'errormessage1', 'key2': 'errormessage2', ...etc }
          return {
            fields: errors,
          }
        }
      },
    },
});

There is even some package for tanstack/react-form that does that. But it's sus, it's not in their main branch

It also would solve the issue with current code I just thought of: if user typed in a password, then matching confirmPassword, then changed the password, confirmPassword field will not raise an error. I actually didnt run your code and tried, but it feels like it will behave like that

I'm totally okay leaving validation per field. But it's worth checking and fixing this "changing password after you typed in proper confirmPassword" thing

confirmPassword: z.string().min(1, { message: t("dashboard.login.passwordMissing") }),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: "dashboard.login.passwordNotMatching",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not t("dashboard.login.passwordNotMatching") here? You would avoid all this startsWith('.dashboard') unpleasantness from component

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ye good point, have change this and removed the startsWith('.dashboard') from the component

textColor={form.state.canSubmit && !isPending ? "var(--color-white)" : "var(--color-grey-400)"}
textHoverColor="var(--color-magnolia)"
disabled={
!form.state.canSubmit || !form.state.values.newPassword || !form.state.values.confirmPassword || isPending

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm almost sure, you can use form.state.isValid instead newPassword and confirmPassword checks. Maybe even omit isValid, since there is a chance it's included in canSubmit

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very true, it would seem that this is indeed covered by the canSubmit so have simplified it to that

@DarrellRoberts

Copy link
Copy Markdown
Collaborator Author

thanks @kiborrdis ! Have implemented the changes

@need4deed need4deed merged commit 9f51182 into develop Jul 13, 2026
1 check passed
@need4deed need4deed deleted the darrell/feat/forgot-password branch July 13, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add password reset flow (forgot password page + email link)

3 participants