stats.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import { Box, Flex } from "reflexbox/styled-components";
  2. import React, { useState, useEffect } from "react";
  3. import formatDate from "date-fns/format";
  4. import { NextPage } from "next";
  5. import Link from "next/link";
  6. import axios from "axios";
  7. import { getAxiosConfig, removeProtocol } from "../utils";
  8. import { Button, NavButton } from "../components/Button";
  9. import { Col, RowCenterV } from "../components/Layout";
  10. import { Area, Bar, Pie } from "../components/Charts";
  11. import PageLoading from "../components/PageLoading";
  12. import AppWrapper from "../components/AppWrapper";
  13. import Text, { H1, H2, H4, Span } from "../components/Text";
  14. import Divider from "../components/Divider";
  15. import { useStoreState } from "../store";
  16. import ALink from "../components/ALink";
  17. import { API, Colors } from "../consts";
  18. interface Props {
  19. domain?: string;
  20. id?: string;
  21. }
  22. const StatsPage: NextPage<Props> = ({ domain, id }) => {
  23. const { isAuthenticated } = useStoreState(s => s.auth);
  24. const [loading, setLoading] = useState(true);
  25. const [error, setError] = useState(false);
  26. const [data, setData] = useState();
  27. const [period, setPeriod] = useState("lastDay");
  28. const stats = data && data[period];
  29. useEffect(() => {
  30. if (!id) return;
  31. axios
  32. .get(`${API.STATS}?id=${id}&domain=${domain}`, getAxiosConfig())
  33. .then(({ data }) => {
  34. setLoading(false);
  35. setError(!data);
  36. setData(data);
  37. })
  38. .catch(() => {
  39. setLoading(false);
  40. setError(true);
  41. });
  42. }, []);
  43. let errorMessage;
  44. if (!isAuthenticated) {
  45. // TODO: use icons
  46. errorMessage = <H2>You need to login to view stats.</H2>;
  47. }
  48. if (!id || error) {
  49. // TODO: use icons
  50. errorMessage = <H2>Couldn't get stats.</H2>;
  51. }
  52. const loader = loading && <PageLoading />;
  53. const total = stats && stats.views.reduce((sum, view) => sum + view, 0);
  54. const periodText = period.includes("last")
  55. ? `the last ${period.replace("last", "").toLocaleLowerCase()}`
  56. : "all time";
  57. return (
  58. <AppWrapper>
  59. {errorMessage ||
  60. loader ||
  61. (data && (
  62. <Col width={1200} maxWidth="95%" alignItems="stretch" m="40px 0">
  63. <Flex justifyContent="space-between" alignItems="center" mb={3}>
  64. <H1 fontSize={[18, 20, 24]} light>
  65. Stats for:{" "}
  66. <ALink href={data.shortLink} title="Short link">
  67. {removeProtocol(data.shortLink)}
  68. </ALink>
  69. </H1>
  70. <Text fontSize={[13, 14]} textAlign="right">
  71. {data.target.length > 80
  72. ? `${data.target
  73. .split("")
  74. .slice(0, 80)
  75. .join("")}...`
  76. : data.target}
  77. </Text>
  78. </Flex>
  79. <Col
  80. backgroundColor="white"
  81. style={{
  82. borderRadius: 12,
  83. boxShadow: "0 6px 15px hsla(200, 20%, 70%, 0.3)",
  84. overflow: "hidden"
  85. }}
  86. >
  87. <RowCenterV
  88. flex="1 1 auto"
  89. backgroundColor={Colors.TableHeadBg}
  90. justifyContent="space-between"
  91. py={[3, 3, 24]}
  92. px={[3, 4]}
  93. >
  94. <H4>
  95. Total clicks: <Span bold>{data.total}</Span>
  96. </H4>
  97. <Flex>
  98. {[
  99. ["allTime", "All Time"],
  100. ["lastMonth", "Month"],
  101. ["lastWeek", "Week"],
  102. ["lastDay", "Day"]
  103. ].map(([p, n]) => (
  104. <NavButton
  105. ml={10}
  106. disabled={p === period}
  107. onClick={() => setPeriod(p as any)}
  108. >
  109. {n}
  110. </NavButton>
  111. ))}
  112. </Flex>
  113. </RowCenterV>
  114. <Col p={[3, 4]}>
  115. <H2 mb={2} light>
  116. <Span
  117. style={{
  118. borderBottom: `1px dotted ${Colors.StatsTotalUnderline}`
  119. }}
  120. bold
  121. >
  122. {total}
  123. </Span>{" "}
  124. tracked clicks in {periodText}.
  125. </H2>
  126. <Text fontSize={[13, 14]} color={Colors.StatsLastUpdateText}>
  127. Last update in{" "}
  128. {formatDate(new Date(data.updatedAt), "hh:mm aa")}
  129. </Text>
  130. <Flex width={1} mt={4}>
  131. <Area data={stats.views} period={period} />
  132. </Flex>
  133. {total > 0 && (
  134. <>
  135. <Divider my={4} />
  136. <Flex width={1}>
  137. <Col flex="1 1 0">
  138. <H2 mb={3} light>
  139. Referrals.
  140. </H2>
  141. <Pie data={stats.stats.referrer} />
  142. </Col>
  143. <Col flex="1 1 0">
  144. <H2 mb={3} light>
  145. Browsers.
  146. </H2>
  147. <Bar data={stats.stats.browser} />
  148. </Col>
  149. </Flex>
  150. <Divider my={4} />
  151. <Flex width={1}>
  152. <Col flex="1 1 0">
  153. <H2 mb={3} light>
  154. Country.
  155. </H2>
  156. <Pie data={stats.stats.country} />
  157. </Col>
  158. <Col flex="1 1 0">
  159. <H2 mb={3} light>
  160. OS.
  161. </H2>
  162. <Bar data={stats.stats.os} />
  163. </Col>
  164. </Flex>
  165. </>
  166. )}
  167. </Col>
  168. </Col>
  169. <Box alignSelf="center" my={64}>
  170. <Link href="/">
  171. <ALink href="/" title="Back to homepage" forButton>
  172. <Button icon="arrow-left">Back to homepage</Button>
  173. </ALink>
  174. </Link>
  175. </Box>
  176. </Col>
  177. ))}
  178. </AppWrapper>
  179. );
  180. };
  181. StatsPage.getInitialProps = ({ query }) => {
  182. return Promise.resolve(query);
  183. };
  184. StatsPage.defaultProps = {
  185. domain: "",
  186. id: ""
  187. };
  188. export default StatsPage;