├── mtgi-v2
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── logo512.png
│ ├── components
│ │ ├── Context.js
│ │ ├── Navbar.js
│ │ ├── Card.js
│ │ ├── CardList.js
│ │ ├── CardModal.js
│ │ ├── Landing.js
│ │ ├── Sort.js
│ │ └── CardForm.js
│ ├── test
│ │ ├── setupTests.js
│ │ └── App.test.js
│ ├── index.js
│ ├── App.css
│ ├── App.js
│ ├── logo.svg
│ └── index.css
├── .gitignore
├── package.json
├── README.md
└── data
│ └── db.json
└── README.md
/mtgi-v2/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/mtgi-v2/src/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StygianLiege/MTG-Inventory/HEAD/mtgi-v2/src/logo512.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MTG-Inventory
2 | Magic: The Gathering Inventory - Navigate to `mtgi-v2` for the application.
3 |
--------------------------------------------------------------------------------
/mtgi-v2/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StygianLiege/MTG-Inventory/HEAD/mtgi-v2/public/favicon.ico
--------------------------------------------------------------------------------
/mtgi-v2/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StygianLiege/MTG-Inventory/HEAD/mtgi-v2/public/logo192.png
--------------------------------------------------------------------------------
/mtgi-v2/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StygianLiege/MTG-Inventory/HEAD/mtgi-v2/public/logo512.png
--------------------------------------------------------------------------------
/mtgi-v2/src/components/Context.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | export const Context = createContext();
4 |
--------------------------------------------------------------------------------
/mtgi-v2/src/test/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/mtgi-v2/src/test/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from '../App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/mtgi-v2/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 | root.render(
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/mtgi-v2/.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 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/mtgi-v2/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 |
--------------------------------------------------------------------------------
/mtgi-v2/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import logo from '../logo512.png';
3 | import { Context } from './Context';
4 |
5 | const Navbar = () => {
6 | const { view, dispatchView, setScroll } = useContext(Context);
7 | const handlePageView = (view) => {
8 | dispatchView(view);
9 | };
10 | return (
11 |
12 |
13 |
handlePageView(0)} className="navbar-button">
14 | Inventory
15 |
16 |
{
18 | if (view === 0) setScroll(window.scrollY);
19 | handlePageView(1);
20 | }}
21 | className="navbar-button"
22 | >
23 | New Card
24 |
25 |
26 | );
27 | };
28 |
29 | export default Navbar;
30 |
--------------------------------------------------------------------------------
/mtgi-v2/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 12vw;
7 | width: 12vw;
8 | margin-top: 1.5vw;
9 | margin-left: 1.5vw;
10 | pointer-events: none;
11 | filter: drop-shadow(0px 0px 3px aliceblue);
12 | }
13 |
14 | @media (prefers-reduced-motion: no-preference) {
15 | .App-logo {
16 | animation: App-logo-spin infinite 20s linear;
17 | }
18 | }
19 |
20 | .App-content {
21 | background-color: #252525;
22 | min-height: 100vh;
23 | display: flex;
24 | flex-direction: column;
25 | align-items: center;
26 | justify-content: center;
27 | font-size: calc(10px + 2vmin);
28 | color: white;
29 | }
30 |
31 | .App-link {
32 | color: #61dafb;
33 | }
34 |
35 | @keyframes App-logo-spin {
36 | from {
37 | transform: rotate(0deg);
38 | }
39 | to {
40 | transform: rotate(360deg);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/mtgi-v2/src/components/Card.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { Context } from './Context';
3 |
4 | const Card = ({ card, url }) => {
5 | const { dispatchCard, dispatchCardView, dispatchView, setScroll } =
6 | useContext(Context);
7 |
8 | const handleCardView = (url, card) => {
9 | // readies CardModal for viewing
10 | dispatchCard(card);
11 | dispatchCardView(url);
12 | };
13 | const handleCardClick = () => {
14 | // sets scroll position for return to CardList
15 | setScroll(window.scrollY);
16 | setTimeout(() => {
17 | // changes view to CardModal
18 | dispatchView(2);
19 | }, 10);
20 | };
21 |
22 | return (
23 |
24 |
handleCardView(url, card)}
27 | onClick={() => handleCardClick()}
28 | >
29 | {card.cardName}
30 |
31 |
32 | );
33 | };
34 |
35 | export default Card;
36 |
--------------------------------------------------------------------------------
/mtgi-v2/src/components/CardList.js:
--------------------------------------------------------------------------------
1 | import Card from './Card';
2 | import { useLayoutEffect, useContext, useEffect } from 'react';
3 | import { Context } from './Context';
4 |
5 | const CardList = ({ cards }) => {
6 | // for tracking scroll position (from Landing)
7 | const { scroll, setScroll } = useContext(Context);
8 | useLayoutEffect(() => {
9 | // sets window scroll position as CardList is rendered
10 | if (scroll) window.scrollTo(0, scroll);
11 | });
12 | useEffect(() => {
13 | // removes scroll setting once CardList is mounted
14 | setScroll(null);
15 | // added line below to kill eslint warning: only needs to fire on mount
16 | // eslint-disable-next-line react-hooks/exhaustive-deps
17 | }, []);
18 | return (
19 |
20 |
21 |
Inventory
22 | {cards.map((card) => (
23 |
24 | ))}
25 |
26 |
27 | );
28 | };
29 |
30 | export default CardList;
31 |
--------------------------------------------------------------------------------
/mtgi-v2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mtgi-v2",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.1.1",
8 | "@testing-library/user-event": "^13.5.0",
9 | "cors": "^2.8.5",
10 | "express": "^4.18.1",
11 | "json-server": "^0.17.0",
12 | "mtgsdk": "^1.0.0",
13 | "react": "^18.1.0",
14 | "react-dom": "^18.1.0",
15 | "react-router-dom": "^6.3.0",
16 | "react-scripts": "5.0.1",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/mtgi-v2/src/App.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route } from 'react-router-dom';
2 | //import logo from './logo512.png';
3 | import Landing from './components/Landing';
4 | //import CardForm from './components/CardForm';
5 | //import './App.css'; /*=> moved to index.css*/
6 | //import CardModal from './components/CardModal';
7 |
8 | function App() {
9 | return (
10 |
11 |
12 | {/*
13 |
14 |
15 |
16 | Add Card
17 |
18 |
*/}
19 |
20 |
21 | {/*
22 |
23 |
24 |
25 | Add Card
26 |
27 |
*/}
28 | } />
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | export default App;
37 |
--------------------------------------------------------------------------------
/mtgi-v2/README.md:
--------------------------------------------------------------------------------
1 | # MTG Inventory v2
2 |
3 | Back and better than ever.
4 |
5 | ## Instructions
6 |
7 | You'll need a browser (I recommend Google Chrome), and Node.js installed on your machine.
8 |
9 | Open a terminal and clone this repo into a folder of your choosing.
10 |
11 | Once cloned, navigate to this application via `cd mtgi-v2` in the terminal.
12 |
13 | Now run `npm install` in the terminal to download the necessary dependencies.
14 |
15 | MTG Inventory is ready to go!
16 |
17 | To boot up the local server/database, run `npx json-server --watch data/db.json --port 8000` next.
18 |
19 | Now open a second terminal and start up the interface with `npm start`.
20 |
21 | The application will be found at `http://localhost:3000/` in your browser.
22 |
23 | The Inventory is your collection of cards.
24 |
25 | It will have some sample cards at first.
26 |
27 | To delete a card, click on the card from the Inventory list, and then click the **Delete** button at the bottom of the card information.
28 |
29 | To add new cards, click **New Card** and fill in the required information.
30 |
31 | When ready, hit **Submit**.
32 |
33 | Your new card will now be in the Inventory.
34 |
35 | Add more cards and watch your collection grow.
36 |
37 | Use the **Search** and **Sort** features to tailor which cards appear and in which order.
38 |
39 | Some stretch features to watch out for:
40 |
41 | - Decks!!
42 |
--------------------------------------------------------------------------------
/mtgi-v2/src/components/CardModal.js:
--------------------------------------------------------------------------------
1 | import { Context } from './Context';
2 | import { useContext } from 'react';
3 |
4 | const CardModal = () => {
5 | const {
6 | curCard,
7 | update,
8 | dispatchUpdate,
9 | dispatchView,
10 | dispatchClearCardView,
11 | } = useContext(Context);
12 | const handleDelete = () => {
13 | fetch('http://localhost:8000/cards/' + curCard.id, {
14 | method: 'DELETE',
15 | })
16 | .then(() => {
17 | dispatchUpdate(update + 1);
18 | dispatchClearCardView();
19 | dispatchView(0);
20 | })
21 | .catch((err) => alert(err.message));
22 | };
23 | return (
24 |
25 |
26 |
27 |
NAME:
28 |
{curCard.cardName}
29 |
TYPE:
30 |
{curCard.cardType}
31 |
CONVERTED MANA COST:
32 |
{curCard.cardManaCost}
33 |
COLOR IDENTITY:
34 |
{curCard.cardColorIdentity}
35 |
36 |
37 |
38 | Delete
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default CardModal;
47 |
--------------------------------------------------------------------------------
/mtgi-v2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 | MTG Inventory v2
25 |
26 |
27 | Your collection...
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/mtgi-v2/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mtgi-v2/src/components/Landing.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import Sort from './Sort';
3 | import CardList from './CardList';
4 | import CardForm from './CardForm';
5 | import CardModal from './CardModal';
6 | import Navbar from './Navbar';
7 | import { Context } from './Context';
8 |
9 | const Landing = () => {
10 | // works with useEffect to pull cards from db.json and set to state
11 | const [cards, setCards] = useState(null);
12 | // view conditionally renders non-Navbar content: CardList = 0, CardForm = 1, CardModal = 2
13 | const [view, setView] = useState(0);
14 | // feeds card to context for CardModal
15 | const [curCard, setCurCard] = useState(null);
16 | // watches for update to json server, increments by one for useEffect dependency array
17 | const [update, setUpdate] = useState(0);
18 | // sets default card viewer image
19 | const [cardUrl, setCardUrl] = useState(
20 | 'https://c1.scryfall.com/file/scryfall-card-backs/large/59/597b79b3-7d77-4261-871a-60dd17403388.jpg?1562636810'
21 | );
22 | // tracks current query string (defaults to all cards)
23 | const [query, setQuery] = useState('http://localhost:8000/cards');
24 | // sets scroll position for CardList
25 | const [scroll, setScroll] = useState(null);
26 |
27 | // fetches cards from db.json and sets them to state
28 | useEffect(() => {
29 | fetch(query)
30 | .then((res) => {
31 | return res.json();
32 | })
33 | .then((data) => setCards(data))
34 | .catch((err) => alert(err.message));
35 | }, [update, query]);
36 |
37 | // sets view for "card-viewer"
38 | const dispatchCardView = (dispatchedCardView) => {
39 | setCardUrl(dispatchedCardView);
40 | return;
41 | };
42 | // sets view for non-Navbar Landing content
43 | const dispatchView = (dispatchedView) => {
44 | setView(dispatchedView);
45 | return;
46 | };
47 | // sets view for CardModal (sets curCard)
48 | const dispatchCard = (dispatchedCard) => {
49 | setCurCard(dispatchedCard);
50 | //console.log(curCard.cardName);
51 | return;
52 | };
53 | // sets update for re-render of card list
54 | const dispatchUpdate = (dispatchedUpdate) => {
55 | setUpdate(dispatchedUpdate);
56 | return;
57 | };
58 | // clears card-viewer after card deletion
59 | const dispatchClearCardView = () => {
60 | setCardUrl(
61 | 'https://c1.scryfall.com/file/scryfall-card-backs/large/59/597b79b3-7d77-4261-871a-60dd17403388.jpg?1562636810'
62 | );
63 | };
64 |
65 | return (
66 |
67 |
83 |
84 | {cards && }
85 | {cards && view === 0 && }
86 | {!cards && view === 0 && Loading...
}
87 | {view === 2 && }
88 | {view !== 1 && (
89 |
90 |
{
94 | if (view === 0) setScroll(window.scrollY);
95 | view === 2 ? setView(0) : setView(2);
96 | }}
97 | />
98 |
99 | )}
100 | {view === 1 && }
101 |
102 |
103 | );
104 | };
105 |
106 | export default Landing;
107 |
--------------------------------------------------------------------------------
/mtgi-v2/data/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "cards": [
3 | {
4 | "id": 1,
5 | "cardName": "Imperial Seal",
6 | "cardType": "Sorcery",
7 | "cardManaCost": "1",
8 | "cardColorIdentity": "Black",
9 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/6/0/6023bfe0-84bf-454c-b29d-5d128fe3e6b5.jpg?1562869020"
10 | },
11 | {
12 | "cardName": "Doom Blade",
13 | "cardType": "Instant",
14 | "cardManaCost": "2",
15 | "cardColorIdentity": "Black",
16 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/png/front/6/e/6e19acff-f3dd-417a-a9ab-ea3e36c1ba61.png?1561983934",
17 | "id": 3
18 | },
19 | {
20 | "cardName": "Phyrexian Altar",
21 | "cardType": "Artifact",
22 | "cardManaCost": "3",
23 | "cardColorIdentity": "Colorless",
24 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/png/front/3/c/3c66c54b-b2d1-494d-ae10-a950c184a52f.png?1547518505",
25 | "id": 4
26 | },
27 | {
28 | "cardName": "Rot Hulk",
29 | "cardType": "Creature - Zombie",
30 | "cardManaCost": "7",
31 | "cardColorIdentity": "Black",
32 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/c/e/ce9cf26d-f214-4e76-b000-8dd895cce8d9.jpg?1592764595",
33 | "id": 5
34 | },
35 | {
36 | "cardName": "Diregraf Captain",
37 | "cardType": "Creature - Zombie Soldier",
38 | "cardManaCost": "3",
39 | "cardColorIdentity": "Blue/Black",
40 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/0/e/0e5f41eb-609b-4882-af9e-904daa717484.jpg?1562898304",
41 | "id": 6
42 | },
43 | {
44 | "cardName": "Carrion Feeder",
45 | "cardType": "Creature - Zombie",
46 | "cardManaCost": "1",
47 | "cardColorIdentity": "Black",
48 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/d/3/d3e33835-a293-4a1f-85d5-8ac22360ef35.jpg?1580014206",
49 | "id": 7
50 | },
51 | {
52 | "cardName": "Counterspell",
53 | "cardType": "Instant",
54 | "cardManaCost": "2",
55 | "cardColorIdentity": "Blue",
56 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/c/c/cca8eb95-d071-46a4-885c-3da25b401806.jpg?1562441143",
57 | "id": 8
58 | },
59 | {
60 | "cardName": "Rhystic Study",
61 | "cardType": "Enchantment",
62 | "cardManaCost": "3",
63 | "cardColorIdentity": "Blue",
64 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/3/3/3394cefd-a3c6-4917-8f46-234e441ecfb6.jpg?1592487887",
65 | "id": 9
66 | },
67 | {
68 | "cardName": "Reassembling Skeleton",
69 | "cardType": "Creature - Skeleton Warrior",
70 | "cardManaCost": "2",
71 | "cardColorIdentity": "Black",
72 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/a/3/a3d4ac21-2203-45f4-b5f2-dc186ccdbe69.jpg?1562303448",
73 | "id": 10
74 | },
75 | {
76 | "cardName": "Damnation",
77 | "cardType": "Sorcery",
78 | "cardManaCost": "4",
79 | "cardColorIdentity": "Black",
80 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/7/f/7fc1d7db-11a3-4ff9-8d27-1fe401053080.jpg?1615223046",
81 | "id": 12
82 | },
83 | {
84 | "cardName": "The Meathook Massacre",
85 | "cardType": "Legendary Enchantment",
86 | "cardManaCost": "2",
87 | "cardColorIdentity": "Black",
88 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/0/8/08950015-eee5-4327-888c-82dfd13bb9ad.jpg?1634350016",
89 | "id": 13
90 | },
91 | {
92 | "cardName": "Gravecrawler",
93 | "cardType": "Creature - Zombie",
94 | "cardManaCost": "1",
95 | "cardColorIdentity": "Black",
96 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/4/8/48d73cb5-22ac-43df-9c4b-0c860bb80b3e.jpg?1562912038",
97 | "id": 14
98 | },
99 | {
100 | "cardName": "Wilhelt, The Rotcleaver",
101 | "cardType": "Legendary Creature - Zombie Warrior",
102 | "cardManaCost": "4",
103 | "cardColorIdentity": "Blue/Black",
104 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/png/front/2/5/2501a911-d072-436d-ae3b-a5164e3b30aa.png?1637627743",
105 | "id": 15
106 | },
107 | {
108 | "cardName": "Lord of the Undead",
109 | "cardType": "Creature - Zombie",
110 | "cardManaCost": "3",
111 | "cardColorIdentity": "Black",
112 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/large/front/7/9/792e773b-5feb-407f-a162-f35d6e693cca.jpg?1562550141",
113 | "id": 16
114 | },
115 | {
116 | "cardName": "Midnight Reaper",
117 | "cardType": "Creature - Zombie Knight",
118 | "cardManaCost": "3",
119 | "cardColorIdentity": "Black",
120 | "cardUrl": "https://c1.scryfall.com/file/scryfall-cards/normal/front/5/e/5e122fe0-51c5-404d-a7b9-3d161a426c35.jpg?1572892997",
121 | "id": 17
122 | }
123 | ]
124 | }
--------------------------------------------------------------------------------
/mtgi-v2/src/components/Sort.js:
--------------------------------------------------------------------------------
1 | import { Context } from './Context';
2 | import { useState, useContext, useEffect } from 'react';
3 |
4 | const Sort = () => {
5 | // sort settings
6 | const [sort, setSort] = useState({
7 | search: '',
8 | alphabetically: false,
9 | manaCost: false,
10 | colorless: false,
11 | white: false,
12 | blue: false,
13 | red: false,
14 | black: false,
15 | green: false,
16 | });
17 | // used at end of useEffect to alter 'query' state in 's
18 | const { setQuery, setScroll } = useContext(Context);
19 |
20 | // generates a new 'query' in upon any value change via 'setQuery'
21 | useEffect(() => {
22 | let newQuery = 'http://localhost:8000/cards';
23 | if (sort.search !== '') newQuery += `?q=${sort.search}`;
24 | let prevSort = false;
25 | if (sort.alphabetically) {
26 | newQuery += '?_sort=cardName';
27 | prevSort = true;
28 | }
29 | if (sort.manaCost) {
30 | newQuery += '?_sort=cardManaCost';
31 | prevSort = true;
32 | }
33 | if (prevSort) newQuery += '&';
34 | if (
35 | sort.colorless ||
36 | sort.white ||
37 | sort.blue ||
38 | sort.red ||
39 | sort.black ||
40 | sort.green
41 | ) {
42 | prevSort
43 | ? (newQuery += 'cardColorIdentity=')
44 | : (newQuery += '?cardColorIdentity=');
45 | }
46 |
47 | let prevColor = false;
48 | for (let i = 0; i <= 5; i++) {
49 | if (sort.colorless && i === 0) {
50 | newQuery += 'Colorless';
51 | prevColor = true;
52 | } else if (sort.white && i === 1) {
53 | prevColor ? (newQuery += '/White') : (newQuery += 'White');
54 | prevColor = true;
55 | } else if (sort.blue && i === 2) {
56 | prevColor ? (newQuery += '/Blue') : (newQuery += 'Blue');
57 | prevColor = true;
58 | } else if (sort.red && i === 3) {
59 | prevColor ? (newQuery += '/Red') : (newQuery += 'Red');
60 | prevColor = true;
61 | } else if (sort.black && i === 4) {
62 | prevColor ? (newQuery += '/Black') : (newQuery += 'Black');
63 | prevColor = true;
64 | } else if (sort.green && i === 5) {
65 | prevColor ? (newQuery += '/Green') : (newQuery += 'Green');
66 | }
67 | }
68 | setQuery(newQuery);
69 | }, [sort, setQuery]);
70 |
71 | return (
72 |
73 | Search:
74 | {
79 | setScroll(null);
80 | setSort({
81 | ...sort,
82 | search: e.target.value,
83 | alphabetically: false,
84 | manaCost: false,
85 | colorless: false,
86 | white: false,
87 | blue: false,
88 | red: false,
89 | black: false,
90 | green: false,
91 | });
92 | }}
93 | />
94 | Sort:
95 | Alphabetically
96 | {
100 | setScroll(null);
101 | setSort({
102 | ...sort,
103 | search: '',
104 | manaCost: false,
105 | alphabetically: !sort.alphabetically,
106 | });
107 | }}
108 | />
109 | Mana Cost
110 | {
114 | setScroll(null);
115 | setSort({
116 | ...sort,
117 | search: '',
118 | manaCost: !sort.manaCost,
119 | alphabetically: false,
120 | });
121 | }}
122 | />
123 | Color Identity:
124 | Colorless
125 | {
129 | setScroll(null);
130 | setSort({ ...sort, search: '', colorless: !sort.colorless });
131 | }}
132 | />
133 | White
134 | {
138 | setScroll(null);
139 | setSort({ ...sort, search: '', white: !sort.white });
140 | }}
141 | />
142 | Blue
143 | {
147 | setScroll(null);
148 | setSort({ ...sort, search: '', blue: !sort.blue });
149 | }}
150 | />
151 | Red
152 | {
156 | setScroll(null);
157 | setSort({ ...sort, search: '', red: !sort.red });
158 | }}
159 | />
160 | Black
161 | {
165 | setScroll(null);
166 | setSort({ ...sort, search: '', black: !sort.black });
167 | }}
168 | />
169 | Green
170 | {
174 | setScroll(null);
175 | setSort({ ...sort, search: '', green: !sort.green });
176 | }}
177 | />
178 |
179 | );
180 | };
181 |
182 | export default Sort;
183 |
--------------------------------------------------------------------------------
/mtgi-v2/src/components/CardForm.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import { Context } from './Context';
3 |
4 | const CardForm = () => {
5 | const [cardName, setCardName] = useState('');
6 | const [cardType, setCardType] = useState('');
7 | const [cardManaCost, setCardManaCost] = useState(0);
8 | const [cardUrl, setCardUrl] = useState('');
9 | const [colorless, setColorless] = useState(false);
10 | const [white, setWhite] = useState(false);
11 | const [blue, setBlue] = useState(false);
12 | const [red, setRed] = useState(false);
13 | const [black, setBlack] = useState(false);
14 | const [green, setGreen] = useState(false);
15 |
16 | const { update, dispatchUpdate, dispatchView } = useContext(Context);
17 |
18 | const handleAddCard = () => {
19 | const cArr = [colorless, white, blue, red, black, green];
20 | let cId = '';
21 | for (let i = 0; i < cArr.length; i++) {
22 | if (cArr[i] && i === 0) {
23 | cId += 'Colorless/';
24 | } else if (cArr[i] && i === 1) {
25 | cId += 'White/';
26 | } else if (cArr[i] && i === 2) {
27 | cId += 'Blue/';
28 | } else if (cArr[i] && i === 3) {
29 | cId += 'Red/';
30 | } else if (cArr[i] && i === 4) {
31 | cId += 'Black/';
32 | } else if (cArr[i] && i === 5) {
33 | cId += 'Green/';
34 | }
35 | }
36 | const cardColorIdentity = cId.slice(0, -1);
37 | const card = {
38 | cardName,
39 | cardType,
40 | cardManaCost,
41 | cardColorIdentity,
42 | cardUrl,
43 | };
44 | console.log(
45 | `Name: ${cardName}, Type: ${cardType}, Mana Cost: ${cardManaCost}, Url: ${cardUrl}`
46 | );
47 | console.log(
48 | `Colorless: ${colorless}, White: ${white}, Blue: ${blue}, Red: ${red}, Black: ${black}, Green: ${green}`
49 | );
50 | console.log(`Color Identity: ${cardColorIdentity}`);
51 | console.log(card);
52 | fetch('http://localhost:8000/cards', {
53 | method: 'POST',
54 | headers: { 'Content-type': 'application/json' },
55 | body: JSON.stringify(card),
56 | })
57 | .then(() => {
58 | console.log('New Card Added');
59 | })
60 | .then(() => {
61 | dispatchUpdate(update + 1);
62 | dispatchView(0);
63 | })
64 | .catch((err) => {
65 | alert(err.message);
66 | });
67 | };
68 |
69 | return (
70 |
71 |
New Card
72 |
170 |
171 | Submit
172 |
173 |
174 | );
175 | };
176 |
177 | export default CardForm;
178 |
--------------------------------------------------------------------------------
/mtgi-v2/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300&display=swap');
2 |
3 | /* FROM App.css */
4 | .App {
5 | text-align: center;
6 | background-color: #252525;
7 | min-height: 100vh;
8 | font-size: calc(10px + 2vmin);
9 | }
10 |
11 | .App-logo {
12 | height: 12vw;
13 | width: 12vw;
14 | margin-bottom: 1.5vw;
15 | margin-top: 1.5vw;
16 | margin-left: 1.5vw;
17 | pointer-events: none;
18 | filter: drop-shadow(0px 0px 5px aliceblue);
19 | }
20 |
21 | /* @media (prefers-reduced-motion: no-preference) {
22 | .App-logo {
23 | animation: App-logo-spin infinite 20s linear;
24 | }
25 | } */
26 |
27 | /* .App-content {
28 | background-color: #252525;
29 | min-height: 100vh;
30 | display: flex;
31 | flex-direction: column;
32 | align-items: center;
33 | justify-content: center;
34 | font-size: calc(10px + 2vmin);
35 | color: white;
36 | } */
37 |
38 | .App-link {
39 | color: #61dafb;
40 | }
41 |
42 | /* @keyframes App-logo-spin {
43 | from {
44 | transform: rotate(0deg);
45 | }
46 | to {
47 | transform: rotate(360deg);
48 | }
49 | } */
50 | /* ^^ FROM App.css ^^ */
51 |
52 | body {
53 | margin: 0;
54 | font-family: 'Fira Code', monospace;
55 | -webkit-font-smoothing: antialiased;
56 | -moz-osx-font-smoothing: grayscale;
57 | }
58 |
59 | code {
60 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
61 | monospace;
62 | }
63 |
64 | * {
65 | color: aliceblue;
66 | user-select: none;
67 | text-shadow: 0px 0px 3px aliceblue;
68 | text-decoration: none;
69 | }
70 | input {
71 | font-family: 'Fira Code', monospace !important;
72 | }
73 | button {
74 | background-color: #252525;
75 | }
76 | button:hover {
77 | cursor: pointer;
78 | box-shadow: 0px 0px 3px aliceblue;
79 | }
80 | a:hover {
81 | color: #252525;
82 | box-shadow: 0px 0px 4px aliceblue;
83 | background-color: aliceblue;
84 | }
85 |
86 | .landing-cards-container {
87 | width: 100%;
88 | height: 100%;
89 | margin: auto;
90 | display: flex;
91 | position: relative;
92 | }
93 | /* navbar */
94 | .navbar {
95 | position: fixed;
96 | float: left;
97 | width: 15vw;
98 | height: 100vw;
99 | border-right: solid 1px;
100 | box-shadow: 0px 0px 4px aliceblue;
101 | display: flex;
102 | flex-direction: column;
103 | }
104 | .navbar-button {
105 | margin-left: 2.5vw;
106 | margin-right: 1.5vw;
107 | margin-top: 1.5vw;
108 | width: 10vw;
109 | }
110 | .navbar-button:hover {
111 | background-color: aliceblue;
112 | color: #252525;
113 | box-shadow: 0px 0px 4px aliceblue;
114 | cursor: pointer;
115 | }
116 |
117 | /* Sort */
118 | .sort {
119 | position: fixed;
120 | margin-left: 15vw;
121 | margin-top: 17vh;
122 | display: flex;
123 | flex-direction: column;
124 | align-items: center;
125 | border-top: solid 1px;
126 | border-bottom: solid 1px;
127 | border-right: solid 1px;
128 | border-color: aliceblue;
129 | padding: 1vw;
130 | padding-bottom: 4vh !important;
131 | box-shadow: 0px 0px 4px aliceblue inset;
132 | /* padding-left: 2vw !important; */
133 | }
134 | .sort:hover {
135 | box-shadow: 0px 0px 8px aliceblue inset;
136 | }
137 | /* .sort-form {
138 | display: flex;
139 | flex-direction: column;
140 | align-items: center;
141 | } */
142 | .sort label {
143 | font-size: small;
144 | margin-top: 1vh;
145 | }
146 | .sort-category {
147 | font-size: x-large !important;
148 | margin-top: 2vh !important;
149 | }
150 | .sort-search {
151 | margin-top: 1vh;
152 | box-shadow: 0px 0px 4px aliceblue;
153 | border: none;
154 | }
155 | .sort-search:focus {
156 | outline: none !important;
157 | box-shadow: 0px 0px 8px aliceblue !important;
158 | }
159 | .sort-color {
160 | margin-top: 2vh !important;
161 | }
162 |
163 | /* Card list */
164 | .card-list {
165 | /*flex-direction: column; */
166 | /* width: 100%; */
167 | margin-top: 2em;
168 | width: 20vw;
169 | /* display: flex;
170 | flex-direction: column;
171 | align-items: center; */
172 | }
173 | .card-list-container {
174 | width: 100%;
175 | display: flex;
176 | flex-direction: column;
177 | margin-left: 33vw;
178 | }
179 |
180 | /* Card Viewer */
181 | .card-viewer {
182 | margin-top: 14vh;
183 | margin-left: 60vw;
184 | position: fixed;
185 | }
186 | .card-viewer img {
187 | border-radius: 20px;
188 | max-height: 700px;
189 | height: 75vh;
190 | box-shadow: 0px 0px 8px aliceblue;
191 | }
192 | .card-viewer img:hover {
193 | cursor: pointer;
194 | }
195 |
196 | /* Individual Card */
197 | .card-data {
198 | width: 100%;
199 | margin: auto;
200 | }
201 | .card-data-name {
202 | width: max-content;
203 | font-size: 20px;
204 | margin-left: auto;
205 | margin-right: auto;
206 | cursor: pointer;
207 | }
208 | .card-data-name:hover {
209 | box-shadow: 0px 0px 4px aliceblue;
210 | background-color: aliceblue;
211 | color: #252525;
212 | }
213 | /* .card-popup {
214 | width: 200px;
215 | z-index: 2;
216 | } */
217 |
218 | /* Card Modal */
219 | .card-modal {
220 | /* position: relative;
221 | opacity: 100%;
222 | z-index: 3; */
223 | margin-top: 14vw;
224 | margin-left: 33vw;
225 | width: 20vw;
226 | /* width: 40%;
227 | margin: auto; */
228 | }
229 | .card-modal-container {
230 | display: flex;
231 | flex-direction: column;
232 | /* justify-content: space-between; */
233 | /* margin-left: 34vw; */
234 | }
235 |
236 | .card-modal-text {
237 | margin-top: auto;
238 | margin-bottom: auto;
239 | }
240 | .card-modal-text p {
241 | font-size: 20px;
242 | }
243 | .card-modal-label {
244 | font-weight: bold;
245 | margin-top: 3vh;
246 | margin-bottom: 0;
247 | }
248 | .card-modal-data {
249 | margin-top: 0;
250 | }
251 | .card-modal-buttons {
252 | display: flex;
253 | justify-content: space-evenly;
254 | }
255 | .card-modal-button {
256 | font-size: medium;
257 | width: 5vw;
258 | /* padding-left: 8px;
259 | padding-right: 8px; */
260 | border: solid 1px;
261 | border-color: aliceblue;
262 | box-shadow: 0px 0px 4px aliceblue;
263 | }
264 | .card-modal-button:hover {
265 | background-color: aliceblue;
266 | color: #252525;
267 | box-shadow: 0px 0px 4px aliceblue;
268 | cursor: pointer;
269 | }
270 |
271 | /* Card Form */
272 | .new-card {
273 | margin-left: 40vw;
274 | margin-top: 25vh;
275 | width: 40vw;
276 | }
277 | .new-card-input-container {
278 | display: flex;
279 | flex-direction: column;
280 | }
281 |
282 | .new-card-text {
283 | display: flex;
284 | flex-direction: column;
285 | font-size: medium;
286 | }
287 |
288 | .new-card-text input {
289 | margin-bottom: 1vw;
290 | box-shadow: 0px 0px 4px aliceblue;
291 | border: none;
292 | }
293 |
294 | .new-card-text input:hover {
295 | box-shadow: 0px 0px 8px aliceblue;
296 | }
297 |
298 | .new-card-text input:focus {
299 | /* textarea:focus,
300 | select:focus { */
301 | outline: none;
302 | box-shadow: 0px 0px 8px aliceblue;
303 | }
304 | .new-card-mana-identity {
305 | display: flex;
306 | justify-content: space-between;
307 | /* flex-direction: column; */
308 | align-items: center;
309 | font-size: medium;
310 | }
311 | .new-card-mana-identity-div {
312 | display: flex;
313 | flex-direction: column;
314 | align-items: center;
315 | width: 7vw;
316 | }
317 |
318 | .new-card-button {
319 | font-size: medium;
320 | width: 5vw;
321 | /* padding-left: 8px;
322 | padding-right: 8px; */
323 | border: solid 1px;
324 | border-color: aliceblue;
325 | box-shadow: 0px 0px 4px aliceblue;
326 | margin: auto;
327 | margin-top: 2vw !important;
328 | }
329 |
330 | .new-card-button:hover {
331 | background-color: aliceblue;
332 | color: #252525;
333 | box-shadow: 0px 0px 4px aliceblue;
334 | cursor: pointer;
335 | }
336 |
337 | input[type='text'],
338 | input[type='number'] {
339 | color: #252525;
340 | text-align: center;
341 | }
342 |
343 | /* Card Form checkboxes */
344 | input[type='checkbox'] {
345 | width: 20px;
346 | height: 20px;
347 | border-radius: 50%;
348 | border-color: aliceblue;
349 | transition: box-shadow 0.3s;
350 | background: #252525;
351 | cursor: pointer;
352 | border: 1px solid;
353 | appearance: none;
354 | -webkit-appearance: none;
355 | filter: drop-shadow(0px 0px 3px aliceblue);
356 | }
357 | input[type='checkbox']:checked {
358 | box-shadow: inset 0 0 0 20px aliceblue;
359 | }
360 | input[type='number']::-webkit-inner-spin-button,
361 | input[type='number']::-webkit-outer-spin-button {
362 | opacity: 1;
363 | }
364 |
--------------------------------------------------------------------------------