diff --git a/.gitignore b/.gitignore index 8f322f0..436eb65 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/README.md b/README.md index f4da3c4..721795c 100644 --- a/README.md +++ b/README.md @@ -1,34 +1 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +# Cat breed guesser \ No newline at end of file diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/layout.tsx b/app/layout.tsx index ae84562..d37d370 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -5,8 +5,7 @@ import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: 'Guess the cat breed!', } export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index b5840ee..0e21740 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,113 +1,188 @@ -import Image from 'next/image' +'use client' +import Image from "next/image"; +import {useEffect, useState} from "react"; +import {Breed, TheCatAPI} from "@thatapicompany/thecatapi"; -export default function Home() { - return ( -
-
-

- Get started by editing  - app/page.tsx -

-
- - By{' '} - Vercel Logo - -
-
+export default function Page() { + const [html, setHtml] = useState(
Loading...
); + const [apiKey, setApiKey] = useState(""); + const [score, setScore] = useState(0); + const [message, setMessage] = useState(""); + const [error, setError] = useState(""); + const [currentImage, setCurrentImage] = useState("/loading.gif"); + const [currentBreed, setCurrentBreed] = useState(Breed.AEGEAN); + const [randomBreeds, setRandomBreeds]: [randomBreeds: Breed[], setRandomBreeds: any] = useState([]); + const [api, setApi] = useState(new TheCatAPI("")); -
- Next.js Logo -
+ useEffect(() => { + if (window.localStorage.getItem("apiKey") !== null && window.localStorage.getItem("apiKey") !== undefined) { + setApiKey(window.localStorage.getItem("apiKey") as string) + } else { + setApiKeyUi() + } + }, []); -
- -

- Docs{' '} - - -> - -

-

- Find in-depth information about Next.js features and API. -

-
+ useEffect(() => { + if (apiKey === "") { + return + } + setMessage("") + setApi(new TheCatAPI(apiKey)) + }, [apiKey]) - -

- Learn{' '} - - -> - -

-

- Learn about Next.js in an interactive course with quizzes! -

-
+ useEffect(() => { + if (apiKey === "") { + return + } + async function check() { + try { + const img= await api.images.searchImages({ + limit: 1, + hasBreeds: true, + mimeTypes: ["jpg", "png"], + }) + if (img[0].breeds === undefined) { + window.localStorage.removeItem("apiKey") + setError("API key error!") + return false + } + return true + } catch (e) { + console.error(e) + window.localStorage.removeItem("apiKey") + setError("API key error!") + return false + } + } + check().then((canContinue) => { + if (!canContinue) { + return + } + setNewImage() + }) + }, [api]) - -

- Templates{' '} - - -> - -

-

- Explore the Next.js 13 playground. -

-
+ useEffect(() => { + if (apiKey === "") { + return + } + if (currentImage === "") return + getRandomBreeds() + }, [currentImage]) - -

- Deploy{' '} - - -> - -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
-
-
- ) + useEffect(() => { + if (apiKey === "") { + return + } + if (randomBreeds.length === 0) return + setGameUi() + }, [randomBreeds]) + + async function setNewImage() { + try { + const img= await api.images.searchImages({ + limit: 1, + hasBreeds: true, + mimeTypes: ["jpg", "png"], + }) + if (img[0].breeds === undefined) { + window.localStorage.removeItem("apiKey") + setMessage("Error!") + return + } + setCurrentBreed(img[0].breeds[0].id) + setCurrentImage(img[0].url) + } catch (e) { + console.error(e) + window.localStorage.removeItem("apiKey") + setMessage("API key error!") + } + } + + useEffect(() => { + if (error == "API key error!") setApiKeyUi() + }, [error]) + + function setApiKeyUi() { + setHtml( +
+
+
{ + e.preventDefault() + // @ts-ignore + const val = e.target[0].value + window.localStorage.setItem("apiKey", val) + setApiKey(val) + }}> + + +
+
+

{error}

+

Please set your own TheCatAPI key.

+

You can get one completely for free here.

+
+ ) + } + + function setGameUi() { + setHtml(
+
+ { + randomBreeds.map((breed, i) => { + return + }) + } +
+
+

Score: {score}

+
+
+

{message}

+
+
+ +
+
+ +
+
) + } + + function shuffle(array: T[]): T[] { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + } + + function getRandomBreeds() { + let keys= Object.values(Breed).filter((breed) => breed !== currentBreed) + keys = shuffle(keys) + let items = [] + for (let i = 0; i < 3; i++) { + items.push(keys[i]) + } + items.push(currentBreed) + setRandomBreeds(shuffle(items)) + } + + function checkAnswer(answer: Breed) { + if (answer === currentBreed) { + setScore(score + 1) + setMessage("Correct!") + } else { + setScore(0) + setMessage("Incorrect!") + } + setNewImage() + } + + return html } diff --git a/next.config.js b/next.config.js index 767719f..b3b866f 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,12 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + images: { + remotePatterns: [ + { + hostname: 'cdn2.thecatapi.com', + } + ] + } +} module.exports = nextConfig diff --git a/package-lock.json b/package-lock.json index 39bd3fa..e46e567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "cbg", "version": "0.1.0", "dependencies": { + "@thatapicompany/thecatapi": "^1.0.2", "@types/node": "20.4.1", "@types/react": "18.2.14", "@types/react-dom": "18.2.6", @@ -394,6 +395,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@thatapicompany/thecatapi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@thatapicompany/thecatapi/-/thecatapi-1.0.2.tgz", + "integrity": "sha512-hg49j/P0uWstsCUMzPZbxQRgKOcNTIVTnkSpEpAmKvKobaBJBtLmiXUDVrU+tEWoS5SVH1vZ0LH9oSZbtymNDA==", + "dependencies": { + "axios": "^0.27.2", + "form-data": "^4.0.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -708,6 +718,11 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -759,6 +774,15 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -995,6 +1019,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1121,6 +1156,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1833,6 +1876,25 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1841,6 +1903,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -2659,6 +2734,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", diff --git a/package.json b/package.json index bb0bbcd..fa19225 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 6767", "build": "next build", - "start": "next start", + "start": "next start -p 6767", "lint": "next lint" }, "dependencies": { + "@thatapicompany/thecatapi": "^1.0.2", "@types/node": "20.4.1", "@types/react": "18.2.14", "@types/react-dom": "18.2.6", diff --git a/public/loading.gif b/public/loading.gif new file mode 100644 index 0000000..b3ee14d Binary files /dev/null and b/public/loading.gif differ diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file