Skip to content

Commit bf6df36

Browse files
authored
Merge pull request #85 from mikecao/dev
v0.20.0
2 parents 226a701 + cb48acd commit bf6df36

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+417
-396
lines changed

assets/redo.svg

Lines changed: 1 addition & 0 deletions
Loading

assets/times.svg

Lines changed: 1 addition & 0 deletions
Loading

components/WebsiteDetails.js

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useState } from 'react';
22
import classNames from 'classnames';
33
import WebsiteChart from 'components/metrics/WebsiteChart';
44
import WorldMap from 'components/common/WorldMap';
55
import Page from 'components/layout/Page';
6-
import WebsiteHeader from 'components/metrics/WebsiteHeader';
76
import MenuLayout from 'components/layout/MenuLayout';
87
import Button from 'components/common/Button';
9-
import { getDateRange } from 'lib/date';
10-
import { get } from 'lib/web';
118
import Arrow from 'assets/arrow-right.svg';
129
import styles from './WebsiteDetails.module.css';
1310
import PagesTable from './metrics/PagesTable';
@@ -18,15 +15,15 @@ import DevicesTable from './metrics/DevicesTable';
1815
import CountriesTable from './metrics/CountriesTable';
1916
import EventsTable from './metrics/EventsTable';
2017
import EventsChart from './metrics/EventsChart';
18+
import useFetch from 'hooks/useFetch';
19+
import Loading from 'components/common/Loading';
2120

22-
export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) {
23-
const [data, setData] = useState();
21+
export default function WebsiteDetails({ websiteId }) {
22+
const { data } = useFetch(`/api/website/${websiteId}`);
2423
const [chartLoaded, setChartLoaded] = useState(false);
2524
const [countryData, setCountryData] = useState();
2625
const [eventsData, setEventsData] = useState();
27-
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
2826
const [expand, setExpand] = useState();
29-
const { startDate, endDate, unit } = dateRange;
3027

3128
const BackButton = () => (
3229
<Button
@@ -48,23 +45,12 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
4845
{ label: 'Browsers', value: 'browser', component: BrowsersTable },
4946
{ label: 'Operating system', value: 'os', component: OSTable },
5047
{ label: 'Devices', value: 'device', component: DevicesTable },
51-
{
52-
label: 'Countries',
53-
value: 'country',
54-
component: props => <CountriesTable {...props} onDataLoad={data => setCountryData(data)} />,
55-
},
48+
{ label: 'Countries', value: 'country', component: CountriesTable },
5649
{ label: 'Events', value: 'event', component: EventsTable },
5750
];
5851

59-
const dataProps = {
60-
websiteId,
61-
startDate,
62-
endDate,
63-
unit,
64-
};
65-
6652
const tableProps = {
67-
...dataProps,
53+
websiteId,
6854
websiteDomain: data?.domain,
6955
limit: 10,
7056
onExpand: handleExpand,
@@ -76,16 +62,10 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
7662
return menuOptions.find(e => e.value === value);
7763
}
7864

79-
async function loadData() {
80-
setData(await get(`/api/website/${websiteId}`));
81-
}
82-
8365
function handleDataLoad() {
84-
if (!chartLoaded) setTimeout(() => setChartLoaded(true), 300);
85-
}
86-
87-
function handleDateChange(values) {
88-
setTimeout(() => setDateRange(values), 300);
66+
if (!chartLoaded) {
67+
setTimeout(() => setChartLoaded(true), 300);
68+
}
8969
}
9070

9171
function handleExpand(value) {
@@ -96,12 +76,6 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
9676
setExpand(getSelectedMenuOption(value));
9777
}
9878

99-
useEffect(() => {
100-
if (websiteId) {
101-
loadData();
102-
}
103-
}, [websiteId]);
104-
10579
if (!data) {
10680
return null;
10781
}
@@ -110,15 +84,16 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
11084
<Page>
11185
<div className="row">
11286
<div className={classNames(styles.chart, 'col')}>
113-
<WebsiteHeader websiteId={websiteId} name={data.name} showLink={false} />
11487
<WebsiteChart
11588
websiteId={websiteId}
89+
title={data.name}
11690
onDataLoad={handleDataLoad}
117-
onDateChange={handleDateChange}
91+
showLink={false}
11892
stickyHeader
11993
/>
12094
</div>
12195
</div>
96+
{!chartLoaded && <Loading />}
12297
{chartLoaded && !expand && (
12398
<>
12499
<div className={classNames(styles.row, 'row')}>
@@ -155,7 +130,7 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
155130
<EventsTable {...tableProps} onDataLoad={setEventsData} />
156131
</div>
157132
<div className="col-12 col-md-12 col-lg-8 pt-5 pb-5">
158-
<EventsChart {...dataProps} />
133+
<EventsChart websiteId={websiteId} />
159134
</div>
160135
</div>
161136
</>

components/WebsiteList.js

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,26 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React from 'react';
22
import { useRouter } from 'next/router';
3-
import WebsiteHeader from 'components/metrics/WebsiteHeader';
43
import WebsiteChart from 'components/metrics/WebsiteChart';
54
import Page from 'components/layout/Page';
65
import Button from 'components/common/Button';
76
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
7+
import useFetch from 'hooks/useFetch';
88
import Arrow from 'assets/arrow-right.svg';
9-
import { get } from 'lib/web';
109
import styles from './WebsiteList.module.css';
1110

1211
export default function WebsiteList() {
13-
const [data, setData] = useState();
1412
const router = useRouter();
15-
16-
async function loadData() {
17-
setData(await get(`/api/websites`));
18-
}
19-
20-
useEffect(() => {
21-
loadData();
22-
}, []);
13+
const { data } = useFetch('/api/websites');
2314

2415
if (!data) {
2516
return null;
2617
}
2718

2819
return (
2920
<Page>
30-
{data?.map(({ website_id, name }) => (
21+
{data.map(({ website_id, name }) => (
3122
<div key={website_id} className={styles.website}>
32-
<WebsiteHeader websiteId={website_id} name={name} showLink />
33-
<WebsiteChart key={website_id} title={name} websiteId={website_id} />
23+
<WebsiteChart websiteId={website_id} title={name} showLink />
3424
</div>
3525
))}
3626
{data.length === 0 && (

components/common/DropDown.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ export default function DropDown({
2020
setShowMenu(state => !state);
2121
}
2222

23-
function handleSelect(value, e) {
23+
function handleSelect(selected, e) {
2424
e.stopPropagation();
2525
setShowMenu(false);
26-
onChange(value);
26+
if (selected !== value) {
27+
onChange(selected);
28+
}
2729
}
2830

2931
useDocumentClick(e => {

components/common/RefreshButton.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import { useDispatch } from 'react-redux';
3+
import { setDateRange } from 'redux/actions/websites';
4+
import Button from './Button';
5+
import Refresh from 'assets/redo.svg';
6+
import { useDateRange } from 'hooks/useDateRange';
7+
8+
export default function RefreshButton({ websiteId }) {
9+
const dispatch = useDispatch();
10+
const dateRange = useDateRange(websiteId);
11+
12+
function handleClick() {
13+
if (dateRange) {
14+
dispatch(setDateRange(websiteId, dateRange));
15+
}
16+
}
17+
18+
return <Button icon={<Refresh />} size="small" onClick={handleClick} />;
19+
}

components/common/Toast.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useEffect } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import { useSpring, animated } from 'react-spring';
4+
import styles from './Toast.module.css';
5+
import Icon from 'components/common/Icon';
6+
import Close from 'assets/times.svg';
7+
8+
export default function Toast({ message, timeout = 3000, onClose }) {
9+
const props = useSpring({
10+
opacity: 1,
11+
transform: 'translate3d(0,0px,0)',
12+
from: { opacity: 0, transform: 'translate3d(0,-40px,0)' },
13+
});
14+
15+
useEffect(() => {
16+
setTimeout(onClose, timeout);
17+
}, []);
18+
19+
return ReactDOM.createPortal(
20+
<animated.div className={styles.toast} style={props} onClick={onClose}>
21+
<div className={styles.message}>{message}</div>
22+
<Icon className={styles.close} icon={<Close />} size="small" />
23+
</animated.div>,
24+
document.getElementById('__modals'),
25+
);
26+
}

components/common/Toast.module.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.toast {
2+
position: absolute;
3+
top: 30px;
4+
left: 0;
5+
right: 0;
6+
width: 300px;
7+
border-radius: 5px;
8+
display: flex;
9+
justify-content: space-between;
10+
align-items: center;
11+
padding: 8px 16px;
12+
color: var(--gray50);
13+
background: var(--green400);
14+
margin: auto;
15+
z-index: 2;
16+
cursor: pointer;
17+
}
18+
19+
.message {
20+
font-size: var(--font-size-normal);
21+
}
22+
23+
.close {
24+
margin-left: 20px;
25+
}

components/metrics/ActiveUsers.js

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,20 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useMemo } from 'react';
22
import { useSpring, animated } from 'react-spring';
33
import classNames from 'classnames';
4-
import { get } from 'lib/web';
4+
import useFetch from 'hooks/useFetch';
55
import styles from './ActiveUsers.module.css';
66

77
export default function ActiveUsers({ websiteId, className }) {
8-
const [count, setCount] = useState(0);
9-
10-
async function loadData() {
11-
const result = await get(`/api/website/${websiteId}/active`);
12-
setCount(result?.[0]?.x);
13-
}
8+
const { data } = useFetch(`/api/website/${websiteId}/active`, {}, { interval: 60000 });
9+
const count = useMemo(() => {
10+
return data?.[0]?.x || 0;
11+
}, [data]);
1412

1513
const props = useSpring({
1614
x: count,
1715
from: { x: 0 },
1816
});
1917

20-
useEffect(() => {
21-
loadData();
22-
23-
const id = setInterval(() => loadData(), 60000);
24-
25-
return () => {
26-
clearInterval(id);
27-
};
28-
}, []);
29-
3018
if (count === 0) {
3119
return null;
3220
}

components/metrics/BarChart.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import classNames from 'classnames';
44
import ChartJS from 'chart.js';
55
import styles from './BarChart.module.css';
66
import { format } from 'date-fns';
7+
import { formatLongNumber } from '../../lib/format';
78

89
export default function BarChart({
910
chartId,
@@ -21,7 +22,7 @@ export default function BarChart({
2122
const chart = useRef();
2223
const [tooltip, setTooltip] = useState({});
2324

24-
const renderLabel = (label, index, values) => {
25+
const renderXLabel = (label, index, values) => {
2526
const d = new Date(values[index].value);
2627
const n = records;
2728

@@ -40,6 +41,10 @@ export default function BarChart({
4041
}
4142
};
4243

44+
const renderYLabel = label => {
45+
return +label > 1 ? formatLongNumber(label) : label;
46+
};
47+
4348
const renderTooltip = model => {
4449
const { opacity, title, body, labelColors } = model;
4550

@@ -82,7 +87,7 @@ export default function BarChart({
8287
tooltipFormat: 'ddd MMMM DD YYYY',
8388
},
8489
ticks: {
85-
callback: renderLabel,
90+
callback: renderXLabel,
8691
minRotation: 0,
8792
maxRotation: 0,
8893
},
@@ -96,6 +101,7 @@ export default function BarChart({
96101
yAxes: [
97102
{
98103
ticks: {
104+
callback: renderYLabel,
99105
beginAtZero: true,
100106
},
101107
stacked,
@@ -119,7 +125,7 @@ export default function BarChart({
119125
const { options } = chart.current;
120126

121127
options.scales.xAxes[0].time.unit = unit;
122-
options.scales.xAxes[0].ticks.callback = renderLabel;
128+
options.scales.xAxes[0].ticks.callback = renderXLabel;
123129
options.animation.duration = animationDuration;
124130

125131
onUpdate(chart.current);

components/metrics/BrowsersTable.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import React from 'react';
22
import MetricsTable from './MetricsTable';
33
import { browserFilter } from 'lib/filters';
44

5-
export default function BrowsersTable({ websiteId, startDate, endDate, limit, onExpand }) {
5+
export default function BrowsersTable({ websiteId, limit, onExpand }) {
66
return (
77
<MetricsTable
88
title="Browsers"
99
type="browser"
1010
metric="Visitors"
1111
websiteId={websiteId}
12-
startDate={startDate}
13-
endDate={endDate}
1412
limit={limit}
1513
dataFilter={browserFilter}
1614
onExpand={onExpand}

components/metrics/CountriesTable.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,13 @@ import React from 'react';
22
import MetricsTable from './MetricsTable';
33
import { countryFilter, percentFilter } from 'lib/filters';
44

5-
export default function CountriesTable({
6-
websiteId,
7-
startDate,
8-
endDate,
9-
limit,
10-
onDataLoad,
11-
onExpand,
12-
}) {
5+
export default function CountriesTable({ websiteId, limit, onDataLoad = () => {}, onExpand }) {
136
return (
147
<MetricsTable
158
title="Countries"
169
type="country"
1710
metric="Visitors"
1811
websiteId={websiteId}
19-
startDate={startDate}
20-
endDate={endDate}
2112
limit={limit}
2213
dataFilter={countryFilter}
2314
onDataLoad={data => onDataLoad(percentFilter(data))}

0 commit comments

Comments
 (0)