Browse Source

Add multi-board feature (#395)

* Add game information in welcome modal

* Fix note scroll event behaviour

* Fix available actions on double click

* Enable multi board feature
Jérémie Pardou-Piquemal 2 years ago
parent
commit
44c0330db4

+ 7 - 2
docs/dev.md

@@ -246,8 +246,13 @@ To deploy an instance in production you need to deploy the same stack as in dev.
 
 
 - You need a Ricochet.js server.
 - You need a Ricochet.js server.
 - You need a Wire.io server.
 - You need a Wire.io server.
-- Build the backend `ricochet.json` file.
-- Build the frontend and deploy it to a CDN.
+- Build the backend `ricochet.json` file. (it will be included automatically with the frontend)
+- Build the frontend and deploy it to a CDN or with any static file server (Apache, Nginx, ...).
+
+**Advice**
+
+- You need to redirect all frontend requests to the `index.html` page as it's a
+  single page app.
 
 
 # Contributing guide
 # Contributing guide
 
 

+ 38 - 9
src/MainRoute.jsx

@@ -33,7 +33,14 @@ const MainRoute = () => {
           },
           },
         }) => {
         }) => {
           // Redirect to new session id
           // Redirect to new session id
-          return <Redirect to={`/session/${uid()}/?fromGame=${gameId}`} />;
+          return (
+            <Redirect
+              to={{
+                pathName: `/session/${uid()}/`,
+                state: { fromGame: gameId },
+              }}
+            />
+          );
         }}
         }}
       </Route>
       </Route>
       {/* for compat with old url scheme */}
       {/* for compat with old url scheme */}
@@ -43,7 +50,14 @@ const MainRoute = () => {
             params: { gameId, sessionId },
             params: { gameId, sessionId },
           },
           },
         }) => {
         }) => {
-          return <Redirect to={`/session/${sessionId}/?fromGame=${gameId}`} />;
+          return (
+            <Redirect
+              to={{
+                pathName: `/session/${sessionId}`,
+                state: { fromGame: gameId },
+              }}
+            />
+          );
         }}
         }}
       </Route>
       </Route>
       {/* Start a new session from this game */}
       {/* Start a new session from this game */}
@@ -58,7 +72,9 @@ const MainRoute = () => {
             <Redirect
             <Redirect
               to={{
               to={{
                 pathname: `/session/${uid()}/`,
                 pathname: `/session/${uid()}/`,
-                search: `?fromGame=${gameId}`,
+                state: {
+                  fromGame: gameId,
+                },
               }}
               }}
             />
             />
           );
           );
@@ -66,14 +82,11 @@ const MainRoute = () => {
       </Route>
       </Route>
       <Route path="/session/:sessionId">
       <Route path="/session/:sessionId">
         {({
         {({
-          location: { search },
           match: {
           match: {
             params: { sessionId },
             params: { sessionId },
           },
           },
+          location: { state: { fromGame } = {} } = {},
         }) => {
         }) => {
-          const params = new URLSearchParams(search);
-          const fromGame = params.get("fromGame");
-
           // Redirect to new session id
           // Redirect to new session id
           return (
           return (
             <WithSocketIO>
             <WithSocketIO>
@@ -102,18 +115,34 @@ const MainRoute = () => {
           match: {
           match: {
             params: { roomId },
             params: { roomId },
           },
           },
+          location: { state: { showInvite = false } = {} } = {},
         }) => (
         }) => (
           <WithSocketIO>
           <WithSocketIO>
-            <RoomView roomId={roomId} />
+            <RoomView roomId={roomId} showInvite={showInvite} />
           </WithSocketIO>
           </WithSocketIO>
         )}
         )}
       </Route>
       </Route>
+      <Route path="/room/" exact>
+        {() => {
+          // Redirect to new room
+          return (
+            <Redirect
+              to={{
+                pathname: `/room/${uid()}/`,
+                state: { showInvite: true },
+              }}
+            />
+          );
+        }}
+      </Route>
       {/* Auth rout */}
       {/* Auth rout */}
       <Route exact path="/login/:userHash/:token">
       <Route exact path="/login/:userHash/:token">
         <AuthView />
         <AuthView />
       </Route>
       </Route>
       {/* Default route */}
       {/* Default route */}
-      <Redirect from="/" to="/games/" exact />
+      <Route path="/" exact>
+        <Redirect to="/games/" />
+      </Route>
       <Route path="/">
       <Route path="/">
         <Home />
         <Home />
       </Route>
       </Route>

BIN
src/favicon.ico


+ 9 - 3
src/gameComponents/Note.jsx

@@ -65,9 +65,15 @@ const Note = ({
       textColor={textColor}
       textColor={textColor}
       width={width}
       width={width}
       height={height}
       height={height}
-      onKeyDown={(e) => e.stopPropagation()}
-      onKeyUp={(e) => e.stopPropagation()}
-      onWheel={(e) => e.stopPropagation()}
+      onKeyDown={(e) =>
+        e.target === document.activeElement && e.stopPropagation()
+      }
+      onKeyUp={(e) =>
+        e.target === document.activeElement && e.stopPropagation()
+      }
+      onWheel={(e) =>
+        e.target === document.activeElement && e.stopPropagation()
+      }
     >
     >
       <label style={{ userSelect: "none" }}>
       <label style={{ userSelect: "none" }}>
         <h3>{label}</h3>
         <h3>{label}</h3>

+ 6 - 2
src/i18n/en.json

@@ -218,7 +218,6 @@
   "items": "One item",
   "items": "One item",
   "items_plural": "{{count}} items",
   "items_plural": "{{count}} items",
   "moreInformation": "<0>For more information, visit <2>github repository</2>.</0>",
   "moreInformation": "<0>For more information, visit <2>github repository</2>.</0>",
-  "welcomeTip": "<0>Tip: Use an audio conferencing system to talk with other players, like <1>Jitsi</1> (free & open-source).</0>",
   "{{count}} player": "{{count}} player",
   "{{count}} player": "{{count}} player",
   "{{count}} player_plural": "{{count}} players",
   "{{count}} player_plural": "{{count}} players",
   "{{min}} - {{max}} players": "{{min}} - {{max}} players",
   "{{min}} - {{max}} players": "{{min}} - {{max}} players",
@@ -260,5 +259,10 @@
   "Generated item": "Generated item",
   "Generated item": "Generated item",
   "Item type": "Generated item type",
   "Item type": "Generated item type",
   "No item type defined": "No item type defined",
   "No item type defined": "No item type defined",
-  "Generator": "Generator"
+  "Generator": "Generator",
+  "Share this link with your friends and start playing immediately.": "Share this link with your friends and start playing immediately.",
+  "Or": "Or",
+  "start a multi room session": "start a multi board session",
+  "Let's go...": "Let's go...",
+  "Choose your board": "Choose your board"
 }
 }

+ 6 - 2
src/i18n/fr.json

@@ -218,7 +218,6 @@
   "items": "Un élément",
   "items": "Un élément",
   "items_plural": "{{count}} éléments",
   "items_plural": "{{count}} éléments",
   "moreInformation": "<0>Pour plus d'informations, visitez le <2>dépôt github</2>.</0>",
   "moreInformation": "<0>Pour plus d'informations, visitez le <2>dépôt github</2>.</0>",
-  "welcomeTip": "<0>Astuce: Utilisez un système de conférence audio pour parler avec vos amis, comme <1>Jitsi</1> (libre & gratuit).</0>",
   "{{count}} player": "{{count}} joueur",
   "{{count}} player": "{{count}} joueur",
   "{{count}} player_plural": "{{count}} joueurs",
   "{{count}} player_plural": "{{count}} joueurs",
   "{{min}} - {{max}} players": "{{min}} - {{max}} joueurs",
   "{{min}} - {{max}} players": "{{min}} - {{max}} joueurs",
@@ -260,5 +259,10 @@
   "Generated item": "Élément généré",
   "Generated item": "Élément généré",
   "Item type": "Type de l'élément généré",
   "Item type": "Type de l'élément généré",
   "No item type defined": "Aucun type sélectionné",
   "No item type defined": "Aucun type sélectionné",
-  "Generator": "Générateur"
+  "Generator": "Générateur",
+  "Share this link with your friends and start playing immediately.": "Partagez ce lien avec vos amis et commencez à jouer.",
+  "Or": "Ou",
+  "start a multi room session": "jouer sur plusieurs tables",
+  "Let's go...": "C'est parti…",
+  "Choose your board": "Choisissez une table"
 }
 }

+ 57 - 0
src/views/BoardView/GameInformation.jsx

@@ -0,0 +1,57 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+import useAsyncEffect from "use-async-effect";
+import { useBoardConfig } from "react-sync-board";
+
+import { getBestTranslationFromConfig } from "../../utils/api";
+
+const GameInformation = () => {
+  const { t, i18n } = useTranslation();
+
+  const [info, setInfo] = React.useState("");
+
+  const [boardConfig] = useBoardConfig();
+
+  const translation = React.useMemo(
+    () => getBestTranslationFromConfig(boardConfig, i18n.languages),
+    [boardConfig, i18n.languages]
+  );
+
+  useAsyncEffect(
+    async (isMounted) => {
+      const marked = (await import("marked")).default;
+      if (!isMounted()) return;
+
+      const renderer = new marked.Renderer();
+      renderer.link = (href, title, text) => {
+        return `<a target="_blank" rel="noopener" href="${href}" title="${title}">${text}</a>`;
+      };
+      setInfo(
+        marked(translation.description || "", {
+          renderer: renderer,
+        })
+      );
+    },
+    [setInfo, translation.description]
+  );
+
+  return (
+    <>
+      <header>
+        <h3>{t("Game information")}</h3>
+      </header>
+      <section>
+        {translation.description && (
+          <div
+            dangerouslySetInnerHTML={{
+              __html: info,
+            }}
+          ></div>
+        )}
+        {!translation.description && <div>{t("No information")}</div>}
+      </section>
+    </>
+  );
+};
+
+export default GameInformation;

+ 3 - 44
src/views/BoardView/InfoModal.jsx

@@ -1,12 +1,10 @@
 import React from "react";
 import React from "react";
 import { useTranslation, Trans } from "react-i18next";
 import { useTranslation, Trans } from "react-i18next";
-import useAsyncEffect from "use-async-effect";
 import styled from "styled-components";
 import styled from "styled-components";
-import { useBoardConfig } from "react-sync-board";
 
 
 import Modal from "../../ui/Modal";
 import Modal from "../../ui/Modal";
 
 
-import { getBestTranslationFromConfig } from "../../utils/api";
+import GameInformation from "./GameInformation";
 
 
 const Kbd = styled.kbd`
 const Kbd = styled.kbd`
   background-color: #eee;
   background-color: #eee;
@@ -25,50 +23,11 @@ const Kbd = styled.kbd`
 `;
 `;
 
 
 const InfoModal = ({ show, setShow }) => {
 const InfoModal = ({ show, setShow }) => {
-  const { t, i18n } = useTranslation();
-
-  const [info, setInfo] = React.useState("");
-
-  const [boardConfig] = useBoardConfig();
-
-  const translation = React.useMemo(
-    () => getBestTranslationFromConfig(boardConfig, i18n.languages),
-    [boardConfig, i18n.languages]
-  );
-
-  useAsyncEffect(
-    async (isMounted) => {
-      const marked = (await import("marked")).default;
-      if (!isMounted()) return;
-
-      const renderer = new marked.Renderer();
-      renderer.link = (href, title, text) => {
-        return `<a target="_blank" rel="noopener" href="${href}" title="${title}">${text}</a>`;
-      };
-      setInfo(
-        marked(translation.description || "", {
-          renderer: renderer,
-        })
-      );
-    },
-    [setInfo, translation.description]
-  );
+  const { t } = useTranslation();
 
 
   return (
   return (
     <Modal title={t("Help & info")} setShow={setShow} show={show}>
     <Modal title={t("Help & info")} setShow={setShow} show={show}>
-      <header>
-        <h3>{t("Game information")}</h3>
-      </header>
-      <section>
-        {translation.description && (
-          <div
-            dangerouslySetInnerHTML={{
-              __html: info,
-            }}
-          ></div>
-        )}
-        {!translation.description && <div>{t("No information")}</div>}
-      </section>
+      <GameInformation />
       <header>
       <header>
         <h3>{t("Board interactions")}</h3>
         <h3>{t("Board interactions")}</h3>
       </header>
       </header>

+ 4 - 3
src/views/BoardView/NavBar.jsx

@@ -132,7 +132,7 @@ const StyledNavBar = styled.div.attrs(() => ({ className: "nav" }))`
   }
   }
 `;
 `;
 
 
-const NavBar = ({ editMode }) => {
+const NavBar = ({ editMode, title }) => {
   const { t, i18n } = useTranslation();
   const { t, i18n } = useTranslation();
   const { sessionId: room } = useSession();
   const { sessionId: room } = useSession();
   const [boardConfig] = useBoardConfig();
   const [boardConfig] = useBoardConfig();
@@ -147,7 +147,6 @@ const NavBar = ({ editMode }) => {
   const [showChangeGameModal, setShowChangeGameModal] = React.useState(false);
   const [showChangeGameModal, setShowChangeGameModal] = React.useState(false);
   const [showInfoModal, setShowInfoModal] = React.useState(false);
   const [showInfoModal, setShowInfoModal] = React.useState(false);
   const [showLink, setShowLink] = React.useState(false);
   const [showLink, setShowLink] = React.useState(false);
-  const [isBeta] = useLocalStorage("isBeta", false);
 
 
   const translation = React.useMemo(
   const translation = React.useMemo(
     () => getBestTranslationFromConfig(boardConfig, i18n.languages),
     () => getBestTranslationFromConfig(boardConfig, i18n.languages),
@@ -232,7 +231,9 @@ const NavBar = ({ editMode }) => {
         </div>
         </div>
 
 
         <div className="nav-center">
         <div className="nav-center">
-          <h3>{translation.name ? translation.name : "Air Board Game"}</h3>
+          <h3>
+            {translation.name ? translation.name : title || "Air Board Game"}
+          </h3>
         </div>
         </div>
 
 
         <div className="nav-right">
         <div className="nav-right">

+ 11 - 18
src/views/BoardView/SelectedItemsPane.jsx

@@ -87,14 +87,16 @@ const SelectedItemsPane = ({ hideMenu = false }) => {
 
 
   const parsedAvailableActions = React.useMemo(
   const parsedAvailableActions = React.useMemo(
     () =>
     () =>
-      availableActions.map(({ name, args }) => {
-        const action = { ...actionMap[name] };
-        action.action = action.action(args);
-        action.label = action.label(args);
-        action.uid = smallUid();
-        return action;
-      }),
-    [actionMap, availableActions]
+      availableActions
+        .map(({ name, args }) => {
+          const action = { ...actionMap[name] };
+          action.action = action.action(args);
+          action.label = action.label(args);
+          action.uid = smallUid();
+          return action;
+        })
+        .filter(({ multiple }) => !multiple || selectedItems.length > 1),
+    [actionMap, availableActions, selectedItems]
   );
   );
 
 
   const showEditButton =
   const showEditButton =
@@ -171,16 +173,7 @@ const SelectedItemsPane = ({ hideMenu = false }) => {
       )}
       )}
       {!boardState.selecting &&
       {!boardState.selecting &&
         parsedAvailableActions.map(
         parsedAvailableActions.map(
-          ({
-            label,
-            action,
-            multiple,
-            edit: onlyEdit,
-            shortcut,
-            icon,
-            uid,
-          }) => {
-            if (multiple && selectedItems.length < 2) return null;
+          ({ label, action, edit: onlyEdit, shortcut, icon, uid }) => {
             if (onlyEdit && !showEdit) return null;
             if (onlyEdit && !showEdit) return null;
             return (
             return (
               <button
               <button

+ 20 - 35
src/views/BoardView/WelcomeModal.jsx

@@ -1,12 +1,11 @@
 import React from "react";
 import React from "react";
 import { useTranslation } from "react-i18next";
 import { useTranslation } from "react-i18next";
-import { Trans } from "react-i18next";
 import styled from "styled-components";
 import styled from "styled-components";
 import { toast } from "react-toastify";
 import { toast } from "react-toastify";
 
 
 import Modal from "../../ui/Modal";
 import Modal from "../../ui/Modal";
 
 
-import useSession from "../../hooks/useSession";
+import GameInformation from "./GameInformation";
 
 
 const StyledUrl = styled.div`
 const StyledUrl = styled.div`
   background-color: var(--color-midGrey);
   background-color: var(--color-midGrey);
@@ -32,20 +31,12 @@ const StyledUrl = styled.div`
 const WelcomeModal = ({ show, setShow, welcome = true }) => {
 const WelcomeModal = ({ show, setShow, welcome = true }) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const currentUrl = window.location.href;
   const currentUrl = window.location.href;
-  const inputRef = React.useRef();
 
 
-  const { sessionId: room } = useSession();
-
-  const handleCopy = () => {
-    inputRef.current.style.display = "block";
-    inputRef.current.select();
-    document.execCommand("copy");
-    inputRef.current.style.display = "none";
+  const handleCopy = async () => {
+    await navigator.clipboard.writeText(window.location.href);
     toast.info(t("Url copied to clipboard!"), { autoClose: 1000 });
     toast.info(t("Url copied to clipboard!"), { autoClose: 1000 });
   };
   };
 
 
-  const meetUrl = `https://meet.jit.si/airboardgame__${room}`;
-
   return (
   return (
     <Modal
     <Modal
       title={welcome ? t("Ready to play ?") : t("Invite more player")}
       title={welcome ? t("Ready to play ?") : t("Invite more player")}
@@ -65,31 +56,25 @@ const WelcomeModal = ({ show, setShow, welcome = true }) => {
             onClick={handleCopy}
             onClick={handleCopy}
           />
           />
         </StyledUrl>
         </StyledUrl>
+        <p>
+          {t(
+            "Share this link with your friends and start playing immediately."
+          )}
+        </p>
 
 
-        <Trans i18nKey="welcomeTip">
-          <p>
-            Tip: Use an audio conferencing system to talk with other players,
-            like <a href={meetUrl}>Jitsi</a> (free & open-source).
-          </p>
-        </Trans>
+        {welcome && (
+          <button
+            onClick={() => {
+              setShow(false);
+            }}
+            className="button success"
+            style={{ margin: "1em auto" }}
+          >
+            {t("I want to play...")}
+          </button>
+        )}
       </section>
       </section>
-      {welcome && (
-        <button
-          onClick={() => {
-            setShow(false);
-          }}
-          className="button success"
-          style={{ margin: "1em auto" }}
-        >
-          {t("I want to play...")}
-        </button>
-      )}
-      <input
-        value={currentUrl}
-        readOnly
-        ref={inputRef}
-        style={{ display: "none" }}
-      />
+      {welcome && <GameInformation />}
     </Modal>
     </Modal>
   );
   );
 };
 };

+ 0 - 1
src/views/Brand.jsx

@@ -50,7 +50,6 @@ const Brand = () => (
     <h1 className="short">
     <h1 className="short">
       <Link to="/">ABG</Link>
       <Link to="/">ABG</Link>
     </h1>
     </h1>
-    <span>Beta</span>
   </StyledBrand>
   </StyledBrand>
 );
 );
 
 

+ 12 - 0
src/views/GameListView.jsx

@@ -2,6 +2,7 @@ import React from "react";
 import { useTranslation, Trans } from "react-i18next";
 import { useTranslation, Trans } from "react-i18next";
 import styled from "styled-components";
 import styled from "styled-components";
 import { useQuery } from "react-query";
 import { useQuery } from "react-query";
+import { NavLink } from "react-router-dom";
 
 
 import SliderRange from "../ui/SliderRange";
 import SliderRange from "../ui/SliderRange";
 import Spinner from "../ui/Spinner";
 import Spinner from "../ui/Spinner";
@@ -74,8 +75,15 @@ const Filter = styled.div`
     width: 100%;
     width: 100%;
     font-size: 3.5vw;
     font-size: 3.5vw;
     padding: 0.5em;
     padding: 0.5em;
+    padding-bottom: 0;
     margin: 0;
     margin: 0;
+    line-height: 1em;
   }
   }
+
+  & .subincentive {
+    font-size: 1.3em;
+  }
+
   @media screen and (max-width: 1024px) {
   @media screen and (max-width: 1024px) {
     & .incentive {
     & .incentive {
       font-size: 32px;
       font-size: 32px;
@@ -271,6 +279,10 @@ const GameListView = () => {
       <Content>
       <Content>
         <Filter>
         <Filter>
           <h2 className="incentive">{t("Start a game now")}</h2>
           <h2 className="incentive">{t("Start a game now")}</h2>
+          <h3 className="subincentive">
+            {t("Or")}{" "}
+            <NavLink to={"/room/"}>{t("start a multi room session")}</NavLink>
+          </h3>
           <input
           <input
             type="search"
             type="search"
             id="game-search"
             id="game-search"

+ 78 - 0
src/views/RoomView/InviteModal.jsx

@@ -0,0 +1,78 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+import styled from "styled-components";
+import { toast } from "react-toastify";
+
+import Modal from "../../ui/Modal";
+
+const StyledUrl = styled.div`
+  background-color: var(--color-midGrey);
+  padding: 0.5em;
+  border-radius: 2px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 2em;
+
+  & span {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+
+  & img {
+    margin-left: 1em;
+    cursor: pointer;
+  }
+`;
+
+const InviteModal = ({ show, setShow, welcome = true }) => {
+  const { t } = useTranslation();
+  const currentUrl = window.location.href;
+
+  const handleCopy = async () => {
+    await navigator.clipboard.writeText(window.location.href);
+    toast.info(t("Url copied to clipboard!"), { autoClose: 1000 });
+  };
+
+  return (
+    <Modal
+      title={welcome ? t("Ready to play ?") : t("Invite more player")}
+      setShow={setShow}
+      show={show}
+    >
+      <header>
+        <h3>{t("Share this link with your friends")}</h3>
+      </header>
+      <section>
+        <StyledUrl>
+          <span>{currentUrl}</span>
+          <img
+            className="copy"
+            src="https://icongr.am/entypo/copy.svg?size=22&color=888886"
+            alt={t("Copy")}
+            onClick={handleCopy}
+          />
+        </StyledUrl>
+        <p>
+          {t(
+            "Share this link with your friends and start playing immediately."
+          )}
+        </p>
+        {welcome && (
+          <button
+            onClick={() => {
+              setShow(false);
+            }}
+            className="button success"
+            style={{ margin: "1em auto" }}
+          >
+            {t("Let's go...")}
+          </button>
+        )}
+      </section>
+    </Modal>
+  );
+};
+
+export default InviteModal;

+ 8 - 5
src/views/RoomView/RoomNavBar.jsx

@@ -2,15 +2,16 @@ import React from "react";
 
 
 import styled from "styled-components";
 import styled from "styled-components";
 import { useTranslation } from "react-i18next";
 import { useTranslation } from "react-i18next";
-
-import useLocalStorage from "../../hooks/useLocalStorage";
 import { useWire } from "react-sync-board";
 import { useWire } from "react-sync-board";
 
 
 import Touch from "../../ui/Touch";
 import Touch from "../../ui/Touch";
 import UserList from "../../users/UserList";
 import UserList from "../../users/UserList";
 import WebConferenceButton from "../../webconf/WebConferenceButton";
 import WebConferenceButton from "../../webconf/WebConferenceButton";
+import InviteModal from "./InviteModal";
 
 
 import Brand from "../Brand";
 import Brand from "../Brand";
+import WelcomeModal from "../BoardView/WelcomeModal";
+import { useLocation } from "react-router";
 
 
 const StyledNavBar = styled.div.attrs(() => ({ className: "nav" }))`
 const StyledNavBar = styled.div.attrs(() => ({ className: "nav" }))`
   position: fixed;
   position: fixed;
@@ -119,7 +120,8 @@ const StyledNavBar = styled.div.attrs(() => ({ className: "nav" }))`
 const RoomNavBar = () => {
 const RoomNavBar = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const { room } = useWire("room");
   const { room } = useWire("room");
-  const [showLink, setShowLink] = React.useState(false);
+  const { state: { showInvite: initialShowInvite } = {} } = useLocation();
+  const [showInvite, setShowInvite] = React.useState(initialShowInvite);
 
 
   return (
   return (
     <StyledNavBar>
     <StyledNavBar>
@@ -128,7 +130,7 @@ const RoomNavBar = () => {
       </div>
       </div>
 
 
       <div className="nav-center">
       <div className="nav-center">
-        <h3>Air Board Game</h3>
+        <h3>{t("Choose your board")}</h3>
       </div>
       </div>
 
 
       <div className="nav-right">
       <div className="nav-right">
@@ -136,7 +138,7 @@ const RoomNavBar = () => {
         {
         {
           <Touch
           <Touch
             onClick={() => {
             onClick={() => {
-              setShowLink(true);
+              setShowInvite(true);
             }}
             }}
             icon="add-user"
             icon="add-user"
             title={t("Invite more player")}
             title={t("Invite more player")}
@@ -144,6 +146,7 @@ const RoomNavBar = () => {
         }
         }
         <WebConferenceButton room={room} />
         <WebConferenceButton room={room} />
       </div>
       </div>
+      <InviteModal show={showInvite} setShow={setShowInvite} />
     </StyledNavBar>
     </StyledNavBar>
   );
   );
 };
 };