Skip to content

Commit

Permalink
ADD: login, logout, notification
Browse files Browse the repository at this point in the history
  • Loading branch information
WCY-dt committed Apr 30, 2024
1 parent 318ddfb commit 24478b7
Show file tree
Hide file tree
Showing 15 changed files with 383 additions and 50 deletions.
23 changes: 21 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mindclip",
"version": "1.1.0",
"version": "2.0.0",
"private": true,
"author": "ch3nyang",
"license": "GPL-3.0-only",
Expand All @@ -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": [
Expand All @@ -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"
}
}
5 changes: 4 additions & 1 deletion src/components/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="App-content">
<Routes>
Expand Down
95 changes: 61 additions & 34 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>(null);
useEffect(() => {
localStorage.removeItem('colorMap');
Expand All @@ -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 (
<header>
<nav className="App-nav">
<a className="App-nav-title" href="/">
<img src="favicon.ico" alt="favicon" className="App-nav-title-icon" />
<p className="App-nav-title-text">MindClip</p>
</a>
<ul className="App-nav-list">
{Object.entries(routes).map(([path, element]) => (
<li className="App-nav-item"><Link to={path}>{element}</Link></li>
))}
</ul>
<div className={`App-nav-search ${isOpen ? 'open' : ''}`}>
{searchTerm ? <Icon className="App-nav-search-icon" icon="ci:close-md" onClick={handleClearFilter} /> : <Icon className="App-nav-search-icon" icon="ci:filter-outline" />}
<input
ref={inputRef}
className="App-nav-search-input"
type="text"
placeholder="Search something..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<button className="App-nav-button" onClick={toggleMenu}>{isOpen ? <Icon icon="ci:window-close" /> : <Icon icon="ci:window-sidebar" />}</button>
{isOpen && (
<div className="App-nav-menu">
{Object.entries(routes).map(([path, element]) => (
<li className="App-nav-item"><Link to={path}>{element}</Link></li>
))}
</div>
)}
</nav>
<div className={`overlay ${isOpen ? 'open' : ''}`} onClick={toggleMenu}></div>
</header>
<header>
<nav className="App-nav">
<div className="App-nav-left">
<a className="App-nav-title" href="/">
<img src="favicon.ico" alt="favicon" className="App-nav-title-icon" />
<p className="App-nav-title-text">MindClip</p>
</a>
</div>
<ul className="App-nav-list">
{Object.entries(routes).map(([path, element]) => (
<li className="App-nav-item"><Link to={path}>{element}</Link></li>
))}
</ul>
<div className="App-nav-right">
<div className={`App-nav-search ${isOpen ? 'open' : ''}`}>
{searchTerm ? <Icon className="App-nav-search-icon" icon="ci:close-md" onClick={handleClearFilter} /> : <Icon className="App-nav-search-icon" icon="ci:filter-outline" />}
<input
ref={inputRef}
className="App-nav-search-input"
type="text"
placeholder="Search something..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<button className={`App-nav-login`} onClick={isLogedIn ? toggleLogout : toggleLogin} title="Login">{isLogedIn ? <Icon icon="ci:log-out" /> : <Icon icon="ci:user-01" />}</button>
</div>
<button className="App-nav-button" onClick={toggleMenu}>{isOpen ? <Icon icon="ci:window-close" /> : <Icon icon="ci:window-sidebar" />}</button>
{isOpen && (
<div className="App-nav-menu">
{Object.entries(routes).map(([path, element]) => (
<li className="App-nav-item"><Link to={path}>{element}</Link></li>
))}
</div>
)}
</nav>
<div className={`overlay ${(isOpen || showLogin) ? 'open' : ''}`} onClick={() => {
if (isOpen) toggleMenu();
if (showLogin) toggleLogin();
}}></div>
{showLogin && <Login setShowLogin={setShowLogin} setIsLogedIn={setIsLogedIn} token={token} setToken={setToken} message={message} setMessage={setMessage} /> }
</header>
)
}

Expand Down
54 changes: 54 additions & 0 deletions src/popups/login.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className="Popup">
<div className="Login-title">Login</div>
<div className="Login-input">
<label htmlFor="username">Username</label>
<input type="text" id="username" name="username" />

<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" />
</div>
<button type="button" className="Login-button" onClick={tryLogin}>confirm</button>
<button type="button" className="Login-close" title="Close" onClick={() => setShowLogin(false)}>
<Icon icon="ci:close-md" />
</button>
</div>
</>
);
}

export default Login;
36 changes: 36 additions & 0 deletions src/popups/notification.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(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 (
<div className={`notification ${visible ? 'show' : ''}`}>
{message}
</div>
);
}

export default Notification;
26 changes: 20 additions & 6 deletions src/routers/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,49 @@ 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<boolean>(true);
const [isLogedIn, setIsLogedIn] = useState<boolean>(false);
const [token, setToken] = useState<string>('');
const [message, setMessage] = useState<string | null>(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 (
<div className="loading"></div>
);
}

return (
<BrowserRouter>
<Header routes={routes} searchTerm={searchTerm} setSearchTerm={setSearchTerm} />

<Content routes={routes} searchTerm={searchTerm} setSearchTerm={setSearchTerm} />
<>
<BrowserRouter>
<Header routes={routes} searchTerm={searchTerm} setSearchTerm={setSearchTerm} isLogedIn={isLogedIn} setIsLogedIn={setIsLogedIn} token={token} setToken={setToken} message={message} setMessage={setMessage} />

<Footer />
</BrowserRouter>
<Content routes={routes} searchTerm={searchTerm} setSearchTerm={setSearchTerm} isLogedIn={isLogedIn} token={token} setToken={setToken}/>

<Footer />
</BrowserRouter>
<Notification message={message} setMessage={setMessage} />
</>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/collectionFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function fetchCollection(setRoutes: (value: {}) => void) {
return new Promise(async (resolve, reject) => {
const api = "https://api.mind.ch3nyang.top";
const api = process.env.REACT_APP_API_URL;
let url = `${api}/collection`;

try {
Expand Down
2 changes: 1 addition & 1 deletion src/services/dataFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function fetchAndFilterData(
setClusters: (value: ClusterProps[]) => void
) {
return new Promise(async (resolve, reject) => {
const api = "https://api.mind.ch3nyang.top";
const api = process.env.REACT_APP_API_URL;
let url = `${api}/card?collection=${dataKey}`;
if (selectedCategory) {
url += `&category=${selectedCategory}`;
Expand Down
Loading

0 comments on commit 24478b7

Please sign in to comment.