Animated metric card.

This commit is contained in:
Mike Cao 2020-07-29 20:09:41 -07:00
parent f9a6f5f637
commit da2d383b71
8 changed files with 41 additions and 13 deletions

View File

@ -10,7 +10,7 @@ export default function Layout({ title, children }) {
<title>umami{title && ` - ${title}`}</title> <title>umami{title && ` - ${title}`}</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
</Head> </Head>

View File

@ -1,11 +1,20 @@
import React from 'react'; import React, { useState } from 'react';
import { useSpring, animated } from 'react-spring';
import styles from './MetricCard.module.css'; import styles from './MetricCard.module.css';
const MetricCard = ({ value, label }) => ( function defaultFormat(n) {
<div className={styles.card}> return Number(n).toFixed(0);
<div className={styles.value}>{value}</div> }
<div className={styles.label}>{label}</div>
</div> const MetricCard = ({ value = 0, label, format = defaultFormat }) => {
); const props = useSpring({ x: value, from: { x: 0 } });
return (
<div className={styles.card}>
<animated.div className={styles.value}>{props.x.interpolate(x => format(x))}</animated.div>
<div className={styles.label}>{label}</div>
</div>
);
};
export default MetricCard; export default MetricCard;

View File

@ -2,7 +2,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
margin-right: 50px; width: 140px;
} }
.value { .value {

View File

@ -26,7 +26,6 @@ export default function PageviewsChart({ data, unit }) {
const renderTooltip = model => { const renderTooltip = model => {
const { caretX, caretY, opacity, title, body, labelColors } = model; const { caretX, caretY, opacity, title, body, labelColors } = model;
console.log(model);
if (!opacity) { if (!opacity) {
setTooltip({ opacity }); setTooltip({ opacity });

View File

@ -24,7 +24,11 @@ export default function WebsiteSummary({ websiteId, startDate, endDate }) {
<div className={styles.container}> <div className={styles.container}>
<MetricCard label="Views" value={pageviews} /> <MetricCard label="Views" value={pageviews} />
<MetricCard label="Visitors" value={uniques} /> <MetricCard label="Visitors" value={uniques} />
<MetricCard label="Bounce rate" value={`${~~((bounces / uniques) * 100)}%`} /> <MetricCard
label="Bounce rate"
value={uniques ? (bounces / uniques) * 100 : 0}
format={n => Number(n).toFixed(0) + '%'}
/>
</div> </div>
); );
} }

View File

@ -2,7 +2,7 @@ import { parse } from 'cookie';
import { verifySecureToken } from './crypto'; import { verifySecureToken } from './crypto';
export default async req => { export default async req => {
const token = parse(req.headers.cookie)['umami.auth']; const token = parse(req.headers.cookie || '')['umami.auth'];
return verifySecureToken(token); return verifySecureToken(token);
}; };

View File

@ -59,6 +59,7 @@
"promise-polyfill": "^8.1.3", "promise-polyfill": "^8.1.3",
"react": "16.13.1", "react": "16.13.1",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-spring": "^8.0.27",
"request-ip": "^2.1.3", "request-ip": "^2.1.3",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"uuid": "^8.2.0", "uuid": "^8.2.0",

View File

@ -1091,6 +1091,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/runtime@^7.3.1":
version "7.10.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c"
integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.8.4": "@babel/runtime@^7.8.4":
version "7.10.4" version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99"
@ -6984,7 +6991,7 @@ prop-types-exact@1.2.0:
object.assign "^4.1.0" object.assign "^4.1.0"
reflect.ownkeys "^0.2.0" reflect.ownkeys "^0.2.0"
prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2: prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -7128,6 +7135,14 @@ react-is@^16.7.0, react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-spring@^8.0.27:
version "8.0.27"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a"
integrity sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==
dependencies:
"@babel/runtime" "^7.3.1"
prop-types "^15.5.8"
react@16.13.1: react@16.13.1:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"