stats.tsx 6.5 KB

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