Skip to content

Commit d689cf8

Browse files
authored
Add initial components and dependencies for the GA4 ecommerce demo (#824)
* 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
1 parent 56e2f31 commit d689cf8

16 files changed

Lines changed: 733 additions & 0 deletions

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
src/images/*
22
**/*.json
3+
**/*.css
34
**/*.lock
45
lib/build/*
56
node_modules/

gatsby-browser.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
import * as React from "react"
2+
import {StoreProvider} from "./src/components/ga4/EnhancedEcommerce/store-context"
13
import CustomLayout from "./gatsby/wrapRootElement.js"
4+
import "./src/styles/ecommerce/variables.css"
25

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

69
export { onInitialClientRender } from "./gatsby/onInitialClientRender"
710
export const wrapPageElement = CustomLayout
11+
12+
// Wrap every page using a StoreProvider object used by eCommerce demo.
13+
export const wrapRootElement = ({ element }) => (
14+
<StoreProvider>{element}</StoreProvider>
15+
)

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"gatsby-plugin-typescript": "^3.4.0",
3030
"gatsby-plugin-use-query-params": "^1.0.1",
3131
"gatsby-source-filesystem": "^3.4.0",
32+
"gatsby-transformer-json": "^3.12.0",
3233
"gatsby-transformer-sharp": "^3.4.0",
3334
"immutable": "^4.0.0-rc.12",
3435
"js-base64": "^3.6.1",
@@ -38,9 +39,11 @@
3839
"react-dom": "^17.0.2",
3940
"react-error-boundary": "^3.1.3",
4041
"react-helmet": "^6.1.0",
42+
"react-icons": "^4.2.0",
4143
"react-json-view": "^1.21.3",
4244
"react-loader-spinner": "^4.0.0",
4345
"react-redux": "^7.2.4",
46+
"react-router-dom": "^6.0.2",
4447
"react-syntax-highlighter": "^15.4.3",
4548
"redux": "^4.0.5",
4649
"use-debounce": "^6.0.0",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.cartButton {
2+
color: var(--text-color-secondary);
3+
grid-area: cartButton;
4+
width: var(--size-input);
5+
height: var(--size-input);
6+
display: flex;
7+
justify-content: center;
8+
align-items: center;
9+
position: relative;
10+
align-self: center;
11+
}
12+
13+
.cartButton:hover {
14+
color: var(--text-color);
15+
}
16+
17+
.badge {
18+
display: flex;
19+
align-items: center;
20+
justify-content: center;
21+
background-color: var(--primary);
22+
box-shadow: 0 0 0 2px white;
23+
color: var(--text-color-inverted);
24+
font-size: var(--text-xs);
25+
font-weight: var(--bold);
26+
border-radius: var(--radius-rounded);
27+
position: absolute;
28+
bottom: 4px;
29+
right: 4px;
30+
height: 16px;
31+
min-width: 16px;
32+
padding: 0 var(--space-sm);
33+
}
34+
35+
.cartButton[aria-current="page"] {
36+
color: var(--primary);
37+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from "react"
2+
import {Link} from "gatsby"
3+
import {badge, cartButton} from "./cart-button.module.css"
4+
import {MdShoppingCart} from 'react-icons/md';
5+
import IconButton from "@material-ui/core/IconButton"
6+
7+
export function CartButton({quantity}) {
8+
return (
9+
<Link
10+
aria-label={`Shopping Cart with ${quantity} items`}
11+
to="/ga4/enhanced-ecommerce/cart"
12+
className={cartButton}
13+
>
14+
<IconButton>
15+
<MdShoppingCart/>
16+
</IconButton>
17+
{quantity > 0 && <div className={badge}>{quantity}</div>}
18+
</Link>
19+
)
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.footerStyle {
2+
margin-top: 100px;
3+
}
4+
5+
.gaConsole {
6+
padding: var(--size-gutter-raw);
7+
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as React from "react"
2+
import {footerStyle, gaConsole} from "./footer.module.css"
3+
import {GaConsole} from "@/components/ga4/EnhancedEcommerce/ga-console";
4+
5+
export function Footer() {
6+
return (
7+
<footer className={footerStyle}>
8+
<GaConsole className={gaConsole}/>
9+
</footer>
10+
)
11+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
.gaConsoleStyle {
2+
align-self: stretch;
3+
height: 100px;
4+
align-items: center;
5+
background: black;
6+
color: white;
7+
overflow: auto;
8+
padding: var(--size-gap) var(--size-gutter);
9+
position: fixed;
10+
bottom: 0;
11+
width: 80%;
12+
opacity: 0.7;
13+
}
14+
15+
.emptyEvents {
16+
text-align: center;
17+
font-weight: var(--medium);
18+
}
19+
20+
.eventLine {
21+
display: flex;
22+
flex-direction: row;
23+
white-space: nowrap;
24+
}
25+
26+
.eventTimestamp {
27+
padding-right: var(--space-md);
28+
}
29+
30+
.eventName {
31+
padding-right: var(--space-md);
32+
font-weight: var(--semibold);
33+
}
34+
35+
.eventDescription {
36+
padding-right: var(--space-md);
37+
}
38+
39+
.eventSnippet {
40+
white-space: nowrap;
41+
overflow: hidden;
42+
text-overflow: ellipsis;
43+
font-style: italic;
44+
color: var(--grey-50);
45+
text-decoration: dotted underline;
46+
cursor: pointer;
47+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import * as React from "react"
2+
import {StoreContext} from "./store-context"
3+
import {emptyEvents, eventDescription, eventLine, eventName, eventSnippet, eventTimestamp, gaConsoleStyle} from "@/components/ga4/EnhancedEcommerce/ga-console.module.css";
4+
import Dialog from '@material-ui/core/Dialog';
5+
import Tabs from '@material-ui/core/Tabs';
6+
import Tab from '@material-ui/core/Tab';
7+
import DialogActions from '@material-ui/core/DialogActions';
8+
import DialogContent from '@material-ui/core/DialogContent';
9+
import DialogContentText from '@material-ui/core/DialogContentText';
10+
import DialogTitle from '@material-ui/core/DialogTitle';
11+
import Button from '@material-ui/core/Button';
12+
import TextField from '@material-ui/core/TextField';
13+
import {Link} from "gatsby";
14+
import {Box, Typography} from "@material-ui/core";
15+
16+
function TabPanel(props) {
17+
const {children, value, index, ...other} = props;
18+
19+
return (
20+
<div
21+
role="tabpanel"
22+
hidden={value !== index}
23+
id={`simple-tabpanel-${index}`}
24+
{...other}
25+
>
26+
{value === index && (
27+
<Box p={3}>
28+
<Typography>{children}</Typography>
29+
</Box>
30+
)}
31+
</div>
32+
);
33+
}
34+
35+
export function GaConsole({className}) {
36+
const {events} = React.useContext(StoreContext)
37+
const [open, setOpen] = React.useState(false);
38+
39+
const [selectedEvent, setSelectedEvent] = React.useState({
40+
key: 0,
41+
timestamp: '',
42+
name: '',
43+
description: '',
44+
snippet: ''
45+
});
46+
47+
const [value, setValue] = React.useState(0);
48+
49+
const handleClickOpen = (eventKey) => () => {
50+
setSelectedEvent(events[events.length - eventKey - 1])
51+
setOpen(true);
52+
};
53+
const handleClose = () => {
54+
setOpen(false);
55+
};
56+
const handleChange = (event, newValue) => {
57+
setValue(newValue);
58+
};
59+
60+
return (
61+
<div className={[gaConsoleStyle, className].join(" ")}>
62+
{events.length ? events.map((event) => (
63+
<div key={event.key} className={eventLine}>
64+
<div className={eventTimestamp}>{event.timestamp}</div>
65+
<div className={eventName}>
66+
<Link
67+
to={`https://developers.google.com/gtagjs/reference/ga4-events#${event.name}`}
68+
aria-label={`GA4 event reference documentation`}
69+
target='_blank'
70+
>
71+
{event.name}
72+
</Link></div>
73+
<div
74+
className={eventDescription}>{event.description}</div>
75+
<div onClick={handleClickOpen(event.key)}
76+
className={eventSnippet}>{event.snippet}</div>
77+
</div>
78+
)) : <div className={emptyEvents}>Start interacting with the store
79+
to see Google Analytics eCommerce events here.</div>}
80+
81+
<Dialog
82+
open={open}
83+
onClose={handleClose}
84+
aria-labelledby="scroll-dialog-title"
85+
aria-describedby="scroll-dialog-description"
86+
>
87+
<DialogTitle id="scroll-dialog-title">Google Analytics eCommerce
88+
event details</DialogTitle>
89+
<DialogContent>
90+
<DialogContentText
91+
tabIndex={-1}
92+
>
93+
<p>{selectedEvent?.timestamp}&nbsp;
94+
<Link
95+
to={`https://developers.google.com/gtagjs/reference/ga4-events#${selectedEvent?.name}`}
96+
aria-label={`GA4 event reference documentation`}
97+
target='_blank'
98+
>
99+
{selectedEvent?.name}
100+
</Link> {selectedEvent?.description}</p>
101+
<Tabs value={value} onChange={handleChange}>
102+
<Tab label="gtag.js Code"/>
103+
<Tab label="Google Tag Manager Code" disabled/>
104+
105+
</Tabs>
106+
<TabPanel index={0}>
107+
Item One
108+
</TabPanel>
109+
<TextField
110+
multiline
111+
defaultValue={selectedEvent?.snippet}
112+
variant="filled"
113+
fullWidth={true}
114+
InputProps={{
115+
readOnly: true,
116+
}}
117+
/>
118+
</DialogContentText>
119+
</DialogContent>
120+
<DialogActions>
121+
<Button color="primary">
122+
Copy
123+
</Button>
124+
<Button onClick={handleClose} color="primary">
125+
Close
126+
</Button>
127+
</DialogActions>
128+
</Dialog>
129+
</div>
130+
)
131+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.container {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
}
6+
7+
.header {
8+
display: grid;
9+
width: 100%;
10+
padding: var(--size-gap) var(--size-gutter);
11+
grid-template-columns: 1fr;
12+
grid-template-areas: "cartButton" "navHeader";
13+
align-items: start;
14+
background-color: var(--background);
15+
}
16+
17+
@media (min-width: 640px) {
18+
.header {
19+
grid-template-columns: 1fr min-content;
20+
grid-template-areas: "navHeader cartButton";
21+
}
22+
}
23+
24+
25+
.nav {
26+
grid-area: navHeader;
27+
align-self: stretch;
28+
}

0 commit comments

Comments
 (0)