feat(analytics): added shopify analytics

This commit is contained in:
oybek 2024-02-09 19:33:54 +04:00
parent 610b0e8692
commit 852c911ac8
8 changed files with 131 additions and 8 deletions

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
.idea

View File

@ -3,6 +3,7 @@ import { GeistSans } from 'geist/font';
import { ensureStartsWith } from 'lib/utils'; import { ensureStartsWith } from 'lib/utils';
import { ReactNode, Suspense } from 'react'; import { ReactNode, Suspense } from 'react';
import './globals.css'; import './globals.css';
import ShopifyAnalytics from 'components/layout/shopify-analytics';
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env; const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
@ -39,6 +40,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
<Suspense> <Suspense>
<main>{children}</main> <main>{children}</main>
</Suspense> </Suspense>
<ShopifyAnalytics />
</body> </body>
</html> </html>
); );

View File

@ -5,7 +5,16 @@ import { addToCart, createCart, getCart, removeFromCart, updateCart } from 'lib/
import { revalidateTag } from 'next/cache'; import { revalidateTag } from 'next/cache';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
export async function addItem(prevState: any, selectedVariantId: string | undefined) { type AddItemResponse = {
cartId?: string;
success: boolean;
message?: string;
};
export async function addItem(
prevState: any,
selectedVariantId: string | undefined
): Promise<AddItemResponse> {
let cartId = cookies().get('cartId')?.value; let cartId = cookies().get('cartId')?.value;
let cart; let cart;
@ -20,14 +29,15 @@ export async function addItem(prevState: any, selectedVariantId: string | undefi
} }
if (!selectedVariantId) { if (!selectedVariantId) {
return 'Missing product variant ID'; return { success: false, message: 'Missing variant ID' };
} }
try { try {
await addToCart(cartId, [{ merchandiseId: selectedVariantId, quantity: 1 }]); await addToCart(cartId, [{ merchandiseId: selectedVariantId, quantity: 1 }]);
revalidateTag(TAGS.cart); revalidateTag(TAGS.cart);
return { success: true };
} catch (e) { } catch (e) {
return 'Error adding item to cart'; return { success: false, message: 'Error adding item to cart' };
} }
} }
@ -41,6 +51,10 @@ export async function removeItem(prevState: any, lineId: string) {
try { try {
await removeFromCart(cartId, [lineId]); await removeFromCart(cartId, [lineId]);
revalidateTag(TAGS.cart); revalidateTag(TAGS.cart);
return {
success: true,
cartId
};
} catch (e) { } catch (e) {
return 'Error removing item from cart'; return 'Error removing item from cart';
} }

View File

@ -7,6 +7,8 @@ import LoadingDots from 'components/loading-dots';
import { ProductVariant } from 'lib/shopify/types'; import { ProductVariant } from 'lib/shopify/types';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useFormState, useFormStatus } from 'react-dom'; import { useFormState, useFormStatus } from 'react-dom';
import { useEffect } from 'react';
import { useShopifyAnalytics } from '../../lib/shopify/hooks/use-shopify-analytics';
function SubmitButton({ function SubmitButton({
availableForSale, availableForSale,
@ -70,7 +72,8 @@ export function AddToCart({
variants: ProductVariant[]; variants: ProductVariant[];
availableForSale: boolean; availableForSale: boolean;
}) { }) {
const [message, formAction] = useFormState(addItem, null); const { sendAddToCart } = useShopifyAnalytics();
const [response, formAction] = useFormState(addItem, null);
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined; const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
const variant = variants.find((variant: ProductVariant) => const variant = variants.find((variant: ProductVariant) =>
@ -81,12 +84,22 @@ export function AddToCart({
const selectedVariantId = variant?.id || defaultVariantId; const selectedVariantId = variant?.id || defaultVariantId;
const actionWithVariant = formAction.bind(null, selectedVariantId); const actionWithVariant = formAction.bind(null, selectedVariantId);
useEffect(() => {
if (response?.success && response.cartId) {
sendAddToCart({
cartId: response.cartId
});
}
}, [response?.success, response?.cartId, sendAddToCart]);
return ( return (
<form action={actionWithVariant}> <form action={actionWithVariant}>
<SubmitButton availableForSale={availableForSale} selectedVariantId={selectedVariantId} /> <SubmitButton availableForSale={availableForSale} selectedVariantId={selectedVariantId} />
<p aria-live="polite" className="sr-only" role="status"> {response?.message && (
{message} <p aria-live="polite" className="sr-only" role="status">
</p> {response.message}
</p>
)}
</form> </form>
); );
} }

View File

@ -0,0 +1,13 @@
'use client';
import { useEffect } from 'react';
import { AnalyticsEventName } from '@shopify/hydrogen-react';
import { useShopifyAnalytics } from 'lib/shopify/hooks/use-shopify-analytics';
export default function ShopifyAnalytics() {
const { sendPageView, pathname } = useShopifyAnalytics();
useEffect(() => {
sendPageView(AnalyticsEventName.PAGE_VIEW);
}, [pathname, sendPageView]);
return null;
}

View File

@ -29,3 +29,8 @@ export const TAGS = {
export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden'; export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden';
export const DEFAULT_OPTION = 'Default Title'; export const DEFAULT_OPTION = 'Default Title';
export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2023-01/graphql.json'; export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2023-01/graphql.json';
export const currency = 'AED';
// use your logic to get language
export const defaultLanguage = 'EN';

View File

@ -0,0 +1,68 @@
import { usePathname } from 'next/navigation';
import {
AnalyticsEventName,
getClientBrowserParameters,
sendShopifyAnalytics,
ShopifyAnalyticsProduct,
ShopifyPageViewPayload,
ShopifySalesChannel,
useShopifyCookies
} from '@shopify/hydrogen-react';
import { currency, defaultLanguage } from 'lib/constants';
const SHOP_ID = process.env.NEXT_PUBLIC_SHOPIFY_SHOP_ID!;
type SendPageViewPayload = {
pageType?: string;
products?: ShopifyAnalyticsProduct[];
collectionHandle?: string;
searchString?: string;
totalValue?: number;
cartId?: string;
};
type SendAddToCartPayload = {
cartId: string;
products?: ShopifyAnalyticsProduct[];
totalValue?: ShopifyPageViewPayload['totalValue'];
};
export function useShopifyAnalytics() {
const pathname = usePathname();
// send page view event
const sendPageView = (
eventName: keyof typeof AnalyticsEventName,
payload?: SendPageViewPayload
) =>
sendShopifyAnalytics({
eventName,
payload: {
...getClientBrowserParameters(),
hasUserConsent: true,
shopifySalesChannel: ShopifySalesChannel.headless,
shopId: `gid://shopify/Shop/${SHOP_ID}`,
currency,
acceptedLanguage: defaultLanguage,
...payload
}
});
// send add to cart event
const sendAddToCart = ({ cartId, totalValue, products }: SendAddToCartPayload) =>
sendPageView(AnalyticsEventName.ADD_TO_CART, {
cartId,
totalValue,
products
});
// setup cookies for shopify analytics & enable user consent
useShopifyCookies({
hasUserConsent: true
});
return {
sendPageView,
sendAddToCart,
pathname
};
}

View File

@ -2,7 +2,7 @@ import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/cons
import { isShopifyError } from 'lib/type-guards'; import { isShopifyError } from 'lib/type-guards';
import { ensureStartsWith } from 'lib/utils'; import { ensureStartsWith } from 'lib/utils';
import { revalidateTag } from 'next/cache'; import { revalidateTag } from 'next/cache';
import { headers } from 'next/headers'; import { cookies, headers } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { import {
addToCartMutation, addToCartMutation,
@ -214,12 +214,19 @@ export async function addToCart(
cartId: string, cartId: string,
lines: { merchandiseId: string; quantity: number }[] lines: { merchandiseId: string; quantity: number }[]
): Promise<Cart> { ): Promise<Cart> {
// get shopify cookies
const shopifyY = cookies()?.get('_shopify_y')?.value;
const shopifyS = cookies()?.get('_shopify_s')?.value;
const res = await shopifyFetch<ShopifyAddToCartOperation>({ const res = await shopifyFetch<ShopifyAddToCartOperation>({
query: addToCartMutation, query: addToCartMutation,
variables: { variables: {
cartId, cartId,
lines lines
}, },
headers: {
...(shopifyY && shopifyS && { cookie: `_shopify_y=${shopifyY}; _shopify_s=${shopifyS};` })
},
cache: 'no-store' cache: 'no-store'
}); });
return reshapeCart(res.body.data.cartLinesAdd.cart); return reshapeCart(res.body.data.cartLinesAdd.cart);