├── Procfile
├── client
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── pictures
│ │ ├── menu.png
│ │ ├── userPage.png
│ │ ├── gameDetails.png
│ │ ├── gameOverview.png
│ │ └── loginScreen.png
│ ├── api
│ │ ├── fetchMechanics.js
│ │ ├── GetDataFromApi.js
│ │ ├── fetchGames.js
│ │ ├── fetchWishlist.js
│ │ └── fetchUser.js
│ ├── animations
│ │ ├── slideIn.js
│ │ ├── morph.js
│ │ ├── textFadeIn.js
│ │ └── alert.js
│ ├── components
│ │ ├── User-page.js
│ │ │ ├── UserName.js
│ │ │ ├── CompareFunction.js
│ │ │ ├── UserNameArea.js
│ │ │ ├── MainContent.js
│ │ │ ├── Title.js
│ │ │ ├── RoundPicture.js
│ │ │ ├── UserNavigation.js
│ │ │ ├── NameText.js
│ │ │ ├── AmountGames.js
│ │ │ ├── RecentlyAdded.js
│ │ │ ├── NavBar.js
│ │ │ └── Placeholder.js
│ │ ├── popup-card
│ │ │ ├── Grid.js
│ │ │ ├── GameName.js
│ │ │ ├── Players.js
│ │ │ ├── TrimText.js
│ │ │ ├── RatingContainer.js
│ │ │ ├── DescriptionContainer.js
│ │ │ ├── DetailButton.js
│ │ │ ├── ModalContainer.js
│ │ │ ├── RemoveButton.js
│ │ │ ├── GamePositioned.js
│ │ │ ├── AddButtonWishlist.js
│ │ │ ├── ConfirmationMessage.js
│ │ │ ├── AddButtonCollection.js
│ │ │ ├── CardBack.js
│ │ │ └── CardModal.js
│ │ ├── IconButton.js
│ │ ├── user-management
│ │ │ ├── Form.js
│ │ │ ├── InputField.js
│ │ │ ├── LoginForm.js
│ │ │ └── RegisterForm.js
│ │ ├── library
│ │ │ ├── BrowseEmpty.js
│ │ │ ├── NavigationMenuButton.js
│ │ │ ├── CollectionItem.js
│ │ │ ├── OptionBox.js
│ │ │ ├── SortModal.js
│ │ │ ├── SearchBar.js
│ │ │ ├── CollectionGrid.js
│ │ │ └── LibraryNav.js
│ │ ├── Header.js
│ │ └── BurgerMenuList.js
│ ├── stories
│ │ ├── Collection.stories.js
│ │ ├── SortModal.stories.js
│ │ ├── Searchbar.stories.js
│ │ ├── LibraryNav.stories.js
│ │ ├── AddButton.stories.js
│ │ ├── Header.stories.js
│ │ ├── OptionBox.stories.js
│ │ ├── IconButtons.stories.js
│ │ ├── CollectionItem.stories.js
│ │ ├── Icons.stories.js
│ │ ├── NavigationMenu.stories.js
│ │ └── DetailCard.stories.js
│ ├── App.test.js
│ ├── themes
│ │ └── dark.js
│ ├── pages
│ │ ├── UserFriends.js
│ │ ├── NotFound.js
│ │ ├── UserStatistics.js
│ │ ├── Impressum.js
│ │ ├── login.js
│ │ ├── register.js
│ │ ├── LibraryBrowse.js
│ │ ├── UserOverview.js
│ │ ├── LibraryGames.js
│ │ ├── LibraryWishlist.js
│ │ ├── Library.js
│ │ └── User.js
│ ├── icons
│ │ ├── BurgerMenuIcon.js
│ │ ├── DoubleArrow.js
│ │ ├── StarRatingEmpty.js
│ │ ├── Magnifier.js
│ │ ├── StarRatingFull.js
│ │ ├── Clear.js
│ │ ├── VerticalPoints.js
│ │ ├── Logout.js
│ │ ├── Star.js
│ │ ├── Dice.js
│ │ ├── Upload.js
│ │ ├── Person.js
│ │ ├── Wishlist.js
│ │ ├── WoodSign.js
│ │ └── Hero.js
│ ├── index.js
│ ├── GlobalStyles.js
│ ├── hooks.js
│ ├── App.js
│ └── serviceWorker.js
├── .storybook
│ ├── addons.js
│ ├── preview-head.html
│ └── config.js
└── package.json
├── app.json
├── .gitignore
├── lib
├── sessions.js
├── database.js
├── users.js
├── games.js
└── wishlist.js
├── README.md
├── now.json
├── LICENSE
├── package.json
└── index.js
/Procfile:
--------------------------------------------------------------------------------
1 | web: node index.js
2 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/public/logo512.png
--------------------------------------------------------------------------------
/client/src/pictures/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/src/pictures/menu.png
--------------------------------------------------------------------------------
/client/src/pictures/userPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/src/pictures/userPage.png
--------------------------------------------------------------------------------
/client/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@storybook/addon-actions/register";
2 | import "@storybook/addon-links/register";
3 |
--------------------------------------------------------------------------------
/client/src/pictures/gameDetails.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/src/pictures/gameDetails.png
--------------------------------------------------------------------------------
/client/src/pictures/gameOverview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/src/pictures/gameOverview.png
--------------------------------------------------------------------------------
/client/src/pictures/loginScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsmunsch/boardhero/HEAD/client/src/pictures/loginScreen.png
--------------------------------------------------------------------------------
/client/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/src/api/fetchMechanics.js:
--------------------------------------------------------------------------------
1 | export function getMechanics() {
2 | return fetch(`/api/mechanics`).then(response => response.json());
3 | }
4 |
--------------------------------------------------------------------------------
/client/src/api/GetDataFromApi.js:
--------------------------------------------------------------------------------
1 | export function getGamesFromApi(name) {
2 | return fetch(`/api/search?name=${name}`).then(response => response.json());
3 | }
4 |
--------------------------------------------------------------------------------
/client/src/animations/slideIn.js:
--------------------------------------------------------------------------------
1 | import { keyframes } from "styled-components";
2 |
3 | export const slideIn = keyframes`
4 | 0% { width: 0px;}
5 | 100% { width: 100%}
6 | `;
7 |
--------------------------------------------------------------------------------
/client/src/animations/morph.js:
--------------------------------------------------------------------------------
1 | import { keyframes } from "styled-components";
2 |
3 | export const morph = keyframes`
4 | 0% { transform: translateX(-100%)}
5 | 100% { transform: translateX(0)}
6 | `;
7 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/UserName.js:
--------------------------------------------------------------------------------
1 | import react from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.section`
5 | display: flex;
6 | flex-direction: column;
7 | `;
8 |
--------------------------------------------------------------------------------
/client/src/stories/Collection.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Library from "../pages/Library";
3 |
4 | export default {
5 | title: "CollectionPage"
6 | };
7 |
8 | export const CollectionPage = () => ;
9 |
--------------------------------------------------------------------------------
/client/src/stories/SortModal.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SortModal from "../components/SortModal";
3 |
4 | export default {
5 | title: "SortModal"
6 | };
7 |
8 | export const twoSortModal = () => ;
9 |
--------------------------------------------------------------------------------
/client/src/stories/Searchbar.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SearchBar from "../components/SearchBar";
3 |
4 | export default {
5 | title: "SearchBarwf"
6 | };
7 |
8 | export const SearchBarPreview = () => ;
9 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/Grid.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Grid = styled.section`
4 | display: grid;
5 | grid-template-rows: 60px 1fr;
6 | margin-left: 15px;
7 | `;
8 |
9 | export default Grid;
10 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/CompareFunction.js:
--------------------------------------------------------------------------------
1 | export default function compare(a, b) {
2 | if (a.date_added > b.date_added) {
3 | return -1;
4 | }
5 | if (a.date_added < b.date_added) {
6 | return 1;
7 | }
8 | return 0;
9 | }
10 |
--------------------------------------------------------------------------------
/client/src/stories/LibraryNav.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import LibraryNav from "../components/LibraryNav";
3 |
4 | export default {
5 | title: "LibraryNav"
6 | };
7 |
8 | export const LibraryNavbar = () => {
9 | return ;
10 | };
11 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/UserNameArea.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const UserNameArea = styled.div`
4 | height: 13%;
5 | display: flex;
6 | align-items: center;
7 | justify-content: space-around;
8 | `;
9 |
10 | export default UserNameArea;
11 |
--------------------------------------------------------------------------------
/client/src/components/IconButton.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const IconButton = styled.button`
4 | border: none;
5 | outline: none;
6 | fill: white;
7 | width: 40px;
8 | height: 40px;
9 | background: none;
10 | `;
11 |
12 | export default IconButton;
13 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/MainContent.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const MainContent = styled.section`
4 | display: flex;
5 | flex-direction: column;
6 | overflow: auto;
7 | flex-grow: 1;
8 | max-height: 50%;
9 | `;
10 |
11 | export default MainContent;
12 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/Title.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Title = styled.div`
4 | display: flex;
5 | font-size: 1.3em;
6 | margin-top: 20px;
7 | justify-content: flex-start;
8 | margin-left: 8%;
9 | min-height: 15px;
10 | `;
11 |
12 | export default Title;
13 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/GameName.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const GameName = styled.h1`
4 | color: ${props => props.theme.brightEffect};
5 | text-align: center;
6 | z-index: 20;
7 | font-size: 1.8em;
8 | margin: 30px 0 15px 0;
9 | `;
10 |
11 | export default GameName;
12 |
--------------------------------------------------------------------------------
/client/src/themes/dark.js:
--------------------------------------------------------------------------------
1 | const darkTheme = {
2 | main: "#22262F",
3 | lightFont: "#fff",
4 | darkFont: "#9F9898",
5 | highlight: "#6F7477",
6 | lightBackground: "#707070",
7 | accent: "#404345",
8 | brightEffect: "#F0A42A",
9 | mainLight: "#2b303b"
10 | };
11 |
12 | export default darkTheme;
13 |
--------------------------------------------------------------------------------
/client/src/components/user-management/Form.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Form = styled.form`
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | margin: auto;
9 | width: 80%;
10 | max-width: 400px;
11 | `;
12 |
13 | export default Form;
14 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/Players.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Players = styled.div`
4 | display: flex;
5 | justify-content: flex-start;
6 | flex-direction: column;
7 | margin: 0px 4px;
8 | fill: white;
9 | color: white;
10 | font-size: 1.4em;
11 | `;
12 |
13 | export default Players;
14 |
--------------------------------------------------------------------------------
/client/src/stories/AddButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import AddButton from "../components/AddButtonCollection";
3 | import Star from "../icons/Star";
4 |
5 | export default {
6 | title: "AddToLibrary"
7 | };
8 |
9 | export const AddCollection = () => (
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/client/src/components/library/BrowseEmpty.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const BrowseEmpty = styled.div`
4 | display: flex;
5 | font-size: 26px;
6 | color: white;
7 | align-items: center;
8 | text-align: center;
9 | height: 60vh;
10 | justify-content: center;
11 | padding: 25px;
12 | `;
13 |
14 | export default BrowseEmpty;
15 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/RoundPicture.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const PictureContainer = styled.img`
4 | width: 90px;
5 | height: 90px;
6 | border-radius: 50%;
7 | border: 2px solid ${props => props.theme.brightEffect};
8 | box-shadow: 2px 2px 2px 2px rgba(0, 0, 0, 0.15);
9 | `;
10 |
11 | export default PictureContainer;
12 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/TrimText.js:
--------------------------------------------------------------------------------
1 | export default function TrimText(str, length, ending) {
2 | if (length == null) {
3 | length = 100;
4 | }
5 | if (ending == null) {
6 | ending = "...";
7 | }
8 | if (str.length > length) {
9 | return str.substring(0, length - ending.length) + ending;
10 | } else {
11 | return str;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/RatingContainer.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const RatingContainer = styled.div`
4 | position: absolute;
5 | top: 65px;
6 | width: 100%;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | color: white;
11 | margin-left: 10px;
12 | `;
13 |
14 | export default RatingContainer;
15 |
--------------------------------------------------------------------------------
/client/src/animations/textFadeIn.js:
--------------------------------------------------------------------------------
1 | import { keyframes } from "styled-components";
2 |
3 | export const textFadeIn = keyframes`
4 | 0% {
5 | opacity: 0;
6 | }
7 | 30% {
8 | opacity: 0;
9 | }
10 | 50% {
11 | opacity: 1;
12 | }
13 | 75% {
14 | opacity: 0;
15 | }
16 | 100% {
17 | opacity: 0;
18 | }
19 | }
20 | `;
21 |
--------------------------------------------------------------------------------
/client/src/pages/UserFriends.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Placeholder from "../components/User-page.js/Placeholder";
3 |
4 | export default function UserFriends() {
5 | return (
6 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/client/src/stories/Header.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "../components/Header";
3 | import SearchBar from "../components/SearchBar";
4 |
5 | export default {
6 | title: "Header"
7 | };
8 |
9 | export const HeaderBar = () => ;
10 |
11 | export const HeaderBarWithSearch = () => (
12 |
15 | );
16 |
--------------------------------------------------------------------------------
/client/src/stories/OptionBox.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import OptionBox from "../components/OptionBox";
3 | import Header from "../components/Header";
4 |
5 | export default {
6 | title: "OptionBox"
7 | };
8 |
9 | export const SortFilterBox = () => ;
10 |
11 | export const HeaderWithOptionBox = () => (
12 | <>
13 |
14 |
15 | >
16 | );
17 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boardhero",
3 | "scripts": {},
4 | "env": {
5 | "DB_NAME": {
6 | "required": true
7 | },
8 | "DB_URL": {
9 | "required": true
10 | }
11 | },
12 | "formation": {
13 | "web": {
14 | "quantity": 1
15 | }
16 | },
17 | "addons": [],
18 | "buildpacks": [
19 | {
20 | "url": "heroku/nodejs"
21 | }
22 | ],
23 | "stack": "heroku-18"
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/icons/BurgerMenuIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function BurgerMenuIcon() {
4 | return (
5 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/icons/DoubleArrow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function DoubleArrow() {
4 | return (
5 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | **/node_modules
5 | **/.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | **/build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | .env
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | .FakeData.js
26 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/UserNavigation.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { BrowserRouter as Router, Link } from "react-router-dom";
3 |
4 | const StyledLink = styled(Link)`
5 | text-decoration: none;
6 | color: inherit;
7 | padding: 10px;
8 | font-size: 1.1em;
9 | border-bottom: ${props =>
10 | props.selected ? `3px solid ${props.theme.brightEffect}` : "none"};
11 | `;
12 |
13 | export default StyledLink;
14 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/DescriptionContainer.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const DescriptionContainer = styled.div`
4 | display: flex;
5 | fill: white;
6 | color: white;
7 | justify-content: center;
8 | margin: 0px 20px 0 10px;
9 | max-height: 40vh;
10 | @media (min-height: 750px) {
11 | max-height: 35vh;
12 | }
13 | overflow-x: scroll;
14 | font-size: 1.2em;
15 | `;
16 |
17 | export default DescriptionContainer;
18 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import * as serviceWorker from "./serviceWorker";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/lib/sessions.js:
--------------------------------------------------------------------------------
1 | const uuidv1 = require("uuid/v1");
2 |
3 | const sessions = {};
4 |
5 | function createSession(user) {
6 | const id = uuidv1();
7 | sessions[id] = user;
8 | return id;
9 | }
10 |
11 | function getUserBySession(id) {
12 | return sessions[id];
13 | }
14 |
15 | function deleteSession(id) {
16 | delete sessions[id];
17 | }
18 |
19 | exports.createSession = createSession;
20 | exports.getUserBySession = getUserBySession;
21 | exports.deleteSession = deleteSession;
22 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/DetailButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import DoubleArrow from "../../icons/DoubleArrow";
4 |
5 | const ButtonContainer = styled.button`
6 | background: transparent;
7 | fill: white;
8 | border: none;
9 | `;
10 |
11 | export default function DetailButton({ onClick }) {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/components/user-management/InputField.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const InputField = styled.input`
4 | padding: 10px;
5 | margin-top: 10px;
6 | font-size: 1.2em;
7 | width: inherit;
8 | border-radius: 2px;
9 | outline: none;
10 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.15);
11 | border: none;
12 | background-color: ${props =>
13 | props.highlight ? props.theme.brightEffect : "white"};
14 | -webkit-appearance: none;
15 | `;
16 |
17 | export default InputField;
18 |
--------------------------------------------------------------------------------
/client/src/GlobalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 |
3 | const GlobalStyles = createGlobalStyle`
4 | html, body {
5 | margin: 0;
6 | padding: 0;
7 | }
8 | *, *::after, *::before {
9 | box-sizing: border-box;
10 | }
11 | body {
12 | font-family: Roboto, sans-serif;
13 | height: 100vh;
14 | width: 100vw;
15 | background: ${props => props.theme.main};
16 | }
17 |
18 | button {
19 | cursor: pointer;
20 | }
21 | `;
22 |
23 | export default GlobalStyles;
24 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/ModalContainer.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const ModalContainer = styled.div`
4 | width: 320px;
5 | height: 480px;
6 | right: 0;
7 | left: 0;
8 | margin-left: auto;
9 | margin-right: auto;
10 | top: 50%;
11 | margin-top: -240px;
12 | background: ${props => props.theme.main};
13 | position: fixed;
14 | z-index: 2;
15 | border: 4px solid white;
16 | border-radius: 15px;
17 | backdrop-filter: blur(2px);
18 | `;
19 |
20 | export default ModalContainer;
21 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/RemoveButton.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const RemoveButton = styled.button`
4 | border-radius: 15px;
5 | background-color: ${props => props.theme.brightEffect};
6 | position: absolute;
7 | bottom: 10px;
8 | right: 50%;
9 | margin-right: -65px;
10 | height: 40px;
11 | width: 130px;
12 | border: none;
13 | outline: none;
14 | box-shadow: 4px 4px 6px 4px rgba(0, 0, 0, 0.15);
15 | font-size: 1.1em;
16 | color: white;
17 | `;
18 |
19 | export default RemoveButton;
20 |
--------------------------------------------------------------------------------
/client/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const FlexContainer = styled.section`
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | width: 100vw;
9 | height: 100vh;
10 | `;
11 |
12 | const Styledh1 = styled.h1`
13 | color: ${props => props.theme.brightEffect};
14 | font-size: 2em;
15 | `;
16 |
17 | export default function NotFound() {
18 | return (
19 |
20 | Error: 404 Not found
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/client/src/pages/UserStatistics.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { getMechanics } from "../api/fetchMechanics";
3 | import Placeholder from "../components/User-page.js/Placeholder";
4 |
5 | export default function UserStatistics() {
6 | const [mechanics, setMechanics] = useState(null);
7 | useEffect(() => {
8 | getMechanics().then(mechanics => setMechanics(mechanics));
9 | }, []);
10 |
11 | return (
12 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/GamePositioned.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { ImageContainer } from "../library/CollectionItem";
3 |
4 | export const GamePositioned = styled(ImageContainer)`
5 | position: absolute;
6 | top: -60px;
7 | margin-top: 0;
8 | left: 50%;
9 | margin-left: -50px;
10 | height: 120px;
11 | width: 120px;
12 | @media (min-width: 600px) {
13 | width: 120px;
14 | height: 120px;
15 | margin-left: -60px;
16 | }
17 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.15);
18 | `;
19 |
20 | export default GamePositioned;
21 |
--------------------------------------------------------------------------------
/client/src/icons/StarRatingEmpty.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function StarRatingEmpty() {
4 | return (
5 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/lib/database.js:
--------------------------------------------------------------------------------
1 | const { MongoClient } = require("mongodb");
2 | let db = null;
3 | async function initDatabase(url, dbName) {
4 | const client = new MongoClient(url, {
5 | useNewUrlParser: true,
6 | useUnifiedTopology: true
7 | });
8 | await client.connect();
9 | db = client.db(process.env.DB_NAME);
10 | }
11 |
12 | async function getCollections(collectionName) {
13 | if (!db) {
14 | throw new Error("You have to initialize the database first");
15 | }
16 | return db.collection(collectionName);
17 | }
18 |
19 | exports.initDatabase = initDatabase;
20 | exports.getCollection = getCollections;
21 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/api/fetchGames.js:
--------------------------------------------------------------------------------
1 | export async function getGamesCollection() {
2 | return fetch("/api/games").then(response => response.json());
3 | }
4 |
5 | export function newGame(game) {
6 | return fetch("/api/games", {
7 | method: "POST",
8 | headers: {
9 | "Content-Type": "application/json"
10 | },
11 |
12 | body: JSON.stringify(game)
13 | });
14 | }
15 |
16 | export function removeGameEntry(game) {
17 | return fetch("/api/gamesremove", {
18 | method: "POST",
19 | headers: {
20 | "Content-Type": "application/json"
21 | },
22 | body: JSON.stringify(game)
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/api/fetchWishlist.js:
--------------------------------------------------------------------------------
1 | export function getWishlistCollection() {
2 | return fetch("/api/wishlist").then(response => response.json());
3 | }
4 |
5 | export function newWishlistEntry(game) {
6 | return fetch("/api/wishlist", {
7 | method: "POST",
8 | headers: {
9 | "Content-Type": "application/json"
10 | },
11 | body: JSON.stringify(game)
12 | });
13 | }
14 |
15 | export function removeGameWishlist(game) {
16 | return fetch("/api/wishlistremove", {
17 | method: "POST",
18 | headers: {
19 | "Content-Type": "application/json"
20 | },
21 | body: JSON.stringify(game)
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/NameText.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | max-width: 70%;
8 | `;
9 |
10 | const Name = styled.span`
11 | font-style: bold;
12 | margin: 0;
13 | font-size: 1.4em;
14 | `;
15 |
16 | const Description = styled.div`
17 | font-weight: 100;
18 | `;
19 |
20 | export default function NameText({ name, description }) {
21 | return (
22 |
23 | {name}
24 | {description}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/client/src/components/library/NavigationMenuButton.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const NavigationMenuButton = styled.button`
4 | display: flex;
5 | width: inherit;
6 | height: 8vh;
7 | align-items: center;
8 | justify-content: flex-start;
9 | border: none;
10 | border-left: ${props => (props.selected ? "3px solid #F0A42A" : "none")};
11 | background-color: ${props =>
12 | props.selected ? props.theme.highlight : props.theme.accent};
13 | color: ${props =>
14 | props.selected ? props.theme.lightFont : props.theme.darkFont};
15 | font-size: 20px;
16 | `;
17 |
18 | export default NavigationMenuButton;
19 |
--------------------------------------------------------------------------------
/client/src/icons/Magnifier.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Magnifier() {
4 | return (
5 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/AmountGames.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | max-width: 70%;
8 | margin-left: 30px;
9 | `;
10 |
11 | const Amount = styled.span`
12 | font-style: bold;
13 | margin: 0;
14 | font-size: 1.2em;
15 | `;
16 |
17 | const Description = styled.div`
18 | font-weight: 100;
19 | `;
20 |
21 | export default function AmountGames({ name, description }) {
22 | return (
23 |
24 | {name}
25 | {description}
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/client/src/stories/IconButtons.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import IconButton from "../components/IconButton";
3 | import BurgerMenu from "../icons/BurgerMenuIcon";
4 | import Magnifier from "../icons/Magnifier";
5 | import VerticalPoints from "../icons/VerticalPoints";
6 |
7 | export default {
8 | title: "IconButtons"
9 | };
10 |
11 | export const BurgerMenuButton = () => (
12 |
13 |
14 |
15 | );
16 |
17 | export const SearchButton = () => (
18 |
19 |
20 |
21 | );
22 |
23 | export const OptionButton = () => (
24 |
25 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/client/src/icons/StarRatingFull.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function StarRatingFull() {
4 | return (
5 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/RecentlyAdded.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import CollectionItem from "../library/CollectionItem";
4 | import AmountGames from "./AmountGames";
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | align-items: center;
9 | margin-left: 7%;
10 | `;
11 |
12 | const Span = styled.span`
13 | margin-top: 20px;
14 | `;
15 |
16 | export default function RecentlyAdded({ src, name, description }) {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/client/src/icons/Clear.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function ClearIcon({ className }) {
5 | return (
6 |
17 | );
18 | }
19 |
20 | const StyledClearIcon = styled(ClearIcon)``;
21 | export default function Clear({ onClick }) {
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/client/src/icons/VerticalPoints.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function VerticalPoints() {
4 | return (
5 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/client/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { configure, addDecorator } from "@storybook/react";
3 | import GlobalStyles from "../src/GlobalStyles";
4 | import styled, { ThemeProvider } from "styled-components";
5 | import darkTheme from "../src/themes/dark";
6 |
7 | // automatically import all files ending in *.stories.js
8 | configure(require.context("../src/stories", true, /\.stories\.js$/), module);
9 |
10 | const Main = styled.div`
11 | margin: 10px;
12 | `;
13 |
14 | // add GlobalStyle for every story
15 | const GlobalStyleDecorator = storyFn => (
16 |
17 |
18 |
19 | {storyFn()}
20 |
21 |
22 | );
23 | addDecorator(GlobalStyleDecorator);
24 |
--------------------------------------------------------------------------------
/client/src/pages/Impressum.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "../components/Header";
3 | import styled from "styled-components";
4 |
5 | const Section = styled.section`
6 | display: flex;
7 | margin: 20px;
8 | color: white;
9 | flex-direction: column;
10 | `;
11 |
12 | export default function Impressum() {
13 | return (
14 | <>
15 |
16 |
25 | >
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/client/src/api/fetchUser.js:
--------------------------------------------------------------------------------
1 | export function fetchUser() {
2 | return fetch("/api/users").then(response => response.json());
3 | }
4 |
5 | export function createUser(user) {
6 | return fetch("/api/users", {
7 | method: "POST",
8 | headers: {
9 | "Content-Type": "application/json"
10 | },
11 |
12 | body: JSON.stringify(user)
13 | });
14 | }
15 |
16 | export function validateCredentials(user) {
17 | return fetch("/api/login", {
18 | method: "POST",
19 | headers: {
20 | "Content-Type": "application/json"
21 | },
22 |
23 | body: JSON.stringify(user)
24 | });
25 | }
26 |
27 | export function unsetUser() {
28 | return fetch("/api/logout", {
29 | method: "POST",
30 | headers: {
31 | "Content-Type": "application/json"
32 | }
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/client/src/icons/Logout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function LogoutIcon({ className }) {
5 | return (
6 |
16 | );
17 | }
18 |
19 | const LogoutIconStyled = styled(LogoutIcon)`
20 | fill: ${props =>
21 | props.selected ? props.theme.lightFont : props.theme.darkFont};
22 | height: 32px;
23 | width: 32px;
24 | margin-right: 5px;
25 | `;
26 |
27 | export default function Logout({ selected }) {
28 | return ;
29 | }
30 |
--------------------------------------------------------------------------------
/lib/users.js:
--------------------------------------------------------------------------------
1 | const { getCollection } = require("./database");
2 |
3 | const collectionName = "users";
4 |
5 | async function setUser(user) {
6 | const Users = await getCollection(collectionName);
7 | await Users.insertOne(user);
8 | }
9 |
10 | async function getUsers(email) {
11 | const UsersCollection = await getCollection(collectionName);
12 | const User = await UsersCollection.findOne({ email: email });
13 | return User;
14 | }
15 |
16 | async function validateUser(credentials) {
17 | const UsersCollection = await getCollection(collectionName);
18 | const Password = await UsersCollection.findOne({
19 | email: credentials.email,
20 | password: credentials.password
21 | });
22 | return Password;
23 | }
24 |
25 | exports.setUser = setUser;
26 | exports.getUsers = getUsers;
27 | exports.validateUser = validateUser;
28 |
--------------------------------------------------------------------------------
/client/src/components/library/CollectionItem.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | export const ImageContainer = styled.img`
5 | width: 88px;
6 | height: 88px;
7 | margin-top: 20px;
8 | @media (min-width: 600px) {
9 | width: 176px;
10 | height: 176px;
11 | margin-left: -35px;
12 | :hover {
13 | transform: scale(1.3);
14 | }
15 | }
16 | border: 4px solid white;
17 | object-fit: cover;
18 | object-position: center center;
19 | border-radius: 15px;
20 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.15);
21 | `;
22 |
23 | export default function CollectionItem({ game, src, onClick, className }) {
24 | return (
25 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/stories/CollectionItem.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CollectionItem from "../components/CollectionItem";
3 | import CollectionGrid from "../components/CollectionGrid";
4 |
5 | export default {
6 | title: "GameCollection"
7 | };
8 |
9 | export const singleGame = () => ;
10 |
11 | export const multipleGames = () => (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/lib/games.js:
--------------------------------------------------------------------------------
1 | const { getCollection } = require("./database");
2 |
3 | const collectionName = "games";
4 |
5 | async function setGames(game) {
6 | const gamesCollection = await getCollection(collectionName);
7 | const gameExists = await gamesCollection.findOne(game);
8 | if (gameExists) return;
9 | await gamesCollection.insertOne(game);
10 | }
11 |
12 | async function getGames(owner) {
13 | const gamesCollection = await getCollection(collectionName);
14 | const games = await gamesCollection.find({ owner: owner }).toArray();
15 | return games;
16 | }
17 |
18 | async function removeGame(name) {
19 | const gamesCollection = await getCollection(collectionName);
20 | const removedGame = await gamesCollection.deleteOne({ name: name });
21 | return removedGame;
22 | }
23 |
24 | exports.setGames = setGames;
25 | exports.getGames = getGames;
26 | exports.removeGame = removeGame;
27 |
--------------------------------------------------------------------------------
/client/src/stories/Icons.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import BurgerMenu from "../icons/BurgerMenuIcon";
3 | import Dice from "../icons/Dice";
4 | import Logout from "../icons/Logout";
5 | import Star from "../icons/Star";
6 | import Upload from "../icons/Upload";
7 | import Magnifier from "../icons/Magnifier";
8 | import VerticalPoints from "../icons/VerticalPoints";
9 | import DoubleArrow from "../icons/DoubleArrow";
10 |
11 | export default {
12 | title: "Icons"
13 | };
14 |
15 | export const MenuIcon = () => ;
16 | export const CollectionIcon = () => ;
17 | export const LogoutIcon = () => ;
18 | export const WishlistIcon = () => ;
19 | export const UploadIcon = () => ;
20 | export const SearchIcon = () => ;
21 | export const FilterIcon = () => ;
22 | export const DetailIcon = () => ;
23 |
--------------------------------------------------------------------------------
/client/src/pages/login.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Hero from "../icons/Hero";
4 | import LoginForm from "../components/user-management/LoginForm";
5 |
6 | const FlexContainer = styled.section`
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 | `;
12 | const VectorContainer = styled.div`
13 | width: 100%;
14 | height: 100%;
15 | margin: 5px;
16 | display: flex;
17 | justify-content: center;
18 | `;
19 |
20 | const Headline = styled.h1`
21 | text-align: center;
22 | color: white;
23 | font-size: 2.5em;
24 | `;
25 |
26 | export default function Login() {
27 | return (
28 |
29 | Boardhero
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/client/src/pages/register.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Hero from "../icons/Hero";
4 | import RegisterForm from "../components/user-management/RegisterForm";
5 |
6 | const FlexContainer = styled.section`
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 | `;
12 | const VectorContainer = styled.div`
13 | width: 100%;
14 | height: 100%;
15 | margin: 5px;
16 | display: flex;
17 | justify-content: center;
18 | `;
19 |
20 | const Headline = styled.h1`
21 | text-align: center;
22 | color: white;
23 | font-size: 2.5em;
24 | `;
25 |
26 | export default function Register() {
27 | return (
28 |
29 | Boardhero
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/lib/wishlist.js:
--------------------------------------------------------------------------------
1 | const { getCollection } = require("./database");
2 |
3 | const collectionName = "wishlist";
4 |
5 | async function setWishlist(game) {
6 | const gamesCollection = await getCollection(collectionName);
7 | const gameExists = await gamesCollection.findOne(game);
8 | if (gameExists) return;
9 | await gamesCollection.insertOne(game);
10 | }
11 |
12 | async function getWishlist(owner) {
13 | const gameCollection = await getCollection(collectionName);
14 | const Collection = await gameCollection.find({ owner: owner }).toArray();
15 | return Collection;
16 | }
17 |
18 | async function removeWishlistEntry(name) {
19 | const gameCollection = await getCollection(collectionName);
20 | const game = await gameCollection.deleteOne({ name: name });
21 | return game;
22 | }
23 |
24 | exports.setWishlist = setWishlist;
25 | exports.getWishlist = getWishlist;
26 | exports.removeWishlistEntry = removeWishlistEntry;
27 |
--------------------------------------------------------------------------------
/client/src/pages/LibraryBrowse.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import BrowseEmpty from "../components/library/BrowseEmpty";
3 | import CollectionGrid from "../components/library/CollectionGrid";
4 | import { getGamesFromApi } from "../api/GetDataFromApi";
5 |
6 | export default function LibraryBrowse({ searchbarInput }) {
7 | const [apiGame, setApiGame] = useState(null);
8 | useEffect(() => {
9 | getGamesFromApi(searchbarInput).then(games => setApiGame(games));
10 | }, [searchbarInput]);
11 | return (
12 | <>
13 | {!searchbarInput && (
14 |
15 | Please use the searchbar to browse through our available games.
16 |
17 | )}
18 | {searchbarInput && apiGame && (
19 |
24 | )}
25 | >
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | # boardhero
4 |
5 | A mobile first application to keep track of your boardgames. Create your own account with a digital portfolio of all the games you own and add the ones you want to your Wishlist.
6 |
7 | # Showcase
8 |
9 | 
10 | 
11 | 
12 | 
13 | 
14 |
15 | # Hosting
16 |
17 | The Website is currently hosted via heroku.
18 |
19 | [Boardhero](https://board-hero.herokuapp.com/)
20 |
21 | ## Usage
22 |
23 | Install client and server dependencies:
24 |
25 | ```
26 | npm install
27 | cd client
28 | npm install
29 | ```
30 |
31 | To start the server and client at the same time:
32 |
33 | ```
34 | npm run dev
35 | ```
36 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/AddButtonWishlist.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const StyledButton = styled.button`
5 | border-radius: 15px;
6 | background-color: ${props => props.theme.brightEffect};
7 | position: absolute;
8 | bottom: 10px;
9 | right: 15px;
10 | height: 40px;
11 | width: 135px;
12 | border: none;
13 | outline: none;
14 | box-shadow: 4px 4px 6px 4px rgba(0, 0, 0, 0.15);
15 | font-size: 1.1em;
16 | color: white;
17 | `;
18 |
19 | export default function AddButtonWishlist({
20 | children,
21 | setStartAnimation,
22 | singleGame,
23 | newWishlistEntry
24 | }) {
25 | return (
26 | {
28 | newWishlistEntry(singleGame);
29 | setStartAnimation(true);
30 | setTimeout(() => {
31 | setStartAnimation(false);
32 | }, 5000);
33 | }}
34 | >
35 | {children}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/ConfirmationMessage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { alert } from "../../animations/alert";
4 | import { textFadeIn } from "../../animations/textFadeIn";
5 |
6 | const MessageBox = styled.div`
7 | background: ${props => props.theme.darkFont};
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | position: fixed;
12 | bottom: -5em;
13 | left: 50%;
14 | margin-left: -175px;
15 | width: 80%px;
16 | height: 65px;
17 | color: #fff;
18 | font-size: 22px;
19 | padding: 0 1.2em 0 0;
20 | z-index: 10;
21 | animation: ${alert} 2.5s ease-in-out;
22 | display: "block";
23 | `;
24 |
25 | const MessageText = styled.span`
26 | animation: ${textFadeIn} 2.5s ease-in-out;
27 | display: "block";
28 | `;
29 |
30 | export default function ConfirmationMessage() {
31 | return (
32 |
33 | Game Succesfully added
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/client/src/icons/Star.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function StarIcon({ className }) {
5 | return (
6 |
16 | );
17 | }
18 |
19 | const StarIconStyled = styled(StarIcon)`
20 | fill: ${props =>
21 | props.selected ? props.theme.lightFont : props.theme.darkFont};
22 | height: 32px;
23 | width: 32px;
24 | margin-right: 5px;
25 | `;
26 |
27 | export default function Star({ selected }) {
28 | return ;
29 | }
30 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boardhero",
3 | "version": 2,
4 | "builds": [
5 | {
6 | "src": "client/package.json",
7 | "use": "@now/static-build",
8 | "config": { "distDir": "build" }
9 | },
10 | { "src": "index.js", "use": "@now/node-server" }
11 | ],
12 | "routes": [
13 | {
14 | "src": "/static/(.*)",
15 | "dest": "/client/static/$1"
16 | },
17 | {
18 | "src": "/api/(.*)",
19 | "dest": "/index.js"
20 | },
21 | {
22 | "src": "/logo192.png",
23 | "dest": "/client/logo192.png"
24 | },
25 | {
26 | "src": "/logo512.png",
27 | "dest": "/client/logo512.png"
28 | },
29 | {
30 | "src": "/favicon.ico",
31 | "dest": "/client/favicon.ico"
32 | },
33 | {
34 | "src": "/manifest.json",
35 | "dest": "/client/manifest.json"
36 | },
37 | {
38 | "src": "/(.*)",
39 | "dest": "/client/index.html"
40 | }
41 | ],
42 | "env": {
43 | "DB_URL": "@db_url",
44 | "DB_NAME": "@db_name"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/client/src/components/library/OptionBox.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const OptionBoxDiv = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | height: 120px;
8 | width: 170px;
9 | align-items: center;
10 | justify-content: center;
11 | background-color: ${props => props.theme.lightBackground};
12 | border: none;
13 | position: absolute;
14 | top: 35px;
15 | right: 25px;
16 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.15);
17 | `;
18 |
19 | const OptionButton = styled.button`
20 | height: 50%;
21 | width: 100%;
22 | background-color: inherit;
23 | border: inherit;
24 | color: white;
25 | font-size: 1.4em;
26 | outline: none;
27 | `;
28 |
29 | export default function OptionBox({ show, onClick }) {
30 | return (
31 | <>
32 | {show && (
33 |
34 | onClick("Sort")}>Sort
35 | Filter
36 |
37 | )}
38 | >
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/client/src/icons/Dice.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function Cubes({ className }) {
5 | return (
6 |
21 | );
22 | }
23 |
24 | const DiceStyled = styled(Cubes)`
25 | fill: ${props =>
26 | props.selected ? props.theme.lightFont : props.theme.darkFont};
27 | height: 32px;
28 | width: 32px;
29 | margin-right: 5px;
30 | `;
31 |
32 | export default function Dice({ selected }) {
33 | return ;
34 | }
35 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/AddButtonCollection.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import PropTypes from "prop-types";
4 |
5 | const StyledButton = styled.button`
6 | border-radius: 15px;
7 | background-color: ${props => props.theme.brightEffect};
8 | position: absolute;
9 | bottom: 10px;
10 | left: 15px;
11 | height: 40px;
12 | width: 135px;
13 | border: none;
14 | color: white;
15 | outline: none;
16 | box-shadow: 4px 4px 6px 4px rgba(0, 0, 0, 0.15);
17 | font-size: 1.1em;
18 | `;
19 |
20 | export default function AddButtonCollection({
21 | children,
22 | singleGame,
23 | setStartAnimation,
24 | newGame
25 | }) {
26 | return (
27 | {
29 | newGame(singleGame);
30 | setStartAnimation(true);
31 | setTimeout(() => {
32 | setStartAnimation(false);
33 | }, 5000);
34 | }}
35 | >
36 | {children}
37 |
38 | );
39 | }
40 |
41 | AddButtonCollection.propTypes = {
42 | children: PropTypes.string
43 | };
44 |
--------------------------------------------------------------------------------
/client/src/components/library/SortModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const PopupContainer = styled.div`
5 | background-color: ${props => props.theme.lightBackground};
6 | color: white;
7 | padding: 30px;
8 | position: fixed;
9 | top: 100px;
10 | left: 50%;
11 | top: 50%;
12 | margin-left: -110px;
13 | margin-top: -80px;
14 | font-size: 1.4em;
15 | z-index: 3;
16 | display: ${props => (props.show ? "block" : "none")};
17 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.15);
18 | `;
19 |
20 | const RadioButtonContainer = styled.div`
21 | display: flex;
22 | margin-top: 10px;
23 | `;
24 |
25 | export default function SortModal({ show }) {
26 | return (
27 |
28 | Sort
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/icons/Upload.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function UploadIcon({ className }) {
5 | return (
6 |
16 | );
17 | }
18 |
19 | const UploadIconStyled = styled(UploadIcon)`
20 | fill: ${props =>
21 | props.selected ? props.theme.lightFont : props.theme.darkFont};
22 | height: 32px;
23 | width: 32px;
24 | margin-right: 5px;
25 | `;
26 |
27 | export default function Upload({ selected }) {
28 | return ;
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 jsmunsch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/client/src/hooks.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | export const useOnClickOutside = (ref, handler) => {
4 | useEffect(() => {
5 | const listener = event => {
6 | if (!ref.current || ref.current.contains(event.target)) {
7 | return;
8 | }
9 | handler(event);
10 | };
11 | document.addEventListener("touchstart", listener);
12 | document.addEventListener("mousedown", listener);
13 | return () => {
14 | document.removeEventListener("touchstart", listener);
15 | document.removeEventListener("mousedown", listener);
16 | };
17 | }, [ref, handler]);
18 | };
19 |
20 | export const useUser = () => {
21 | const [user, setUser] = React.useState(localStorage.getItem("user"));
22 |
23 | useEffect(() => {
24 | const handleStorageChange = () => {
25 | console.log("storage changed");
26 | setUser(localStorage.getItem("user"));
27 | };
28 |
29 | window.addEventListener("storage", handleStorageChange);
30 |
31 | return () => {
32 | window.removeEventListener("storage", handleStorageChange);
33 | };
34 | }, []);
35 |
36 | return [user];
37 | };
38 |
--------------------------------------------------------------------------------
/client/src/icons/Person.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function PersonIcon({ className }) {
5 | return (
6 |
14 | );
15 | }
16 |
17 | const PersonStyled = styled(PersonIcon)`
18 | fill: ${props =>
19 | props.selected ? props.theme.lightFont : props.theme.darkFont};
20 | height: 32px;
21 | width: 32px;
22 | margin-right: 5px;
23 | `;
24 |
25 | export default function Person({ selected }) {
26 | return ;
27 | }
28 |
--------------------------------------------------------------------------------
/client/src/pages/UserOverview.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import MainContent from "../components/User-page.js/MainContent";
3 | import Title from "../components/User-page.js/Title";
4 | import RecentlyAdded from "../components/User-page.js/RecentlyAdded";
5 | import Placeholder from "../components/User-page.js/Placeholder";
6 | import { useHistory } from "react-router-dom";
7 |
8 | export default function UserOverview({ filteredGames }) {
9 | let history = useHistory();
10 | return (
11 |
12 | {filteredGames.length === 0 && (
13 | history.push("/library/browse")}
17 | />
18 | )}
19 | {filteredGames.length > 0 && (
20 |
21 | Recently Added
22 |
23 | )}
24 | {filteredGames.slice(0, 5).map(game => (
25 |
31 | ))}
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import StyledLink from "./UserNavigation";
4 | import {
5 | BrowserRouter as Router,
6 | useRouteMatch,
7 | useLocation
8 | } from "react-router-dom";
9 |
10 | const ScrollBar = styled.div`
11 | display: flex;
12 | height: 8%;
13 | overflow-x: auto;
14 | align-items: center;
15 | justify-content: space-around;
16 | border-top: 1px solid ${props => props.theme.brightEffect};
17 | border-bottom: 1px solid ${props => props.theme.brightEffect};
18 | `;
19 |
20 | export default function NavBar() {
21 | let { url } = useRouteMatch();
22 | let location = useLocation();
23 | return (
24 |
25 |
29 | Overview
30 |
31 |
35 | Statistics
36 |
37 |
41 | Friends
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boardhero",
3 | "version": "1.0.0",
4 | "engines": {
5 | "node": "12.x"
6 | },
7 | "description": "",
8 | "main": "server.js",
9 | "scripts": {
10 | "client": "cd client && npm start",
11 | "build": "cd client && npm run build",
12 | "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
13 | "dev:server": "npm run build && cd .. && npm start",
14 | "server": "nodemon index.js",
15 | "start": "node index.js",
16 | "heroku-prebuild": "cd client && npm ci --only=prod",
17 | "reset": "killall -9 node"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/jsmunsch/boardhero"
22 | },
23 | "author": "Jonas Munsch",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/jsmunsch/boardhero"
27 | },
28 | "homepage": "https://github.com/jsmunsch/boardhero#readme",
29 | "dependencies": {
30 | "axios": "^0.19.0",
31 | "cookie-parser": "^1.4.4",
32 | "dotenv": "^8.2.0",
33 | "express": "^4.17.1",
34 | "mongodb": "^3.3.2",
35 | "node": "^12.13.0",
36 | "now": "^16.4.4",
37 | "uuid": "^3.3.3"
38 | },
39 | "devDependencies": {
40 | "concurrently": "^5.0.0",
41 | "nodemon": "^1.19.3",
42 | "prettier": "^1.18.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/client/src/stories/NavigationMenu.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import StyledMenuButton from "../components/NavigationMenuButton";
3 | import Dice from "../icons/Dice";
4 | import Person from "../icons/Person";
5 | import Upload from "../icons/Upload";
6 | import Star from "../icons/Star";
7 |
8 | export default {
9 | title: "NavigationMenu"
10 | };
11 |
12 | export const NavButtonInactive = () => (
13 |
14 |
15 | Collection
16 |
17 | );
18 |
19 | export const NavButtonActive = () => (
20 |
21 |
22 | Peter
23 |
24 | );
25 |
26 | export const NavigationMenu = () => (
27 | <>
28 | onClick("Collection")} selected>
29 |
30 | Collection
31 |
32 | onClick("User")}>
33 |
34 | User
35 |
36 | onClick("Share")}>
37 |
38 | Share
39 |
40 | onClick("Wishlist")}>
41 |
42 | Wishlist
43 |
44 | >
45 | );
46 |
47 | function onClick(input) {
48 | console.log(input);
49 | }
50 |
--------------------------------------------------------------------------------
/client/src/components/User-page.js/Placeholder.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import PropTypes from "prop-types";
4 |
5 | const Container = styled.section`
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | margin: 15px;
10 | background: ${props => props.theme.mainLight};
11 | flex-grow: 1;
12 | flex-direction: column;
13 | `;
14 |
15 | const TextContainer = styled.div`
16 | width: 80%;
17 | max-width: 420px;
18 | text-align: center;
19 | `;
20 |
21 | const FriendButton = styled.button`
22 | border-radius: 5px;
23 | width: 60%;
24 | max-width: 320px;
25 | height: 15%;
26 | margin: 20px;
27 | background: ${props => props.theme.brightEffect};
28 | color: white;
29 | border: none;
30 | font-size: 1.1em;
31 | box-shadow: 4px 4px 6px 4px rgba(0, 0, 0, 0.15);
32 | padding: 20px;
33 | line-height: 1px;
34 | `;
35 |
36 | export default function Placeholder({ text, buttonText, onClick }) {
37 | return (
38 |
39 | {text}
40 |
41 | {buttonText}
42 |
43 |
44 | );
45 | }
46 |
47 | Placeholder.propTypes = {
48 | text: PropTypes.string,
49 | buttonText: PropTypes.string
50 | };
51 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boardhero",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.19.0",
7 | "prop-types": "^15.7.2",
8 | "react": "^16.10.2",
9 | "react-dom": "^16.10.2",
10 | "react-rating": "^2.0.0",
11 | "react-router-dom": "^5.1.2",
12 | "react-scripts": "3.2.0",
13 | "styled-components": "^4.4.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject",
20 | "storybook": "start-storybook -p 9009 -s public",
21 | "build-storybook": "build-storybook -s public"
22 | },
23 | "eslintConfig": {
24 | "extends": "react-app"
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | },
38 | "devDependencies": {
39 | "@storybook/addon-actions": "^5.2.3",
40 | "@storybook/addon-links": "^5.2.3",
41 | "@storybook/addons": "^5.2.3",
42 | "@storybook/react": "^5.2.3",
43 | "eslint": "^6.5.1",
44 | "prettier": "^1.18.2",
45 | "dotenv": "^8.2.0"
46 | },
47 | "proxy": "http://localhost:8080"
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/stories/DetailCard.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CollectionItemPositioned, {
3 | GamePositioned
4 | } from "../components/GamePositioned";
5 | import CardModal from "../components/CardModal";
6 | import CardBadge from "../components/CardBadge";
7 | import CardDetails from "../components/CardDetails";
8 | import CardCategories from "../components/CardCategories";
9 | import CardTitle from "../components/CardTitle";
10 | import CardFlip from "../components/CardFlip";
11 |
12 | export default {
13 | title: "DetailCard"
14 | };
15 |
16 | export const CardWithImage = () => (
17 |
18 |
19 |
20 | );
21 |
22 | export const test = () => (
23 |
24 |
25 | Fest für Odin
26 |
27 | Players: 2-4
28 | Time: test
29 | Categories
30 |
31 | Economics
32 | Sci-Fi
33 | Territory-Building
34 |
35 | Mechanics
36 |
37 | Area Control
38 | Route/Network Building
39 |
40 |
41 |
42 | );
43 |
--------------------------------------------------------------------------------
/client/src/pages/LibraryGames.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 | import CollectionGrid from "../components/library/CollectionGrid";
4 | import { getGamesCollection } from "../api/fetchGames";
5 | import Placeholder from "../components/User-page.js/Placeholder";
6 | import { useHistory } from "react-router-dom";
7 |
8 | const FlexContainer = styled.div`
9 | display: flex;
10 | height: 300px;
11 | color: white;
12 | `;
13 |
14 | export default function LibraryCollection({ searchbarInput }) {
15 | const [games, setGames] = useState(null);
16 | let history = useHistory();
17 |
18 | React.useEffect(() => {
19 | getGamesCollection().then(gameArray => {
20 | setGames(gameArray);
21 | });
22 | }, []);
23 |
24 | const searchGames = (games || []).filter(info =>
25 | info.name.toLowerCase().includes(searchbarInput.toLowerCase())
26 | );
27 |
28 | return (
29 | <>
30 | {games && games.length === 0 && (
31 |
32 | history.push("/library/browse")}
36 | />
37 |
38 | )}
39 |
40 |
41 | >
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/client/src/pages/LibraryWishlist.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 | import CollectionGrid from "../components/library/CollectionGrid";
4 | import { getWishlistCollection } from "../api/fetchWishlist";
5 | import { useHistory } from "react-router-dom";
6 | import Placeholder from "../components/User-page.js/Placeholder";
7 |
8 | const FlexContainer = styled.div`
9 | display: flex;
10 | height: 300px;
11 | color: white;
12 | `;
13 |
14 | export default function LibraryWishlist({ searchbarInput }) {
15 | let history = useHistory();
16 | React.useEffect(() => {
17 | getWishlistCollection().then(gameArray => {
18 | setWishlistGame(gameArray);
19 | });
20 | }, []);
21 |
22 | const [wishlistGame, setWishlistGame] = useState(null);
23 | const searchWishlistGames = (wishlistGame || []).filter(info =>
24 | info.name.toLowerCase().includes(searchbarInput.toLowerCase())
25 | );
26 | return (
27 | <>
28 | {wishlistGame && wishlistGame.length === 0 && (
29 |
30 | history.push("/library/browse")}
34 | />
35 |
36 | )}
37 |
38 | >
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/client/src/components/popup-card/CardBack.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CollectionItemPositioned from "./GamePositioned";
3 | import CardGameName from "./GameName";
4 | import DetailButton from "./DetailButton";
5 | import Grid from "./Grid";
6 | import Rating from "react-rating";
7 | import StarRatingFull from "../../icons/StarRatingFull";
8 | import StarRatingEmpty from "../../icons/StarRatingEmpty";
9 | import RatingContainer from "./RatingContainer";
10 | import ModalContainer from "./ModalContainer";
11 | import DescriptionContainer from "./DescriptionContainer";
12 |
13 | export default function CardBack({ singleGame, setShowBack, showBack }) {
14 | return (
15 |
16 |
17 |
18 | }
22 | emptySymbol={}
23 | />
24 | ({singleGame.num_user_ratings})
25 |
26 |
27 |
28 | Description
29 |
30 |
33 |
34 | setShowBack(!showBack)} />
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ThemeProvider } from "styled-components";
3 | import GlobalStyles from "./GlobalStyles";
4 | import darkTheme from "./themes/dark";
5 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
6 | import Login from "./pages/login";
7 | import Library from "./pages/Library";
8 | import Register from "./pages/register";
9 | import { useUser } from "./hooks";
10 | import NotFound from "./pages/NotFound";
11 | import User from "./pages/User";
12 | import Impressum from "./pages/Impressum";
13 |
14 | function App() {
15 | const [user] = useUser();
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {user && (
29 | <>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | >
41 | )}
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
51 | export default App;
52 |
--------------------------------------------------------------------------------
/client/src/components/library/SearchBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { morph } from "../../animations/morph";
4 | import { slideIn } from "../../animations/slideIn";
5 |
6 | const StyledSearchBar = styled.div`
7 | display: flex;
8 | flex-direction: row;
9 | flex-grow: 1;
10 | justify-content: space-between;
11 | align-items: center;
12 | background-color: inherit;
13 | color: white;
14 | margin-left: 10px;
15 | `;
16 |
17 | const StyledInput = styled.input`
18 | background-color: inherit;
19 | border: 1px solid ${props => props.theme.brightEffect};
20 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.15);
21 | ::placeholder {
22 | color: white;
23 | }
24 | :focus {
25 | outline: none;
26 | }
27 | font-size: 18px;
28 | padding: 10px;
29 | width: 100%;
30 | fill: white;
31 | color: white;
32 | animation: ${slideIn} 0.25s ease-in;
33 | -webkit-appearance: none;
34 | `;
35 |
36 | export default function SearchBar({ active, onSearch }) {
37 | let throttleTimeout;
38 |
39 | function handleInputChange(event) {
40 | clearTimeout(throttleTimeout);
41 | const inputValue = event.target.value;
42 | throttleTimeout = setTimeout(function() {
43 | onSearch(inputValue);
44 | }, 225);
45 | }
46 | return (
47 |
48 | {active && (
49 |
55 | )}
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 |
28 | Boardhero
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/client/src/components/library/CollectionGrid.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 | import CollectionItem from "./CollectionItem";
4 | import CardModal from "../popup-card/CardModal";
5 |
6 | const BodyGrid = styled.div`
7 | width: 100%;
8 | display: grid;
9 | grid-template-columns: repeat(3, 90px);
10 | grid-column-gap: 8%;
11 | grid-row-gap: 30px;
12 | background-color: ${props => props.theme.main};
13 | justify-content: center;
14 | @media (min-width: 600px) {
15 | grid-column-gap: 130px;
16 | }
17 | @media (min-width: 800px) {
18 | grid-template-columns: repeat(4, 90px);
19 | }
20 | @media (min-width: 1400px) {
21 | grid-template-columns: repeat(5, 90px);
22 | }
23 | `;
24 |
25 | const WrapperDiv = styled.div`
26 | display: flex;
27 | align-items: center;
28 | justify-content: center;
29 | width: 100%;
30 | height: 100%;
31 | background-color: ${props => props.theme.main};
32 | padding: 10px;
33 | `;
34 | export default function CollectionGrid({
35 | collection,
36 | enabled,
37 | removeGame,
38 | removeWishlist
39 | }) {
40 | const [showModal, setShowModal] = useState(false);
41 | const [selectedGame, setSelectedGame] = useState();
42 |
43 | return (
44 | <>
45 | {showModal && (
46 | setShowModal(false)}
49 | enabled={enabled}
50 | removeGame={removeGame}
51 | removeWishlist={removeWishlist}
52 | />
53 | )}
54 |
55 |
56 | {collection.map(game => (
57 | {
60 | setSelectedGame(game);
61 | setShowModal(true);
62 | }}
63 | src={game.image_url}
64 | />
65 | ))}
66 |
67 |
68 | >
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/client/src/animations/alert.js:
--------------------------------------------------------------------------------
1 | import { keyframes } from "styled-components";
2 |
3 | export const alert = keyframes`
4 | 0% {
5 | bottom: -5em;
6 | width: 65px;
7 | margin-left: -32.5px;
8 | border-radius: 50%;
9 | }
10 | 7% {
11 | bottom: 3em;
12 | width: 65px;
13 | margin-left: -32.5px;
14 | border-radius: 50%;
15 | }
16 | 12% {
17 | bottom: 3em;
18 | width: 65px;
19 | margin-left: -32.5px;
20 | border-radius: 50%;
21 | }
22 | 25% {
23 | bottom: 3em;
24 | width: 300px;
25 | margin-left: -150px;
26 | border-radius: 15px;
27 | }
28 | 37% {
29 | bottom: 3em;
30 | width: 300px;
31 | margin-left: -150px;
32 | }
33 | 42% {
34 | bottom: 3em;
35 | width: 300px;
36 | margin-left: -150px;
37 | }
38 | 47% {
39 | bottom: 3.5em;
40 | width: 300px;
41 | margin-left: -150px;
42 | border-radius: 15px;
43 | }
44 | 50% {
45 | bottom: 3.5em;
46 | width: 300px;
47 | margin-left: -150px;
48 | border-radius: 15px;
49 | }
50 | 53% {
51 | bottom: 3.5em;
52 | width: 300px;
53 | margin-left: -150px;
54 | border-radius: 15px;
55 | }
56 | 58% {
57 | bottom: 3em;
58 | width: 300px;
59 | margin-left: -150px;
60 | }
61 | 63% {
62 | bottom: 3em;
63 | width: 300px;
64 | margin-left: -150px;
65 | }
66 | 75% {
67 | bottom: 3em;
68 | width: 300px;
69 | margin-left: -150px;
70 | border-radius: 15px;
71 | }
72 | 87% {
73 | bottom: 3em;
74 | width: 65px;
75 | margin-left: -32.5px;
76 | border-radius: 50%;
77 | }
78 | 92% {
79 | bottom: 3em;
80 | width: 65px;
81 | margin-left: -32.5px;
82 | border-radius: 50%;
83 | }
84 | 100% {
85 | bottom: -5em;
86 | width: 65px;
87 | margin-left: -32.5px;
88 | border-radius: 50%;
89 | }
90 | }
91 | `;
92 |
--------------------------------------------------------------------------------
/client/src/pages/Library.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Header from "../components/Header";
3 | import LibraryNav from "../components/library/LibraryNav";
4 | import OptionBox from "../components/library/OptionBox";
5 | import SortModal from "../components/library/SortModal";
6 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
7 | import LibraryCollection from "./LibraryGames";
8 | import LibraryBrowse from "./LibraryBrowse";
9 | import LibraryWishlist from "./LibraryWishlist";
10 | import { useLocation } from "react-router-dom";
11 |
12 | export default function Library() {
13 | let location = useLocation();
14 |
15 | const [currentNavigation, setCurrentNavigation] = useState(location.pathname);
16 | const [toggleOptions, setToggleOptions] = useState(false);
17 | const [displaySort, setDisplaySort] = useState(false);
18 | const [showSearchbar, setShowSearchbar] = useState(false);
19 | const [searchbarInput, setSearchbarInput] = React.useState("");
20 |
21 | function handleSearch(value) {
22 | setSearchbarInput(value);
23 | }
24 |
25 | return (
26 | <>
27 |