From bfd2ba7b0ac26d817c0d624a47589f2edde65c86 Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Sun, 27 Mar 2022 21:11:58 +0700 Subject: [PATCH 01/12] Added puzzle information --- .../PuzzleInfomation.module.scss | 23 ++++++++++ src/components/App/PuzzleInfomation/index.tsx | 45 +++++++++++++++++++ src/components/App/index.tsx | 28 ++++-------- src/pages/admin/index.tsx | 38 +++++++++------- 4 files changed, 100 insertions(+), 34 deletions(-) create mode 100644 src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss create mode 100644 src/components/App/PuzzleInfomation/index.tsx diff --git a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss new file mode 100644 index 0000000..12a1af1 --- /dev/null +++ b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss @@ -0,0 +1,23 @@ +.contentWrap { + h3 { + margin: 1rem 0; + } + + .tableHeader { + // make this a grid of 5 columns + display: grid; + grid-template-columns: 3fr repeat(4, 1fr); + + h4 { + margin: 0; + } + } + + .tableBody { + .tableRow { + display: grid; + grid-template-columns: 3fr repeat(4, 1fr); + gap: 1rem; + } + } +} diff --git a/src/components/App/PuzzleInfomation/index.tsx b/src/components/App/PuzzleInfomation/index.tsx new file mode 100644 index 0000000..1933144 --- /dev/null +++ b/src/components/App/PuzzleInfomation/index.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { PuzzleCustom } from '../../../types/api/puzzles/puzzle'; +import styles from './PuzzleInfomation.module.scss'; + +const PuzzleInfomation = ({ puzzlesList }: { puzzlesList: PuzzleCustom[] }) => { + return ( +
+

Puzzle Infomation

+
+

Puzzle Name

+

Difficulty

+

Success Rate

+

Average Age

+

Average Range

+
+
+
+ {puzzlesList.map((puzzle) => ( +
+
+ {puzzle.name} +
+
+ {puzzle.difficulty} +
+
+ {/* TODO: Calculate success Rate */} + N/A +
+
+ {/* TODO: Implement Age */} + N/A +
+
+ {/* TODO: Implement Age */} + N/A +
+
+ ))} +
+
+ ); +} + +export default PuzzleInfomation; diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 08dbf5b..c053f94 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -1,19 +1,9 @@ -import CardGrid from './CardGrid'; -import QRGenerator from './QRGenerator'; -import PuzzleGenerate from './PuzzleGenerate'; -import PuzzleInput from './PuzzleInput'; -import MapRenderer from './MapRenderer'; -import MapGeocoder from './MapGeocoder'; -import FeedbackGif from './FeedbackGif'; -import SignUpDialog from './SignUpDialog'; - -export { - CardGrid, - QRGenerator, - PuzzleGenerate, - PuzzleInput, - MapRenderer, - MapGeocoder, - FeedbackGif, - SignUpDialog -}; +export { default as CardGrid } from './CardGrid'; +export { default as QRGenerator } from './QRGenerator'; +export { default as PuzzleGenerate } from './PuzzleGenerate'; +export { default as PuzzleInput } from './PuzzleInput'; +export { default as MapRenderer } from './MapRenderer'; +export { default as MapGeocoder } from './MapGeocoder'; +export { default as FeedbackGif } from './FeedbackGif'; +export { default as SignUpDialog } from './SignUpDialog'; +export { default as PuzzleInfomation } from './PuzzleInfomation'; diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index ca574c8..631c16a 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -2,12 +2,29 @@ import * as React from 'react'; import styles from '../../styles/pages/admin.module.scss'; import Router from 'next/router'; import { useSession } from 'next-auth/react'; -import { PuzzleGenerate } from '../../components/App'; +import { PuzzleGenerate, PuzzleInfomation } from '../../components/App'; import { getAllPuzzles, isAdmin } from '../../services'; import { GetServerSideProps } from 'next'; import { Header } from '../../components/Global'; +import type { PuzzleCustom } from '../../types/api/puzzles/puzzle'; -const Admin = ({ puzzlesList }) => { +const Admin = ({ puzzlesList }: { puzzlesList: PuzzleCustom[] }) => { + return ( + <> +
+ {/** TODO: Create Header for admin page */} +

Admin Dashboard

+
+ {/* */} +
+ +
+ + ) + +}; + +const AdminValidation = ({ puzzlesList }: { puzzlesList: PuzzleCustom[] }) => { const { data: session, status } = useSession(); const [validAdmin, setValidAdmin] = React.useState(null); @@ -20,7 +37,7 @@ const Admin = ({ puzzlesList }) => { const email = session?.user?.email; React.useEffect(() => { const checkAdmin = async () => { - setValidAdmin(await isAdmin(email)); + setValidAdmin(await isAdmin({ email })); }; email && checkAdmin(); }, [email]); @@ -31,16 +48,7 @@ const Admin = ({ puzzlesList }) => { if (status === 'authenticated') { if (validAdmin) { - return ( - <> -
-

ADMIN PAGE 🤓

- {/** TODO: Create Header for admin page */} -
- -
- - ); + return } else { validAdmin === false && Router.push('/403'); } // This will need adjustment since it's just a prototype @@ -55,7 +63,7 @@ const Admin = ({ puzzlesList }) => { ) ); -}; +} export const getServerSideProps: GetServerSideProps = async context => { const puzzlesList = await getAllPuzzles(); @@ -64,4 +72,4 @@ export const getServerSideProps: GetServerSideProps = async context => { }; }; -export default Admin; +export default AdminValidation; From 84d35c9a0f83f90a8275823a202c1f149c5268a7 Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Mon, 28 Mar 2022 00:19:10 +0700 Subject: [PATCH 02/12] Added puzzle map choice --- package-lock.json | 85 ++++++++++++++++ package.json | 2 + .../LocationSearchModal.module.scss | 20 ++++ .../App/LocationSearchModal/index.tsx | 98 +++++++++++++++++++ src/components/App/MapRenderer/index.tsx | 22 ++++- .../PuzzleInfomation.module.scss | 4 + src/components/App/index.tsx | 1 + src/components/Global/Input/index.tsx | 33 ++++++- src/pages/admin/index.tsx | 26 ++++- src/pages/puzzles/map/index.tsx | 2 +- src/styles/pages/admin.module.scss | 24 +++++ src/types/global.ts | 2 + src/types/map.ts | 2 + 13 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 src/components/App/LocationSearchModal/LocationSearchModal.module.scss create mode 100644 src/components/App/LocationSearchModal/index.tsx diff --git a/package-lock.json b/package-lock.json index 4684cc2..33e0f0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-hot-toast": "^2.2.0", + "react-modal": "^3.14.4", "sass": "^1.42.1", "superjson": "^1.8.0", "swr": "^1.1.2" @@ -32,6 +33,7 @@ "@testing-library/react": "^12.1.1", "@types/faker": "5.5.9", "@types/jest": "^27.0.2", + "@types/react-modal": "^3.13.1", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", @@ -2136,6 +2138,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-modal": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.13.1.tgz", + "integrity": "sha512-iY/gPvTDIy6Z+37l+ibmrY+GTV4KQTHcCyR5FIytm182RQS69G5ps4PH2FxtC7bAQ2QRHXMevsBgck7IQruHNg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -4806,6 +4817,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -9420,6 +9436,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.14.4.tgz", + "integrity": "sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17" + } + }, "node_modules/react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -10818,6 +10857,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.0.tgz", @@ -12644,6 +12691,15 @@ "csstype": "^3.0.2" } }, + "@types/react-modal": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.13.1.tgz", + "integrity": "sha512-iY/gPvTDIy6Z+37l+ibmrY+GTV4KQTHcCyR5FIytm182RQS69G5ps4PH2FxtC7bAQ2QRHXMevsBgck7IQruHNg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -14681,6 +14737,11 @@ "strip-final-newline": "^2.0.0" } }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -18080,6 +18141,22 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-modal": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.14.4.tgz", + "integrity": "sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg==", + "requires": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -19148,6 +19225,14 @@ "makeerror": "1.0.12" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.0.tgz", diff --git a/package.json b/package.json index dd7be37..adad5f2 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-hot-toast": "^2.2.0", + "react-modal": "^3.14.4", "sass": "^1.42.1", "superjson": "^1.8.0", "swr": "^1.1.2" @@ -37,6 +38,7 @@ "@testing-library/react": "^12.1.1", "@types/faker": "5.5.9", "@types/jest": "^27.0.2", + "@types/react-modal": "^3.13.1", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", diff --git a/src/components/App/LocationSearchModal/LocationSearchModal.module.scss b/src/components/App/LocationSearchModal/LocationSearchModal.module.scss new file mode 100644 index 0000000..661c974 --- /dev/null +++ b/src/components/App/LocationSearchModal/LocationSearchModal.module.scss @@ -0,0 +1,20 @@ +.modalWraper { + display: flex; + flex-direction: column; + gap: 1rem; + height: 40rem; + width: 40rem; + + .reviewSubmit { + display: flex; + flex-direction: row; + gap: 1rem; + + & > :first-child { + width: 100%; + & > * { + text-align: center; + } + } + } +} diff --git a/src/components/App/LocationSearchModal/index.tsx b/src/components/App/LocationSearchModal/index.tsx new file mode 100644 index 0000000..fbc7b56 --- /dev/null +++ b/src/components/App/LocationSearchModal/index.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import { useState, useEffect, useCallback } from 'react'; +import { Geocoder } from '@maptiler/geocoder'; +import styles from './LocationSearchModal.module.scss'; +import MapGeocoder from '../MapGeocoder'; +import type { MapAnchor, MapMarker } from '../../../types/map'; +import MapRenderer from '../MapRenderer'; +import { Button, Input } from '../../Global'; +import toast from 'react-hot-toast'; + +const MAPTILER_ACCESS_TOKEN = process.env.NEXT_PUBLIC_MAPTILER_ACCESS_TOKEN; + +type LocationSearchModalProps = { + address: string; + setAddress: React.Dispatch>; + setLatitude: React.Dispatch>; + setLongitude: React.Dispatch>; + setModalIsOpen: React.Dispatch>; +}; + +const LocationSearchModal = ({ address, setAddress, setLatitude, setLongitude, setModalIsOpen }: LocationSearchModalProps) => { + const [userMarker, setUserMarker] = useState(null); + const [mapCenter, setMapCenter] = useState(null); + const [tempMarker, setTempMarker] = useState(null); + + useEffect((): void => { + navigator.geolocation.getCurrentPosition( + position => { + const anchor: MapAnchor = [ + position.coords.latitude, + position.coords.longitude + ]; + const zoom = 16; + setUserMarker({ anchor, zoom }); + setMapCenter(anchor); + }, + () => { + const anchor: MapAnchor = [49.88307, -119.48568]; + setMapCenter(anchor); + } + ); + }, []); + + useEffect(() => { + if (!tempMarker) return; + + const lat = tempMarker.anchor[0].toString(); + const lon = tempMarker.anchor[1].toString(); + setLatitude(lat); + setLongitude(lon); + + let geocoder = new Geocoder({ + key: MAPTILER_ACCESS_TOKEN + }); + + geocoder.geocode(`${lon},${lat}`).then(results => { + if (!results) return; + setAddress(results.features[0]['place_name']); + }); + }, [setAddress, setLatitude, setLongitude, tempMarker]); + + return ( +
+ + {userMarker && } +
+ +
+
+ ); +} + +export default LocationSearchModal; diff --git a/src/components/App/MapRenderer/index.tsx b/src/components/App/MapRenderer/index.tsx index 96da5ac..c8d7a51 100644 --- a/src/components/App/MapRenderer/index.tsx +++ b/src/components/App/MapRenderer/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Map, Marker, ZoomControl } from 'pigeon-maps'; import { maptiler } from 'pigeon-maps/providers'; import mapRendererStyles from './MapRenderer.module.scss'; -import { MapMarker, MapRendererProps } from '../../../types/map'; +import { MapAnchor, MapMarker, MapRendererProps } from '../../../types/map'; const MAPTILER_ACCESS_TOKEN = process.env.NEXT_PUBLIC_MAPTILER_ACCESS_TOKEN; @@ -12,12 +12,19 @@ const MapRenderer = ({ markers, userMarker, mapCenter, - setMapCenter + setMapCenter, + tempMarker, + setTempMarker, }: MapRendererProps) => { const setMapFocus = (marker: MapMarker): void => { + console.log(tempMarker) setMapCenter(marker.anchor); }; + const handleOnClickMap = (latLng: MapAnchor) => { + setTempMarker({ anchor: latLng, zoom: userMarker.zoom }); + } + return (
{ setMapCenter(center); }} + onClick={({ event, latLng, pixel }) => { console.log(event); handleOnClickMap(latLng); }} > {markers.map((marker, index) => ( setMapFocus(userMarker)} /> )} + { + tempMarker && ( + setMapFocus(tempMarker)} + /> + ) + }
diff --git a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss index 12a1af1..3c6afde 100644 --- a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss +++ b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss @@ -1,5 +1,8 @@ +@import '../../../styles/abstracts/_mixins/text.scss'; + .contentWrap { h3 { + @include font-header; margin: 1rem 0; } @@ -9,6 +12,7 @@ grid-template-columns: 3fr repeat(4, 1fr); h4 { + @include font-header; margin: 0; } } diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index c053f94..1b0a337 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -7,3 +7,4 @@ export { default as MapGeocoder } from './MapGeocoder'; export { default as FeedbackGif } from './FeedbackGif'; export { default as SignUpDialog } from './SignUpDialog'; export { default as PuzzleInfomation } from './PuzzleInfomation'; +export { default as LocationSearchModal } from './LocationSearchModal'; diff --git a/src/components/Global/Input/index.tsx b/src/components/Global/Input/index.tsx index 175a4f7..11f9860 100644 --- a/src/components/Global/Input/index.tsx +++ b/src/components/Global/Input/index.tsx @@ -10,13 +10,43 @@ const Input = ({ maxLength, minLength, labelText, - setInputVal + value, + setInputVal, + disabled, }: InputProps) => { const [input, setInput] = useState(''); useEffect(() => { setInputVal && setInputVal(input); }, [input, setInputVal]); + + if (value && setInputVal) return ( + <> +
+ {labelText && ( + + )} + { + setInputVal(event.target.value); + }} + disabled={disabled} // This will be false if disabled is not passed in + /> +
+ + ); + return ( <>
@@ -38,6 +68,7 @@ const Input = ({ onChange={event => { setInput(event.target.value); }} + disabled={disabled} // This will be false if disabled is not passed in />
diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index 631c16a..70d5c1b 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -1,14 +1,21 @@ import * as React from 'react'; +import { useState } from 'react'; import styles from '../../styles/pages/admin.module.scss'; import Router from 'next/router'; import { useSession } from 'next-auth/react'; -import { PuzzleGenerate, PuzzleInfomation } from '../../components/App'; +import Modal from 'react-modal'; +import { LocationSearchModal, PuzzleGenerate, PuzzleInfomation } from '../../components/App'; import { getAllPuzzles, isAdmin } from '../../services'; import { GetServerSideProps } from 'next'; import { Header } from '../../components/Global'; import type { PuzzleCustom } from '../../types/api/puzzles/puzzle'; const Admin = ({ puzzlesList }: { puzzlesList: PuzzleCustom[] }) => { + const [modalIsOpen, setModalIsOpen] = React.useState(false); + + const [address, setAddress] = useState(''); + const [latitude, setLatitude] = useState(''); + const [longitude, setLongitude] = useState(''); return ( <>
@@ -19,6 +26,23 @@ const Admin = ({ puzzlesList }: { puzzlesList: PuzzleCustom[] }) => {
+ + {setModalIsOpen(false)}} + // style={customStyles} + contentLabel="Example Modal" + overlayClassName={styles.modalOverlay} + > + + ) diff --git a/src/pages/puzzles/map/index.tsx b/src/pages/puzzles/map/index.tsx index 05d6ef9..66e868b 100644 --- a/src/pages/puzzles/map/index.tsx +++ b/src/pages/puzzles/map/index.tsx @@ -5,7 +5,7 @@ import puzzleMapStyles from '../../../styles/pages/PuzzleMap.module.scss'; import { Filter, Header } from '../../../components/Global'; import { getAllPuzzleInstances } from '../../../services/puzzleInstance'; import { PuzzleMapProps } from '../../../types/puzzle'; -import { MapAnchor, MapMarker } from '../../../types/map'; +import type { MapAnchor, MapMarker } from '../../../types/map'; const PuzzleMap = ({ puzzleInstances }: PuzzleMapProps) => { const [userMarker, setUserMarker] = useState(null); diff --git a/src/styles/pages/admin.module.scss b/src/styles/pages/admin.module.scss index 7805267..dec96b2 100644 --- a/src/styles/pages/admin.module.scss +++ b/src/styles/pages/admin.module.scss @@ -1,5 +1,29 @@ +@import '../abstracts/variables'; + .contentWrap { display: flex; padding: 0 3rem; gap: 5rem; } + +.modalOverlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba($grey, 0.8);; +} + +.modal { + top: 50%; + left: 50%; + right: auto; + bottom: auto; + transform: translate(-50%, -50%); + position: absolute; + margin-right: -50%; + padding: 2rem; + background-color: $white; + border-radius: 0.5rem; +} diff --git a/src/types/global.ts b/src/types/global.ts index 10ea25d..bec9963 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -20,5 +20,7 @@ export type InputProps = { maxLength?: number; minLength?: number; labelText?: string; + value?: string; setInputVal?: Dispatch>; + disabled?: boolean; }; diff --git a/src/types/map.ts b/src/types/map.ts index 1f3e751..9136f7f 100644 --- a/src/types/map.ts +++ b/src/types/map.ts @@ -12,4 +12,6 @@ export type MapRendererProps = { userMarker: MapMarker; mapCenter: MapAnchor; setMapCenter: Dispatch>; + tempMarker?: MapMarker; + setTempMarker?: Dispatch>; }; From 777c6efe58ff3ea22fb429b7ab6da98e0260a4f6 Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Mon, 28 Mar 2022 17:08:29 +0700 Subject: [PATCH 03/12] Finished Adding Puzzle Generate --- .../App/LocationSearchModal/index.tsx | 15 ++- src/components/App/MapRenderer/index.tsx | 1 + .../PuzzleGenerate/PuzzleGenerate.module.scss | 81 ++++++++++-- src/components/App/PuzzleGenerate/index.tsx | 117 ++++++++++-------- .../PuzzleInfomation.module.scss | 2 +- src/components/Global/Input/index.tsx | 3 + src/pages/admin/index.tsx | 39 ++---- src/styles/pages/admin.module.scss | 49 ++++---- src/types/global.ts | 1 + 9 files changed, 195 insertions(+), 113 deletions(-) diff --git a/src/components/App/LocationSearchModal/index.tsx b/src/components/App/LocationSearchModal/index.tsx index fbc7b56..4d06771 100644 --- a/src/components/App/LocationSearchModal/index.tsx +++ b/src/components/App/LocationSearchModal/index.tsx @@ -12,13 +12,15 @@ const MAPTILER_ACCESS_TOKEN = process.env.NEXT_PUBLIC_MAPTILER_ACCESS_TOKEN; type LocationSearchModalProps = { address: string; + latitude: string; + longtitude: string; setAddress: React.Dispatch>; setLatitude: React.Dispatch>; setLongitude: React.Dispatch>; setModalIsOpen: React.Dispatch>; }; -const LocationSearchModal = ({ address, setAddress, setLatitude, setLongitude, setModalIsOpen }: LocationSearchModalProps) => { +const LocationSearchModal = ({ address, latitude, longtitude, setAddress, setLatitude, setLongitude, setModalIsOpen }: LocationSearchModalProps) => { const [userMarker, setUserMarker] = useState(null); const [mapCenter, setMapCenter] = useState(null); const [tempMarker, setTempMarker] = useState(null); @@ -41,6 +43,15 @@ const LocationSearchModal = ({ address, setAddress, setLatitude, setLongitude, s ); }, []); + useEffect((): void => { + if (!latitude || !longtitude || !userMarker || !setTempMarker) return; + + setTempMarker({ + anchor: [parseFloat(latitude), parseFloat(longtitude)], + zoom: userMarker.zoom, + }) + }, [latitude, longtitude, userMarker, setTempMarker]); + useEffect(() => { if (!tempMarker) return; @@ -74,7 +85,7 @@ const LocationSearchModal = ({ address, setAddress, setLatitude, setLongitude, s { + if (!setTempMarker) return; setTempMarker({ anchor: latLng, zoom: userMarker.zoom }); } diff --git a/src/components/App/PuzzleGenerate/PuzzleGenerate.module.scss b/src/components/App/PuzzleGenerate/PuzzleGenerate.module.scss index a3033cc..5469dbb 100644 --- a/src/components/App/PuzzleGenerate/PuzzleGenerate.module.scss +++ b/src/components/App/PuzzleGenerate/PuzzleGenerate.module.scss @@ -1,16 +1,15 @@ @import '../../../styles/abstracts/_mixins/text.scss'; - +@import '../../../styles/abstracts/variables'; .form { display: flex; flex-direction: column; - margin: 0 2rem; - gap: 1rem; + gap: 0.5rem; & > input { @include font-body; } - & > h2 { + & > h3 { margin: 0; } @@ -24,11 +23,53 @@ } } - .selections { - width: 50%; - height: 120%; - cursor: pointer; - @include font-body; + .selectionWrapper { + @include font-button; + color: $black; + display: inline-block; + overflow: hidden; + border: 2px solid #4b4b4b; + box-sizing: border-box; + border-radius: 999px; + width: 30rem; + flex: 1; + padding: 0.5rem 1rem; + position: relative; + + .selections { + cursor: pointer; + font-size: 0.875rem; + width: 120%; + height: 120%; + outline: none; + border: 0; + + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + } + &::after { + content: '▼'; + font-size: 0.8rem; + pointer-events: none; + position: absolute; + right: 1rem; + top: 0.75rem; + } + } + + .hintWrapper { + & > :first-child { + width: 30rem; + } + } + + .addressWrapper { + & > :first-child { + width: 23rem; + } + display: flex; + gap: 1rem; } .qrCode { @@ -37,3 +78,25 @@ justify-content: center; } } + +.modalOverlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba($grey, 0.8); + + .modal { + top: 50%; + left: 50%; + right: auto; + bottom: auto; + transform: translate(-50%, -50%); + position: absolute; + margin-right: -50%; + padding: 2rem; + background-color: $white; + border-radius: 0.5rem; + } +} diff --git a/src/components/App/PuzzleGenerate/index.tsx b/src/components/App/PuzzleGenerate/index.tsx index d9c4cd4..fa6d984 100644 --- a/src/components/App/PuzzleGenerate/index.tsx +++ b/src/components/App/PuzzleGenerate/index.tsx @@ -2,18 +2,19 @@ import * as React from 'react'; import { useState } from 'react'; import styles from './PuzzleGenerate.module.scss'; import toast from 'react-hot-toast'; +import Modal from 'react-modal'; import { Button, Input } from '../../Global'; -import { QRGenerator } from '..'; +import { LocationSearchModal } from '..'; import { createPuzzleInstance } from '../../../services'; +import { PuzzleCustom } from '../../../types/api/puzzles/puzzle'; -const PuzzleGenerate = ({ puzzlesList }) => { +const PuzzleGenerate = ({ puzzlesList, modalIsOpen, setModalIsOpen }) => { const [hint, setHint] = useState(''); const [latitude, setLatitude] = useState(''); const [longitude, setLongitude] = useState(''); const [address, setAddress] = useState(''); const [puzzleId, setPuzzleId] = useState(''); - const [puzzleInstanceData, setPuzzleInstanceData] = useState(); const handleSubmit = () => { const puzzleInstancePromise = async () => { @@ -25,15 +26,13 @@ const PuzzleGenerate = ({ puzzlesList }) => { hint ); - if (puzzleInstance.error) { + if (puzzleInstance?.error) { throw new Error(puzzleInstance.message); - } else { - setPuzzleInstanceData(puzzleInstance); } }; toast.promise(puzzleInstancePromise(), { loading: 'Making your puzzle instance... ⚙️', - success: 'Success', + success: 'Successfully added new puzzle instance', error: err => err.message }); }; @@ -41,71 +40,87 @@ const PuzzleGenerate = ({ puzzlesList }) => { return ( <>
-

Make a puzzle instance

- -
- - - -
-
- {puzzlesList.length > 0 && ( +

Set New Puzzle Location

+
+ {puzzlesList.length > 0 ? ( + ) : ( + <> + )}
+
+ +
+
+ setModalIsOpen(true)} + /> +
- {puzzleInstanceData && ( -
- {/** TODO: Create link to puzzle map page */} - {/** TODO: Make it copiable */} - -
- )}
+ { setModalIsOpen(false) }} + contentLabel="Example Modal" + overlayClassName={styles.modalOverlay} + appElement={document.getElementById('__next') as HTMLElement} + > + + ); }; diff --git a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss index 3c6afde..baf36b2 100644 --- a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss +++ b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss @@ -21,7 +21,7 @@ .tableRow { display: grid; grid-template-columns: 3fr repeat(4, 1fr); - gap: 1rem; + margin-bottom: .5rem; } } } diff --git a/src/components/Global/Input/index.tsx b/src/components/Global/Input/index.tsx index 11f9860..e500a94 100644 --- a/src/components/Global/Input/index.tsx +++ b/src/components/Global/Input/index.tsx @@ -13,6 +13,7 @@ const Input = ({ value, setInputVal, disabled, + onClick }: InputProps) => { const [input, setInput] = useState(''); @@ -42,6 +43,7 @@ const Input = ({ setInputVal(event.target.value); }} disabled={disabled} // This will be false if disabled is not passed in + onClick={onClick} />
@@ -69,6 +71,7 @@ const Input = ({ setInput(event.target.value); }} disabled={disabled} // This will be false if disabled is not passed in + onClick={onClick} /> diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index 70d5c1b..efba9f6 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -1,10 +1,8 @@ import * as React from 'react'; -import { useState } from 'react'; import styles from '../../styles/pages/admin.module.scss'; import Router from 'next/router'; import { useSession } from 'next-auth/react'; -import Modal from 'react-modal'; -import { LocationSearchModal, PuzzleGenerate, PuzzleInfomation } from '../../components/App'; +import { PuzzleGenerate, PuzzleInfomation } from '../../components/App'; import { getAllPuzzles, isAdmin } from '../../services'; import { GetServerSideProps } from 'next'; import { Header } from '../../components/Global'; @@ -12,37 +10,22 @@ import type { PuzzleCustom } from '../../types/api/puzzles/puzzle'; const Admin = ({ puzzlesList }: { puzzlesList: PuzzleCustom[] }) => { const [modalIsOpen, setModalIsOpen] = React.useState(false); - - const [address, setAddress] = useState(''); - const [latitude, setLatitude] = useState(''); - const [longitude, setLongitude] = useState(''); return ( <>
{/** TODO: Create Header for admin page */} -

Admin Dashboard

-
- {/* */} +

Admin Dashboard

+
- +
+ + +
+
+ Hi +
- - {setModalIsOpen(false)}} - // style={customStyles} - contentLabel="Example Modal" - overlayClassName={styles.modalOverlay} - > - - + ) diff --git a/src/styles/pages/admin.module.scss b/src/styles/pages/admin.module.scss index dec96b2..f6a8900 100644 --- a/src/styles/pages/admin.module.scss +++ b/src/styles/pages/admin.module.scss @@ -1,29 +1,34 @@ @import '../abstracts/variables'; -.contentWrap { - display: flex; - padding: 0 3rem; - gap: 5rem; +.adminTitle { + padding: .5rem 3rem; + margin: 0; + background-color: $white; } -.modalOverlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba($grey, 0.8);; +.adminHeaderSep { + margin: 0; + margin-bottom: 1rem; } -.modal { - top: 50%; - left: 50%; - right: auto; - bottom: auto; - transform: translate(-50%, -50%); - position: absolute; - margin-right: -50%; - padding: 2rem; - background-color: $white; - border-radius: 0.5rem; +.contentWrap { + display: grid; + grid-template-columns: 6fr 3fr; + + .contentLeftWrap { + display: flex; + flex-direction: column; + padding: 0 3rem; + gap: 3rem; + margin-right: 5rem; + } } + +.adminSeperator { + height: 100%; + border-left: 1px solid $grey; + position: absolute; + top: 0; + left: calc(100% / (6 + 3) * 6 - 2%); + z-index: -1000000000000; +} \ No newline at end of file diff --git a/src/types/global.ts b/src/types/global.ts index bec9963..235a4f4 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -23,4 +23,5 @@ export type InputProps = { value?: string; setInputVal?: Dispatch>; disabled?: boolean; + onClick?: () => void }; From 75010f8d0b0d00d3532a4f95062376b0a4608589 Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Mon, 28 Mar 2022 17:19:22 +0700 Subject: [PATCH 04/12] Change color of seperator --- .../App/PuzzleInfomation/PuzzleInfomation.module.scss | 4 ++-- src/styles/abstracts/_variables.scss | 1 + src/styles/pages/admin.module.scss | 9 +++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss index baf36b2..e8f0f1f 100644 --- a/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss +++ b/src/components/App/PuzzleInfomation/PuzzleInfomation.module.scss @@ -9,7 +9,7 @@ .tableHeader { // make this a grid of 5 columns display: grid; - grid-template-columns: 3fr repeat(4, 1fr); + grid-template-columns: 2fr repeat(4, 1fr); h4 { @include font-header; @@ -20,7 +20,7 @@ .tableBody { .tableRow { display: grid; - grid-template-columns: 3fr repeat(4, 1fr); + grid-template-columns: 2fr repeat(4, 1fr); margin-bottom: .5rem; } } diff --git a/src/styles/abstracts/_variables.scss b/src/styles/abstracts/_variables.scss index 6107abf..5d851ef 100644 --- a/src/styles/abstracts/_variables.scss +++ b/src/styles/abstracts/_variables.scss @@ -25,6 +25,7 @@ $info: $white; $light: $white; $shadow-color: rgba($black, 0.15); +$grey-border: #9a9a9a; // Product Pages $max-product-content-width: 1200px; diff --git a/src/styles/pages/admin.module.scss b/src/styles/pages/admin.module.scss index f6a8900..b98f212 100644 --- a/src/styles/pages/admin.module.scss +++ b/src/styles/pages/admin.module.scss @@ -1,5 +1,6 @@ @import '../abstracts/variables'; + .adminTitle { padding: .5rem 3rem; margin: 0; @@ -13,7 +14,7 @@ .contentWrap { display: grid; - grid-template-columns: 6fr 3fr; + grid-template-columns: 5fr 3fr; .contentLeftWrap { display: flex; @@ -26,9 +27,9 @@ .adminSeperator { height: 100%; - border-left: 1px solid $grey; + border-left: 1px solid $grey-border; position: absolute; top: 0; - left: calc(100% / (6 + 3) * 6 - 2%); - z-index: -1000000000000; + left: calc(100% / (5 + 3) * 5 - 2%); + z-index: -1; } \ No newline at end of file From ad75f53596b4bc1c52603e5b97bf88ed40fe8a15 Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Mon, 28 Mar 2022 22:04:06 +0700 Subject: [PATCH 05/12] Added puzzle instances --- package-lock.json | 19 +++ package.json | 1 + .../DisplayPuzzleInstances.module.scss | 81 +++++++++++++ .../App/DisplayPuzzleInstances/index.tsx | 111 ++++++++++++++++++ src/components/App/PuzzleGenerate/index.tsx | 5 +- src/components/App/QRGenerator/index.tsx | 5 +- src/components/App/index.tsx | 1 + src/pages/admin/index.tsx | 4 +- src/services/index.ts | 2 + 9 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss create mode 100644 src/components/App/DisplayPuzzleInstances/index.tsx diff --git a/package-lock.json b/package-lock.json index 33e0f0f..a424a57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@testing-library/react": "^12.1.1", "@types/faker": "5.5.9", "@types/jest": "^27.0.2", + "@types/qrcode.react": "^1.0.2", "@types/react-modal": "^3.13.1", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^4.33.0", @@ -2128,6 +2129,15 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, + "node_modules/@types/qrcode.react": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.2.tgz", + "integrity": "sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react": { "version": "17.0.37", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", @@ -12681,6 +12691,15 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, + "@types/qrcode.react": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.2.tgz", + "integrity": "sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react": { "version": "17.0.37", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", diff --git a/package.json b/package.json index adad5f2..d103661 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@testing-library/react": "^12.1.1", "@types/faker": "5.5.9", "@types/jest": "^27.0.2", + "@types/qrcode.react": "^1.0.2", "@types/react-modal": "^3.13.1", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^4.33.0", diff --git a/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss b/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss new file mode 100644 index 0000000..3eac56b --- /dev/null +++ b/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss @@ -0,0 +1,81 @@ +@import '../../../styles/abstracts/variables'; +@import '../../../styles/abstracts/_mixins/text.scss'; + + +.puzzleInstancesWrapper { + display: flex; + flex-direction: column; + + .puzzleInstancesHeader { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 2rem; + + .puzzleInstancesHeaderSelectWrapper { + @include font-button; + color: $black; + display: inline-block; + overflow: hidden; + border: 2px solid #4b4b4b; + box-sizing: border-box; + border-radius: 999px; + flex: 1; + padding: 0.5rem 1rem; + position: relative; + margin-right: 2rem; + + .puzzleInstancesHeaderSelect { + cursor: pointer; + font-size: 0.875rem; + width: 120%; + height: 120%; + outline: none; + border: 0; + + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + } + &::after { + content: '▼'; + font-size: 0.8rem; + pointer-events: none; + position: absolute; + right: 1rem; + top: 0.75rem; + } + + } + } + + .puzzleInstancesBody { + .puzzleInstancesBodyTitle { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + text-align: center; + } + .puzzleInstancesBodyContent { + overflow-y: auto; + height: 60vh ; + .puzzleInstancesBodyContentRow { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + text-align: center; + margin-bottom: 1rem; + &>:first-child { + text-align: left; + } + align-items: center; + + .puzzleInstancesBodyContentQR { + width: 10rem; + padding: 0; + margin: 0; + } + } + } + } +} \ No newline at end of file diff --git a/src/components/App/DisplayPuzzleInstances/index.tsx b/src/components/App/DisplayPuzzleInstances/index.tsx new file mode 100644 index 0000000..453bbe0 --- /dev/null +++ b/src/components/App/DisplayPuzzleInstances/index.tsx @@ -0,0 +1,111 @@ +import * as React from 'react'; +import { useState, useEffect } from 'react'; +import styles from './DisplayPuzzleInstances.module.scss'; +import { PuzzleCustom } from '../../../types/api/puzzles/puzzle'; +import { getAllPuzzleInstances } from '../../../services'; +import { PuzzleInstance } from '@prisma/client'; +import { Button } from '../../Global'; +import { QRGenerator } from '../../App'; + +type DisplayPuzzleInstancesProps = { + puzzlesList: PuzzleCustom[], +} + +const DisplayPuzzleInstances = ({ puzzlesList }: DisplayPuzzleInstancesProps) => { + const [puzzleId, setPuzzleId] = useState(''); + const [puzzleInstances, setPuzzleInstances] = useState([]); + const [displayPuzzleInstances, setDisplayPuzzleInstances] = useState([]); + + useEffect(() => { + const getPuzzleInstances = async () => { + const retrivedPuzzleInstances = await getAllPuzzleInstances(); + setPuzzleInstances(retrivedPuzzleInstances); + }; + + getPuzzleInstances(); + }, []); + + useEffect(() => { + if (!puzzleInstances || !setDisplayPuzzleInstances) return; + + if (!puzzleId || puzzleId === '') { + setDisplayPuzzleInstances(puzzleInstances); + } else { + setDisplayPuzzleInstances(puzzleInstances.filter(pzl => pzl.puzzleId.toString() === puzzleId)); + } + }, [puzzleId, puzzleInstances, setDisplayPuzzleInstances]); + + return ( +
+ {/* HEADER */} +
+
+

See Locations

+
+ {/* Select puzzle */} +
+ {puzzlesList.length > 0 ? ( + + ) : ( + + )} +
+
+ + {/* BODY */} +
+ {/* 3 cols table title */} +
+

Address

+

QR Code

+

Actions

+
+ + {/* 3 cols table content */} +
+ {displayPuzzleInstances.length > 0 && ( + displayPuzzleInstances.map((puzzleInstance: PuzzleInstance, index: number) => ( +
+
+ {puzzleInstance.address} +
+
+ +
+
+
+
+ )) + )} +
+
+
+ ); +}; + +export default DisplayPuzzleInstances; diff --git a/src/components/App/PuzzleGenerate/index.tsx b/src/components/App/PuzzleGenerate/index.tsx index fa6d984..b811d62 100644 --- a/src/components/App/PuzzleGenerate/index.tsx +++ b/src/components/App/PuzzleGenerate/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useState } from 'react'; +import { useState } from 'react';`` import styles from './PuzzleGenerate.module.scss'; import toast from 'react-hot-toast'; import Modal from 'react-modal'; @@ -45,7 +45,7 @@ const PuzzleGenerate = ({ puzzlesList, modalIsOpen, setModalIsOpen }) => { {puzzlesList.length > 0 ? ( setPuzzleId(e.currentTarget.value)} >\ diff --git a/src/components/App/QRGenerator/index.tsx b/src/components/App/QRGenerator/index.tsx index 3fd47fe..7fef1c2 100644 --- a/src/components/App/QRGenerator/index.tsx +++ b/src/components/App/QRGenerator/index.tsx @@ -2,13 +2,12 @@ import * as React from 'react'; import styles from './QRGenerator.module.scss'; import QRCode from 'qrcode.react'; -const QRGenerator = ({ text }: { text: string }) => { +const QRGenerator = ({ text, className }: { text: string, className?: string }) => { return ( {
- Hi +
diff --git a/src/services/index.ts b/src/services/index.ts index 92ada21..0b81aee 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,6 +2,7 @@ import { signUp } from './signup'; import { resetPassword } from './resetPassword'; import { getAllPuzzles } from './puzzle'; import { + getAllPuzzleInstances, getPuzzleInstance, createPuzzleInstance, submitPuzzleInstance @@ -12,6 +13,7 @@ export { signUp, resetPassword, getAllPuzzles, + getAllPuzzleInstances, getPuzzleInstance, createPuzzleInstance, submitPuzzleInstance, From 0e1945ccec9a769c95231eb3bd0b540efc6c4976 Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Mon, 28 Mar 2022 22:12:30 +0700 Subject: [PATCH 06/12] Add placeholder and todo --- .../DisplayPuzzleInstances.module.scss | 9 ++++++ .../App/DisplayPuzzleInstances/index.tsx | 29 ++++++++++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss b/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss index 3eac56b..e4eba23 100644 --- a/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss +++ b/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss @@ -75,6 +75,15 @@ padding: 0; margin: 0; } + + .puzzleInstancesBodyContentActions { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + gap: .5rem; + } } } } diff --git a/src/components/App/DisplayPuzzleInstances/index.tsx b/src/components/App/DisplayPuzzleInstances/index.tsx index 453bbe0..2d85826 100644 --- a/src/components/App/DisplayPuzzleInstances/index.tsx +++ b/src/components/App/DisplayPuzzleInstances/index.tsx @@ -6,6 +6,7 @@ import { getAllPuzzleInstances } from '../../../services'; import { PuzzleInstance } from '@prisma/client'; import { Button } from '../../Global'; import { QRGenerator } from '../../App'; +import toast from 'react-hot-toast'; type DisplayPuzzleInstancesProps = { puzzlesList: PuzzleCustom[], @@ -16,15 +17,19 @@ const DisplayPuzzleInstances = ({ puzzlesList }: DisplayPuzzleInstancesProps) => const [puzzleInstances, setPuzzleInstances] = useState([]); const [displayPuzzleInstances, setDisplayPuzzleInstances] = useState([]); - useEffect(() => { - const getPuzzleInstances = async () => { - const retrivedPuzzleInstances = await getAllPuzzleInstances(); - setPuzzleInstances(retrivedPuzzleInstances); - }; + const getPuzzleInstances = async () => { + const retrivedPuzzleInstances = await getAllPuzzleInstances(); + setPuzzleInstances(retrivedPuzzleInstances); + }; + useEffect(() => { getPuzzleInstances(); }, []); + useEffect(() => { + getPuzzleInstances(); + }, [puzzlesList]); + useEffect(() => { if (!puzzleInstances || !setDisplayPuzzleInstances) return; @@ -90,13 +95,23 @@ const DisplayPuzzleInstances = ({ puzzlesList }: DisplayPuzzleInstancesProps) => text={JSON.stringify(puzzleInstance)} /> -
+
+
From 73fcfbea42b23f02c52cfd68b33ff7ed0ec17b8f Mon Sep 17 00:00:00 2001 From: Kiet Phan Date: Mon, 28 Mar 2022 22:18:14 +0700 Subject: [PATCH 07/12] Prettify --- src/__mocks__/index.ts | 2 +- src/__mocks__/pages/api/puzzles/index.ts | 38 ++++---- .../pages/api/admin/validate.test.ts | 10 +- .../pages/api/auth/reset-password.test.ts | 15 ++- src/__tests__/pages/api/auth/signup.test.ts | 7 +- src/__tests__/pages/api/puzzles/id.test.ts | 36 ++++---- .../pages/api/puzzles/instances/index.test.ts | 4 +- src/__tests__/pages/user/index.test.ts | 7 +- .../DisplayPuzzleInstances.module.scss | 10 +- .../App/DisplayPuzzleInstances/index.tsx | 91 +++++++++++-------- .../App/LocationSearchModal/index.tsx | 40 +++++--- src/components/App/MapRenderer/index.tsx | 29 +++--- src/components/App/PuzzleGenerate/index.tsx | 30 +++--- .../PuzzleInfomation.module.scss | 2 +- src/components/App/PuzzleInfomation/index.tsx | 12 +-- src/components/App/QRGenerator/index.tsx | 8 +- .../App/SignUpDialog/SignUpDialog.module.scss | 10 +- src/components/App/SignUpDialog/index.tsx | 29 +++--- src/components/App/index.tsx | 2 +- src/components/Global/Input/index.tsx | 55 +++++------ src/pages/_app.tsx | 11 +-- src/pages/admin/index.tsx | 23 +++-- src/pages/auth/signup/index.tsx | 4 +- src/styles/pages/admin.module.scss | 5 +- src/types/global.ts | 2 +- 25 files changed, 267 insertions(+), 215 deletions(-) diff --git a/src/__mocks__/index.ts b/src/__mocks__/index.ts index 901f3a0..9b6c4ce 100644 --- a/src/__mocks__/index.ts +++ b/src/__mocks__/index.ts @@ -1,3 +1,3 @@ -import { PrismaClient } from "@prisma/client"; +import { PrismaClient } from '@prisma/client'; export const prisma = new PrismaClient(); diff --git a/src/__mocks__/pages/api/puzzles/index.ts b/src/__mocks__/pages/api/puzzles/index.ts index 74ab120..2fffd09 100644 --- a/src/__mocks__/pages/api/puzzles/index.ts +++ b/src/__mocks__/pages/api/puzzles/index.ts @@ -13,32 +13,36 @@ import { mockQuestion } from './instances/create'; -export const mockPuzzleInstance = (puzzle?: Puzzle): Promise => { +export const mockPuzzleInstance = ( + puzzle?: Puzzle +): Promise => { return prisma.puzzleInstance.create({ data: { hint: mockHint(), longitude: mockLongtitude(), latitude: mockLatitude(), address: mockAdress(), - puzzle: puzzle ? { - connect: { - id: puzzle.id - } - } : { - create: { - name: mockName(), - difficulty: mockDifficulty(), - content: [mockContent(), mockContent(), mockContent()], - question: mockQuestion(), - variables: {}, - isGenerated: mockBoolean(), - puzzleType: { + puzzle: puzzle + ? { + connect: { + id: puzzle.id + } + } + : { create: { - name: mockName() + name: mockName(), + difficulty: mockDifficulty(), + content: [mockContent(), mockContent(), mockContent()], + question: mockQuestion(), + variables: {}, + isGenerated: mockBoolean(), + puzzleType: { + create: { + name: mockName() + } + } } } - } - } } }); }; diff --git a/src/__tests__/pages/api/admin/validate.test.ts b/src/__tests__/pages/api/admin/validate.test.ts index 0f30f24..825fdba 100644 --- a/src/__tests__/pages/api/admin/validate.test.ts +++ b/src/__tests__/pages/api/admin/validate.test.ts @@ -50,10 +50,11 @@ describe('/api/admin/validate: Success', () => { await adminValidate(req, res); expect(status).toHaveBeenNthCalledWith(1, 200); - expect(json).toHaveBeenNthCalledWith(1, + expect(json).toHaveBeenNthCalledWith( + 1, expect.objectContaining({ email: adminEmail, - password: expect.any(String), + password: expect.any(String) }) ); }); @@ -92,10 +93,11 @@ describe('/api/admin/validate: Success', () => { await adminValidate(req, res); expect(status).toHaveBeenNthCalledWith(1, 403); - expect(json).toHaveBeenNthCalledWith(1, + expect(json).toHaveBeenNthCalledWith( + 1, expect.objectContaining({ email: userEmail, - password: expect.any(String), + password: expect.any(String) }) ); }); diff --git a/src/__tests__/pages/api/auth/reset-password.test.ts b/src/__tests__/pages/api/auth/reset-password.test.ts index 5bd3f5b..1ddd7cd 100644 --- a/src/__tests__/pages/api/auth/reset-password.test.ts +++ b/src/__tests__/pages/api/auth/reset-password.test.ts @@ -159,7 +159,10 @@ describe('/api/auth/reset-password: Failed', () => { await resetPasswordHandler(req, res); - req = { ...req, body: { ...req.body, email: undefined } as resetPasswordProps } as NextApiRequest; + req = { + ...req, + body: { ...req.body, email: undefined } as resetPasswordProps + } as NextApiRequest; await resetPasswordHandler(req, res); @@ -190,7 +193,10 @@ describe('/api/auth/reset-password: Failed', () => { await resetPasswordHandler(req, res); - req = { ...req, body: { ...req.body, oldPassword: undefined } as resetPasswordProps } as NextApiRequest; + req = { + ...req, + body: { ...req.body, oldPassword: undefined } as resetPasswordProps + } as NextApiRequest; await resetPasswordHandler(req, res); @@ -221,7 +227,10 @@ describe('/api/auth/reset-password: Failed', () => { await resetPasswordHandler(req, res); - req = { ...req, body: { ...req.body, newPassword: undefined } as resetPasswordProps } as NextApiRequest; + req = { + ...req, + body: { ...req.body, newPassword: undefined } as resetPasswordProps + } as NextApiRequest; await resetPasswordHandler(req, res); diff --git a/src/__tests__/pages/api/auth/signup.test.ts b/src/__tests__/pages/api/auth/signup.test.ts index 70070bf..f947f58 100644 --- a/src/__tests__/pages/api/auth/signup.test.ts +++ b/src/__tests__/pages/api/auth/signup.test.ts @@ -35,10 +35,11 @@ describe('/api/auth/signup: Succeeded', () => { await signUpHandler(req, res); expect(status).toHaveBeenNthCalledWith(1, 201); - expect(json).toHaveBeenNthCalledWith(1, + expect(json).toHaveBeenNthCalledWith( + 1, expect.objectContaining({ email, - password: expect.any(String), + password: expect.any(String) }) ); @@ -93,7 +94,7 @@ describe('/api/auth/signup: Succeeded', () => { expect(json).toHaveBeenCalledWith( expect.objectContaining({ email: user.email, - password: expect.any(String), + password: expect.any(String) }) ); }); diff --git a/src/__tests__/pages/api/puzzles/id.test.ts b/src/__tests__/pages/api/puzzles/id.test.ts index b3548bf..5fa6705 100644 --- a/src/__tests__/pages/api/puzzles/id.test.ts +++ b/src/__tests__/pages/api/puzzles/id.test.ts @@ -10,27 +10,27 @@ beforeEach(async () => { }); describe('/api/puzzles/[id]: Succeeded', () => { - it('sucessfully retrieves puzzle', async () => { - const newPuzzle = await mockPuzzle(); - const json = jest.fn(); - const status = jest.fn(() => { - return { json }; - }); - const res = { - status - } as unknown as NextApiResponse; + it('sucessfully retrieves puzzle', async () => { + const newPuzzle = await mockPuzzle(); + const json = jest.fn(); + const status = jest.fn(() => { + return { json }; + }); + const res = { + status + } as unknown as NextApiResponse; - const req = { - query: { - id: newPuzzle.id, - } - } as unknown as NextApiRequest; + const req = { + query: { + id: newPuzzle.id + } + } as unknown as NextApiRequest; - await getPuzzleById(req, res); + await getPuzzleById(req, res); - expect(json).toHaveBeenNthCalledWith(1, newPuzzle); - expect(status).toHaveBeenNthCalledWith(1, 200); - }); + expect(json).toHaveBeenNthCalledWith(1, newPuzzle); + expect(status).toHaveBeenNthCalledWith(1, 200); + }); }); // TODO: Create failed tests diff --git a/src/__tests__/pages/api/puzzles/instances/index.test.ts b/src/__tests__/pages/api/puzzles/instances/index.test.ts index b488fb9..f3e62c9 100644 --- a/src/__tests__/pages/api/puzzles/instances/index.test.ts +++ b/src/__tests__/pages/api/puzzles/instances/index.test.ts @@ -47,7 +47,9 @@ describe('/api/puzzles/instances', () => { ]) }); }); - expect(json.mock.calls[0][0].puzzleInstances.length).toEqual(numPuzzleInstances); + expect(json.mock.calls[0][0].puzzleInstances.length).toEqual( + numPuzzleInstances + ); }); }); diff --git a/src/__tests__/pages/user/index.test.ts b/src/__tests__/pages/user/index.test.ts index ca83acf..a2316a5 100644 --- a/src/__tests__/pages/user/index.test.ts +++ b/src/__tests__/pages/user/index.test.ts @@ -33,12 +33,13 @@ describe('/api/user: Succeeded', () => { const res = { status } as unknown as NextApiResponse; - + await usersHandler(req, res); - expect(json).toHaveBeenNthCalledWith(1, + expect(json).toHaveBeenNthCalledWith( + 1, expect.objectContaining({ email, - password: expect.any(String), + password: expect.any(String) }) ); expect(status).toHaveBeenNthCalledWith(1, 200); diff --git a/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss b/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss index e4eba23..bbd368a 100644 --- a/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss +++ b/src/components/App/DisplayPuzzleInstances/DisplayPuzzleInstances.module.scss @@ -1,7 +1,6 @@ @import '../../../styles/abstracts/variables'; @import '../../../styles/abstracts/_mixins/text.scss'; - .puzzleInstancesWrapper { display: flex; flex-direction: column; @@ -46,7 +45,6 @@ right: 1rem; top: 0.75rem; } - } } @@ -58,14 +56,14 @@ } .puzzleInstancesBodyContent { overflow-y: auto; - height: 60vh ; + height: 60vh; .puzzleInstancesBodyContentRow { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; text-align: center; margin-bottom: 1rem; - &>:first-child { + & > :first-child { text-align: left; } align-items: center; @@ -82,9 +80,9 @@ justify-content: center; align-items: center; text-align: center; - gap: .5rem; + gap: 0.5rem; } } } } -} \ No newline at end of file +} diff --git a/src/components/App/DisplayPuzzleInstances/index.tsx b/src/components/App/DisplayPuzzleInstances/index.tsx index 2d85826..77928e4 100644 --- a/src/components/App/DisplayPuzzleInstances/index.tsx +++ b/src/components/App/DisplayPuzzleInstances/index.tsx @@ -9,13 +9,17 @@ import { QRGenerator } from '../../App'; import toast from 'react-hot-toast'; type DisplayPuzzleInstancesProps = { - puzzlesList: PuzzleCustom[], -} + puzzlesList: PuzzleCustom[]; +}; -const DisplayPuzzleInstances = ({ puzzlesList }: DisplayPuzzleInstancesProps) => { +const DisplayPuzzleInstances = ({ + puzzlesList +}: DisplayPuzzleInstancesProps) => { const [puzzleId, setPuzzleId] = useState(''); const [puzzleInstances, setPuzzleInstances] = useState([]); - const [displayPuzzleInstances, setDisplayPuzzleInstances] = useState([]); + const [displayPuzzleInstances, setDisplayPuzzleInstances] = useState< + PuzzleInstance[] + >([]); const getPuzzleInstances = async () => { const retrivedPuzzleInstances = await getAllPuzzleInstances(); @@ -36,7 +40,9 @@ const DisplayPuzzleInstances = ({ puzzlesList }: DisplayPuzzleInstancesProps) => if (!puzzleId || puzzleId === '') { setDisplayPuzzleInstances(puzzleInstances); } else { - setDisplayPuzzleInstances(puzzleInstances.filter(pzl => pzl.puzzleId.toString() === puzzleId)); + setDisplayPuzzleInstances( + puzzleInstances.filter(pzl => pzl.puzzleId.toString() === puzzleId) + ); } }, [puzzleId, puzzleInstances, setDisplayPuzzleInstances]); @@ -55,7 +61,9 @@ const DisplayPuzzleInstances = ({ puzzlesList }: DisplayPuzzleInstancesProps) => value={puzzleId} onChange={e => setPuzzleId(e.currentTarget.value)} > - + {puzzlesList.map((puzzle: PuzzleCustom, index: number) => (