diff --git a/package-lock.json b/package-lock.json index 74e9f57..ea81c4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "picrinth-admin", "version": "0.0.0", "dependencies": { + "@material/material-color-utilities": "^0.3.0", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-router-dom": "^7.7.1" + "react-router-dom": "^7.7.1", + "react-toastify": "^11.0.5" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -1023,6 +1025,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@material/material-color-utilities": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.3.0.tgz", + "integrity": "sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==", + "license": "Apache-2.0" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1894,6 +1902,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2947,6 +2964,19 @@ "react-dom": ">=18" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/package.json b/package.json index 53d5769..b4a937f 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "@material/material-color-utilities": "^0.3.0", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-router-dom": "^7.7.1" + "react-router-dom": "^7.7.1", + "react-toastify": "^11.0.5" }, "devDependencies": { "@eslint/js": "^9.32.0", diff --git a/public/test.mp4 b/public/test.mp4 new file mode 100644 index 0000000..9b0c25e Binary files /dev/null and b/public/test.mp4 differ diff --git a/src/App.tsx b/src/App.tsx index cc3821b..b3c418c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,14 @@ import { BrowserRouter as Router, Routes, Route, Navigate, Outlet, useLocation } from 'react-router-dom'; -import {useContext, useEffect, useState } from "react"; +import {useEffect, useState } from "react"; -import AuthContext, { AuthProvider } from "./auth/auth-provider" +import { ToastContainer } from "react-toastify"; +import { + argbFromHex, + themeFromSourceColor, + applyTheme, +} from "@material/material-color-utilities"; + +import { AuthProvider } from "./auth/auth-provider" import ping from "./auth/ping"; import Login from "./pages/login/login" @@ -14,8 +21,22 @@ import useAuth from './auth/auth'; function App() { + const theme = themeFromSourceColor(argbFromHex("ffddaf")); + const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + applyTheme(theme, { target: document.body, dark: systemDark }); return ( + <> + } /> @@ -30,13 +51,14 @@ function App() { 404} /> + ); } const PrivateRoute = () => { - const { token } = useAuth(); + const { token, loading } = useAuth(); const location = useLocation(); const [checked, setChecked] = useState(false); const [isValid, setIsValid] = useState(false); @@ -49,14 +71,15 @@ const PrivateRoute = () => { return; } const result = await ping(token); - console.log(result) setIsValid(result); setChecked(true); }; - checkAuth(); - }, [token]); + if (!loading) { + checkAuth(); + } + }, [token, loading]); - if (!checked) { + if (loading || !checked) { return
Checking server availability...
; } diff --git a/src/auth/auth-provider.tsx b/src/auth/auth-provider.tsx index cf99ebf..51f12fe 100644 --- a/src/auth/auth-provider.tsx +++ b/src/auth/auth-provider.tsx @@ -4,40 +4,40 @@ import { type JSX, useEffect, useState, createContext } from "react" type AuthContextType = { token: string | null; setToken: (token: string | null) => void; + loading: boolean; }; const AuthContext = createContext({ token: null, setToken: () => {}, + loading: true, }); export const AuthProvider = ({ children }: { children: JSX.Element }) => { const [token, setTokenState] = useState(null); + const [loading, setLoading] = useState(true); useEffect(() => { const savedToken = localStorage.getItem("token"); if (savedToken) { setTokenState(savedToken); - console.log("meow") } - console.log(savedToken) + setLoading(false) }, []); const setToken = (newToken: string | null) => { setTokenState(newToken); if (newToken) { localStorage.setItem("token", newToken); - console.log("saved") } else { localStorage.removeItem("token"); - console.log("removed") } }; return ( - + {children} ); diff --git a/src/pages/login/login.tsx b/src/pages/login/login.tsx index 2706331..78e8869 100644 --- a/src/pages/login/login.tsx +++ b/src/pages/login/login.tsx @@ -1,5 +1,8 @@ import { useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; + +import { toast } from "react-toastify" + import useAuth from "../../auth/auth"; @@ -12,36 +15,52 @@ const Login = () => { const from = location.state?.from?.pathname || "/"; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - + const handleSubmit = () => { // TODO: request to API - if (username === "admin" && password === "1234") { + if ((username === "admin" && password === "1234") || (username === "a" && password === "a")) { const token = "todo.jwt.token"; setToken(token); navigate(from, { replace: true }); } else { - alert("Wrong login or password"); + toast.error("Wrong login or password"); } }; return ( -
+

Login

-
+ {e.preventDefault(); handleSubmit()}} + id="form-id" + style={{ + flexDirection: "column", + display: "flex", + gap: "5px", + width: "100%", + maxWidth: "400px", + alignItems: "center", + }} + >
- setUsername(e.target.value)} required />
- setPassword(e.target.value)} required