GameListView.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import React from "react";
  2. import { Link, useLocation } from "react-router-dom";
  3. import { useTranslation, Trans } from "react-i18next";
  4. import styled from "styled-components";
  5. import CookieConsent from "react-cookie-consent";
  6. import { getGames } from "../utils/api";
  7. import Account from "../components/Account";
  8. import useAuth from "../hooks/useAuth";
  9. import "react-confirm-alert/src/react-confirm-alert.css";
  10. import useLocalStorage from "../hooks/useLocalStorage";
  11. import GameListItem from "./GameListItem";
  12. import AboutModal from "./AboutModal";
  13. const GameView = styled.div`
  14. min-height: 100vh;
  15. flex-direction: column;
  16. & > footer {
  17. width: 100%;
  18. grid-column: 1 / 4;
  19. margin-top: 1em;
  20. padding: 0.5em 0;
  21. text-align: center;
  22. background-color: #00000099;
  23. }
  24. `;
  25. const Brand = styled.div`
  26. position: relative;
  27. display: inline-block;
  28. & h1 {
  29. font-weight: 700;
  30. font-size: 24px;
  31. & a {
  32. color: var(--font-color);
  33. }
  34. }
  35. & .beta {
  36. position: absolute;
  37. top: 5px;
  38. left: 175px;
  39. text-transform: uppercase;
  40. font-weight: 300;
  41. font-size: 0.9em;
  42. }
  43. @media screen and (max-width: 640px) {
  44. & {
  45. }
  46. & h1 {
  47. }
  48. }
  49. `;
  50. const Nav = styled.nav`
  51. background-color: var(--bg-color);
  52. position: relative;
  53. padding: 0 5%;
  54. display: flex;
  55. align-items: center;
  56. & .brand {
  57. flex: 1;
  58. }
  59. & button,
  60. & .button {
  61. background: none;
  62. text-transform: uppercase;
  63. font-weight: 300;
  64. font-size: 1.3em;
  65. border-radius: 0;
  66. color: var(--font-color2);
  67. }
  68. & button:hover,
  69. & .button:hover {
  70. color: var(--font-color);
  71. border-bottom: 1px solid var(--color-primary);
  72. }
  73. `;
  74. const Header = styled.header`
  75. background-color: var(--bg-color);
  76. position: relative;
  77. background: linear-gradient(
  78. 180deg,
  79. rgba(0, 0, 0, 1) 0%,
  80. rgba(0, 0, 0, 0.6) 40%,
  81. rgba(0, 0, 0, 0.6) 60%,
  82. rgba(0, 0, 0, 1) 100%
  83. ),
  84. 100% 50% / contain no-repeat url(/hero.png);
  85. padding: 14vh 5%;
  86. margin-bottom: 20px;
  87. & .baseline {
  88. font-weigth: 800;
  89. font-size: 3.2vw;
  90. line-height: 1.2em;
  91. }
  92. & .subbaseline {
  93. color: var(--font-color2);
  94. font-size: 1.4vw;
  95. }
  96. @media screen and (max-width: 640px) {
  97. & {
  98. }
  99. & .new-game {
  100. }
  101. & .login {
  102. }
  103. & .baseline {
  104. }
  105. }
  106. `;
  107. const Filter = styled.div`
  108. & .incentive {
  109. width: 100%;
  110. text-align: center;
  111. font-size: 3.5vw;
  112. padding: 0.5em;
  113. margin: 0;
  114. }
  115. `;
  116. const Content = styled.div`
  117. background-color: var(--bg-secondary-color);
  118. `;
  119. const GameList = styled.ul`
  120. list-style: none;
  121. margin: 0;
  122. padding: 0 5%;
  123. display: flex;
  124. flex-wrap: wrap;
  125. align-items: flex-start;
  126. justify-content: space-between;
  127. gap: 5%;
  128. @media screen and (max-width: 640px) {
  129. }
  130. `;
  131. const useQuery = () => {
  132. return new URLSearchParams(useLocation().search);
  133. };
  134. const GameListView = () => {
  135. const { t } = useTranslation();
  136. const [isBeta, setIsBeta] = useLocalStorage("isBeta", false);
  137. if (isBeta) {
  138. console.log("Beta activated");
  139. }
  140. const [cookieConsent, setCookieConsent] = useLocalStorage(
  141. "cookieConsent",
  142. false
  143. );
  144. const [showAboutModal, setShowAboutModal] = React.useState(false);
  145. let query = useQuery();
  146. const [gameList, setGameList] = React.useState([]);
  147. const { isAuthenticated, userId } = useAuth();
  148. React.useEffect(() => {
  149. getGames().then((content) => {
  150. setGameList(
  151. content.sort((a, b) => {
  152. const [nameA, nameB] = [
  153. a.board.defaultName || a.board.name,
  154. b.board.defaultName || b.board.name,
  155. ];
  156. if (nameA < nameB) {
  157. return -1;
  158. }
  159. if (nameA > nameB) {
  160. return 1;
  161. }
  162. return 0;
  163. })
  164. );
  165. });
  166. }, [isAuthenticated]);
  167. const forceBeta = query.get("beta") === "true";
  168. React.useEffect(() => {
  169. if (forceBeta) {
  170. setIsBeta(true);
  171. }
  172. }, [forceBeta, setIsBeta]);
  173. return (
  174. <>
  175. <GameView>
  176. <Nav>
  177. <Brand className="brand">
  178. <h1>
  179. <a href="/">Air Board Game</a>
  180. </h1>
  181. <span className="beta">Beta</span>
  182. </Brand>
  183. {isAuthenticated && (
  184. <Link to={`/game/`} className="button new-game">
  185. {t("Create new game")}
  186. </Link>
  187. )}
  188. <Account className="login" disabled={!cookieConsent} />
  189. </Nav>
  190. <Header>
  191. <Trans i18nKey="baseline">
  192. <h2 className="baseline">
  193. Play board games online
  194. <br />
  195. with your friends - for free!
  196. </h2>
  197. <p className="subbaseline">
  198. Choose from our selection or create your own.
  199. <br />
  200. No need to sign up. Just start a game and share the link with your
  201. friends.
  202. </p>
  203. </Trans>
  204. </Header>
  205. <Content>
  206. <Filter>
  207. <h2 className="incentive">{t("Start a game now")}</h2>
  208. </Filter>
  209. <GameList>
  210. {gameList
  211. .filter(
  212. ({ published, owner }) =>
  213. published || (userId && (!owner || owner === userId))
  214. )
  215. .map((game) => (
  216. <GameListItem key={game.id} game={game} userId={userId} />
  217. ))}
  218. </GameList>
  219. </Content>
  220. <footer>
  221. <button
  222. className="button clear"
  223. onClick={() => setShowAboutModal(true)}
  224. >
  225. {t("About")}
  226. </button>
  227. </footer>
  228. </GameView>
  229. <AboutModal show={showAboutModal} setShow={setShowAboutModal} />
  230. <CookieConsent
  231. location="bottom"
  232. buttonText={t("Got it!")}
  233. enableDeclineButton
  234. declineButtonText={t("Refuse")}
  235. cookieName="cookieConsent"
  236. onAccept={() => setCookieConsent(true)}
  237. containerClasses="cookie"
  238. expires={150}
  239. buttonStyle={{
  240. color: "var(--font-color)",
  241. backgroundColor: "var(--color-secondary)",
  242. }}
  243. style={{
  244. backgroundColor: "#000000CE",
  245. boxShadow:
  246. "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
  247. }}
  248. >
  249. {t("This site use a cookie only to know if your are authenticated.")}
  250. </CookieConsent>
  251. </>
  252. );
  253. };
  254. export default GameListView;