gatsby-plugin-google-analytics#trackCustomEvent JavaScript Examples
The following examples show how to use
gatsby-plugin-google-analytics#trackCustomEvent.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: high-risk.js From warsinhk with MIT License | 6 votes |
HighRiskCardItem = ({ node, i18n, t, isActive }) => (
<HighRiskCardContainer
isActive={isActive}
onClick={() =>
trackCustomEvent({
category: "high_risk_list",
action: "click_item",
label: `${node.node.sub_district_zh} | ${node.node.location_zh}`,
})
}
>
<Item node={node.node} i18n={i18n} t={t} />
</HighRiskCardContainer>
)
Example #2
Source File: RandomButton.js From pdxtipjar with MIT License | 6 votes |
RandomButton = ({ text, handleClick }) => {
return (
<button
className="random-button"
onClick={() => {
trackCustomEvent({
category: "Random Button",
action: "Click",
label: text,
});
getSheets()
.then(response => {
const rows = response.slice(1); // Skip header row
const randomInt = getRandomInt(rows.length);
return handleClick(rows[randomInt]);
})
.catch(err => console.log(err));
}}
>
{text}
</button>
);
}
Example #3
Source File: page-layout.js From guitar-book with MIT License | 5 votes |
function handleToggleAll(expanded) {
trackCustomEvent({
category: GA_EVENT_CATEGORY_SIDEBAR,
action: 'Toggle all',
label: expanded ? 'expand' : 'collapse'
});
}
Example #4
Source File: wars-tips.js From warsinhk with MIT License | 5 votes |
WarTipsPage = ({ data, location }) => {
const { t, i18n } = useTranslation()
const [selectedTag, setSelectedTag] = useState(null)
const filterByTags = ({ node }) => {
if (!node.tags || !selectedTag) {
return true
}
return !!node.tags.split(",").find(tag => tag === selectedTag)
}
const getAllTags = edges => {
return _uniq(
_flatten(
edges.map(edge => (edge.node.tags ? edge.node.tags.split(",") : []))
)
)
}
const shorten = str => {
return str ? `${str.substring(0, 50)}...` : ""
}
React.useEffect(() => {
if (location.hash) {
const tag = decodeURIComponent(location.hash.replace(/^#/, ""))
setSelectedTag(tag)
}
}, [location.hash])
return (
<Layout>
<SEO title="WarsTipsPage" />
<Typography variant="h2">{t("wars_tips.title")}</Typography>
{getAllTags(data.allWarsTip.edges).map(tag => (
<Button
key={tag}
size="small"
color={tag === selectedTag ? "secondary" : "primary"}
onClick={evt => {
const tagToSet = tag === selectedTag ? null : tag
setSelectedTag(tagToSet)
window.location.href = `#${tagToSet || ""}`
trackCustomEvent({
category: "wars_tips",
action: "click_tag",
label: tag,
})
evt.stopPropagation()
evt.preventDefault()
}}
>
{`#${tag}`}
</Button>
))}
<InfiniteScroll
list={data.allWarsTip.edges.filter(filterByTags)}
step={{ mobile: 5 }}
Wrapper={ResponsiveWrapper}
onItem={({ node }, index) => (
<CardContainer key={index}>
<MediaCard
imageUrl={node.image_url}
title={node.title}
text={shorten(node.text)}
tags={node.tags ? node.tags.split(",") : []}
sourceDescription={node.source_description}
sourceUrl={node.source_url}
onTagClicked={tag => {
setSelectedTag(tag === selectedTag ? null : tag)
}}
uri={getWarTipPath(i18n.language, node.title)}
/>
</CardContainer>
)}
/>
</Layout>
)
}
Example #5
Source File: MediaCard.js From warsinhk with MIT License | 5 votes |
export function MediaCard(props) {
const {
title,
text,
imageUrl,
sourceDescription,
sourceUrl,
tags,
onTagClicked,
uri,
} = props
const { t } = useTranslation()
return (
<StyledMediaCard>
<>
<Link to={uri}>
<StyledCardMedia image={imageUrl} title="Contemplative Reptile" />
</Link>
<StyledCardContent>
{tags &&
tags.map((tag, index) => (
<Button
key={index}
size="small"
color="primary"
href={sourceUrl}
onClick={evt => {
onTagClicked(tag)
trackCustomEvent({
category: "wars_tips",
action: "click_tag",
label: tag,
})
evt.stopPropagation()
evt.preventDefault()
}}
>
{`#${tag}`}
</Button>
))}
<Link to={uri}>
<Typography gutterBottom variant="h5" component="h2">
{title}
</Typography>
{text && (
<Typography variant="body2" color="textSecondary" component="p">
{text}
</Typography>
)}
</Link>
</StyledCardContent>
</>
<CardActions>
<Button
size="small"
color="primary"
href={sourceUrl}
onClick={() => {
trackCustomEvent({
category: "wars_tips",
action: "click_source",
label: sourceUrl,
})
}}
>
{`${t("wars_tips.source")}${sourceDescription}`}
</Button>
</CardActions>
</StyledMediaCard>
)
}
Example #6
Source File: section-nav.js From webrtc-tutorial with MIT License | 5 votes |
function handleHeadingClick(event) {
trackCustomEvent({
category: 'Section Nav',
action: 'Heading click',
label: event.target.innerText
});
}
Example #7
Source File: FeedbackForm.js From Lambda with MIT License | 5 votes |
FeedbackForm = () => {
const [feedbackGiven, setFeedbackGiven] = useState(false);
if (feedbackGiven) {
return 'Thanks for letting us know!';
} else {
return (
<span>
Is this page useful?
<button
css={[sharedStyles.articleLayout.feedbackButton, {marginLeft: '6px'}]}
aria-label="Yes"
onClick={(e) => {
e.preventDefault();
trackCustomEvent({
category: 'Feedback Button',
action: 'feedback',
label: window.location.pathname,
value: 1,
});
setFeedbackGiven(true);
}}
>
<svg
css={{
transform: 'translateY(0.1em)',
}}
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 81.13 89.76"
>
<path d="M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z" />
</svg>
</button>
<button
css={[sharedStyles.articleLayout.feedbackButton, {marginLeft: '3px'}]}
aria-label="No"
onClick={(e) => {
e.preventDefault();
trackCustomEvent({
category: 'Feedback Button',
action: 'feedback',
label: window.location.pathname,
value: 0,
});
setFeedbackGiven(true);
}}
>
<svg
css={{
transform: 'scale(-1, -1) translateY(-.6em)',
}}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 81.13 89.76"
>
<path d="M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z" />
</svg>
</button>
</span>
);
}
}
Example #8
Source File: hero.js From bartzalewski.com-v2 with MIT License | 5 votes |
export default function Hero() {
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
author
role
}
}
}
`)
const { author, role } = data.site.siteMetadata
return (
<HeroSection id="home">
<Header />
<HeroContainer className="container hero__container">
<div className="container__hero--secondary container--secondary">
<Container className="container--primary">
<Left className="hero__left">
<Greeting className="colored">Hi, I am</Greeting>
<Author>{author}</Author>
<Role className="hero__sub">{role}</Role>
<Description className="hero__desc section__desc">
I specialize in designing, building, shipping, and scaling
beautiful, usable products with blazing-fast efficiency.
</Description>
<Link
href="#projects"
className="btn btn--primary"
onClick={(e) => {
e.preventDefault()
trackCustomEvent({
category: 'See my work Button',
action: 'Click',
label: 'Gatsby Google Analytics See my work Button',
})
}}
>
See my work
</Link>
</Left>
<Right className="hero__right">
<Board />
</Right>
<Image
className="decoration decoration__cross"
src={cross}
alt="cross"
loading="lazy"
/>
<Image
className="decoration decoration__tick"
src={tick}
alt="tick"
loading="lazy"
/>
<Image
className="decoration decoration__circle"
src={circle}
alt="circle"
loading="lazy"
/>
</Container>
</div>
</HeroContainer>
</HeroSection>
)
}
Example #9
Source File: section-nav.js From guitar-book with MIT License | 5 votes |
function handleHeadingClick(event) {
trackCustomEvent({
category: 'Section Nav',
action: 'Heading click',
label: event.target.innerText
});
}
Example #10
Source File: page-layout.js From guitar-book with MIT License | 5 votes |
function handleToggleCategory(label, expanded) {
trackCustomEvent({
category: GA_EVENT_CATEGORY_SIDEBAR,
action: 'Toggle category',
label,
value: Number(expanded)
});
}
Example #11
Source File: CTA.js From website with MIT License | 4 votes |
CTA = props => {
const {
link: linkDefault,
text: textDefault,
align = 'left',
newTab,
iconConfig,
color,
button = false,
showRightArrow = false,
showLeftArrow = false,
typeLayout = '',
buttonSize,
customClick,
buttonDisplay,
fontSize,
buttonGradient,
downloadBrowsers,
eventCategory,
eventLabel,
hubSpotForm,
buttonSecondary,
socialLink,
} = props
const [keyBrowser, setKeyBrowser] = React.useState('chrome')
const isButton = buttonDisplay || button
const defaultIconConfig = { width: '1.5em', height: '0.5em', fill: 'black' }
const icon = { ...defaultIconConfig, fill: color, ...iconConfig }
const isDownloadBrowser = !isEmpty(downloadBrowsers)
const [delayShow, setDelayShow] = React.useState(isDownloadBrowser)
const [showPopup, setShowPopup] = React.useState(false)
let text = textDefault,
link = linkDefault,
label = eventLabel,
lowerBrowserName = lowerCase(browserName),
iconBrowser = ''
if (isDownloadBrowser && keyBrowser && downloadBrowsers[keyBrowser]) {
label = eventLabel.replace('$browser', downloadBrowsers[keyBrowser]?.text)
text = textDefault.replace('$browser', downloadBrowsers[keyBrowser]?.text)
if (['ios', 'android', 'not-supported'].includes(keyBrowser)) {
text = downloadBrowsers[keyBrowser]?.text
}
link = downloadBrowsers[keyBrowser]?.link
iconBrowser = downloadBrowsers[keyBrowser]?.icon
}
const onClosePopup = () => {
setShowPopup(false)
}
const handleCustomClick = e => {
if (hubSpotForm) {
setShowPopup(true)
return
}
if (customClick) {
e.preventDefault()
customClick()
}
if (eventCategory && eventLabel) {
trackCustomEvent({
category: eventCategory,
action: 'Click',
label: label,
})
}
}
React.useEffect(() => {
if (isDownloadBrowser) {
if (
isMobile &&
((isAndroid && downloadBrowsers['android']) ||
(isIOS && downloadBrowsers['ios']))
) {
if (isAndroid && downloadBrowsers['android']) {
setKeyBrowser('android')
} else if (isIOS && downloadBrowsers['ios']) {
setKeyBrowser('ios')
}
} else {
if (typeof navigator?.brave !== 'undefined') {
lowerBrowserName = 'brave'
}
if (downloadBrowsers[lowerBrowserName]) {
setKeyBrowser(lowerBrowserName)
} else if (downloadBrowsers['not-supported']) {
setKeyBrowser('not-supported')
} else {
setKeyBrowser('chrome')
}
}
setDelayShow(false)
}
}, [downloadBrowsers, isDownloadBrowser, lowerBrowserName])
let ele = (
<CTAContainer
className={classnames('ctaModuleContainer', {
socialLink: socialLink,
})}
align={align}
>
<ContentWrapper
to={link}
newTab={newTab || isDownloadBrowser}
color={color}
typeLayout={typeLayout}
onClick={handleCustomClick}
>
{socialLink ? <SocialIcon name={socialLink} /> : null}
<LinkTitle
className={classnames({
[`leftArrow`]: showLeftArrow,
[`rightArrow`]: showRightArrow || socialLink,
})}
>
{showLeftArrow ? <Arrow {...icon} transform={'rotate(180)'} /> : null}
{text}
{showRightArrow || socialLink ? <Arrow {...icon} /> : null}
</LinkTitle>
</ContentWrapper>
</CTAContainer>
)
if (isButton) {
ele = (
<Button
size={buttonSize}
link={link}
text={text}
className={keyBrowser}
newTab={newTab || isDownloadBrowser}
color={buttonSecondary ? 'secondary' : color}
customClick={handleCustomClick}
fontSize={fontSize}
buttonGradient={buttonGradient}
eventCategory={eventCategory}
eventLabel={eventLabel}
iconUrl={!delayShow ? iconBrowser : ''}
iconPosition={['ios', 'android'].includes(keyBrowser) ? 'start' : 'end'}
hide={delayShow}
/>
)
}
if (
isDownloadBrowser &&
!Object.keys(downloadBrowsers).includes(lowerBrowserName) &&
downloadBrowsers['browsers-supported']
) {
ele = (
<BrowserWrapper>
<BrowserInfo>
<BrowserInfoTitle>
{downloadBrowsers['browsers-supported'].text}
</BrowserInfoTitle>
<BrowserInfoDesc>
{downloadBrowsers['browsers-supported'].description}
</BrowserInfoDesc>
</BrowserInfo>
<BrowserList>
{Object.keys(downloadBrowsers).map(key => {
const { link, icon, text } = downloadBrowsers[key]
if (['chrome', 'firefox', 'brave', 'edge'].includes(key)) {
return (
<BrowserItem key={text} to={link} newTab>
<Image src={icon} />
<BrowserName>{text}</BrowserName>
</BrowserItem>
)
}
return null
})}
</BrowserList>
</BrowserWrapper>
)
}
return (
<>
{ele}
{hubSpotForm ? (
<Popup showPopup={showPopup} onClosePopup={onClosePopup}>
{contentfulModuleToComponent({
...hubSpotForm,
})}
</Popup>
) : null}
</>
)
}
Example #12
Source File: hero.js From personal-site with MIT License | 4 votes |
export default function Hero() {
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
author
role
}
}
}
`)
const { author, role } = data.site.siteMetadata
return (
<StyledHero id="home">
<Header />
<div className="container hero__container">
<div className="container__hero--secondary container--secondary">
<StyledContainer className="container--primary">
<p className="colored" data-sal="slide-up" data-sal-easing="ease">
Hi, I am
</p>
<h1 data-sal="slide-up" data-sal-delay="100" data-sal-easing="ease">
{author}
</h1>
<h2
data-sal="slide-up"
data-sal-delay="200"
data-sal-easing="ease"
className="hero__sub"
>
{role}
</h2>
<p
data-sal="slide-up"
data-sal-delay="300"
data-sal-easing="ease"
className="hero__desc section__desc"
>
I specialize in designing, building, shipping, and scaling
beautiful, usable products with blazing-fast efficiency.
</p>
<div
data-sal="slide-up"
data-sal-delay="400"
data-sal-easing="ease"
>
<a
href="#contact"
className="btn btn--primary"
onClick={e => {
e.preventDefault()
trackCustomEvent({
category: "Get in touch Button",
action: "Click",
label: "Gatsby Google Analytics Get in touch Button",
})
}}
>
Get in touch
</a>
</div>
<img
className="decoration decoration__cross"
src={cross}
alt="cross"
loading="lazy"
/>
<img
className="decoration decoration__tick"
src={tick}
alt="tick"
loading="lazy"
/>
<img
className="decoration decoration__circle"
src={circle}
alt="circle"
loading="lazy"
/>
</StyledContainer>
</div>
<Board />
</div>
</StyledHero>
)
}
Example #13
Source File: highRiskMap.js From warsinhk with MIT License | 4 votes |
render() {
const { useHorizontalLayout } = this.state
const {
height,
width,
theme,
fullscreenEnabled,
toggleFullScreen,
} = this.props
return (
<div
style={{
position: "relative",
height,
width,
}}
>
<LeafletStyleOverride />
<div
style={{
position: "absolute",
top: 0,
left: useHorizontalLayout ? 480 : 0,
bottom: useHorizontalLayout ? 0 : height / 2,
right: 0,
zIndex: 0,
}}
>
<div
ref={el => (this.mapContainer = el)}
style={{ width: "100%", height: "100%" }}
/>
{this.state.showLegend && (
<div
style={{
position: "absolute",
background: "rgba(255,255,255,0.9)",
bottom: "64px",
right: 0,
zIndex: 500,
pointerEvents: "none",
}}
>
{this.state.legend}
</div>
)}
<div
style={{
position: "absolute",
bottom: "16px",
right: "48px",
zIndex: 501,
}}
>
<IconButton
color={this.state.showLegend ? "secondary" : "primary"}
onClick={() => {
trackCustomEvent({
category: "high_risk_map",
action: "toggle_legend",
label: this.state.showLegend ? "enable" : "disable",
})
this.setState({ showLegend: !this.state.showLegend })
}}
>
<NotListedLocationIcon />
</IconButton>
</div>
<div
style={{
position: "absolute",
bottom: "16px",
right: 0,
zIndex: 550,
}}
>
<IconButton
color="primary"
onClick={() => {
trackCustomEvent({
category: "high_risk_map",
action: "toggle_fullscreen",
label: fullscreenEnabled ? "enable" : "disable",
})
toggleFullScreen()
}}
>
{fullscreenEnabled ? <FullscreenExitIcon /> : <FullscreenIcon />}
</IconButton>
</div>
</div>
<div
style={{
position: "absolute",
top: useHorizontalLayout ? 56 : height / 2,
left: 0,
width: useHorizontalLayout ? 480 : width,
height: useHorizontalLayout ? height - 56 : height / 2,
backgroundColor: theme.palette.background.paper,
}}
>
<AutoSizer>
{({ width, height }) => (
<List
ref={el => (this.list = el)}
height={height}
overscanRowCount={8}
rowCount={this.props.filteredLocations.length}
rowHeight={this.cache.rowHeight}
rowRenderer={this.rowRenderer}
deferredMeasurementCache={this.cache}
width={width}
scrollToIndex={this.state.scrollToIndex || 0}
scrollToAlignment="start"
activeDataPoint={this.state.activeDataPoint}
/>
)}
</AutoSizer>
</div>
<div
style={
!useHorizontalLayout
? {
position: "absolute",
top: theme.spacing(1),
left: theme.spacing(2),
right: theme.spacing(2),
opacity: 0.96,
}
: {
position: "absolute",
top: 0,
left: 0,
width: 480,
height: 56,
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(1),
paddingLeft: "20px",
paddingRight: "20px",
backgroundColor: theme.palette.background.paper,
}
}
>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "flex-start",
}}
>
<div style={{ flex: 1 }}>{this.props.selectBar}</div>
<DateButton
color={this.props.dateFilterEnabled ? "secondary" : "primary"}
onClick={() => {
trackCustomEvent({
category: "high_risk_map",
action: "click_date_filter",
label: this.props.dateFilterEnabled ? "enable" : "disable",
})
this.setState({ showDatePicker: !this.state.showDatePicker })
}}
>
<DateRangeIcon />
</DateButton>
</div>
{this.state.showDatePicker && this.props.datePicker}
</div>
</div>
)
}
Example #14
Source File: MultiPurposeSearch.js From warsinhk with MIT License | 4 votes |
MultiPurposeSearch = props => {
const {
options,
list,
placeholder,
onListFiltered,
searchKey,
filterWithOr = true,
} = props
const [filters, setFilters] = useState([])
const [histories, setHistories] = useState([])
const sortedOptions = options.map(opt => ({
...opt,
options: sortOptionsWithHistories(opt.options, histories),
}))
const { t, i18n } = useTranslation()
useEffect(() => {
const v = loadFromLocalStorage(searchKey)
if (v) {
setHistories(JSON.parse(v))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const customStyles = {
control: () => ({
display: "flex",
backgroundColor: "#FFF",
border: "1px solid hsl(0, 0%, 80%)",
borderRadius: "6px",
padding: "2px",
}),
placeholder: () => ({
fontSize: "14px",
}),
menu: () => ({
backgroundColor: "#FFF",
zIndex: 999,
}),
multiValue: styles => ({
...styles,
background: "#505096",
color: "#ffffff",
padding: "3px 3px 3px 10px",
borderRadius: "16px",
}),
multiValueLabel: () => ({
fontSize: "14px",
fontWeight: 700,
}),
groupHeading: styles => ({
...styles,
fontVariant: "normal",
fontSize: "14px",
}),
}
return (
<AsyncSelect
styles={customStyles}
closeMenuOnSelect={false}
loadOptions={(input, callback) =>
callback(filterSearchOptions(sortedOptions, input, 5))
}
isMulti
placeholder={placeholder}
noOptionsMessage={() => t("text.not_found")}
defaultOptions={filterSearchOptions(sortedOptions, null, 10)}
onChange={selectedArray => {
trackCustomEvent({
category: searchKey,
action: "search_input",
label: selectedArray ? selectedArray.join(",") : "",
})
if (selectedArray && selectedArray.length > (filters || []).length) {
let historiesToSave = [
...histories,
selectedArray[selectedArray.length - 1],
]
if (historiesToSave.length >= 10) {
historiesToSave.shift()
}
setHistories(historiesToSave)
saveToLocalStorage(searchKey, JSON.stringify(historiesToSave))
onListFiltered(filterValues(i18n, list, selectedArray, filterWithOr))
} else if (selectedArray && selectedArray.length > 0) {
onListFiltered(filterValues(i18n, list, selectedArray, filterWithOr))
} else {
// return whole list if input is empty
onListFiltered(list)
}
setFilters(selectedArray)
}}
/>
)
}
Example #15
Source File: ShareButton.js From warsinhk with MIT License | 4 votes |
function ShareButton(props) {
const [anchorEl, setAnchorEl] = React.useState(null)
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
siteUrl
}
}
}
`
)
function getPageUrl() {
let url = `${site.siteMetadata.siteUrl}${fullPath}`
if (props.caseId) {
url = `${site.siteMetadata.siteUrl}${fullPath}/${props.caseId}`
}
if (!isSSR()) {
url = url + decodeURIComponent(window.location.hash)
}
return url
}
function handleShareButtonClick(event) {
if (!isSSR() && isWebShareAPISupported()) {
const data = getWebShareData(getPageUrl())
if (navigator.canShare(data)) {
navigator.share(data).then(() => {
trackCustomEvent({
category: "general",
action: "click",
label: "share",
})
})
return
}
}
setAnchorEl(event.currentTarget)
}
function handleShareButtonClose(media) {
setAnchorEl(null)
if (typeof media === "string") {
trackCustomEvent({
category: "general",
action: "click",
label: `share_${media}`,
})
}
}
const { pathname: fullPath } = useLocation()
const url = getPageUrl()
return (
<>
<StyledIconButton
color="inherit"
aria-label="Share"
aria-controls="share-menu"
aria-haspopup="true"
onClick={handleShareButtonClick}
>
<ShareIcon />
</StyledIconButton>
<Menu
id="share-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleShareButtonClose}
>
<MenuItem onClick={() => handleShareButtonClose("facebook")}>
<FacebookShareButton
url={getShareUrl(url, "facebook")}
children={<FacebookIcon size={32} round={true} />}
/>
</MenuItem>
<MenuItem onClick={() => handleShareButtonClose("telegram")}>
<TelegramShareButton
url={getShareUrl(url, "telegram")}
children={<TelegramIcon size={32} round={true} />}
/>
</MenuItem>
<MenuItem onClick={() => handleShareButtonClose("whatsapp")}>
<WhatsappShareButton
url={getShareUrl(url, "whatsapp")}
children={<WhatsappIcon size={32} round={true} />}
/>
</MenuItem>
<MenuItem onClick={() => handleShareButtonClose("twitter")}>
<TwitterShareButton
url={getShareUrl(url, "twitter")}
children={<TwitterIcon size={32} round={true} />}
/>
</MenuItem>
<MenuItem onClick={() => handleShareButtonClose("link")}>
<StyledCopyIcon
onClick={() => {
navigator.clipboard.writeText(url)
}}
/>
</MenuItem>
</Menu>
</>
)
}
Example #16
Source File: about-us.js From warsinhk with MIT License | 4 votes |
AboutUsPage = props => {
const { data } = props
const { t } = useTranslation()
const contributors = React.useMemo(
() =>
[]
.concat(data.configJson.credits.contributors)
.sort((a, b) => (a > b ? 1 : -1)),
[data.configJson.credits]
)
const volunteers = React.useMemo(
() =>
[]
.concat(data.configJson.credits.volunteers)
.sort((a, b) => (a.name > b.name ? 1 : -1)),
[data.configJson.credits.volunteers]
)
const designers = React.useMemo(
() =>
[]
.concat(data.configJson.credits.designers)
.sort((a, b) => (a.name > b.name ? 1 : -1)),
[data.configJson.credits.designers]
)
return (
<Layout>
<SEO title="AboutUsPage" />
<SplitWrapper>
<SessionWrapper>
<Typography variant="h2" style={{ marginBottom: 16 }}>
{t("about_us.title")}
</Typography>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_1") }}
/>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_2") }}
/>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_3") }}
/>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.donation") }}
/>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_4") }}
/>
<Button
style={{ marginTop: 8 }}
variant="outlined"
color="primary"
size="small"
startIcon={<FaFacebookF size="0.8rem" />}
href="https://www.facebook.com/vote4hongkong/"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "https://www.facebook.com/vote4hongkong/",
})
}}
>
{t("about_us.vote4hk_fb")}
</Button>
<Grid container spacing={2} style={{ marginTop: 16 }}>
<Grid item md={6}>
<Typography variant="h3" style={{ marginBottom: 8 }}>
{t("about_us.g0vhk_title")}
</Typography>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.g0vhk_1") }}
/>
{/* <Button
component="button"
variant="outlined"
color="primary"
size="small"
startIcon={mapIcon("attach_money")}
href="https://www.collaction.hk/s/g0vhk/fund"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "https://www.collaction.hk/s/g0vhk/fund",
})
}}
>
{t("about_us.donate_g0vhk")}
</Button> */}
</Grid>
<Grid item md={6}>
<Typography variant="h3" style={{ marginBottom: 8 }}>
{t("about_us.sooc_title")}
</Typography>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.sooc_1") }}
/>
{/* <Button
variant="outlined"
color="primary"
size="small"
startIcon={mapIcon("attach_money")}
href="https://www.collaction.hk/s/station/fund"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "https://www.collaction.hk/s/station/fund",
})
}}
>
{t("about_us.donate_sooc")}
</Button> */}
</Grid>
<Grid item xs={12}>
<Typography variant="h3" style={{ marginBottom: 8 }}>
{t("about_us.contact_title")}
</Typography>
<Paragraph
dangerouslySetInnerHTML={{
__html: t("about_us.contact_method"),
}}
/>
</Grid>
</Grid>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.open_source") }}
style={{ marginTop: 16 }}
/>
<Paragraph
dangerouslySetInnerHTML={{ __html: t("about_us.citation") }}
style={{ marginTop: 16 }}
/>
<LinkBox>
<Button
variant="outlined"
color="primary"
size="small"
startIcon={<FaGithubAlt />}
href="https://github.com/nandiheath/warsinhk"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "https://github.com/nandiheath/warsinhk",
})
}}
>
{t("about_us.github")}
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={mapIcon("insert_drive_file")}
href="https://docs.google.com/spreadsheets/d/e/2PACX-1vT6aoKk3iHmotqb5_iHggKc_3uAA901xVzwsllmNoOpGgRZ8VAA3TSxK6XreKzg_AUQXIkVX5rqb0Mo/pub?gid=0&range=A2:ZZ&output=csv"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "high_risk_source_data",
})
}}
>
{t("about_us.high_risk")} 2020
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={mapIcon("insert_drive_file")}
href="https://docs.google.com/spreadsheets/d/e/2PACX-1vQVRg6iiYOHZwLsXdZE6TVWBO7Cldi07NUnbeVY3nI97_IjyG3jiWnjaUS51HRNJI1fN3io1paMa6jZ/pub?gid=0&range=A2:ZZ&output=csv"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "high_risk_source_data",
})
}}
>
{t("about_us.high_risk")} 2021
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={mapIcon("insert_drive_file")}
href="https://docs.google.com/spreadsheets/d/e/2PACX-1vTl_YWJy_osrNeOD0ufyQH4CuWTKCX9ng-tUPpIFXsAdk_ry2uciIt752f9a-yd83IGUtsw2rHQNB0s/pub?gid=0&range=A2:ZZ&output=csv"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "high_risk_source_data",
})
}}
>
{t("about_us.high_risk")} 2022
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={mapIcon("insert_drive_file")}
href="https://docs.google.com/spreadsheets/d/e/2PACX-1vSr2xYotDgnAq6bqm5Nkjq9voHBKzKNWH2zvTRx5LU0jnpccWykvEF8iB_0g7Tzo2pwzkTuM3ETlr_h/pub?gid=0&range=A2:ZZ&output=csv"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "wars_cases_source_data",
})
}}
>
{t("about_us.wars_cases")} 2020
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={mapIcon("insert_drive_file")}
href="https://docs.google.com/spreadsheets/d/e/2PACX-1vT-Xw-QHYydz_kJCJLBqTKGbb2OF8_gisdUsduPbdR6Dp3tLbWxy_mkfRx2tMmGJ0q64uNsLLv3bbfb/pub?gid=0&range=A2:ZZ&output=csv"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "wars_cases_source_data",
})
}}
>
{t("about_us.wars_cases")} 2021
</Button>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={mapIcon("insert_drive_file")}
href="https://docs.google.com/spreadsheets/d/e/2PACX-1vTEJyeLTOgntmUjeRhyEB1w_eFD6BUKAEgkR47pp3yXY_XB3IlF7DstsAA0pHz33h2pzGIxGbvGhjMe/pub?gid=0&range=A2:ZZ&output=csv"
target="_blank"
rel="noopener noreferer"
onClick={() => {
trackCustomEvent({
category: "about_us",
action: "click",
label: "wars_cases_source_data",
})
}}
>
{t("about_us.wars_cases")} 2022
</Button>
</LinkBox>
</SessionWrapper>
<SessionWrapper>
<Typography variant="h2">{t("about_us.volunteers")}</Typography>
<Grid container spacing={1} style={{ marginTop: 8 }}>
{volunteers.map(item => (
<Grid item key={item.id} xs={6} md={4}>
<Volunteer
item={item}
siteUrl={data.site.siteMetadata.siteUrl}
/>
</Grid>
))}
</Grid>
<Typography variant="h2" style={{ marginTop: 16 }}>
{t("about_us.designers")}
</Typography>
<Grid container spacing={1} style={{ marginTop: 8 }}>
{designers.map(item => (
<Grid item key={item.id} xs={6} md={4}>
<Volunteer
item={item}
siteUrl={data.site.siteMetadata.siteUrl}
/>
</Grid>
))}
</Grid>
<Typography variant="h2" style={{ marginTop: 16 }}>
{t("about_us.contributors")}
</Typography>
<Grid container spacing={1} style={{ marginTop: 8 }}>
{contributors.map(item => (
<Grid item xs={6} md={4} key={item}>
<Contributor githubId={item} />
</Grid>
))}
</Grid>
</SessionWrapper>
</SplitWrapper>
</Layout>
)
}
Example #17
Source File: cases.js From warsinhk with MIT License | 4 votes |
CasesPageMain = ({ dispatch, view }) => {
const data = useAllCasesData()
const { i18n, t } = useTranslation()
const patientTrackKeyedByCaseNo = useMemo(
() => _keyBy(data.patient_track.group, "fieldValue"),
[data]
)
// Do the sorting here since case_no is string instead of int
const [cases, groupArrayColumnOptions] = useMemo(() => {
const groupArray = data.allWarsCaseRelation.edges.flatMap(
({ node }, index) =>
node.case_no.split`,`.map(nodeCase => ({
...node,
id: index + 1,
related_cases: node.case_no,
case_no: +nodeCase,
}))
)
const groupArrayByCaseNo = _groupBy(groupArray, "case_no")
const groupArrayColumnOptions = data.allWarsCaseRelation.edges.map(
({ node }, index) => ({
value: index + 1,
label: node[`name_${i18n.language}`],
})
)
const cases = data.allWarsCase.edges
.map(i => ({
node: {
...i.node,
case_no_num: +i.node.case_no,
age_num: +i.node.age,
groups: groupArrayByCaseNo[i.node.case_no] || [],
group_ids: (groupArrayByCaseNo[i.node.case_no] || []).map(i => i.id),
},
}))
.sort((edge1, edge2) => {
const res = edge2.node.confirmation_date.localeCompare(
edge1.node.confirmation_date
)
if (res === 0) {
return parseInt(edge2.node.case_no) - parseInt(edge1.node.case_no)
}
return res
})
return [cases, groupArrayColumnOptions]
}, [data, i18n.language])
const [internalCount, setInternalCounter] = useState(0)
const [filteredCases, setFilteredCases] = useState([])
const [selectedCase, setSelectedCase] = useState(null)
// 1: by date : from latest to oldest
// 2: by date : from oldest to latest
// 3: by area : from greatest to least
// 4: by area : from least to greatest
// 5: by group : from more to less
// 6: by group : from less to more
// 7: by status
const [selectedGroupButton, setGroupButton] = useState(1)
const { pathname } = useLocation()
const caseCodeMatch = pathname.match(/cases\/([^/]+)/)
const toFilterEntry = ([key, value]) => [`node.${key}`, value]
const parseToFilter = str => {
if (/^[-A-Z0-9]+\.\.+[-A-Z0-9]+$/i.test(str))
return { between: str.split(/\.\.+/) }
if (/^[><]=[-A-Z0-9]+$/i.test(str))
return { [str[0] === ">" ? "gte" : "lte"]: str.slice(2, str.length) }
if (/^[><][-A-Z0-9]+$/i.test(str))
return { [str[0] === ">" ? "gt" : "lt"]: str.slice(1, str.length) }
if (/^[-A-Z0-9]+$/i.test(str)) return str
return
}
const dateRangeOptionPresets = [
{
label: t("cases.filters_last_n_days", { n: 7 }),
value: `${moment()
.subtract(6, "day")
.format("YYYY-MM-DD")}..${moment().format(`YYYY-MM-DD`)}`,
},
{
label: t("cases.filters_previous_n_days", { n: 7 }),
value: `${moment()
.subtract(13, "day")
.format("YYYY-MM-DD")}..${moment()
.subtract(7, "day")
.format(`YYYY-MM-DD`)}`,
},
{
label: t("cases.filters_last_n_days", { n: 14 }),
value: `${moment()
.subtract(13, "day")
.format("YYYY-MM-DD")}..${moment().format(`YYYY-MM-DD`)}`,
},
{
label: t("cases.filters_previous_n_days", { n: 14 }),
value: `${moment()
.subtract(27, "day")
.format("YYYY-MM-DD")}..${moment()
.subtract(14, "day")
.format(`YYYY-MM-DD`)}`,
},
{
label: t("cases.filters_this_month"),
value: `${moment().format(`[>]YYYY-MM`)}`,
},
{
label: t("cases.filters_previous_month"),
value: `${moment()
.subtract(1, "month")
.format("YYYY-MM")}..${moment().format(`YYYY-MM`)}`,
},
]
const options = useMemo(() => {
const stringOrFilterEntry = ([key, value]) => {
const filterPhrases = value
.split(/,|\s+/g)
.filter(phase => phase && /\w$/.test(phase))
.map(parseToFilter)
.reduce(
(acc, curr) => {
if (curr) {
curr.constructor === String
? acc.inq.push(curr)
: acc.or.push(curr)
}
return acc
},
{ or: [], inq: [] }
)
return [
filterPhrases.inq.length
? { [`node.${key}`]: { inq: filterPhrases.inq } }
: undefined,
...filterPhrases.or.map(phrase => ({ [`node.${key}`]: phrase })),
].filter(Boolean)
}
return [
{
label: t("search.group"),
options: groupArrayColumnOptions,
orderOptionsByFilterCount: true,
realFieldName: "group_ids",
toFilterEntry,
},
{
label: t("search.classification"),
options: createDedupOptions(i18n, cases, "classification"),
orderOptionsByFilterCount: true,
realFieldName: "classification_" + i18n.language,
toFilterEntry,
},
{
label: t("search.district"),
options: createDedupOptions(i18n, cases, "citizenship_district"),
orderOptionsByFilterCount: true,
realFieldName: "citizenship_district_" + i18n.language,
toFilterEntry,
},
{
label: t("search.citizenship"),
options: createDedupOptions(i18n, cases, "citizenship"),
orderOptionsByFilterCount: true,
realFieldName: "citizenship_" + i18n.language,
toFilterEntry,
},
{
label: t("search.case_status"),
options: createDedupOptions(i18n, cases, "status"),
orderOptionsByFilterCount: true,
realFieldName: "status_" + i18n.language,
toFilterEntry,
},
{
label: t("search.hospital"),
options: createDedupOptions(i18n, cases, "hospital"),
orderOptionsByFilterCount: true,
realFieldName: "hospital_" + i18n.language,
toFilterEntry,
},
{
label: t("search.case_no"),
realFieldName: "case_no_num",
filterType: "string",
options: [],
toFilterEntry: stringOrFilterEntry,
isOrFilter: true,
filterPlaceholder: "e.g. 1,3,10..20",
},
{
label: t("dashboard.patient_confirm_date"),
realFieldName: "confirmation_date",
filterType: "string",
options: dateRangeOptionPresets,
toFilterEntry: stringOrFilterEntry,
isOrFilter: true,
filterPlaceholder: "e.g. 2020-06..2020-07-21",
},
{
label: t("dashboard.patient_onset_date"),
realFieldName: "onset_date",
options: [
{ label: t("cases.status_asymptomatic"), value: "asymptomatic,none" },
...dateRangeOptionPresets,
],
filterType: "string",
toFilterEntry: stringOrFilterEntry,
isOrFilter: true,
filterPlaceholder: "e.g. 2020-06..2020-07-21",
},
{
label: t("cases_visual.age"),
realFieldName: "age_num",
filterType: "string",
options: [],
toFilterEntry: stringOrFilterEntry,
isOrFilter: true,
filterPlaceholder: "e.g. 10..20,>60,>=50",
},
{
label: t("cases_visual.gender"),
realFieldName: "gender",
options: [
{
value: "M",
label: t("dashboard.gender_M"),
},
{
value: "F",
label: t("dashboard.gender_F"),
},
],
toFilterEntry,
},
]
}, [cases, dateRangeOptionPresets, groupArrayColumnOptions, i18n, t])
const onListFiltered = useCallback(data => {
if (data !== filteredCases) {
setFilteredCases(data)
setInternalCounter(i => i + 1)
}
}, [filteredCases])
// Calculate how much cards we should preload in order to scorll to that position
let preloadedCases = cases.length - parseInt(selectedCase) + 1
if (isNaN(preloadedCases)) {
preloadedCases = 15
}
const renderCaseCard = useMemo(() => {
const RenderSingleCaseCard = (node, i) => (
<WarsCaseCard
node={node}
i18n={i18n}
t={t}
key={`${i}-${node.id}`}
// isSelected={selectedCase === item.node.case_no}
// ref={selectedCase === item.node.case_no ? selectedCard : null}
patientTrack={
patientTrackKeyedByCaseNo[node.case_no]
? [patientTrackKeyedByCaseNo[node.case_no]]
: null
}
handleClose={
view === CASES_BOX_VIEW ? e => setSelectedCase(null) : undefined
}
/>
)
return RenderSingleCaseCard
}, [i18n, patientTrackKeyedByCaseNo, t, view])
const Legend = () => {
const items = [
{
icon: <MaleIcon />,
text: t("dashboard.gender_M"),
},
{
icon: <FemaleIcon />,
text: t("dashboard.gender_F"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("discharged").main}
/>
),
text: t("cases.status_discharged"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("pending_admission").main}
/>
),
text: t("cases.status_pending_admission"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("stable").main}
/>
),
text: t("cases.status_stable"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("hospitalised_again").main}
/>
),
text: t("cases.status_hospitalised_again"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("serious").main}
/>
),
text: t("cases.status_serious"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("critical").main}
/>
),
text: t("cases.status_critical"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("deceased").main}
/>
),
text: t("cases.status_deceased"),
},
{
icon: (
<Circle
width={48}
height={48}
bgColor={mapColorForStatus("no_admission").main}
/>
),
text: t("cases.status_no_admission"),
},
{
icon: <ImportIcon />,
text: t("cases.imported"),
},
{
icon: <UnknownIcon />,
text: t("cases.unknown"),
},
]
return (
<Accordion
style={{ marginBottom: 16 }}
title={
<LegendTitle>
<QuestionIcon />
<span>{t("cases.legend")}</span>
</LegendTitle>
}
content={
<LegendContent>
{items.map((item, i) => (
<div key={i} className="item">
{item.icon}
<span>{item.text}</span>
</div>
))}
</LegendContent>
}
/>
)
}
const toggleGroupingButtons = [
"cases.toggle_date",
"cases.toggle_date_reverse",
"cases.toggle_area",
"cases.toggle_area_reverse",
"cases.toggle_group",
"cases.toggle_group_reverse",
"cases.toggle_status",
]
const handleBoxClick = item => {
setSelectedCase(item)
trackCustomEvent({
category: "cases",
action: "click_avatar",
label: item.case_no,
})
}
const isCaseNumberMatch =
caseCodeMatch && filteredCases.length === 1 && filteredCases[0]
return (
<Layout
onClick={e =>
typeof e.target.className === "string" &&
!e.target.className.includes("wars_box") &&
setSelectedCase(null)
}
>
{isCaseNumberMatch ? (
<SEO
titleOveride={t("case.title")}
// TODO: duplicated entries, filter out in SEO later?
meta={[
{
property: `og:title`,
content: `${t("index.title")} | ${t("case.case_no", {
case_no: filteredCases[0].node.case_no,
})}`,
},
{
property: `og:description`,
content: withLanguage(i18n, filteredCases[0].node, "detail"),
},
]}
/>
) : (
<SEO title="ConfirmedCasePage" />
)}
<TitleContainer>
<Typography variant="h2">{t("cases.title")}</Typography>
<span>
<BoxViewIcon
className={view === CASES_BOX_VIEW && "active"}
onClick={() => {
dispatch({
type: CASES_BOX_VIEW,
})
trackCustomEvent({
category: "cases",
action: "toggle_view",
label: "BOX_VIEW",
})
}}
/>
<CardViewIcon
className={view === CASES_CARD_VIEW && "active"}
onClick={() => {
dispatch({
type: CASES_CARD_VIEW,
})
trackCustomEvent({
category: "cases",
action: "toggle_view",
label: "CARD_VIEW",
})
}}
/>
</span>
</TitleContainer>
<PageContent>
<ConfirmedCasesSummary />
{view === CASES_BOX_VIEW && <Legend />}
<Typography variant="h5" style={{ marginTop: 16 }}>
{t("cases.filters")}
</Typography>
<TagStyleFilter
key={pathname}
list={cases}
placeholder={t("search.case_placeholder")}
options={options}
searchKey="case"
onListFiltered={onListFiltered}
filterWithOr={false}
initialFilters={
caseCodeMatch
? [
{
label: caseCodeMatch[1],
filterName: t("search.case_no"),
realFieldName: "case_no_num",
field: "case_no_num",
value: caseCodeMatch[1],
},
]
: []
}
/>
{view === CASES_BOX_VIEW && (
<DefaultSelect
value={selectedGroupButton}
onChange={event => {
setGroupButton(event.target.value)
trackCustomEvent({
category: "cases",
action: "set_grouping",
label: toggleGroupingButtons[event.target.value - 1],
})
}}
displayEmpty
IconComponent={SortIcon}
>
{toggleGroupingButtons.map((groupBy, index) => (
<MenuItem key={index} value={index + 1}>
{t(groupBy)}
</MenuItem>
))}
</DefaultSelect>
)}
<Typography variant="h6" style={{ marginTop: 16 }}>
{filteredCases.length > 1
? t("cases.filter_results_plural", { count: filteredCases.length })
: t("cases.filter_results", { count: filteredCases.length })}
</Typography>
</PageContent>
{view === CASES_BOX_VIEW ? (
<>
<WarsCaseBoxContainer
filteredCases={filteredCases}
handleBoxClick={handleBoxClick}
selectedGroupButton={selectedGroupButton}
/>
{selectedCase && (
<SelectedCardContainer>
{renderCaseCard(selectedCase)}
</SelectedCardContainer>
)}
</>
) : (
<InfiniteScroll
key={internalCount} // The state of this InfiniteScroll Component need to be discarded when filteredCases changes.
list={filteredCases.map(c => c.node)}
step={{ mobile: 5, preload: preloadedCases }}
onItem={renderCaseCard}
Wrapper={ResponsiveWrapper}
/>
)}
</Layout>
)
}
Example #18
Source File: index.js From warsinhk with MIT License | 4 votes |
export default function IndexPage({ data }) {
const { t } = useTranslation()
const [modules, setModules] = React.useState([])
const [showSettings, setShowSettings] = React.useState(false)
// TODO: useMemo to cache the components
const components = {}
const registerComponent = (
key,
titleKey,
component,
{ rowSpan = 1, showTitle = true } = {}
) => {
components[key] = {
id: key,
title: t(titleKey),
component,
rowSpan,
showTitle,
}
}
const renderComponent = (key, data) => {
if (components[key]) {
const Component = components[key].component
return (
<ModuleContainer>
{components[key].showTitle && (
<Typography variant="h2">{components[key].title}</Typography>
)}
<Suspense fallback={<ComponentLoading />}>
<Component data={data} />
</Suspense>
</ModuleContainer>
)
}
return <></>
}
const registerComponents = () => {
registerComponent(
"daily_stat",
"dashboard.latest_figures",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/DailyStats.js"
)
)
)
registerComponent(
"important_information",
"dashboard.important_information",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/ImportantInformation.js"
)
),
{
showTitle: false,
}
)
registerComponent(
"confirmed_chart",
"dashboard.case_highlights_area",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedCaseVisual"
)
),
{
rowSpan: 4,
}
)
registerComponent(
"confirmed_digest_gender",
"dashboard.case_highlights_gender",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedCaseDigestGender"
)
),
{
rowSpan: 2,
}
)
registerComponent(
"passenger_daily",
"dashboard.daily_passengers",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/PassengerDailyFigure"
)
)
)
registerComponent(
"confirmed_digest_age",
"dashboard.case_highlights_age",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedCaseDigestAge"
)
)
)
registerComponent(
"outbound_alert",
"dashboard.outbound_alert",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/OutboundAlert.js"
)
),
{
rowSpan: 3,
showTitle: false,
}
)
registerComponent(
"carousel",
"dashboard.carousel",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/Carousel.js"
)
),
{
showTitle: false,
}
)
registerComponent(
"friendly_links",
"dashboard.friendly_links",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/FriendlyLinks.js"
)
),
{
showTitle: false,
}
)
registerComponent(
"latest_cases",
"dashboard.latest_cases",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/LatestCases.js"
)
),
{
rowSpan: 6,
}
)
registerComponent(
"epidemic_chart",
"dashboard.epidemic_chart",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/EpidemicChart.js"
)
),
{
rowSpan: 4,
}
)
registerComponent(
"isolation_beds",
"dashboard.isolation_beds",
React.lazy(() =>
import(
/* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedLineChart.js"
)
),
{
rowSpan: 2,
}
)
}
const handleModuleChange = id => {
const index = modules.indexOf(id)
if (index >= 0) {
modules.splice(index, 1)
} else {
modules.push(id)
}
setModules([...modules])
saveToLocalStorage(LOCAL_STORAGE_KEY_DASHBOARD, [...modules])
trackCustomEvent({
category: "dashboard",
action: "module_change",
label: modules.join(","),
})
}
// load the settings from localStorage
const LOCAL_STORAGE_KEY_DASHBOARD = "index-dashboard-module"
const LOCAL_STORAGE_KEY_DASHBOARD_NEW_MODULES = "index-dashboard-module-new"
registerComponents()
React.useEffect(() => {
const moduleString = loadFromLocalStorage(LOCAL_STORAGE_KEY_DASHBOARD)
const loadedNewModules = loadFromLocalStorage(
LOCAL_STORAGE_KEY_DASHBOARD_NEW_MODULES
)
let newModules = []
if (loadedNewModules) {
const alreadyLoadedNewModules = loadedNewModules.split(",")
NEW_FEATURES.forEach(f => {
if (alreadyLoadedNewModules.indexOf(f) < 0) {
newModules.push(f)
}
})
} else {
newModules = NEW_FEATURES
}
saveToLocalStorage(LOCAL_STORAGE_KEY_DASHBOARD_NEW_MODULES, NEW_FEATURES)
if (moduleString) {
setModules(moduleString.split(",").concat(newModules))
} else {
// default modules
setModules(DEFAULT_MODULES.concat(newModules))
}
// eslint-disable-line
}, [])
// store the information of which module on left/right (only for desktop)
const columnMap = []
let left = 0
let right = 0
for (let i = 0; i < modules.length; i++) {
const m = components[modules[i]]
if (right >= left) {
left += _get(m, "rowSpan", 1)
columnMap.push("left")
} else {
right += _get(m, "rowSpan", 1)
columnMap.push("right")
}
}
return (
<>
<SEO title="Home" />
<Layout>
<IndexContainer>
{showSettings && (
<ModuleContainer className="settingContainer">
<FormControl component="fieldset">
<FormLabel component="legend">
{t("dashboard.settings")}
</FormLabel>
{Object.values(components).map(component => (
<FormGroup>
<FormControlLabel
control={
<Checkbox
color="primary"
checked={modules.indexOf(component.id) >= 0}
onChange={() => handleModuleChange(component.id)}
/>
}
label={component.title}
/>
</FormGroup>
))}
</FormControl>
</ModuleContainer>
)}
<SplitWrapper>
<SessionWrapper>
<IndexAlertMessage />
{modules
.filter((_, i) => columnMap[i] === "left")
.map((m, i) => (
<React.Fragment key={i}>
{renderComponent(m, data)}
</React.Fragment>
))}
</SessionWrapper>
<SessionWrapper>
{modules
.filter((_, i) => columnMap[i] === "right")
.map((m, i) => (
<React.Fragment key={i}>
{renderComponent(m, data)}
</React.Fragment>
))}
</SessionWrapper>
</SplitWrapper>
<Fab
color="primary"
className="fab"
onClick={() => {
if (!showSettings) {
window.scrollTo(0, 0)
}
setShowSettings(!showSettings)
}}
>
<SettingIcon />
</Fab>
</IndexContainer>
</Layout>
</>
)
}
Example #19
Source File: updates.js From warsinhk with MIT License | 4 votes |
NewsPage = props => {
const { data } = props
const { i18n, t } = useTranslation()
const paragraphArray = data.allUpdates.edges
.filter(e => e.node.type === "paragraph")
.map(e => withLanguage(i18n, e.node, "text"))
const listItemArray = data.allUpdates.edges
.filter(e => e.node.type === "item")
.map(e => withLanguage(i18n, e.node, "text"))
const govNews = data.allGovNews.edges.map(edge => edge.node)
const renderNotes = () => {
return (
<>
<Typography variant="h2">{t("updates.title")}</Typography>
<UpdatesContainer>
<p
dangerouslySetInnerHTML={{
__html: t("importantInformation.chp_hotline"),
}}
/>
<p
dangerouslySetInnerHTML={{
__html: t("importantInformation.had_hotline"),
}}
/>
{paragraphArray.map((p, i) => (
<p key={i} dangerouslySetInnerHTML={{ __html: p }} />
))}
<ol type="i">
{listItemArray.map((li, i) => (
<li key={i} dangerouslySetInnerHTML={{ __html: li }} />
))}
</ol>
</UpdatesContainer>
</>
)
}
const renderGovNews = () => {
return (
<>
<Typography variant="h2">{t("gov_news.title")}</Typography>
<Typography variant="body2">
<Link href={t("gov_news.url")} target="_blank">
{t("gov_news.source")}
</Link>
</Typography>
<Typography variant="body2">
{t("waiting_time.last_updated")}
{_get(govNews, "[0].date", "")}
</Typography>
<ResponsiveWrapper>
{govNews.map((node, index) => (
<GovNewsCard key={index} node={node} i18n={i18n} />
))}
</ResponsiveWrapper>
</>
)
}
const tabs = [
{
name: "updates",
title: t("updates.title"),
content: renderNotes(),
},
{
name: "gov_news",
title: t("gov_news.title"),
content: renderGovNews(),
},
]
return (
<Layout>
<SEO title="UpdatesPage" />
<SimpleTabs
tabs={tabs}
onTabChange={name => {
trackCustomEvent({
category: "news",
action: "tab_select",
label: name,
})
}}
/>
</Layout>
)
}
Example #20
Source File: multi-code-block.js From guitar-book with MIT License | 4 votes |
export function MultiCodeBlock(props) {
const {codeBlocks, titles} = useMemo(() => {
const defaultState = {
codeBlocks: {},
titles: {}
};
if (!Array.isArray(props.children)) {
return defaultState;
}
return props.children.reduce((acc, child, index, array) => {
const lang = getLang(child);
if (lang) {
return {
...acc,
codeBlocks: {
...acc.codeBlocks,
[lang]: child
}
};
}
if (child.props.className === 'gatsby-code-title') {
const nextNode = array[index + 1];
const title = child.props.children;
const lang = getLang(nextNode);
if (nextNode && title && lang) {
return {
...acc,
titles: {
...acc.titles,
[lang]: title
}
};
}
}
return acc;
}, defaultState);
}, [props.children]);
const languages = useMemo(() => Object.keys(codeBlocks), [codeBlocks]);
const [selectedLanguage, setSelectedLanguage] = useContext(
SelectedLanguageContext
);
if (!languages.length) {
return props.children;
}
function handleLanguageChange(language) {
setSelectedLanguage(language);
trackCustomEvent({
category: GA_EVENT_CATEGORY_CODE_BLOCK,
action: 'Change language',
label: language
});
}
const defaultLanguage = languages[0];
const renderedLanguage =
selectedLanguage in codeBlocks ? selectedLanguage : defaultLanguage;
return (
<Container>
<MultiCodeBlockContext.Provider
value={{
selectedLanguage: renderedLanguage,
languages: languages.map(lang => ({
lang,
label:
// try to find a label or capitalize the provided lang
langLabels[lang] || lang.charAt(0).toUpperCase() + lang.slice(1)
})),
onLanguageChange: handleLanguageChange
}}
>
<div className="gatsby-code-title">{titles[renderedLanguage]}</div>
{codeBlocks[renderedLanguage]}
</MultiCodeBlockContext.Provider>
</Container>
);
}