Skip to content

Commit

Permalink
Add initial components and dependencies for the GA4 ecommerce demo (#824
Browse files Browse the repository at this point in the history
)

* Add the global context object of the GA4 ecommerce demo

* Wrap every page using a StoreProvider object used by eCommerce demo.

* Add CSS variables used by eCommerce demo.

* Add dependencies for eCommerce demo app.

* Add "Go to Cart" button component.

* Add "Navigation bar" component.

* Add "header" component.

* Add "footer" component.

* Add "Google Analytics Console" component which displays all ecommerce events generated by the demo app.

* Do not lint CSS

* Add support for CSS modules

* Address type check errors by introducing interfaces.

* Address type check errors by introducing interfaces.

* add EOF
  • Loading branch information
ikuleshov authored Feb 18, 2022
1 parent 56e2f31 commit d689cf8
Show file tree
Hide file tree
Showing 16 changed files with 733 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
src/images/*
**/*.json
**/*.css
**/*.lock
lib/build/*
node_modules/
8 changes: 8 additions & 0 deletions gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import * as React from "react"
import {StoreProvider} from "./src/components/ga4/EnhancedEcommerce/store-context"
import CustomLayout from "./gatsby/wrapRootElement.js"
import "./src/styles/ecommerce/variables.css"

// TODO - look into making this work like gatsby-node & use typescript for the
// things that are imported/exported.

export { onInitialClientRender } from "./gatsby/onInitialClientRender"
export const wrapPageElement = CustomLayout

// Wrap every page using a StoreProvider object used by eCommerce demo.
export const wrapRootElement = ({ element }) => (
<StoreProvider>{element}</StoreProvider>
)
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"gatsby-plugin-typescript": "^3.4.0",
"gatsby-plugin-use-query-params": "^1.0.1",
"gatsby-source-filesystem": "^3.4.0",
"gatsby-transformer-json": "^3.12.0",
"gatsby-transformer-sharp": "^3.4.0",
"immutable": "^4.0.0-rc.12",
"js-base64": "^3.6.1",
Expand All @@ -38,9 +39,11 @@
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-helmet": "^6.1.0",
"react-icons": "^4.2.0",
"react-json-view": "^1.21.3",
"react-loader-spinner": "^4.0.0",
"react-redux": "^7.2.4",
"react-router-dom": "^6.0.2",
"react-syntax-highlighter": "^15.4.3",
"redux": "^4.0.5",
"use-debounce": "^6.0.0",
Expand Down
37 changes: 37 additions & 0 deletions src/components/ga4/EnhancedEcommerce/cart-button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.cartButton {
color: var(--text-color-secondary);
grid-area: cartButton;
width: var(--size-input);
height: var(--size-input);
display: flex;
justify-content: center;
align-items: center;
position: relative;
align-self: center;
}

.cartButton:hover {
color: var(--text-color);
}

.badge {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--primary);
box-shadow: 0 0 0 2px white;
color: var(--text-color-inverted);
font-size: var(--text-xs);
font-weight: var(--bold);
border-radius: var(--radius-rounded);
position: absolute;
bottom: 4px;
right: 4px;
height: 16px;
min-width: 16px;
padding: 0 var(--space-sm);
}

.cartButton[aria-current="page"] {
color: var(--primary);
}
20 changes: 20 additions & 0 deletions src/components/ga4/EnhancedEcommerce/cart-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from "react"
import {Link} from "gatsby"
import {badge, cartButton} from "./cart-button.module.css"
import {MdShoppingCart} from 'react-icons/md';
import IconButton from "@material-ui/core/IconButton"

export function CartButton({quantity}) {
return (
<Link
aria-label={`Shopping Cart with ${quantity} items`}
to="/ga4/enhanced-ecommerce/cart"
className={cartButton}
>
<IconButton>
<MdShoppingCart/>
</IconButton>
{quantity > 0 && <div className={badge}>{quantity}</div>}
</Link>
)
}
8 changes: 8 additions & 0 deletions src/components/ga4/EnhancedEcommerce/footer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.footerStyle {
margin-top: 100px;
}

.gaConsole {
padding: var(--size-gutter-raw);

}
11 changes: 11 additions & 0 deletions src/components/ga4/EnhancedEcommerce/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from "react"
import {footerStyle, gaConsole} from "./footer.module.css"
import {GaConsole} from "@/components/ga4/EnhancedEcommerce/ga-console";

export function Footer() {
return (
<footer className={footerStyle}>
<GaConsole className={gaConsole}/>
</footer>
)
}
47 changes: 47 additions & 0 deletions src/components/ga4/EnhancedEcommerce/ga-console.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.gaConsoleStyle {
align-self: stretch;
height: 100px;
align-items: center;
background: black;
color: white;
overflow: auto;
padding: var(--size-gap) var(--size-gutter);
position: fixed;
bottom: 0;
width: 80%;
opacity: 0.7;
}

.emptyEvents {
text-align: center;
font-weight: var(--medium);
}

.eventLine {
display: flex;
flex-direction: row;
white-space: nowrap;
}

.eventTimestamp {
padding-right: var(--space-md);
}

.eventName {
padding-right: var(--space-md);
font-weight: var(--semibold);
}

.eventDescription {
padding-right: var(--space-md);
}

.eventSnippet {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-style: italic;
color: var(--grey-50);
text-decoration: dotted underline;
cursor: pointer;
}
131 changes: 131 additions & 0 deletions src/components/ga4/EnhancedEcommerce/ga-console.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import * as React from "react"
import {StoreContext} from "./store-context"
import {emptyEvents, eventDescription, eventLine, eventName, eventSnippet, eventTimestamp, gaConsoleStyle} from "@/components/ga4/EnhancedEcommerce/ga-console.module.css";
import Dialog from '@material-ui/core/Dialog';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import {Link} from "gatsby";
import {Box, Typography} from "@material-ui/core";

function TabPanel(props) {
const {children, value, index, ...other} = props;

return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
{...other}
>
{value === index && (
<Box p={3}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}

export function GaConsole({className}) {
const {events} = React.useContext(StoreContext)
const [open, setOpen] = React.useState(false);

const [selectedEvent, setSelectedEvent] = React.useState({
key: 0,
timestamp: '',
name: '',
description: '',
snippet: ''
});

const [value, setValue] = React.useState(0);

const handleClickOpen = (eventKey) => () => {
setSelectedEvent(events[events.length - eventKey - 1])
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleChange = (event, newValue) => {
setValue(newValue);
};

return (
<div className={[gaConsoleStyle, className].join(" ")}>
{events.length ? events.map((event) => (
<div key={event.key} className={eventLine}>
<div className={eventTimestamp}>{event.timestamp}</div>
<div className={eventName}>
<Link
to={`https://developers.google.com/gtagjs/reference/ga4-events#${event.name}`}
aria-label={`GA4 event reference documentation`}
target='_blank'
>
{event.name}
</Link></div>
<div
className={eventDescription}>{event.description}</div>
<div onClick={handleClickOpen(event.key)}
className={eventSnippet}>{event.snippet}</div>
</div>
)) : <div className={emptyEvents}>Start interacting with the store
to see Google Analytics eCommerce events here.</div>}

<Dialog
open={open}
onClose={handleClose}
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
>
<DialogTitle id="scroll-dialog-title">Google Analytics eCommerce
event details</DialogTitle>
<DialogContent>
<DialogContentText
tabIndex={-1}
>
<p>{selectedEvent?.timestamp}&nbsp;
<Link
to={`https://developers.google.com/gtagjs/reference/ga4-events#${selectedEvent?.name}`}
aria-label={`GA4 event reference documentation`}
target='_blank'
>
{selectedEvent?.name}
</Link> {selectedEvent?.description}</p>
<Tabs value={value} onChange={handleChange}>
<Tab label="gtag.js Code"/>
<Tab label="Google Tag Manager Code" disabled/>

</Tabs>
<TabPanel index={0}>
Item One
</TabPanel>
<TextField
multiline
defaultValue={selectedEvent?.snippet}
variant="filled"
fullWidth={true}
InputProps={{
readOnly: true,
}}
/>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button color="primary">
Copy
</Button>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</div>
)
}
28 changes: 28 additions & 0 deletions src/components/ga4/EnhancedEcommerce/header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
}

.header {
display: grid;
width: 100%;
padding: var(--size-gap) var(--size-gutter);
grid-template-columns: 1fr;
grid-template-areas: "cartButton" "navHeader";
align-items: start;
background-color: var(--background);
}

@media (min-width: 640px) {
.header {
grid-template-columns: 1fr min-content;
grid-template-areas: "navHeader cartButton";
}
}


.nav {
grid-area: navHeader;
align-self: stretch;
}
26 changes: 26 additions & 0 deletions src/components/ga4/EnhancedEcommerce/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react"
import {StoreContext} from "./store-context"
import {CartButton} from "./cart-button"
import {Navigation} from "./navigation"

import {container, header, nav,} from "./header.module.css"

export function Header() {
const {cart} = React.useContext(StoreContext)

const items = cart ? cart : []

const quantity = items.reduce((total, item) => {
return total + item.quantity
}, 0)

return (

<div className={container}>
<header className={header}>
<Navigation className={nav}/>
<CartButton quantity={quantity}/>
</header>
</div>
)
}
36 changes: 36 additions & 0 deletions src/components/ga4/EnhancedEcommerce/navigation.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.navStyle {
display: flex;
flex-direction: row;
align-items: center;
overflow-x: auto;
white-space: nowrap;
font-weight: var(--medium);
}

.navLink {
cursor: pointer;
text-decoration: none;
height: var(--size-input);
display: flex;
color: var(--text-color-secondary);
align-items: center;
padding-left: var(--space-md);
padding-right: var(--space-md);
}

.navLink:hover {
color: var(--text-color);
}

.activeLink,
.navLink[aria-active="page"] {
color: var(--primary);
text-decoration: underline;
text-decoration-thickness: 2px;
text-underline-offset: 4px;
}

.activeLink:hover,
.navLink[aria-active="page"]:hover {
color: var(--primary);
}
Loading

0 comments on commit d689cf8

Please sign in to comment.