Skip to content

Commit 290ccf2

Browse files
committed
Resolve eslint issues
1 parent 62168e0 commit 290ccf2

File tree

10 files changed

+320
-189
lines changed

10 files changed

+320
-189
lines changed

core/app/[locale]/(default)/cart/_components/cart-item.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { FragmentOf, graphql } from '~/client/graphql';
44
import { BcImage } from '~/components/bc-image';
55

66
import { ItemQuantity } from './item-quantity';
7-
import { RemoveItem, RemoveGiftCertificate } from './remove-item';
7+
import { RemoveGiftCertificate, RemoveItem } from './remove-item';
88

99
const PhysicalItemFragment = graphql(`
1010
fragment PhysicalItemFragment on CartPhysicalItem {
@@ -261,7 +261,7 @@ export const CartItem = ({ currencyCode, product }: Props) => {
261261
<div className="flex flex-col gap-2 md:items-end">
262262
<div>
263263
{product.originalPrice.value &&
264-
product.originalPrice.value !== product.listPrice.value ? (
264+
product.originalPrice.value !== product.listPrice.value ? (
265265
<p className="text-lg font-bold line-through">
266266
{format.number(product.originalPrice.value * product.quantity, {
267267
style: 'currency',
@@ -301,21 +301,28 @@ export const CartGiftCertificate = ({ currencyCode, giftCertificate }: GiftCerti
301301
return (
302302
<li>
303303
<div className="flex gap-4 border-t border-t-gray-200 py-4 md:flex-row">
304-
<div className="flex justify-center items-center w-24 md:w-[144px]">
304+
<div className="flex w-24 items-center justify-center md:w-[144px]">
305305
<h2 className="text-lg font-bold">{giftCertificate.theme}</h2>
306306
</div>
307307

308308
<div className="flex-1">
309309
<div className="flex flex-col gap-2 md:flex-row">
310310
<div className="flex flex-1 flex-col gap-2">
311-
<p className="text-xl font-bold md:text-2xl">{format.number(giftCertificate.amount.value, {
312-
style: 'currency',
313-
currency: currencyCode,
314-
})} Gift Certificate</p>
315-
311+
<p className="text-xl font-bold md:text-2xl">
312+
{format.number(giftCertificate.amount.value, {
313+
style: 'currency',
314+
currency: currencyCode,
315+
})}{' '}
316+
Gift Certificate
317+
</p>
318+
316319
<p className="text-md text-gray-500">{giftCertificate.message}</p>
317-
<p className="text-sm text-gray-500">To: {giftCertificate.recipient.name} ({giftCertificate.recipient.email})</p>
318-
<p className="text-sm text-gray-500">From: {giftCertificate.sender.name} ({giftCertificate.sender.email})</p>
320+
<p className="text-sm text-gray-500">
321+
To: {giftCertificate.recipient.name} ({giftCertificate.recipient.email})
322+
</p>
323+
<p className="text-sm text-gray-500">
324+
From: {giftCertificate.sender.name} ({giftCertificate.sender.email})
325+
</p>
319326

320327
<div className="hidden md:block">
321328
<RemoveGiftCertificate currency={currencyCode} giftCertificate={giftCertificate} />

core/app/[locale]/(default)/cart/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ export default async function Cart() {
8787
<CartItem currencyCode={cart.currencyCode} key={product.entityId} product={product} />
8888
))}
8989
{giftCertificates.map((giftCertificate) => (
90-
<CartGiftCertificate currencyCode={cart.currencyCode} key={giftCertificate.name} giftCertificate={giftCertificate} />
90+
<CartGiftCertificate
91+
currencyCode={cart.currencyCode}
92+
giftCertificate={giftCertificate}
93+
key={giftCertificate.name}
94+
/>
9195
))}
9296
</ul>
9397

core/app/[locale]/(default)/gift-certificates/_actions/add-to-cart.tsx

Lines changed: 125 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,131 @@
33
import { revalidateTag } from 'next/cache';
44
import { cookies } from 'next/headers';
55
import { getFormatter, getTranslations } from 'next-intl/server';
6+
import { z } from 'zod';
67

78
import { addCartLineItem } from '~/client/mutations/add-cart-line-item';
8-
import { createCartWithGiftCertificate } from '../_mutations/create-cart-with-gift-certificate';
99
import { getCart } from '~/client/queries/get-cart';
1010
import { TAGS } from '~/client/tags';
1111

12-
const GIFT_CERTIFICATE_THEMES = ['GENERAL', 'BIRTHDAY', 'BOY', 'CELEBRATION', 'CHRISTMAS', 'GIRL', 'NONE'];
13-
type giftCertificateTheme = "GENERAL" | "BIRTHDAY" | "BOY" | "CELEBRATION" | "CHRISTMAS" | "GIRL" | "NONE";
12+
import { createCartWithGiftCertificate } from '../_mutations/create-cart-with-gift-certificate';
1413

15-
export const addGiftCertificateToCart = async (data: FormData) => {
14+
const giftCertificateThemes = [
15+
'GENERAL',
16+
'BIRTHDAY',
17+
'BOY',
18+
'CELEBRATION',
19+
'CHRISTMAS',
20+
'GIRL',
21+
'NONE',
22+
] as const;
23+
24+
const GiftCertificateThemeSchema = z.enum(giftCertificateThemes);
25+
26+
const ValidatedFormDataSchema = z.object({
27+
theme: GiftCertificateThemeSchema,
28+
amount: z.number().positive(),
29+
senderEmail: z.string().email(),
30+
senderName: z.string().min(1),
31+
recipientEmail: z.string().email(),
32+
recipientName: z.string().min(1),
33+
message: z.string().nullable(),
34+
});
35+
36+
type ValidatedFormData = z.infer<typeof ValidatedFormDataSchema>;
37+
38+
const CartResponseSchema = z.object({
39+
status: z.enum(['success', 'error']),
40+
data: z.unknown().optional(),
41+
error: z.string().optional(),
42+
});
43+
44+
type CartResponse = z.infer<typeof CartResponseSchema>;
45+
46+
function parseFormData(data: FormData): ValidatedFormData {
47+
const theme = data.get('theme');
48+
const amount = data.get('amount');
49+
const senderEmail = data.get('senderEmail');
50+
const senderName = data.get('senderName');
51+
const recipientEmail = data.get('recipientEmail');
52+
const recipientName = data.get('recipientName');
53+
const message = data.get('message');
54+
55+
// Parse and validate the form data
56+
const validatedData = ValidatedFormDataSchema.parse({
57+
theme,
58+
amount: amount ? Number(amount) : undefined,
59+
senderEmail,
60+
senderName,
61+
recipientEmail,
62+
recipientName,
63+
message: message ? String(message) : null,
64+
});
65+
66+
return validatedData;
67+
}
68+
69+
export async function addGiftCertificateToCart(data: FormData): Promise<CartResponse> {
1670
const format = await getFormatter();
1771
const t = await getTranslations('GiftCertificate.Actions.AddToCart');
1872

19-
let theme = String(data.get('theme')) as giftCertificateTheme;
20-
const amount = Number(data.get('amount'));
21-
const senderEmail = String(data.get('senderEmail'));
22-
const senderName = String(data.get('senderName'));
23-
const recipientEmail = String(data.get('recipientEmail'));
24-
const recipientName = String(data.get('recipientName'));
25-
const message = data.get('message') ? String(data.get('message')) : null;
26-
27-
if (!GIFT_CERTIFICATE_THEMES.includes(theme)) {
28-
theme = 'GENERAL'
29-
}
30-
31-
const giftCertificate = {
32-
name: t('certificateName', {
33-
amount: format.number(amount, {
34-
style: 'currency',
35-
currency: 'USD', // TODO: Determine this from the selected currency
36-
})
37-
}),
38-
theme,
39-
amount,
40-
"quantity": 1,
41-
"sender": {
42-
"email": senderEmail,
43-
"name": senderName,
44-
},
45-
"recipient": {
46-
"email": recipientEmail,
47-
"name": recipientName,
48-
},
49-
message,
50-
}
51-
52-
const cartId = cookies().get('cartId')?.value;
53-
let cart;
54-
5573
try {
56-
cart = await getCart(cartId);
74+
const validatedData = parseFormData(data);
75+
76+
const giftCertificate = {
77+
name: t('certificateName', {
78+
amount: format.number(validatedData.amount, {
79+
style: 'currency',
80+
currency: 'USD',
81+
}),
82+
}),
83+
theme: validatedData.theme,
84+
amount: validatedData.amount,
85+
quantity: 1,
86+
sender: {
87+
email: validatedData.senderEmail,
88+
name: validatedData.senderName,
89+
},
90+
recipient: {
91+
email: validatedData.recipientEmail,
92+
name: validatedData.recipientName,
93+
},
94+
message: validatedData.message,
95+
};
96+
97+
const cartId = cookies().get('cartId')?.value;
98+
let cart;
99+
100+
if (cartId) {
101+
cart = await getCart(cartId);
102+
}
57103

58104
if (cart) {
59105
cart = await addCartLineItem(cart.entityId, {
60-
giftCertificates: [
61-
giftCertificate
62-
],
106+
giftCertificates: [giftCertificate],
63107
});
64108

65109
if (!cart?.entityId) {
66-
return { status: 'error', error: t('error') };
110+
return CartResponseSchema.parse({
111+
status: 'error',
112+
error: t('error'),
113+
});
67114
}
68115

69116
revalidateTag(TAGS.cart);
70117

71-
return { status: 'success', data: cart };
118+
return CartResponseSchema.parse({
119+
status: 'success',
120+
data: cart,
121+
});
72122
}
73123

74124
cart = await createCartWithGiftCertificate([giftCertificate]);
75125

76126
if (!cart?.entityId) {
77-
return { status: 'error', error: t('error') };
127+
return CartResponseSchema.parse({
128+
status: 'error',
129+
error: t('error'),
130+
});
78131
}
79132

80133
cookies().set({
@@ -88,12 +141,33 @@ export const addGiftCertificateToCart = async (data: FormData) => {
88141

89142
revalidateTag(TAGS.cart);
90143

91-
return { status: 'success', data: cart };
144+
return CartResponseSchema.parse({
145+
status: 'success',
146+
data: cart,
147+
});
92148
} catch (error: unknown) {
149+
if (error instanceof z.ZodError) {
150+
// Handle validation errors
151+
const errorMessage = error.errors
152+
.map((err) => `${err.path.join('.')}: ${err.message}`)
153+
.join(', ');
154+
155+
return CartResponseSchema.parse({
156+
status: 'error',
157+
error: errorMessage,
158+
});
159+
}
160+
93161
if (error instanceof Error) {
94-
return { status: 'error', error: error.message };
162+
return CartResponseSchema.parse({
163+
status: 'error',
164+
error: error.message,
165+
});
95166
}
96167

97-
return { status: 'error', error: t('error') };
168+
return CartResponseSchema.parse({
169+
status: 'error',
170+
error: t('error'),
171+
});
98172
}
99-
};
173+
}
Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,75 @@
1-
'use server'
1+
'use server';
22

33
import { getTranslations } from 'next-intl/server';
4+
import { z } from 'zod';
45

5-
export async function lookupGiftCertificateBalance(code: string) {
6+
const giftCertificateSchema = z.object({
7+
code: z.string(),
8+
balance: z.string(),
9+
currency_code: z.string(),
10+
});
11+
12+
interface SuccessResponse {
13+
balance: number;
14+
currencyCode: string;
15+
}
16+
17+
interface ErrorResponse {
18+
error: string;
19+
details?: unknown;
20+
}
21+
22+
type LookupResponse = SuccessResponse | ErrorResponse;
23+
24+
export async function lookupGiftCertificateBalance(code: string): Promise<LookupResponse> {
625
const t = await getTranslations('GiftCertificate.Actions.Lookup');
726

827
if (!code) {
9-
return { error: t('noCode') }
28+
return { error: t('noCode') };
1029
}
1130

12-
const apiUrl = `https://api.bigcommerce.com/stores/${process.env.BIGCOMMERCE_STORE_HASH}/v2/gift_certificates`
31+
const apiUrl = `https://api.bigcommerce.com/stores/${process.env.BIGCOMMERCE_STORE_HASH}/v2/gift_certificates`;
1332
const headers = {
1433
'Content-Type': 'application/json',
1534
'X-Auth-Token': process.env.GIFT_CERTIFICATE_V3_API_TOKEN ?? '',
16-
'Accept': 'application/json'
17-
}
35+
Accept: 'application/json',
36+
};
1837

1938
try {
2039
const response = await fetch(`${apiUrl}?limit=1&code=${encodeURIComponent(code)}`, {
2140
method: 'GET',
22-
headers: headers
23-
})
41+
headers,
42+
});
2443

2544
if (response.status === 404 || response.status === 204) {
26-
return { error: t('notFound') }
45+
return { error: t('notFound') };
2746
}
2847

2948
if (!response.ok) {
30-
console.error(`v2 Gift Certificate API responded with status ${response.status}: ${response.statusText}`)
31-
return { error: t('error') }
49+
return { error: t('error') };
50+
}
51+
52+
const parseResult = z.array(giftCertificateSchema).safeParse(await response.json());
53+
54+
if (!parseResult.success) {
55+
return { error: t('error') };
3256
}
3357

34-
const data = await response.json()
35-
36-
if (Array.isArray(data) && data.length > 0 && typeof data[0].balance !== 'undefined') {
37-
// There isn't a way to query the exact code in the v2 Gift Certificate API,
38-
// so we'll loop through the results to make sure it's not a partial match
39-
for (const certificate of data) {
40-
if (certificate.code === code) {
41-
return { balance: parseFloat(data[0].balance), currencyCode: data[0].currency_code }
42-
}
43-
}
44-
45-
// No exact match, so consider it not found
46-
return { error: t('notFound') }
47-
} else {
48-
console.error('Unexpected v2 Gift Certificate API response structure')
49-
return { error: t('error') }
58+
const data = parseResult.data;
59+
const certificate = data.find((cert) => cert.code === code);
60+
61+
if (!certificate) {
62+
return { error: t('notFound') };
5063
}
64+
65+
return {
66+
balance: parseFloat(certificate.balance),
67+
currencyCode: certificate.currency_code,
68+
};
5169
} catch (error) {
52-
console.error('Error checking gift certificate balance:', error)
53-
return { error: t('error') }
70+
return {
71+
error: t('error'),
72+
details: error,
73+
};
5474
}
5575
}

0 commit comments

Comments
 (0)