├── 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 | logo 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 | logo 14 | 15 | 16 | Add Card 17 | 18 |
*/} 19 |
20 | 21 | {/*
22 | logo 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 | 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 | {'derp...'} { 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 | 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 | 95 | 96 | { 100 | setScroll(null); 101 | setSort({ 102 | ...sort, 103 | search: '', 104 | manaCost: false, 105 | alphabetically: !sort.alphabetically, 106 | }); 107 | }} 108 | /> 109 | 110 | { 114 | setScroll(null); 115 | setSort({ 116 | ...sort, 117 | search: '', 118 | manaCost: !sort.manaCost, 119 | alphabetically: false, 120 | }); 121 | }} 122 | /> 123 | 124 | 125 | { 129 | setScroll(null); 130 | setSort({ ...sort, search: '', colorless: !sort.colorless }); 131 | }} 132 | /> 133 | 134 | { 138 | setScroll(null); 139 | setSort({ ...sort, search: '', white: !sort.white }); 140 | }} 141 | /> 142 | 143 | { 147 | setScroll(null); 148 | setSort({ ...sort, search: '', blue: !sort.blue }); 149 | }} 150 | /> 151 | 152 | { 156 | setScroll(null); 157 | setSort({ ...sort, search: '', red: !sort.red }); 158 | }} 159 | /> 160 | 161 | { 165 | setScroll(null); 166 | setSort({ ...sort, search: '', black: !sort.black }); 167 | }} 168 | /> 169 | 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 |
73 |
74 |
75 | 76 | setCardName(e.target.value)} 81 | /> 82 | 83 | setCardType(e.target.value)} 88 | /> 89 | 90 | setCardManaCost(e.target.value)} 96 | /> 97 | 98 | setCardUrl(e.target.value)} 103 | /> 104 |
105 |
106 |

Color Identity:

107 |
108 | 109 | setColorless(!colorless)} 115 | /> 116 |
117 |
118 | 119 | setWhite(!white)} 125 | /> 126 |
127 |
128 | 129 | setBlue(!blue)} 135 | /> 136 |
137 |
138 | 139 | setRed(!red)} 145 | /> 146 |
147 |
148 | 149 | setBlack(!black)} 155 | /> 156 |
157 |
158 | 159 | setGreen(!green)} 165 | /> 166 |
167 |
168 |
169 |
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 | --------------------------------------------------------------------------------