Skip to content

Commit 82a1d2b

Browse files
committed
chore: refactor add to cart feature
1 parent c820176 commit 82a1d2b

8 files changed

Lines changed: 125 additions & 41 deletions

File tree

public/locales/en-GB/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
"title": "New product",
100100
"success": "A product has been successfully added to your cart.",
101101
"error": "Something went wrong with adding a product to a cart. Pleas try again or contact us.",
102+
"unknown-product-error": "Product doesn't exist. It may be unavailable or removed from the store.",
103+
"product-not-available-error": "Product is not available. It may be out of stock or removed from the store.",
102104
"not-authenticated": "Please log in in order to add products."
103105
},
104106
"dialog": {

src/features/carts/components/AddToCartButton/AddToCartButton.tsx

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Button, type ButtonProps } from "@chakra-ui/react";
22

33
import { useAuthStore } from "@/features/auth/application/auth-store";
44
import { useProductAddedDialogStore } from "@/features/carts/components/AddToCartButton/use-product-added-dialog-store";
5-
import { useAddToCart } from "@/features/carts/providers/use-add-to-cart";
5+
import {
6+
useAddToCart,
7+
UnknownProductError,
8+
ProductNotAvailableError,
9+
} from "@/features/carts/providers/use-add-to-cart";
610
import { useTranslations } from "@/lib/i18n/use-transations";
711

812
import { useAddToCartNotifications } from "./use-add-to-cart-notifications";
@@ -18,28 +22,45 @@ const AddToCartButton = ({ productId, colorPalette = "gray" }: IProps) => {
1822
const t = useTranslations("features.carts.add-to-cart");
1923

2024
const [add, isLoading] = useAddToCart();
21-
const { notifyFailure, notifySuccess, notifyNotAuthenticated } =
22-
useAddToCartNotifications();
25+
const {
26+
notifyFailure,
27+
notifySuccess,
28+
notifyNotAuthenticated,
29+
notifyUnknownProduct,
30+
notifyProductNotAvailable,
31+
} = useAddToCartNotifications();
2332
const onOpen = useProductAddedDialogStore((store) => store.onOpen);
2433

34+
const onAdd = async () => {
35+
if (!isAuthenticated) {
36+
return notifyNotAuthenticated();
37+
}
38+
39+
try {
40+
await add({ productId, quantity: 1 });
41+
notifySuccess();
42+
onOpen(cartId);
43+
} catch (e) {
44+
if (e instanceof UnknownProductError) {
45+
notifyUnknownProduct();
46+
return;
47+
}
48+
49+
if (e instanceof ProductNotAvailableError) {
50+
notifyProductNotAvailable();
51+
return;
52+
}
53+
54+
notifyFailure();
55+
}
56+
};
57+
2558
return (
2659
<Button
2760
w="100%"
2861
colorPalette={colorPalette}
2962
loading={isLoading}
30-
onClick={async () => {
31-
if (!isAuthenticated) {
32-
return notifyNotAuthenticated();
33-
}
34-
35-
try {
36-
await add({ productId });
37-
notifySuccess();
38-
onOpen(cartId);
39-
} catch {
40-
notifyFailure();
41-
}
42-
}}
63+
onClick={onAdd}
4364
>
4465
{t("button")}
4566
</Button>

src/features/carts/components/AddToCartButton/use-add-to-cart-notifications.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,25 @@ export const useAddToCartNotifications = () => {
2626
description: t("not-authenticated"),
2727
});
2828

29-
return { notifySuccess, notifyFailure, notifyNotAuthenticated } as const;
29+
const notifyUnknownProduct = () =>
30+
toast({
31+
status: "error",
32+
title: t("title"),
33+
description: t("unknown-product-error"),
34+
});
35+
36+
const notifyProductNotAvailable = () =>
37+
toast({
38+
status: "error",
39+
title: t("title"),
40+
description: t("product-not-available-error"),
41+
});
42+
43+
return {
44+
notifySuccess,
45+
notifyFailure,
46+
notifyNotAuthenticated,
47+
notifyUnknownProduct,
48+
notifyProductNotAvailable,
49+
} as const;
3050
};
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { useAuthStore } from "@/features/auth/application/auth-store";
2-
import { useAddToCartMutation } from "@/lib/api/carts/{cart-id}/add-to-cart-command";
2+
import {
3+
useAddToCartMutation,
4+
UnknownProductError,
5+
ProductNotAvailableError,
6+
} from "@/lib/api/carts/{cart-id}/add-to-cart-command";
7+
import { UnauthorizedError } from "@/lib/types/unauthorized-error";
38

4-
interface IAddToCartValues {
9+
interface AddToCartPayload {
510
productId: number;
611
quantity?: number;
712
}
@@ -11,12 +16,14 @@ export const useAddToCart = () => {
1116
const userId = useAuthStore((store) => store.user?.id);
1217
const [mutateAsync, isLoading] = useAddToCartMutation();
1318

14-
const handler = (body: IAddToCartValues) => {
19+
const handler = (body: AddToCartPayload) => {
1520
if (!cartId || !userId) {
16-
throw new Error("User not authenticated");
21+
throw new UnauthorizedError();
1722
}
1823
return mutateAsync({ ...body, cartId, userId });
1924
};
2025

2126
return [handler, isLoading] as const;
2227
};
28+
29+
export { UnknownProductError, ProductNotAvailableError };

src/lib/api/carts/{cart-id}/add-to-cart-command.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ import { useMutation } from "@tanstack/react-query";
33
import { DateVO } from "@/lib/date/date";
44
import { httpService } from "@/lib/http";
55
import { Logger } from "@/lib/logger";
6+
import { UnknownError } from "@/lib/types/unknown-error";
67

7-
interface IAddToCartValues {
8+
interface AddToCartPayload {
89
productId: number;
910
quantity?: number;
1011
cartId: number;
1112
userId: number;
1213
}
1314

14-
interface IAddToCartDto {
15+
interface AddToCartDto {
1516
userId: number;
1617
date: string;
1718
products: { productId: number; quantity: number }[];
@@ -21,33 +22,50 @@ export const useAddToCartMutation = () => {
2122
const { mutateAsync, isPending } = useMutation<
2223
void,
2324
unknown,
24-
IAddToCartValues
25+
AddToCartPayload
2526
>({
2627
mutationFn: (body) =>
27-
httpService.put<void, IAddToCartDto>(`carts/${body.cartId}`, {
28+
httpService.put<void, AddToCartDto>(`carts/${body.cartId}`, {
2829
userId: body.userId,
2930
date: DateVO.now(),
3031
products: [{ productId: body.productId, quantity: body.quantity ?? 1 }],
3132
}),
3233
});
3334

34-
const handler = (body: IAddToCartValues) => {
35-
return mutateAsync(body)
36-
.then(async () => {
37-
// optionally mutate related data
38-
})
39-
.catch((e) => {
40-
// listen for a specific error and act respectively (e.g. throwing a specific error and catch it later)
41-
42-
// notify backend about the error if needed
43-
Logger.error(
44-
"An error occurred during adding an item to the cart",
45-
e as Error
46-
);
47-
48-
throw e;
49-
});
35+
const handler = async (body: AddToCartPayload) => {
36+
try {
37+
return await mutateAsync(body);
38+
} catch (e) {
39+
Logger.error(
40+
"An error occurred during adding an item to the cart",
41+
e as Error
42+
);
43+
44+
if (httpService.isError(e) && e.message === "Unknown product") {
45+
throw new UnknownProductError();
46+
}
47+
48+
if (httpService.isError(e) && e.message === "Product not available") {
49+
throw new ProductNotAvailableError();
50+
}
51+
52+
throw new UnknownError();
53+
}
5054
};
5155

5256
return [handler, isPending] as const;
5357
};
58+
59+
export class UnknownProductError extends Error {
60+
constructor() {
61+
super("Unknown product");
62+
this.name = "UnknownProductError";
63+
}
64+
}
65+
66+
export class ProductNotAvailableError extends Error {
67+
constructor() {
68+
super("Product not available");
69+
this.name = "ProductNotAvailableError";
70+
}
71+
}

src/lib/http/http-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,8 @@ export class HttpService<
7373
private configure(customOptions?: Options): Options {
7474
return Object.assign(this.client.options, customOptions);
7575
}
76+
77+
public isError(error: unknown): error is Error {
78+
return error instanceof Error;
79+
}
7680
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class UnauthorizedError extends Error {
2+
constructor() {
3+
super("User not authenticated");
4+
this.name = "UnauthorizedError";
5+
}
6+
}

src/lib/types/unknown-error.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class UnknownError extends Error {
2+
constructor() {
3+
super("An unknown error occurred");
4+
this.name = "UnknownError";
5+
}
6+
}

0 commit comments

Comments
 (0)