From 8203b10b71e843319f104cd29c6bc253bb8a72b9 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 14 Apr 2024 19:55:07 +0100 Subject: [PATCH] add basic player page --- next.config.mjs | 5 ++ package.json | 3 + pnpm-lock.yaml | 113 ++++++++++++++++++++++++++++++- src/app/layout.tsx | 5 +- src/app/page.tsx | 13 ++-- src/app/player/page.tsx | 13 ++++ src/{lib => common}/utils.ts | 0 src/common/web-request.ts | 18 +++++ src/components/player-search.tsx | 77 +++++++++++++++++++++ src/components/ui/button.tsx | 47 +++++++++++++ src/components/ui/input.tsx | 22 ++++++ src/consts.ts | 1 + 12 files changed, 308 insertions(+), 9 deletions(-) create mode 100644 src/app/player/page.tsx rename src/{lib => common}/utils.ts (100%) create mode 100644 src/common/web-request.ts create mode 100644 src/components/player-search.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/consts.ts diff --git a/next.config.mjs b/next.config.mjs index f020e7d..7d5a3e3 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -8,6 +8,11 @@ const nextConfig = { hostname: "git.fascinated.cc", pathname: "/**", }, + { + protocol: "https", + hostname: "api.mcutils.xyz", + pathname: "/**", + }, ], }, }; diff --git a/package.json b/package.json index 699947e..2f5bdf5 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-slot": "^1.0.2", + "axios": "^1.6.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-react": "^0.368.0", @@ -16,6 +18,7 @@ "next-themes": "^0.3.0", "react": "^18", "react-dom": "^18", + "react-toastify": "^10.0.5", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eaa490..a6175bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.2.78)(react@18.2.0) + axios: + specifier: ^1.6.8 + version: 1.6.8 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -26,6 +32,9 @@ dependencies: react-dom: specifier: ^18 version: 18.2.0(react@18.2.0) + react-toastify: + specifier: ^10.0.5 + version: 10.0.5(react-dom@18.2.0)(react@18.2.0) tailwind-merge: specifier: ^2.2.2 version: 2.2.2 @@ -284,6 +293,35 @@ packages: requiresBuild: true optional: true + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-slot@1.0.2(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + /@rushstack/eslint-patch@1.10.2: resolution: {integrity: sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==} dev: true @@ -311,7 +349,6 @@ packages: /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - dev: true /@types/react-dom@18.2.25: resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==} @@ -324,7 +361,6 @@ packages: dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 - dev: true /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} @@ -564,6 +600,10 @@ packages: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} dev: true + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -576,6 +616,16 @@ packages: engines: {node: '>=4'} dev: true + /axios@1.6.8: + resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -689,6 +739,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -712,7 +769,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -790,6 +846,11 @@ packages: object-keys: 1.1.1 dev: true + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1297,6 +1358,16 @@ packages: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true + /follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -1310,6 +1381,15 @@ packages: cross-spawn: 7.0.3 signal-exit: 4.1.0 + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -1848,6 +1928,18 @@ packages: braces: 3.0.2 picomatch: 2.3.1 + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -2201,6 +2293,10 @@ packages: react-is: 16.13.1 dev: true + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2223,6 +2319,17 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true + /react-toastify@10.0.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + dependencies: + clsx: 2.1.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ac5cda5..8ee34e3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,9 @@ import Container from "@/components/container"; import ThemeProvider from "@/components/theme-provider"; -import { Inter } from "next/font/google"; +import { ToastContainer } from "react-toastify"; +import { Inter } from "next/font/google"; +import "react-toastify/dist/ReactToastify.css"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); @@ -17,6 +19,7 @@ export default function RootLayout({ + {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index cfdd834..ae2e159 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,12 +13,15 @@ const buttons: Button[] = [ export default function Home() { return (
-

Minecraft Utilities

-

- We provide a convenient API for Minecraft, simplifying the usage for players and developers. -

+

Minecraft Utilities

+
+

Minecraft Utilities offers you many endpoints to get information about a minecraft server or a player.

+

+ You can use this information on your minecraft server or website. We offer you a simple and easy to use API. +

+
-
+
{buttons.map((button, index) => { return ( +
+

Search for a Player

+

You can enter a players uuid or username to get information about the player.

+
+ +
+ ); +} diff --git a/src/lib/utils.ts b/src/common/utils.ts similarity index 100% rename from src/lib/utils.ts rename to src/common/utils.ts diff --git a/src/common/web-request.ts b/src/common/web-request.ts new file mode 100644 index 0000000..6401f7f --- /dev/null +++ b/src/common/web-request.ts @@ -0,0 +1,18 @@ +import { API_ENDPOINT } from "@/consts"; +import axios from "axios"; + +export default class WebRequest { + private API_ENDPOINT: string = API_ENDPOINT; + + /** + * Gets a response from the server + * + * @param endpoint the endpoint to send the request to + * @returns the response from the server + */ + public static get(endpoint: string) { + return axios.get(API_ENDPOINT + endpoint, { + validateStatus: () => true, // Do not throw an error on non-200 status codes + }); + } +} diff --git a/src/components/player-search.tsx b/src/components/player-search.tsx new file mode 100644 index 0000000..fcba5c0 --- /dev/null +++ b/src/components/player-search.tsx @@ -0,0 +1,77 @@ +"use client"; + +import WebRequest from "@/common/web-request"; +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; +import { toast } from "react-toastify"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; + +export default function PlayerSearch() { + const [playerId, setPlayerId] = useState(null); + const [player, setPlayer] = useState(null); + + const handleSearch = () => { + if (playerId === null || playerId.length <= 0) { + toast.error("Please enter a player ID"); + return; + } + searchPlayer(playerId); + }; + + /** + * Searches for a player by their id + * + * @param playerId the player's id + */ + const searchPlayer = async (playerId: string) => { + const response = await WebRequest.get("/player/" + playerId); + if (response.status !== 200) { + toast.error("Player not found"); + return; + } + const { cache, player } = response.data; + setPlayer(player); + console.log(player); + }; + + return ( +
+
+ setPlayerId(e.target.value)} /> + +
+ + {player && ( +
+
+ The player's Head +
+

+ Unique ID: {player.uniqueId} +

+

+ Name: {player.username} +

+
+

Skin Parts

+
+ {Object.keys(player.skin.parts).map((part: any, index: number) => { + return ( +

+ + {part} + +

+ ); + })} +
+
+
+
+
+ )} +
+ ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..6df88c6 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,47 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/common/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ; + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..5141afd --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; + +import { cn } from "@/common/utils"; + +export interface InputProps extends React.InputHTMLAttributes {} + +const Input = React.forwardRef(({ className, type, ...props }, ref) => { + return ( + + ); +}); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..81b4074 --- /dev/null +++ b/src/consts.ts @@ -0,0 +1 @@ +export const API_ENDPOINT = "https://api.mcutils.xyz";