Skip to content

Commit

Permalink
Resolve eslint issues
Browse files Browse the repository at this point in the history
  • Loading branch information
becomevocal committed Oct 31, 2024
1 parent 62168e0 commit 290ccf2
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 189 deletions.
27 changes: 17 additions & 10 deletions core/app/[locale]/(default)/cart/_components/cart-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FragmentOf, graphql } from '~/client/graphql';
import { BcImage } from '~/components/bc-image';

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

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

<div className="flex-1">
<div className="flex flex-col gap-2 md:flex-row">
<div className="flex flex-1 flex-col gap-2">
<p className="text-xl font-bold md:text-2xl">{format.number(giftCertificate.amount.value, {
style: 'currency',
currency: currencyCode,
})} Gift Certificate</p>

<p className="text-xl font-bold md:text-2xl">
{format.number(giftCertificate.amount.value, {
style: 'currency',
currency: currencyCode,
})}{' '}
Gift Certificate
</p>

<p className="text-md text-gray-500">{giftCertificate.message}</p>
<p className="text-sm text-gray-500">To: {giftCertificate.recipient.name} ({giftCertificate.recipient.email})</p>
<p className="text-sm text-gray-500">From: {giftCertificate.sender.name} ({giftCertificate.sender.email})</p>
<p className="text-sm text-gray-500">
To: {giftCertificate.recipient.name} ({giftCertificate.recipient.email})
</p>
<p className="text-sm text-gray-500">
From: {giftCertificate.sender.name} ({giftCertificate.sender.email})
</p>

<div className="hidden md:block">
<RemoveGiftCertificate currency={currencyCode} giftCertificate={giftCertificate} />
Expand Down
6 changes: 5 additions & 1 deletion core/app/[locale]/(default)/cart/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ export default async function Cart() {
<CartItem currencyCode={cart.currencyCode} key={product.entityId} product={product} />
))}
{giftCertificates.map((giftCertificate) => (
<CartGiftCertificate currencyCode={cart.currencyCode} key={giftCertificate.name} giftCertificate={giftCertificate} />
<CartGiftCertificate
currencyCode={cart.currencyCode}
giftCertificate={giftCertificate}
key={giftCertificate.name}
/>
))}
</ul>

Expand Down
176 changes: 125 additions & 51 deletions core/app/[locale]/(default)/gift-certificates/_actions/add-to-cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,131 @@
import { revalidateTag } from 'next/cache';
import { cookies } from 'next/headers';
import { getFormatter, getTranslations } from 'next-intl/server';
import { z } from 'zod';

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

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

export const addGiftCertificateToCart = async (data: FormData) => {
const giftCertificateThemes = [
'GENERAL',
'BIRTHDAY',
'BOY',
'CELEBRATION',
'CHRISTMAS',
'GIRL',
'NONE',
] as const;

const GiftCertificateThemeSchema = z.enum(giftCertificateThemes);

const ValidatedFormDataSchema = z.object({
theme: GiftCertificateThemeSchema,
amount: z.number().positive(),
senderEmail: z.string().email(),
senderName: z.string().min(1),
recipientEmail: z.string().email(),
recipientName: z.string().min(1),
message: z.string().nullable(),
});

type ValidatedFormData = z.infer<typeof ValidatedFormDataSchema>;

const CartResponseSchema = z.object({
status: z.enum(['success', 'error']),
data: z.unknown().optional(),
error: z.string().optional(),
});

type CartResponse = z.infer<typeof CartResponseSchema>;

function parseFormData(data: FormData): ValidatedFormData {
const theme = data.get('theme');
const amount = data.get('amount');
const senderEmail = data.get('senderEmail');
const senderName = data.get('senderName');
const recipientEmail = data.get('recipientEmail');
const recipientName = data.get('recipientName');
const message = data.get('message');

// Parse and validate the form data
const validatedData = ValidatedFormDataSchema.parse({
theme,
amount: amount ? Number(amount) : undefined,
senderEmail,
senderName,
recipientEmail,
recipientName,
message: message ? String(message) : null,
});

return validatedData;
}

export async function addGiftCertificateToCart(data: FormData): Promise<CartResponse> {
const format = await getFormatter();
const t = await getTranslations('GiftCertificate.Actions.AddToCart');

let theme = String(data.get('theme')) as giftCertificateTheme;
const amount = Number(data.get('amount'));
const senderEmail = String(data.get('senderEmail'));
const senderName = String(data.get('senderName'));
const recipientEmail = String(data.get('recipientEmail'));
const recipientName = String(data.get('recipientName'));
const message = data.get('message') ? String(data.get('message')) : null;

if (!GIFT_CERTIFICATE_THEMES.includes(theme)) {
theme = 'GENERAL'
}

const giftCertificate = {
name: t('certificateName', {
amount: format.number(amount, {
style: 'currency',
currency: 'USD', // TODO: Determine this from the selected currency
})
}),
theme,
amount,
"quantity": 1,
"sender": {
"email": senderEmail,
"name": senderName,
},
"recipient": {
"email": recipientEmail,
"name": recipientName,
},
message,
}

const cartId = cookies().get('cartId')?.value;
let cart;

try {
cart = await getCart(cartId);
const validatedData = parseFormData(data);

const giftCertificate = {
name: t('certificateName', {
amount: format.number(validatedData.amount, {
style: 'currency',
currency: 'USD',
}),
}),
theme: validatedData.theme,
amount: validatedData.amount,
quantity: 1,
sender: {
email: validatedData.senderEmail,
name: validatedData.senderName,
},
recipient: {
email: validatedData.recipientEmail,
name: validatedData.recipientName,
},
message: validatedData.message,
};

const cartId = cookies().get('cartId')?.value;
let cart;

if (cartId) {
cart = await getCart(cartId);
}

if (cart) {
cart = await addCartLineItem(cart.entityId, {
giftCertificates: [
giftCertificate
],
giftCertificates: [giftCertificate],
});

if (!cart?.entityId) {
return { status: 'error', error: t('error') };
return CartResponseSchema.parse({
status: 'error',
error: t('error'),
});
}

revalidateTag(TAGS.cart);

return { status: 'success', data: cart };
return CartResponseSchema.parse({
status: 'success',
data: cart,
});
}

cart = await createCartWithGiftCertificate([giftCertificate]);

if (!cart?.entityId) {
return { status: 'error', error: t('error') };
return CartResponseSchema.parse({
status: 'error',
error: t('error'),
});
}

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

revalidateTag(TAGS.cart);

return { status: 'success', data: cart };
return CartResponseSchema.parse({
status: 'success',
data: cart,
});
} catch (error: unknown) {
if (error instanceof z.ZodError) {
// Handle validation errors
const errorMessage = error.errors
.map((err) => `${err.path.join('.')}: ${err.message}`)
.join(', ');

return CartResponseSchema.parse({
status: 'error',
error: errorMessage,
});
}

if (error instanceof Error) {
return { status: 'error', error: error.message };
return CartResponseSchema.parse({
status: 'error',
error: error.message,
});
}

return { status: 'error', error: t('error') };
return CartResponseSchema.parse({
status: 'error',
error: t('error'),
});
}
};
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,75 @@
'use server'
'use server';

import { getTranslations } from 'next-intl/server';
import { z } from 'zod';

export async function lookupGiftCertificateBalance(code: string) {
const giftCertificateSchema = z.object({
code: z.string(),
balance: z.string(),
currency_code: z.string(),
});

interface SuccessResponse {
balance: number;
currencyCode: string;
}

interface ErrorResponse {
error: string;
details?: unknown;
}

type LookupResponse = SuccessResponse | ErrorResponse;

export async function lookupGiftCertificateBalance(code: string): Promise<LookupResponse> {
const t = await getTranslations('GiftCertificate.Actions.Lookup');

if (!code) {
return { error: t('noCode') }
return { error: t('noCode') };
}

const apiUrl = `https://api.bigcommerce.com/stores/${process.env.BIGCOMMERCE_STORE_HASH}/v2/gift_certificates`
const apiUrl = `https://api.bigcommerce.com/stores/${process.env.BIGCOMMERCE_STORE_HASH}/v2/gift_certificates`;
const headers = {
'Content-Type': 'application/json',
'X-Auth-Token': process.env.GIFT_CERTIFICATE_V3_API_TOKEN ?? '',
'Accept': 'application/json'
}
Accept: 'application/json',
};

try {
const response = await fetch(`${apiUrl}?limit=1&code=${encodeURIComponent(code)}`, {
method: 'GET',
headers: headers
})
headers,
});

if (response.status === 404 || response.status === 204) {
return { error: t('notFound') }
return { error: t('notFound') };
}

if (!response.ok) {
console.error(`v2 Gift Certificate API responded with status ${response.status}: ${response.statusText}`)
return { error: t('error') }
return { error: t('error') };
}

const parseResult = z.array(giftCertificateSchema).safeParse(await response.json());

if (!parseResult.success) {
return { error: t('error') };
}

const data = await response.json()

if (Array.isArray(data) && data.length > 0 && typeof data[0].balance !== 'undefined') {
// There isn't a way to query the exact code in the v2 Gift Certificate API,
// so we'll loop through the results to make sure it's not a partial match
for (const certificate of data) {
if (certificate.code === code) {
return { balance: parseFloat(data[0].balance), currencyCode: data[0].currency_code }
}
}

// No exact match, so consider it not found
return { error: t('notFound') }
} else {
console.error('Unexpected v2 Gift Certificate API response structure')
return { error: t('error') }
const data = parseResult.data;
const certificate = data.find((cert) => cert.code === code);

if (!certificate) {
return { error: t('notFound') };
}

return {
balance: parseFloat(certificate.balance),
currencyCode: certificate.currency_code,
};
} catch (error) {
console.error('Error checking gift certificate balance:', error)
return { error: t('error') }
return {
error: t('error'),
details: error,
};
}
}
Loading

0 comments on commit 290ccf2

Please sign in to comment.