stats.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. key={p}
  109. >
  110. {n}
  111. </NavButton>
  112. ))}
  113. </Flex>
  114. </RowCenterV>
  115. <Col p={[3, 4]}>
  116. <H2 mb={2} light>
  117. <Span
  118. style={{
  119. borderBottom: `1px dotted ${Colors.StatsTotalUnderline}`
  120. }}
  121. bold
  122. >
  123. {total}
  124. </Span>{" "}
  125. tracked clicks in {periodText}.
  126. </H2>
  127. <Text fontSize={[13, 14]} color={Colors.StatsLastUpdateText}>
  128. Last update in{" "}
  129. {formatDate(new Date(data.updatedAt), "hh:mm aa")}
  130. </Text>
  131. <Flex width={1} mt={4}>
  132. <Area data={stats.views} period={period} />
  133. </Flex>
  134. {total > 0 && (
  135. <>
  136. <Divider my={4} />
  137. <Flex width={1}>
  138. <Col flex="1 1 0">
  139. <H2 mb={3} light>
  140. Referrals.
  141. </H2>
  142. <Pie data={stats.stats.referrer} />
  143. </Col>
  144. <Col flex="1 1 0">
  145. <H2 mb={3} light>
  146. Browsers.
  147. </H2>
  148. <Bar data={stats.stats.browser} />
  149. </Col>
  150. </Flex>
  151. <Divider my={4} />
  152. <Flex width={1}>
  153. <Col flex="1 1 0">
  154. <H2 mb={3} light>
  155. Country.
  156. </H2>
  157. <Pie data={stats.stats.country} />
  158. </Col>
  159. <Col flex="1 1 0">
  160. <H2 mb={3} light>
  161. OS.
  162. </H2>
  163. <Bar data={stats.stats.os} />
  164. </Col>
  165. </Flex>
  166. </>
  167. )}
  168. </Col>
  169. </Col>
  170. <Box alignSelf="center" my={64}>
  171. <Link href="/">
  172. <ALink href="/" title="Back to homepage" forButton>
  173. <Button icon="arrow-left">Back to homepage</Button>
  174. </ALink>
  175. </Link>
  176. </Box>
  177. </Col>
  178. ))}
  179. </AppWrapper>
  180. );
  181. };
  182. StatsPage.getInitialProps = ({ query }) => {
  183. return Promise.resolve(query);
  184. };
  185. StatsPage.defaultProps = {
  186. domain: "",
  187. id: ""
  188. };
  189. export default StatsPage;