├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── app ├── favicon.ico ├── globals.css └── layout.tsx ├── bun.lockb ├── components ├── AccountPopup.tsx ├── Play.tsx └── Upload.tsx ├── init.sh ├── next.config.mjs ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── admin.tsx ├── api │ ├── challenge │ │ └── [mapId].ts │ ├── maps.ts │ ├── maps │ │ └── search.ts │ └── upload.ts └── index.tsx ├── postcss.config.mjs ├── public ├── googlef4fc2bef84ece1c5.html ├── next.svg ├── sitemap.xml ├── sitemap2.xml └── vercel.svg ├── src ├── lib │ └── mongodb.ts └── styles │ ├── AccountPopup.module.css │ ├── Admin.module.css │ ├── Home.module.css │ ├── Play.module.css │ ├── Upload.module.css │ └── globals.css ├── srcpages_app.js ├── srcstylesglobals.css ├── tailwind.config.ts ├── tsconfig.json ├── types ├── challenge.ts └── map.ts ├── utils └── challenge.ts └── wrangler.jsonc /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # 29 | /python 30 | 31 | # local env files 32 | .env*.local 33 | .env*.production 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | /pages/api/dev 41 | 42 | .idea/ 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 2 | Public License 3 | 4 | By exercising the Licensed Rights (defined below), You accept and agree 5 | to be bound by the terms and conditions of this Creative Commons 6 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 7 | ("Public License"). To the extent this Public License may be 8 | interpreted as a contract, You are granted the Licensed Rights in 9 | consideration of Your acceptance of these terms and conditions, and the 10 | Licensor grants You such rights in consideration of benefits the 11 | Licensor receives from making the Licensed Material available under 12 | these terms and conditions. 13 | 14 | 15 | Section 1 -- Definitions. 16 | 17 | a. Adapted Material means material subject to Copyright and Similar 18 | Rights that is derived from or based upon the Licensed Material 19 | and in which the Licensed Material is translated, altered, 20 | arranged, transformed, or otherwise modified in a manner requiring 21 | permission under the Copyright and Similar Rights held by the 22 | Licensor. For purposes of this Public License, where the Licensed 23 | Material is a musical work, performance, or sound recording, 24 | Adapted Material is always produced where the Licensed Material is 25 | synched in timed relation with a moving image. 26 | 27 | b. Adapter's License means the license You apply to Your Copyright 28 | and Similar Rights in Your contributions to Adapted Material in 29 | accordance with the terms and conditions of this Public License. 30 | 31 | c. BY-NC-SA Compatible License means a license listed at 32 | creativecommons.org/compatiblelicenses, approved by Creative 33 | Commons as essentially the equivalent of this Public License. 34 | 35 | d. Copyright and Similar Rights means copyright and/or similar rights 36 | closely related to copyright including, without limitation, 37 | performance, broadcast, sound recording, and Sui Generis Database 38 | Rights, without regard to how the rights are labeled or 39 | categorized. For purposes of this Public License, the rights 40 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 41 | Rights. 42 | 43 | e. Effective Technological Measures means those measures that, in the 44 | absence of proper authority, may not be circumvented under laws 45 | fulfilling obligations under Article 11 of the WIPO Copyright 46 | Treaty adopted on December 20, 1996, and/or similar international 47 | agreements. 48 | 49 | f. Exceptions and Limitations means fair use, fair dealing, and/or 50 | any other exception or limitation to Copyright and Similar Rights 51 | that applies to Your use of the Licensed Material. 52 | 53 | g. License Elements means the license attributes listed in the name 54 | of a Creative Commons Public License. The License Elements of this 55 | Public License are Attribution, NonCommercial, and ShareAlike. 56 | 57 | h. Licensed Material means the artistic or literary work, database, 58 | or other material to which the Licensor applied this Public 59 | License. 60 | 61 | i. Licensed Rights means the rights granted to You subject to the 62 | terms and conditions of this Public License, which are limited to 63 | all Copyright and Similar Rights that apply to Your use of the 64 | Licensed Material and that the Licensor has authority to license. 65 | 66 | j. Licensor means the individual(s) or entity(ies) granting rights 67 | under this Public License. 68 | 69 | k. NonCommercial means not primarily intended for or directed towards 70 | commercial advantage or monetary compensation. For purposes of 71 | this Public License, the exchange of the Licensed Material for 72 | other material subject to Copyright and Similar Rights by digital 73 | file-sharing or similar means is NonCommercial provided there is 74 | no payment of monetary compensation in connection with the 75 | exchange. 76 | 77 | l. Share means to provide material to the public by any means or 78 | process that requires permission under the Licensed Rights, such 79 | as reproduction, public display, public performance, distribution, 80 | dissemination, communication, or importation, and to make material 81 | available to the public including in ways that members of the 82 | public may access the material from a place and at a time 83 | individually chosen by them. 84 | 85 | m. Sui Generis Database Rights means rights other than copyright 86 | resulting from Directive 96/9/EC of the European Parliament and of 87 | the Council of 11 March 1996 on the legal protection of databases, 88 | as amended and/or succeeded, as well as other essentially 89 | equivalent rights anywhere in the world. 90 | 91 | n. You means the individual or entity exercising the Licensed Rights 92 | under this Public License. Your has a corresponding meaning. 93 | 94 | 95 | Section 2 -- Scope. 96 | 97 | a. License grant. 98 | 99 | 1. Subject to the terms and conditions of this Public License, 100 | the Licensor hereby grants You a worldwide, royalty-free, 101 | non-sublicensable, non-exclusive, irrevocable license to 102 | exercise the Licensed Rights in the Licensed Material to: 103 | 104 | a. reproduce and Share the Licensed Material, in whole or 105 | in part, for NonCommercial purposes only; and 106 | 107 | b. produce, reproduce, and Share Adapted Material for 108 | NonCommercial purposes only. 109 | 110 | 2. Exceptions and Limitations. For the avoidance of doubt, where 111 | Exceptions and Limitations apply to Your use, this Public 112 | License does not apply, and You do not need to comply with 113 | its terms and conditions. 114 | 115 | 3. Term. The term of this Public License is specified in Section 116 | 6(a). 117 | 118 | 4. Media and formats; technical modifications allowed. The 119 | Licensor authorizes You to exercise the Licensed Rights in 120 | all media and formats whether now known or hereafter created, 121 | and to make technical modifications necessary to do so. The 122 | Licensor waives and/or agrees not to assert any right or 123 | authority to forbid You from making technical modifications 124 | necessary to exercise the Licensed Rights, including 125 | technical modifications necessary to circumvent Effective 126 | Technological Measures. For purposes of this Public License, 127 | simply making modifications authorized by this Section 2(a) 128 | (4) never produces Adapted Material. 129 | 130 | 5. Downstream recipients. 131 | 132 | a. Offer from the Licensor -- Licensed Material. Every 133 | recipient of the Licensed Material automatically 134 | receives an offer from the Licensor to exercise the 135 | Licensed Rights under the terms and conditions of this 136 | Public License. 137 | 138 | b. Additional offer from the Licensor -- Adapted Material. 139 | Every recipient of Adapted Material from You 140 | automatically receives an offer from the Licensor to 141 | exercise the Licensed Rights in the Adapted Material 142 | under the conditions of the Adapter's License You apply. 143 | 144 | c. No downstream restrictions. You may not offer or impose 145 | any additional or different terms or conditions on, or 146 | apply any Effective Technological Measures to, the 147 | Licensed Material if doing so restricts exercise of the 148 | Licensed Rights by any recipient of the Licensed 149 | Material. 150 | 151 | 6. No endorsement. Nothing in this Public License constitutes or 152 | may be construed as permission to assert or imply that You 153 | are, or that Your use of the Licensed Material is, connected 154 | with, or sponsored, endorsed, or granted official status by, 155 | the Licensor or others designated to receive attribution as 156 | provided in Section 3(a)(1)(A)(i). 157 | 158 | b. Other rights. 159 | 160 | 1. Moral rights, such as the right of integrity, are not 161 | licensed under this Public License, nor are publicity, 162 | privacy, and/or other similar personality rights; however, to 163 | the extent possible, the Licensor waives and/or agrees not to 164 | assert any such rights held by the Licensor to the limited 165 | extent necessary to allow You to exercise the Licensed 166 | Rights, but not otherwise. 167 | 168 | 2. Patent and trademark rights are not licensed under this 169 | Public License. 170 | 171 | 3. To the extent possible, the Licensor waives any right to 172 | collect royalties from You for the exercise of the Licensed 173 | Rights, whether directly or through a collecting society 174 | under any voluntary or waivable statutory or compulsory 175 | licensing scheme. In all other cases the Licensor expressly 176 | reserves any right to collect such royalties, including when 177 | the Licensed Material is used other than for NonCommercial 178 | purposes. 179 | 180 | 181 | Section 3 -- License Conditions. 182 | 183 | Your exercise of the Licensed Rights is expressly made subject to the 184 | following conditions. 185 | 186 | a. Attribution. 187 | 188 | 1. If You Share the Licensed Material (including in modified 189 | form), You must: 190 | 191 | a. retain the following if it is supplied by the Licensor 192 | with the Licensed Material: 193 | 194 | i. identification of the creator(s) of the Licensed 195 | Material and any others designated to receive 196 | attribution, in any reasonable manner requested by 197 | the Licensor (including by pseudonym if 198 | designated); 199 | 200 | ii. a copyright notice; 201 | 202 | iii. a notice that refers to this Public License; 203 | 204 | iv. a notice that refers to the disclaimer of 205 | warranties; 206 | 207 | v. a URI or hyperlink to the Licensed Material to the 208 | extent reasonably practicable; 209 | 210 | b. indicate if You modified the Licensed Material and 211 | retain an indication of any previous modifications; and 212 | 213 | c. indicate the Licensed Material is licensed under this 214 | Public License, and include the text of, or the URI or 215 | hyperlink to, this Public License. 216 | 217 | 2. You may satisfy the conditions in Section 3(a)(1) in any 218 | reasonable manner based on the medium, means, and context in 219 | which You Share the Licensed Material. For example, it may be 220 | reasonable to satisfy the conditions by providing a URI or 221 | hyperlink to a resource that includes the required 222 | information. 223 | 3. If requested by the Licensor, You must remove any of the 224 | information required by Section 3(a)(1)(A) to the extent 225 | reasonably practicable. 226 | 227 | b. ShareAlike. 228 | 229 | In addition to the conditions in Section 3(a), if You Share 230 | Adapted Material You produce, the following conditions also apply. 231 | 232 | 1. The Adapter's License You apply must be a Creative Commons 233 | license with the same License Elements, this version or 234 | later, or a BY-NC-SA Compatible License. 235 | 236 | 2. You must include the text of, or the URI or hyperlink to, the 237 | Adapter's License You apply. You may satisfy this condition 238 | in any reasonable manner based on the medium, means, and 239 | context in which You Share Adapted Material. 240 | 241 | 3. You may not offer or impose any additional or different terms 242 | or conditions on, or apply any Effective Technological 243 | Measures to, Adapted Material that restrict exercise of the 244 | rights granted under the Adapter's License You apply. 245 | 246 | 247 | Section 4 -- Sui Generis Database Rights. 248 | 249 | Where the Licensed Rights include Sui Generis Database Rights that 250 | apply to Your use of the Licensed Material: 251 | 252 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 253 | to extract, reuse, reproduce, and Share all or a substantial 254 | portion of the contents of the database for NonCommercial purposes 255 | only; 256 | 257 | b. if You include all or a substantial portion of the database 258 | contents in a database in which You have Sui Generis Database 259 | Rights, then the database in which You have Sui Generis Database 260 | Rights (but not its individual contents) is Adapted Material, 261 | including for purposes of Section 3(b); and 262 | 263 | c. You must comply with the conditions in Section 3(a) if You Share 264 | all or a substantial portion of the contents of the database. 265 | 266 | For the avoidance of doubt, this Section 4 supplements and does not 267 | replace Your obligations under this Public License where the Licensed 268 | Rights include other Copyright and Similar Rights. 269 | 270 | 271 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 272 | 273 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 274 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 275 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 276 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 277 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 278 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 279 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 280 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 281 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 282 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 283 | 284 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 285 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 286 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 287 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 288 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 289 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 290 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 291 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 292 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 293 | 294 | c. The disclaimer of warranties and limitation of liability provided 295 | above shall be interpreted in a manner that, to the extent 296 | possible, most closely approximates an absolute disclaimer and 297 | waiver of all liability. 298 | 299 | 300 | Section 6 -- Term and Termination. 301 | 302 | a. This Public License applies for the term of the Copyright and 303 | Similar Rights licensed here. However, if You fail to comply with 304 | this Public License, then Your rights under this Public License 305 | terminate automatically. 306 | 307 | b. Where Your right to use the Licensed Material has terminated under 308 | Section 6(a), it reinstates: 309 | 310 | 1. automatically as of the date the violation is cured, provided 311 | it is cured within 30 days of Your discovery of the 312 | violation; or 313 | 314 | 2. upon express reinstatement by the Licensor. 315 | 316 | For the avoidance of doubt, this Section 6(b) does not affect any 317 | right the Licensor may have to seek remedies for Your violations 318 | of this Public License. 319 | 320 | c. For the avoidance of doubt, the Licensor may also offer the 321 | Licensed Material under separate terms or conditions or stop 322 | distributing the Licensed Material at any time; however, doing so 323 | will not terminate this Public License. 324 | 325 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 326 | License. 327 | 328 | 329 | Section 7 -- Other Terms and Conditions. 330 | 331 | a. The Licensor shall not be bound by any additional or different 332 | terms or conditions communicated by You unless expressly agreed. 333 | 334 | b. Any arrangements, understandings, or agreements regarding the 335 | Licensed Material not stated herein are separate from and 336 | independent of the terms and conditions of this Public License. 337 | 338 | 339 | Section 8 -- Interpretation. 340 | 341 | a. For the avoidance of doubt, this Public License does not, and 342 | shall not be interpreted to, reduce, limit, restrict, or impose 343 | conditions on any use of the Licensed Material that could lawfully 344 | be made without permission under this Public License. 345 | 346 | b. To the extent possible, if any provision of this Public License is 347 | deemed unenforceable, it shall be automatically reformed to the 348 | minimum extent necessary to make it enforceable. If the provision 349 | cannot be reformed, it shall be severed from this Public License 350 | without affecting the enforceability of the remaining terms and 351 | conditions. 352 | 353 | c. No term or condition of this Public License will be waived and no 354 | failure to comply consented to unless expressly agreed to by the 355 | Licensor. 356 | 357 | d. Nothing in this Public License constitutes or may be interpreted 358 | as a limitation upon, or waiver of, any privileges and immunities 359 | that apply to the Licensor or You, including from the legal 360 | processes of any jurisdiction or authority. 361 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌍 Challenge Finder 2 | 3 | Welcome to **Challenge Finder** — the easy way to find and play GeoGuessr challenges on your favorite maps! This project provides a streamlined way to search for maps and instantly get a challenge link, so you can start playing with just one click. 4 | 5 | ![Challenge Finder Screenshot](https://i.imgur.com/05X4938.png) 6 | 7 | ## 🚀 Features 8 | 9 | - **Search and Play**: Find GeoGuessr maps by name or browse through the available maps. 10 | - **One-click Challenge Links**: Instantly get a random challenge link for a specific map. 11 | - **User-contributed Challenges**: Help the community by submitting your own challenge links! 12 | - **Options**: Specify the game mode and time limits to your liking. 13 | 14 | ## 🛠 Tech Stack 15 | 16 | - **Frontend**: [Next.js](https://nextjs.org/), [React](https://reactjs.org/), TypeScript 17 | - **Backend**: MongoDB, Node.js API 18 | - **Hosting**: [Vercel](https://vercel.com/) 19 | 20 | ## 📦 Installation 21 | 22 | To run the project locally, follow these steps: 23 | 24 | ```bash 25 | git clone https://github.com/Yaffles/challenge-finder.git 26 | cd challenge-finder 27 | npm install 28 | npm run dev 29 | ``` 30 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yaffles/challenge-finder/a0d00f39c0aac3664592b02375d9e3deb3933153/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yaffles/challenge-finder/a0d00f39c0aac3664592b02375d9e3deb3933153/bun.lockb -------------------------------------------------------------------------------- /components/AccountPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import styles from '../src/styles/AccountPopup.module.css'; 3 | import '../src/styles/globals.css'; 4 | 5 | const setCookie = (name: string, value: string, days = 365) => { 6 | const date = new Date(); 7 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 8 | const expires = "expires=" + date.toUTCString(); 9 | document.cookie = name + "=" + value + ";" + expires + ";path=/"; 10 | }; 11 | 12 | const getCookie = (name: string) => { 13 | const nameEQ = name + "="; 14 | const ca = document.cookie.split(';'); 15 | for (let i = 0; i < ca.length; i++) { 16 | let c = ca[i]; 17 | while (c.charAt(0) === ' ') c = c.substring(1, c.length); 18 | if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); 19 | } 20 | return null; 21 | }; 22 | const generateRandomString = (length = 16) => { 23 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 24 | let result = ''; 25 | for (let i = 0; i < length; i++) { 26 | result += characters.charAt(Math.floor(Math.random() * characters.length)); 27 | } 28 | return result; 29 | }; 30 | 31 | const AccountPopup: React.FC = () => { 32 | const [userId, setUserId] = useState(null); 33 | const [showPopup, setShowPopup] = useState(false); 34 | 35 | useEffect(() => { 36 | let storedUserId = getCookie('userId'); 37 | if (!storedUserId) { 38 | storedUserId = generateRandomString(); // Generate a 16-char ID 39 | setCookie('userId', storedUserId); // Store it as a cookie 40 | } 41 | setUserId(storedUserId); // Set it in the state 42 | }, []); 43 | 44 | const togglePopup = () => { 45 | setShowPopup(!showPopup); 46 | // reset the user ID if the popup is closed 47 | if (!showPopup) { 48 | setUserId(getCookie('userId')); 49 | } 50 | } 51 | 52 | const copyToClipboard = () => { 53 | if (userId) { 54 | navigator.clipboard.writeText(userId); 55 | alert('User ID copied to clipboard!'); 56 | } 57 | }; 58 | 59 | const handleUserIdChange = (e: React.ChangeEvent) => { 60 | setUserId(e.target.value); 61 | }; 62 | 63 | const saveUserId = () => { 64 | if (!userId) { 65 | alert('User ID cannot be empty!'); 66 | } 67 | else if (userId.length != 16) { 68 | alert('User ID must be 16 characters long!'); 69 | } 70 | else { 71 | setCookie('userId', userId); 72 | alert('User ID updated!'); 73 | } 74 | }; 75 | 76 | return ( 77 | <> 78 | 81 | 82 | {showPopup && ( 83 |
84 |
85 |

Your User ID

86 |

This ID is used to prevent duplicate challenges. You can copy it to another device to sign in

87 | 88 |
89 | 90 | 91 |
92 | 93 |
94 |
95 | )} 96 | 97 | ); 98 | }; 99 | 100 | export default AccountPopup; 101 | -------------------------------------------------------------------------------- /components/Play.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import styles from '../src/styles/Play.module.css'; 3 | import { Map } from '@/types/map'; 4 | 5 | interface PopupProps { 6 | show: boolean; 7 | onClose: () => void; 8 | map: Map | null; 9 | } 10 | 11 | const timeLimits = [0, 10, 30, 60, 120, 180, 240, 300, 360]; // Corresponding to Any, 10s, 30s, 1min, 2min, 3min, 4min, Infinite 12 | 13 | 14 | const Popup: React.FC = ({ show, onClose, map }) => { 15 | const popupRef = useRef(null); 16 | const [timeLimitIndex, setTimeLimitIndex] = useState(0); 17 | 18 | useEffect(() => { 19 | const handleClickOutside = (event: MouseEvent) => { 20 | if (popupRef.current && !popupRef.current.contains(event.target as Node)) { 21 | onClose(); 22 | } 23 | }; 24 | 25 | const handleEscapeKey = (event: KeyboardEvent) => { 26 | if (event.key === 'Escape') { 27 | onClose(); 28 | } 29 | }; 30 | 31 | document.addEventListener('mousedown', handleClickOutside); 32 | document.addEventListener('keydown', handleEscapeKey); 33 | 34 | return () => { 35 | document.removeEventListener('mousedown', handleClickOutside); 36 | document.removeEventListener('keydown', handleEscapeKey); 37 | }; 38 | }, [onClose]); 39 | 40 | if (!show || !map) { 41 | return null; 42 | } 43 | 44 | const openLink = async (challengeId: string) => { 45 | try { 46 | const newWindow = window.open('', '_blank'); 47 | const challengeLink = `https://www.geoguessr.com/challenge/${challengeId}`; 48 | if (newWindow) { 49 | newWindow.location.href = challengeLink; 50 | } else { 51 | window.location.href = challengeLink; // Fallback if the new window is blocked 52 | } 53 | } catch (error) { 54 | console.error('Error fetching the challenge:', error); 55 | } 56 | }; 57 | 58 | const handlePlay = async (type?: string) => { 59 | let url = `/api/challenge/${map._id}?`; 60 | if (type) { 61 | url += `type=${type}&`; 62 | } 63 | url += `timeLimit=${timeLimits[timeLimitIndex]}`; 64 | const response = await fetch(url); 65 | if (response.status == 404) { 66 | alert('No challenges found for this setting. Please try again with different settings or wait one day for it to generate.'); 67 | return 68 | }; 69 | const challengeId: string = await response.text(); 70 | openLink(challengeId); 71 | }; 72 | 73 | const handleSliderChange = (event: React.ChangeEvent) => { 74 | setTimeLimitIndex(Number(event.target.value)); 75 | }; 76 | 77 | const formatTimeLimit = (limit: number) => { 78 | switch (limit) { 79 | case 0: return 'Any'; 80 | case 10: return '10s'; 81 | case 30: return '30s'; 82 | case 60: return '1min'; 83 | case 120: return '2min'; 84 | case 180: return '3min'; 85 | case 240: return '4min'; 86 | case 300: return '5min'; 87 | default: return 'Infinite'; 88 | } 89 | }; 90 | 91 | return ( 92 |
93 |
94 |
95 | 96 | {map.name} 97 |

98 | {Intl.NumberFormat('en-AU', { useGrouping: true }).format( 99 | map.likes 100 | )}{' '} 101 | likes 102 |

103 |

{map.description}

104 |
105 |
106 | 115 | 118 |
119 |
120 | 121 | 122 | 123 | 124 |
125 | 126 |
127 |
128 |
129 |
130 | ); 131 | }; 132 | 133 | export default Popup; 134 | -------------------------------------------------------------------------------- /components/Upload.tsx: -------------------------------------------------------------------------------- 1 | // components/Popup.tsx 2 | import React from 'react'; 3 | import styles from '../src/styles/Upload.module.css'; 4 | 5 | interface PopupProps { 6 | show: boolean; 7 | onClose: () => void; 8 | } 9 | 10 | const Popup: React.FC = ({ show, onClose }) => { 11 | const [inputValue, setInputValue] = React.useState(''); 12 | 13 | if (!show) { 14 | return null; 15 | } 16 | 17 | const handleInputChange = (event: React.ChangeEvent) => { 18 | setInputValue(event.target.value); 19 | }; 20 | 21 | const handleSubmit = async () => { 22 | console.log('Submitted value', inputValue) 23 | 24 | // send text to api 25 | try { 26 | const response = await fetch('/api/upload', { 27 | method: 'POST', 28 | headers: { 29 | 'Content-Type': 'application/json', 30 | }, 31 | body: JSON.stringify({ input: inputValue }), 32 | }); 33 | 34 | if (!response.ok) { 35 | throw new Error('Network response was not ok'); 36 | } 37 | 38 | const result = await response.json(); 39 | console.log('Success:', result); 40 | // popup 41 | alert(result.message); 42 | } catch (error) { 43 | console.error('Error:', error); 44 | } 45 | 46 | 47 | // onClose() 48 | } 49 | 50 | return ( 51 |
52 |
53 | 54 |

Upload a challenge!

55 |

Please upload one challenge link on each line.

56 |