Skip to content

Commit

Permalink
feat(core): remove account status provider with toast messages (#1749)
Browse files Browse the repository at this point in the history
* chore(core): rename translation key

* feat(core): convert change/forgot password over to taosts

* feat(core): convert login over to taosts

* feat(core): convert reset password over to taosts

* feat(core): convert the rest of the auth pages to toasts

* chore(core): remove the account status providers
  • Loading branch information
chanceaclark authored Dec 12, 2024
1 parent 608b886 commit cacdd22
Show file tree
Hide file tree
Showing 46 changed files with 511 additions and 594 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-rocks-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Change the rest of the auth pages to use toasts.
5 changes: 5 additions & 0 deletions .changeset/late-boats-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Converts the reset password messages over to using a toast.
5 changes: 5 additions & 0 deletions .changeset/old-bananas-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Remove the account state provider components
5 changes: 5 additions & 0 deletions .changeset/proud-queens-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Converts the login messages over to using a toast.
5 changes: 5 additions & 0 deletions .changeset/young-pugs-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Converts the change/forgot password messages over to using a toast.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ const ChangePasswordMutation = graphql(`
}
`);

export const changePassword = async (_previousState: unknown, formData: FormData) => {
interface ChangePasswordResponse {
status: 'success' | 'error';
message: string;
}

export const changePassword = async (formData: FormData): Promise<ChangePasswordResponse> => {
const t = await getTranslations('ChangePassword');

try {
Expand All @@ -61,29 +66,24 @@ export const changePassword = async (_previousState: unknown, formData: FormData

const result = response.data.customer.resetPassword;

if (result.errors.length === 0) {
return { status: 'success', messages: [''] };
if (result.errors.length > 0) {
result.errors.forEach((error) => {
throw new Error(error.message);
});
}

return {
status: 'error',
messages: result.errors.map((error) => error.message),
status: 'success',
message: t('confirmChangePassword'),
};
} catch (error: unknown) {
if (error instanceof ZodError) {
return {
status: 'error',
messages: error.issues.map(({ path, message }) => `${path.toString()}: ${message}.`),
};
}

if (error instanceof Error) {
if (error instanceof Error || error instanceof ZodError) {
return {
status: 'error',
messages: [error.message],
message: error.message,
};
}

return { status: 'error', messages: [t('Errors.error')] };
return { status: 'error', message: t('Errors.error') };
}
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use client';

import { AlertCircle, Check } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { ChangeEvent, useActionState, useRef, useState } from 'react';
import { ChangeEvent, useState } from 'react';
import { useFormStatus } from 'react-dom';
import { toast } from 'react-hot-toast';

import { Button } from '~/components/ui/button';
import {
Expand All @@ -14,11 +16,8 @@ import {
FormSubmit,
Input,
} from '~/components/ui/form';
import { Message } from '~/components/ui/message';
import { useRouter } from '~/i18n/routing';

import { useAccountStatusContext } from '../../../account/_components/account-status-provider';
import { SubmitMessagesList } from '../../../account/_components/submit-messages-list';
import { changePassword } from '../_actions/change-password';

interface Props {
Expand Down Expand Up @@ -47,92 +46,91 @@ const SubmitButton = () => {
export const ChangePasswordForm = ({ customerId, customerToken }: Props) => {
const t = useTranslations('ChangePassword.Form');

const form = useRef<HTMLFormElement>(null);
const router = useRouter();
const [state, formAction] = useActionState(changePassword, {
status: 'idle',
messages: [''],
});

const [newPassword, setNewPasssword] = useState('');
const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState(true);
const { setAccountState } = useAccountStatusContext();

const handleNewPasswordChange = (e: ChangeEvent<HTMLInputElement>) =>
setNewPasssword(e.target.value);

const handleConfirmPasswordValidation = (e: ChangeEvent<HTMLInputElement>) => {
const confirmPassword = e.target.value;

setIsConfirmPasswordValid(confirmPassword === newPassword);
};

if (state.status === 'success') {
setAccountState({ status: 'success', messages: [t('confirmChangePassword')] });
const handleChangePassword = async (formData: FormData) => {
const { status, message } = await changePassword(formData);

if (status === 'error') {
toast.error(message, {
icon: <AlertCircle className="text-error-secondary" />,
});

return;
}

toast.success(message, {
icon: <Check className="text-success-secondary" />,
});

router.push('/login');
}
};

return (
<>
{state.status === 'error' && (
<Message className="mb-8 w-full text-gray-500" variant={state.status}>
<SubmitMessagesList messages={state.messages} />
</Message>
)}

<Form action={formAction} className="mb-14 flex flex-col gap-4 md:py-4 lg:p-0" ref={form}>
<Field className="hidden" name="customer-id">
<FieldControl asChild>
<Input id="customer-id" readOnly type="number" value={customerId} />
</FieldControl>
</Field>
<Field className="hidden" name="customer-token">
<FieldControl asChild>
<Input id="customer-token" readOnly type="text" value={customerToken} />
</FieldControl>
</Field>
<Field className="relative space-y-2 pb-7" name="new-password">
<FieldLabel htmlFor="new-password" isRequired={true}>
{t('newPasswordLabel')}
</FieldLabel>
<FieldControl asChild>
<Input
autoComplete="none"
error={state.status === 'error'}
id="new-password"
onChange={handleNewPasswordChange}
required
type="password"
/>
</FieldControl>
</Field>

<Field className="relative space-y-2 pb-7" name="confirm-password">
<FieldLabel htmlFor="confirm-password" isRequired={true}>
{t('confirmPasswordLabel')}
</FieldLabel>
<FieldControl asChild>
<Input
autoComplete="none"
error={!isConfirmPasswordValid || state.status === 'error'}
id="confirm-password"
onChange={handleConfirmPasswordValidation}
onInvalid={handleConfirmPasswordValidation}
required
type="password"
/>
</FieldControl>
<FieldMessage
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs text-error"
match={(value: string) => value !== newPassword}
>
{t('confirmPasswordValidationMessage')}
</FieldMessage>
</Field>

<FormSubmit asChild>
<SubmitButton />
</FormSubmit>
</Form>
</>
<Form action={handleChangePassword} className="mb-14 flex flex-col gap-4 md:py-4 lg:p-0">
<Field className="hidden" name="customer-id">
<FieldControl asChild>
<Input id="customer-id" readOnly type="number" value={customerId} />
</FieldControl>
</Field>
<Field className="hidden" name="customer-token">
<FieldControl asChild>
<Input id="customer-token" readOnly type="text" value={customerToken} />
</FieldControl>
</Field>
<Field className="relative space-y-2 pb-7" name="new-password">
<FieldLabel htmlFor="new-password" isRequired={true}>
{t('newPasswordLabel')}
</FieldLabel>
<FieldControl asChild>
<Input
autoComplete="none"
id="new-password"
onChange={handleNewPasswordChange}
required
type="password"
/>
</FieldControl>
</Field>

<Field className="relative space-y-2 pb-7" name="confirm-password">
<FieldLabel htmlFor="confirm-password" isRequired={true}>
{t('confirmPasswordLabel')}
</FieldLabel>
<FieldControl asChild>
<Input
autoComplete="none"
error={!isConfirmPasswordValid}
id="confirm-password"
onChange={handleConfirmPasswordValidation}
onInvalid={handleConfirmPasswordValidation}
required
type="password"
/>
</FieldControl>
<FieldMessage
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs text-error"
match={(value: string) => value !== newPassword}
>
{t('confirmPasswordValidationMessage')}
</FieldMessage>
</Field>

<FormSubmit asChild>
<SubmitButton />
</FormSubmit>
</Form>
);
};
2 changes: 1 addition & 1 deletion core/app/[locale]/(default)/(auth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default async function Layout({ children, params }: Props) {
const { locale } = await params;

if (session) {
redirect({ href: '/account', locale });
redirect({ href: '/account/orders', locale });
}

return children;
Expand Down
10 changes: 9 additions & 1 deletion core/app/[locale]/(default)/(auth)/login/_actions/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { getLocale } from 'next-intl/server';
import { Credentials, signIn } from '~/auth';
import { redirect } from '~/i18n/routing';

export const login = async (_previousState: unknown, formData: FormData) => {
interface LoginResponse {
status: 'success' | 'error';
}

export const login = async (formData: FormData): Promise<LoginResponse> => {
try {
const locale = await getLocale();

Expand All @@ -23,6 +27,10 @@ export const login = async (_previousState: unknown, formData: FormData) => {
});

redirect({ href: '/account/orders', locale });

return {
status: 'success',
};
} catch (error: unknown) {
rethrow(error);

Expand Down
Loading

0 comments on commit cacdd22

Please sign in to comment.