diff --git a/package-lock.json b/package-lock.json index ff1c1cc..7be71d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindclip", - "version": "1.1.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindclip", - "version": "1.1.0", + "version": "2.0.0", "license": "GPL-3.0-only", "dependencies": { "react": "^18.2.0", @@ -19,6 +19,7 @@ "@iconify/react": "^4.1.1", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", + "cross-env": "^7.0.3", "typescript": "^4.9.5" } }, @@ -5374,6 +5375,24 @@ "node": ">=10" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "license": "MIT", diff --git a/package.json b/package.json index df9d3a4..705548e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mindclip", - "version": "1.1.0", + "version": "2.0.0", "private": true, "author": "ch3nyang", "license": "GPL-3.0-only", @@ -13,8 +13,8 @@ "react-scripts": "5.0.1" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build" + "start": "cross-env REACT_APP_API_URL=http://127.0.0.1:8787 react-scripts start", + "build": "cross-env REACT_APP_API_URL=https://api.mind.ch3nyang.top react-scripts build" }, "eslintConfig": { "extends": [ @@ -37,6 +37,7 @@ "@iconify/react": "^4.1.1", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", + "cross-env": "^7.0.3", "typescript": "^4.9.5" } } diff --git a/src/components/content.tsx b/src/components/content.tsx index 948b901..4f8add7 100644 --- a/src/components/content.tsx +++ b/src/components/content.tsx @@ -7,9 +7,12 @@ interface ContentProps { routes: { [key: string]: string }; searchTerm: string; setSearchTerm: (value: string) => void; + isLogedIn: boolean; + token: string; + setToken: (value: string) => void; } -function Content({ routes, searchTerm, setSearchTerm }: ContentProps) { +function Content({ routes, searchTerm, setSearchTerm, isLogedIn, token, setToken }: ContentProps) { return (
diff --git a/src/components/header.tsx b/src/components/header.tsx index 41a0c3d..9d6a3e3 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,15 +1,22 @@ import { Icon } from '@iconify/react'; import React, { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; +import Login from '../popups/login'; import '../styles/header.css'; interface HeaderProps { routes: { [key: string]: string }; searchTerm: string; setSearchTerm: (value: string) => void; + isLogedIn: boolean; + setIsLogedIn: (value: boolean) => void; + token: string; + setToken: (value: string) => void; + message: string | null; + setMessage: (value: string | null) => void; } -function Header({ routes, searchTerm, setSearchTerm }: HeaderProps) { +function Header({ routes, searchTerm, setSearchTerm, isLogedIn, setIsLogedIn, token, setToken, message, setMessage }: HeaderProps) { const inputRef = useRef(null); useEffect(() => { localStorage.removeItem('colorMap'); @@ -25,40 +32,60 @@ function Header({ routes, searchTerm, setSearchTerm }: HeaderProps) { const [isOpen, setIsOpen] = useState(false); const toggleMenu = () => setIsOpen(!isOpen); + const [showLogin, setShowLogin] = useState(false); + const toggleLogin = () => { + setShowLogin(!showLogin); + }; + const toggleLogout = () => { + setIsLogedIn(false); + setToken(''); + localStorage.removeItem('token'); + setMessage('Successfully logged out'); + }; + return ( -
- -
-
+
+ +
{ + if (isOpen) toggleMenu(); + if (showLogin) toggleLogin(); + }}>
+ {showLogin && } +
) } diff --git a/src/popups/login.tsx b/src/popups/login.tsx new file mode 100644 index 0000000..2f3a5f9 --- /dev/null +++ b/src/popups/login.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Icon } from '@iconify/react'; +import loginHandler from '../services/loginHandler'; +import '../styles/popup.css'; +import '../styles/login.css'; + +interface LoginProps { + setShowLogin: (value: boolean) => void; + setIsLogedIn: (value: boolean) => void; + token: string; + setToken: (value: string) => void; + message: string | null; + setMessage: (value: string | null) => void; +} + +function Login({ setShowLogin, setIsLogedIn, token, setToken, message, setMessage }: LoginProps) { + const tryLogin = async () => { + const username = (document.getElementById("username") as HTMLInputElement).value; + const password = (document.getElementById("password") as HTMLInputElement).value; + const loginResult = await loginHandler({ username, password, setToken }); + + console.log(loginResult); + + if (loginResult === true) { + setShowLogin(false); + localStorage.setItem('token', token); + setIsLogedIn(true); + setMessage('Successfully logged in'); + } else { + alert('Failed to login. Please try again.'); + } + }; + + return ( + <> +
+
Login
+
+ + + + + +
+ + +
+ + ); +} + +export default Login; \ No newline at end of file diff --git a/src/popups/notification.tsx b/src/popups/notification.tsx new file mode 100644 index 0000000..574b7a3 --- /dev/null +++ b/src/popups/notification.tsx @@ -0,0 +1,36 @@ +import React, { useState, useEffect } from 'react'; +import '../styles/notification.css'; + +interface NotificationProps { + message: string | null; + setMessage: (value: string | null) => void; +} + +function Notification({ message, setMessage }: NotificationProps) { + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (message !== null) { + setVisible(true); + const visibilityTimer = setTimeout(() => { + setVisible(false); + }, 2000); + const messageTimer = setTimeout(() => { + setMessage(null); + }, 4000); + return () => { + clearTimeout(visibilityTimer); + clearTimeout(messageTimer); + }; + } + return; + }, [message, setMessage]); + + return ( +
+ {message} +
+ ); +} + +export default Notification; \ No newline at end of file diff --git a/src/routers/router.tsx b/src/routers/router.tsx index 93f3b0d..6a1cd5f 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -5,20 +5,32 @@ import { fetchCollection } from '../services/collectionFetcher'; import Header from '../components/header'; import Content from '../components/content'; import Footer from '../components/footer'; +import Notification from '../popups/notification'; import '../styles/loading.css'; function Router() { const [searchTerm, setSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(true); + const [isLogedIn, setIsLogedIn] = useState(false); + const [token, setToken] = useState(''); + const [message, setMessage] = useState(null); const [routes, setRoutes] = useState({}); + useEffect(() => { setIsLoading(true); fetchCollection(setRoutes) .finally(() => setIsLoading(false)); }, []); + useEffect(() => { + if (localStorage.getItem('token')) { + setIsLogedIn(true); + setToken(localStorage.getItem('token') as string); + } + }, []); + if (isLoading) { return (
@@ -26,14 +38,16 @@ function Router() { } return ( - -
- - + <> + +
-