Skip to content

Commit

Permalink
Feat: starter release v 1.2.0 (#9)
Browse files Browse the repository at this point in the history
- Adds `useLoadScript` hook to hooks, which provides a proper way to inject third party scripts into the body or head without hydration errors (Remix-specific gotcha)
- Removes `BodyScripts` and `HeadScripts` files with `Scripts` file, which utilizes `useLoadScript` to properly inject GTM and prevent hydration errors
- Adds new route file `($locale).api.marketing.tsx`, which sets up API requests to subscribe email and phone numbers to a marketing list. Klaviyo is integrated by default
- Adds `useMarketingListSubscribe` hook to hooks, which uses the api/marketing route to provide front end form logic for the email or phone subscriptions
- Adds `MarketingSignup` to sections, which adds a default form for either email or phone subscriptions to a page
- Adds `ProductReviews` to sections, which can be customized to inject a product’s review widget onto a PDP
- Updates to packages `@shopify/hydrogen`, `@remix-run/dev`, and `@remix-run/eslint-config` to latest versions
- Updates `ProductsSlider` to fetch all products before rendering product items to eliminate edge case of rendering an inactive product
- Adds additional logic server side to prevent duplicate collection filter url parameters from showing up in the filters summary
- Updates `DataLayer` files and hooks to be able to know to either send out events as Elevar, GA4 or event listeners (Fueled), based on the presence (or absence) of specific env vars
- Updates data layer product objects to include a `collections` array
- Adds currency symbol support for mulit-range slider for collection filter price range
- Prioritizes alt text set in media manager over alt text set locally in the section
- Updates description on all customizer alt text fields regarding prioritization
- Fixes styling issue of cart totals section on cart page on desktop view
- Fixes incorrect style class preventing filters drawer from opening on tablet viewport
- Swaps instances of `typeof window !== ‘undefined’` with `typeof document !== ‘undefined`, per Remix documentation
- Adds `heading` to tailwind config to separate body and heading font faces by default
  • Loading branch information
jeremyagabriel authored Apr 5, 2024
1 parent 00bd580 commit 277cc65
Show file tree
Hide file tree
Showing 96 changed files with 1,510 additions and 513 deletions.
2 changes: 1 addition & 1 deletion app/components/Cart/CartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function CartPage() {
</div>

{hasCartLines && (
<div className="flex flex-col md:gap-4">
<div className="flex flex-col overflow-hidden md:gap-4">
<div className="[&>div]:max-md:border-t-0 [&>div]:md:rounded [&>div]:md:border [&>div]:md:border-border">
<CartTotals settings={settings} />
</div>
Expand Down
12 changes: 11 additions & 1 deletion app/components/Collection/Collection.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import {useMemo, useState} from 'react';
import {useSiteSettings} from '@pack/react';
import {flattenConnection} from '@shopify/hydrogen';

import type {SiteSettings} from '~/lib/types';
import {useColorSwatches, useDataLayerViewCollection} from '~/hooks';
import {
useColorSwatches,
useDataLayerViewCollection,
useDataLayerViewSearchResults,
} from '~/hooks';

import {
CollectionDesktopFilters,
Expand Down Expand Up @@ -48,6 +53,11 @@ export function Collection({
useDataLayerViewCollection({
collection: isSearchResults ? null : collection,
});
useDataLayerViewSearchResults({
isSearchPage: isSearchResults,
products: flattenConnection(products),
searchTerm,
});

return (
<CollectionFiltersProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function CollectionFilterOption({
<MultiRangeSlider
key={`${currentMin}-${currentMax}`}
canReset={isActive}
isPrice
min={defaultMin}
minValue={currentMin}
max={defaultMax}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {ChangeEvent} from 'react';
import {useCallback, useEffect, useState, useRef} from 'react';
import {useCallback, useEffect, useMemo, useState, useRef} from 'react';

import {useLocale} from '~/hooks';

interface MultiRangeSliderProps {
min: number;
Expand All @@ -19,6 +21,7 @@ export const MultiRangeSlider = ({
minValue,
max,
maxValue,
isPrice,
canReset = false,
onChange,
onReset,
Expand All @@ -30,6 +33,12 @@ export const MultiRangeSlider = ({
const minValRef = useRef<HTMLInputElement>(null);
const maxValRef = useRef<HTMLInputElement>(null);
const range = useRef<HTMLDivElement>(null);
const locale = useLocale();

const currencySymbol = useMemo(() => {
if (!isPrice) return '';
return locale.label?.split(' ').pop()?.split(')')[0].trim() || '';
}, [isPrice, locale.label]);

// Convert to percentage
const getPercent = useCallback(
Expand Down Expand Up @@ -117,8 +126,14 @@ export const MultiRangeSlider = ({
className="absolute z-[2] h-1 rounded-[3px] bg-black"
></div>
<div className="absolute left-0 mt-5 flex w-full justify-between text-sm text-text">
<p>{minVal}</p>
<p>{maxVal}</p>
<p>
{isPrice ? currencySymbol : ''}
{minVal}
</p>
<p>
{isPrice ? currencySymbol : ''}
{maxVal}
</p>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function CollectionMobileFilters({
return (
<Drawer
ariaName="cart drawer"
className="sm:hidden"
className="md:hidden"
heading="Filters"
onClose={() => setMobileFiltersOpen(false)}
open={mobileFiltersOpen}
Expand Down
4 changes: 2 additions & 2 deletions app/components/Collection/CollectionGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function CollectionGrid({
const productNodes = nodes as Product[];
return (
<div className="flex flex-col gap-4">
<PreviousLink className={`btn-pill self-center`}>
<PreviousLink className={`btn-pill relative self-center`}>
{isLoading && (
<LoadingDots
status="Loading previous products"
Expand Down Expand Up @@ -84,7 +84,7 @@ export function CollectionGrid({
})}
</ul>

<NextLink className={`btn-pill flex self-center`}>
<NextLink className={`btn-pill relative flex self-center`}>
{isLoading && (
<LoadingDots
status="Loading more products"
Expand Down
2 changes: 1 addition & 1 deletion app/components/Collection/CollectionPromoTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function CollectionPromoTile({tile}: CollectionPromoTileProps) {
{image?.src && !videoSrc && (
<Image
data={{
altText: alt || image?.altText,
altText: image.altText || alt,
url: image.src,
width: image.width,
height: image.height,
Expand Down
85 changes: 57 additions & 28 deletions app/components/DataLayer/DataLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,99 +7,128 @@ import {
useDataLayerCart,
useDataLayerCollection,
useDataLayerCustomer,
useDataLayerEvent,
useDataLayerInit,
useDataLayerProduct,
useDataLayerSearch,
useDataLayerSubscribe,
} from './hooks';

// Envs to set:
// * PUBLIC_GA4_TAG_ID // enables GA4 analytics, e.g. G-XXXXXXXXXX
/*
* Env to set, only if applicable:
* PUBLIC_ELEVAR_SIGNING_KEY // enables Elevar data layer, e.g. 1234567890
* --> from the url within the `fetch()` call in the script, take the unique string of characters in between `/configs/` and `/config.json`
* PUBLIC_GA4_TAG_ID // enables GA4 analytics, e.g. G-XXXXXXXXXX
*/

const DEBUG = true;

export function DataLayer() {
const {ENV} = useRootLoaderData();
const {currency: currencyCode} = useLocale();
const {handleDataLayerEvent} = useDataLayerEvent({DEBUG, ENV});

const {generateUserProperties, userProperties} = useDataLayerInit({
DEBUG,
handleDataLayerEvent,
});

const {userDataEvent, userDataEventTriggered} = useDataLayerCustomer({
currencyCode,
DEBUG,
handleDataLayerEvent,
userProperties,
});

useDataLayerAccount({
currencyCode,
DEBUG,
generateUserProperties,
handleDataLayerEvent,
userDataEvent,
userDataEventTriggered,
});

useDataLayerCart({
currencyCode,
DEBUG,
handleDataLayerEvent,
userDataEvent,
userDataEventTriggered,
userProperties,
});

useDataLayerProduct({
DEBUG,
handleDataLayerEvent,
userDataEvent,
userProperties,
});

useDataLayerCollection({
DEBUG,
handleDataLayerEvent,
userDataEvent,
userDataEventTriggered,
userProperties,
});

useDataLayerSearch({
DEBUG,
handleDataLayerEvent,
userDataEvent,
userDataEventTriggered,
userProperties,
});

useDataLayerSubscribe({
DEBUG,
handleDataLayerEvent,
userDataEventTriggered,
});

return (
<>
{ENV.PUBLIC_GA4_TAG_ID && (
if (ENV?.PUBLIC_ELEVAR_SIGNING_KEY) {
return (
<Script
type="module"
id="elevar-script"
dangerouslySetInnerHTML={{
__html: `try {
const response = await fetch("${`https://shopify-gtm-suite.getelevar.com/configs/${ENV?.PUBLIC_ELEVAR_SIGNING_KEY}/config.json`}");
const config = await response.json();
const scriptUrl = config.script_src_custom_pages;
if (scriptUrl) {
const { handler } = await import(scriptUrl);
await handler(config);
}
} catch (error) {
console.error("Elevar Error:", error);
}`,
}}
/>
);
}

if (ENV?.PUBLIC_GA4_TAG_ID) {
return (
<>
<Script
id="gtag-script"
type="text/javascript"
async
src={`https://www.googletagmanager.com/gtag/js?id=${ENV.PUBLIC_GA4_TAG_ID}`}
/>
)}

<Script
id="gtag-config"
type="text/javascript"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
if (${!!ENV.PUBLIC_GA4_TAG_ID}) {
<Script
id="gtag-config"
type="text/javascript"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${ENV.PUBLIC_GA4_TAG_ID}');
}
`,
}}
/>
</>
);
`,
}}
/>
</>
);
}

return null;
}

DataLayer.displayName = 'DataLayer';
1 change: 1 addition & 0 deletions app/components/DataLayer/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {useDataLayerAccount} from './useDataLayerAccount';
export {useDataLayerCart} from './useDataLayerCart';
export {useDataLayerCollection} from './useDataLayerCollection';
export {useDataLayerCustomer} from './useDataLayerCustomer';
export {useDataLayerEvent} from './useDataLayerEvent';
export {useDataLayerInit} from './useDataLayerInit';
export {useDataLayerProduct} from './useDataLayerProduct';
export {useDataLayerSearch} from './useDataLayerSearch';
Expand Down
21 changes: 6 additions & 15 deletions app/components/DataLayer/hooks/useDataLayerAccount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useCallback, useEffect, useState} from 'react';
import {v4 as uuidv4} from 'uuid';
import type {
CurrencyCode,
Customer,
Expand All @@ -9,14 +8,14 @@ import {useCustomer, useGlobal} from '~/hooks';

export function useDataLayerAccount({
currencyCode,
DEBUG,
generateUserProperties,
handleDataLayerEvent,
userDataEvent,
userDataEventTriggered,
}: {
currencyCode?: CurrencyCode | undefined;
DEBUG?: boolean;
generateUserProperties: (arg0: any) => any;
handleDataLayerEvent: (event: Record<string, any>) => void;
userDataEvent: (arg0: any) => void;
userDataEventTriggered: boolean;
}) {
Expand All @@ -28,27 +27,19 @@ export function useDataLayerAccount({

const loggedInEvent = useCallback(({userProperties}: any) => {
const event = {
event: 'login',
event_id: uuidv4(),
event_time: new Date().toISOString(),
event: 'dl_login',
user_properties: userProperties,
};

if (window.gtag) window.gtag('event', event.event, event);
if (DEBUG) console.log(`DataLayer:gtag:${event.event}`, event);
handleDataLayerEvent(event);
setLoggedIn(false);
}, []);

const registeredEvent = useCallback(({userProperties}: any) => {
const event = {
event: 'sign_up',
event_id: uuidv4(),
event_time: new Date().toISOString(),
event: 'dl_sign_up',
user_properties: userProperties,
};

if (window.gtag) window.gtag('event', event.event, event);
if (DEBUG) console.log(`DataLayer:gtag:${event.event}`, event);
handleDataLayerEvent(event);
setRegistered(false);
}, []);

Expand Down
Loading

0 comments on commit 277cc65

Please sign in to comment.