Skip to content

Commit

Permalink
feat(core): use toasts for address deletions (#1728)
Browse files Browse the repository at this point in the history
  • Loading branch information
chanceaclark authored Dec 10, 2024
1 parent c8b1995 commit d7dbd7a
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 88 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-sloths-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Convert the messages that were displayed when deleting an address over to using the toast functionality.
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
'use server';

import { revalidatePath } from 'next/cache';
import { revalidateTag } from 'next/cache';
import { getTranslations } from 'next-intl/server';

import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql } from '~/client/graphql';

import { State } from '../../settings/change-password/_actions/change-password';
import { TAGS } from '~/client/tags';

const DeleteCustomerAddressMutation = graphql(`
mutation DeleteCustomerAddressMutation(
Expand All @@ -19,11 +18,9 @@ const DeleteCustomerAddressMutation = graphql(`
errors {
__typename
... on CustomerAddressDeletionError {
__typename
message
}
... on CustomerNotLoggedInError {
__typename
message
}
}
Expand All @@ -32,7 +29,12 @@ const DeleteCustomerAddressMutation = graphql(`
}
`);

export const deleteAddress = async (addressId: number): Promise<State> => {
interface DeleteAddressResponse {
status: 'success' | 'error';
message: string;
}

export const deleteAddress = async (addressId: number): Promise<DeleteAddressResponse> => {
const t = await getTranslations('Account.Addresses.Delete');
const customerAccessToken = await getSessionCustomerAccessToken();

Expand All @@ -50,24 +52,24 @@ export const deleteAddress = async (addressId: number): Promise<State> => {

const result = response.data.customer.deleteCustomerAddress;

revalidatePath('/account/addresses', 'page');

if (result.errors.length === 0) {
return { status: 'success', messages: [t('success')] };
if (result.errors.length > 0) {
result.errors.forEach((error) => {
// Throw the first error message, as we should only handle one error at a time
throw new Error(error.message);
});
}

return {
status: 'error',
messages: result.errors.map((error) => error.message),
};
revalidateTag(TAGS.customer);

return { status: 'success', message: t('success') };
} catch (error: unknown) {
if (error instanceof Error) {
return {
status: 'error',
messages: [error.message],
message: error.message,
};
}

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

import { AlertCircle, Check } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { PropsWithChildren, useEffect } from 'react';
import { PropsWithChildren } from 'react';
import { toast } from 'react-hot-toast';

import { ExistingResultType } from '~/client/util';
import { Link } from '~/components/link';
import { Button } from '~/components/ui/button';
import { Message } from '~/components/ui/message';
import { useRouter } from '~/i18n/routing';

import { useAccountStatusContext } from '../../_components/account-status-provider';
import { Modal } from '../../_components/modal';
import { SubmitMessagesList } from '../../_components/submit-messages-list';
import { deleteAddress } from '../_actions/delete-address';
import { getCustomerAddresses } from '../page-data';

export type Addresses = ExistingResultType<typeof getCustomerAddresses>['addresses'];

interface AddressChangeProps {
addressId: number;
isAddressRemovable: boolean;
}

const AddressChangeButtons = ({ addressId, isAddressRemovable }: AddressChangeProps) => {
const { setAccountState } = useAccountStatusContext();
const t = useTranslations('Account.Addresses');

const handleDeleteAddress = async () => {
const submit = await deleteAddress(addressId);

if (submit.status === 'success') {
setAccountState({
status: 'success',
messages: submit.messages || [''],
});
}
};

return (
<div className="flex w-fit gap-x-2 divide-y-0">
<Button aria-label={t('editButton')} asChild variant="secondary">
<Link href={`/account/addresses/edit/${addressId}`}>{t('editButton')}</Link>
</Button>
<Modal
actionHandler={handleDeleteAddress}
confirmationText={t('confirmDeleteAddress')}
title={t('deleteModalTitle')}
>
<Button aria-label={t('deleteButton')} disabled={!isAddressRemovable} variant="subtle">
{t('deleteButton')}
</Button>
</Modal>
</div>
);
};

interface AddressBookProps {
customerAddresses: Addresses;
addressesCount: number;
totalAddresses: number;
}

export const AddressBook = ({
children,
addressesCount,
totalAddresses,
customerAddresses,
}: PropsWithChildren<AddressBookProps>) => {
const t = useTranslations('Account.Addresses');
const { accountState } = useAccountStatusContext();
const router = useRouter();

useEffect(() => {
if (customerAddresses.length === 0) {
router.push(`/account/addresses/`);
const handleDeleteAddress = (addressId: number) => async () => {
const { status, message } = await deleteAddress(addressId);

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

return;
}
}, [customerAddresses, router]);

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

return (
<>
{(accountState.status === 'error' || accountState.status === 'success') && (
<Message className="mb-8 w-full text-gray-500" variant={accountState.status}>
<SubmitMessagesList messages={accountState.messages} />
</Message>
)}
{!addressesCount && <p className="border-t py-12 text-center">{t('emptyAddresses')}</p>}
{totalAddresses === 0 && <p className="border-t py-12 text-center">{t('emptyAddresses')}</p>}
<ul className="mb-12">
{customerAddresses.map(
({
Expand Down Expand Up @@ -111,7 +74,24 @@ export const AddressBook = ({
</p>
<p>{countryCode}</p>
</div>
<AddressChangeButtons addressId={entityId} isAddressRemovable={addressesCount > 1} />
<div className="flex w-fit gap-x-2 divide-y-0">
<Button aria-label={t('editButton')} asChild variant="secondary">
<Link href={`/account/addresses/edit/${entityId}`}>{t('editButton')}</Link>
</Button>
<Modal
actionHandler={handleDeleteAddress(entityId)}
confirmationText={t('confirmDeleteAddress')}
title={t('deleteModalTitle')}
>
<Button
aria-label={t('deleteButton')}
disabled={totalAddresses === 1}
variant="subtle"
>
{t('deleteButton')}
</Button>
</Modal>
</div>
</li>
),
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,11 @@ export const EditAddressForm = ({
const submit = await deleteAddress(address.entityId);

if (submit.status === 'success') {
setAccountState({ status: submit.status, messages: submit.messages || [''] });
setAccountState({ status: submit.status, messages: [submit.message] });
}

if (submit.status === 'error') {
setAccountState({ status: submit.status, messages: submit.messages || [''] });
setAccountState({ status: submit.status, messages: [submit.message] });
}

router.push('/account/addresses');
Expand Down
5 changes: 3 additions & 2 deletions core/app/[locale]/(default)/account/addresses/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { client } from '~/client';
import { FormFieldValuesFragment } from '~/client/fragments/form-fields-values';
import { PaginationFragment } from '~/client/fragments/pagination';
import { graphql } from '~/client/graphql';
import { TAGS } from '~/client/tags';

const GetCustomerAddressesQuery = graphql(
`
Expand Down Expand Up @@ -59,7 +60,7 @@ export const getCustomerAddresses = cache(
document: GetCustomerAddressesQuery,
variables: { ...paginationArgs },
customerAccessToken,
fetchOptions: { cache: 'no-store' },
fetchOptions: { cache: 'no-store', next: { tags: [TAGS.customer] } },
});

const addresses = response.data.customer?.addresses;
Expand All @@ -70,7 +71,7 @@ export const getCustomerAddresses = cache(

return {
pageInfo: addresses.pageInfo,
addressesCount: addresses.collectionInfo?.totalItems ?? 0,
totalAddresses: addresses.collectionInfo?.totalItems ?? 0,
addresses: removeEdgesAndNodes({ edges: addresses.edges }),
};
},
Expand Down
4 changes: 2 additions & 2 deletions core/app/[locale]/(default)/account/addresses/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export default async function Addresses({ searchParams }: Props) {
notFound();
}

const { addresses, pageInfo, addressesCount } = data;
const { addresses, pageInfo, totalAddresses } = data;
const { hasNextPage, hasPreviousPage, startCursor, endCursor } = pageInfo;

return (
<>
<TabHeading heading="addresses" />
<AddressBook addressesCount={addressesCount} customerAddresses={addresses} key={endCursor}>
<AddressBook customerAddresses={addresses} key={endCursor} totalAddresses={totalAddresses}>
<Pagination
className="my-0 inline-flex justify-center text-center"
endCursor={endCursor ?? undefined}
Expand Down
1 change: 1 addition & 0 deletions core/client/tags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const TAGS = {
cart: 'cart',
checkout: 'checkout',
customer: 'customer',
} as const;
14 changes: 8 additions & 6 deletions core/tests/ui/e2e/account.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ test('My Account tabs are displayed and clickable', async ({ page, account }) =>
// More info: https://github.com/amannn/next-intl/issues/1335
await expect(page).toHaveURL('/account/');

await expect(page.getByRole('link', { name: 'Orders' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Addresses' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Account settings' })).toBeVisible();
const tabs = page.getByRole('navigation', { name: 'Account Tabs' });

await page.getByRole('link', { name: 'Orders' }).click();
await expect(tabs.getByRole('link', { name: 'Orders' })).toBeVisible();
await expect(tabs.getByRole('link', { name: 'Addresses' })).toBeVisible();
await expect(tabs.getByRole('link', { name: 'Account settings' })).toBeVisible();

await tabs.getByRole('link', { name: 'Orders' }).click();
await expect(page).toHaveURL('/account/orders/');
await expect(page.getByRole('heading', { name: 'Orders' })).toBeVisible();

await page.getByRole('link', { name: 'Addresses' }).click();
await tabs.getByRole('link', { name: 'Addresses' }).click();
await expect(page).toHaveURL('/account/addresses/');
await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();

await page.getByRole('link', { name: 'Account settings' }).click();
await tabs.getByRole('link', { name: 'Account settings' }).click();
await expect(page).toHaveURL('/account/settings/');
await expect(page.getByRole('heading', { name: 'Account settings' })).toBeVisible();

Expand Down
4 changes: 2 additions & 2 deletions core/tests/ui/e2e/addresses.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test('Add new address', async ({ page, account }) => {
await page.getByRole('button', { name: 'Add new address' }).click();
await page.waitForURL('/account/addresses/');

await expect(page.getByText('Address added to your account.')).toBeVisible();
// await expect(page.getByText('Address added to your account.')).toBeVisible();

await customer.logout();
});
Expand All @@ -47,7 +47,7 @@ test('Edit address', async ({ page, account }) => {
await page.getByRole('button', { name: 'Edit address' }).click();
await page.waitForURL('/account/addresses/');

await expect(page.getByText('The address has been successfully updated.')).toBeVisible();
// await expect(page.getByText('The address has been successfully updated.')).toBeVisible();

await customer.logout();
});
Expand Down

0 comments on commit d7dbd7a

Please sign in to comment.