From 8e430f1e720414cc48c455c0c1477c3369491969 Mon Sep 17 00:00:00 2001 From: "ojspp000@naver.com" Date: Sun, 25 Aug 2024 23:32:31 +0900 Subject: [PATCH] =?UTF-8?q?Feat=20adminpage=20'adminpage=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/assets/admin_logo.svg | 9 ++ public/assets/admin_page_logo.svg | 9 ++ src/App.jsx | 2 + src/components/Adminnavbar.jsx | 91 +++++++++++++++++ src/css/components/AdminNavbar.css | 151 ++++++++++++++++++++++++++++ src/css/pages/Adminpage.css | 152 +++++++++++++++++++++++++++++ src/pages/Adminpage.jsx | 36 +++++++ src/pages/Adminpage_login.jsx | 12 +++ src/pages/Adminpage_unlogin.jsx | 93 ++++++++++++++++++ 9 files changed, 555 insertions(+) create mode 100644 public/assets/admin_logo.svg create mode 100644 public/assets/admin_page_logo.svg create mode 100644 src/components/Adminnavbar.jsx create mode 100644 src/css/components/AdminNavbar.css create mode 100644 src/css/pages/Adminpage.css create mode 100644 src/pages/Adminpage.jsx create mode 100644 src/pages/Adminpage_login.jsx create mode 100644 src/pages/Adminpage_unlogin.jsx diff --git a/public/assets/admin_logo.svg b/public/assets/admin_logo.svg new file mode 100644 index 0000000..aa095b1 --- /dev/null +++ b/public/assets/admin_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/admin_page_logo.svg b/public/assets/admin_page_logo.svg new file mode 100644 index 0000000..28a67d2 --- /dev/null +++ b/public/assets/admin_page_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/App.jsx b/src/App.jsx index 76fcaec..48dd0b8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -17,6 +17,7 @@ import Redirection from "./pages/RedirectionPage.jsx"; import OpenExternalBrowser from "./OpenExternalBrowser.jsx"; import Userinfo from "./pages/User_info_page.jsx"; import { BrowserRouter, Route, Routes } from "react-router-dom"; +import Adminpage from "./pages/Adminpage.jsx"; import "./App.css"; import "./axiosConfig.jsx"; import Matching from "./pages/Matching.jsx"; @@ -36,6 +37,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/Adminnavbar.jsx b/src/components/Adminnavbar.jsx new file mode 100644 index 0000000..f7ba240 --- /dev/null +++ b/src/components/Adminnavbar.jsx @@ -0,0 +1,91 @@ +import React, { useState, useEffect } from 'react'; +import '../css/components/AdminNavbar.css'; + +function AdminNavbar() { + const [activeMenu, setActiveMenu] = useState('main'); + const [menuOpen, setMenuOpen] = useState(false); + const [chargeRequestCount, setChargeRequestCount] = useState(3); + + const handleMenuClick = (menu) => { + setActiveMenu(menu); + setMenuOpen(false); + }; + + const toggleMenu = () => { + setMenuOpen(!menuOpen); + }; + + // This useEffect hook will simulate fetching data from the backend + useEffect(() => { + // WebSocket 연결 로직 예시 + const socket = new WebSocket('ws://your-websocket-url'); + + socket.onopen = () => { + console.log('WebSocket connected'); + }; + + socket.onmessage = (event) => { + const data = JSON.parse(event.data); + if (data.topic === '/topic/chargeRequests') { + setChargeRequestCount(data.requestCount); // 충전 요청 건수를 상태에 저장 + } + }; + + socket.onclose = () => { + console.log('WebSocket disconnected'); + }; + + return () => { + socket.close(); // 컴포넌트가 언마운트될 때 WebSocket 연결 해제 + }; + }, []); + + return ( +
+ {/* 로고 */} + Logo + + {/* 햄버거 메뉴 아이콘 */} +
+ ☰ +
+ + {/* 메뉴 */} +
+
handleMenuClick('main')} + > + Main +
+
handleMenuClick('request')} + > + 충전요청 + {chargeRequestCount > 0 && ( + {chargeRequestCount} + )} +
+
handleMenuClick('user-management')} + > + 가입자관리 +
+
handleMenuClick('team-management')} + > + 팀관리 +
+
+
+

관리자 오준석님

+

가톨릭대학교

+
+
+ ); +} + +export default AdminNavbar; diff --git a/src/css/components/AdminNavbar.css b/src/css/components/AdminNavbar.css new file mode 100644 index 0000000..a52d41f --- /dev/null +++ b/src/css/components/AdminNavbar.css @@ -0,0 +1,151 @@ +.admin-navbar { + position: fixed; /* 화면 위에 고정 */ + top: 0; + left: 0; + width: calc(100vw - 48px); + display: flex; + justify-content: space-between; + align-items: center; + background-color: white; + padding: 10px 20px; + border-bottom: 2px solid #e0e0e0; + z-index: 1000; /* 다른 요소들 위에 표시되도록 z-index 설정 */ +} + +.admin-navbar .logo { + width: 140px; + height: 40px; /* 로고 이미지 크기 조정 */ + margin-left: 24px; +} + +.admin-navbar .menu { + display: flex; + gap: 48px; +} + +.admin-navbar .menu-item { + color: gray; + font-weight: normal; + font-size: 24px; + cursor: pointer; + + transition: color 0.3s, font-weight 0.3s; + + position: relative; /* 밑줄을 추가하기 위해 상대 위치 설정 */ +} + +.admin-navbar .menu-item.active { + color: black; + font-weight: bold; +} + +.admin-navbar .menu-item.active:after { + content: ''; + position: absolute; + bottom: -12px; /* navbar의 경계선에 맞추기 위한 위치 설정 */ + left: 0; + width: 100%; + height: 2px; + background-color: black; +} + +.admin-navbar .admin-info { + background-color: #e0e0e0; + padding: 8px 12px; + width: 110px; + height: 40px; +} + +.admin-info_admin { + font-size: 10px; + color: black; + font-weight: bold; + margin: 0px; +} + +.admin-info_class { + font-size: 10px; + color: #797777; +} +.admin-navbar .hamburger-menu { + display: none; + font-size: 30px; + cursor: pointer; +} +/* 반응형 디자인을 위한 미디어 쿼리 */ +@media (max-width: 700px) { + .admin-navbar { + flex-direction: column; + align-items: flex-start; + padding: 10px; + width: 100%; + } + + .admin-navbar .logo { + margin-bottom: 10px; + } + + .admin-navbar .hamburger-menu { + display: block; + } + .hamburger-menu{ + position: fixed; + top: 5px; + right: 10px; + } + .admin-navbar .menu { + flex-direction: column; + gap: 10px; + width: 50%; + display: none; + } + + .admin-navbar .menu.open { + display: flex; + } + + .admin-navbar .menu-item { + font-size: 24px; + margin-right: 0; + text-align: left; + } + + .admin-navbar .menu-item.active:after { + bottom: -8px; + height: 2px; + } + + .admin-navbar .admin-info { + width: 100%; + background-color: #f0f0f0; + padding: 10px; + margin-top: 10px; + text-align: left; + display: none; + } +} +.request-count { + background-color: #FF775E; + color: white; + border-radius: 50%; + padding: 2px 6px; + font-size: 10px; + margin-left: 3px; +} +@media (max-width: 480px) { + .admin-navbar .menu-item { + font-size: 16px; + } + + .admin-navbar .admin-info { + padding: 8px; + } + + .admin-navbar .logo { + width: 100px; + height: 30px; + } + .admin-info.open { + display: block; + } +} diff --git a/src/css/pages/Adminpage.css b/src/css/pages/Adminpage.css new file mode 100644 index 0000000..39e27d7 --- /dev/null +++ b/src/css/pages/Adminpage.css @@ -0,0 +1,152 @@ +.login-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; /* 화면의 전체 높이를 차지하도록 설정 */ + background-color: #F4F4F4; + +} + +.logo { + width: 500px; + height: 69px; +} + +.partners-page { + font-size: 48px; + margin-top: 13px; + margin-bottom: 20px; + font-weight: bold; +} + +.login-input { + width: 70%; /* 입력창의 너비를 80%로 설정 */ + padding: 0.9em; /* 10px = 0.625em */ + margin-bottom: 0.625em; /* 10px = 0.625em */ + border: 1px solid #ccc; + border-radius: 0.8em; /* 둥글게 설정, 8px = 0.5em */ + box-shadow: 0 0 0.625em rgba(0, 0, 0, 0.1); /* 입력창에도 그림자 추가 */ +} + +.login-button { + margin-top: 20px; + width: 80%; /* 로그인 버튼의 너비를 85%로 설정 */ + padding: 0.9em; /* 10px = 0.625em */ + background-color: #d3d3d3; /* 기본 상태의 lightest grayscale */ + color: #727272; + border: none; + border-radius: 1em; /* 둥글게 설정, 8px = 0.5em */ + cursor: pointer; + font-size: 1em; /* 16px = 1em */ + transition: background-color 0.3s ease; /* 배경색 전환을 위한 부드러운 효과 */ + box-shadow: 0 0 0.625em rgba(0, 0, 0, 0.1); /* 버튼에도 그림자 추가 */ +} + +.login-button:hover { + background-color: #b0b0b0; /* hover 시 약간 더 어두운 grayscale */ +} + +.login-button:active { + background-color: #8a8a8a; /* 클릭 시 더 어두운 grayscale */ +} +.link-row { + display: flex; + justify-content:center; /* 두 링크를 한 줄에 배치하고 간격 조정 */ +} +.links-container { + margin-top: 1.5em; /* 버튼과 링크들 사이에 간격 추가 */ + display: flex; + flex-direction: column; + gap: 0.5em; /* 링크들 사이 간격 */ +} +.login-link { + color: #505050; /* GRAYSCALE DARK */ + font-size: 1.125em; /* 18px */ + text-decoration: none; /* 밑줄 제거 */ + transition: color 0.3s ease; + margin-right: 10px; +} + +.login-link:hover { + color: #303030; /* hover 시 더 진한 회색 */ +} + +.login-link-contact { + display: block; + margin-top: 0.5em; /* 위의 링크들과 간격 추가 */ +} +.login-link:hover{ + color: #8a8787; +} +.checkbox-container { + display: flex; + align-items: center; + justify-content: flex-start; + margin-bottom: 1em; /* 로그인 버튼과의 간격 추가 */ + font-size: 1em; /* 글씨 크기를 약간 키움 */ + color: #505050; /* GRAYSCALE DARK 색상 */ + cursor: pointer; + margin-left: 80px; +} + +/* 기본 체크박스 숨기기 */ +#show-password-checkbox { + display: none; +} + +/* 커스텀 체크박스 스타일 */ +.custom-checkbox { + width: 1.25em; /* 커스텀 체크박스 크기 */ + height: 1.25em; + background-color: #f4f4f4; /* 체크박스 기본 배경색 */ + border: 1px solid #ccc; + border-radius: 0.25em; + position: relative; + cursor: pointer; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +/* 체크 상태일 때 커스텀 체크박스 스타일 */ +.custom-checkbox::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0.6em; + height: 0.3em; + border-left: 2px solid black; + border-bottom: 2px solid black; + transform: translate(-50%, -50%) rotate(-45deg); + opacity: 0; + transition: opacity 0.3s ease; +} + +/* 체크박스가 체크된 경우 */ +#show-password-checkbox:checked + .custom-checkbox::after { + opacity: 1; +} + +/* 체크박스 라벨 스타일 */ +.checkbox-label { + margin-left: 0.5em; /* 체크박스와 텍스트 사이의 간격 */ + cursor: pointer; + user-select: none; /* 텍스트 선택 방지 */ + transition: color 0.3s ease; +} + +/* 체크박스가 활성화된 경우 */ +.checkbox-container:hover .custom-checkbox { + background-color: #e0e0e0; + border-color: #b0b0b0; +} +/* 반응형 디자인 */ +@media (max-width: 768px) { + .login-box { + width: 80%; + padding: 0.9375em; /* 15px = 0.9375em */ + } + .logo { + width: 100%; + height: auto; + } +} diff --git a/src/pages/Adminpage.jsx b/src/pages/Adminpage.jsx new file mode 100644 index 0000000..3f4f304 --- /dev/null +++ b/src/pages/Adminpage.jsx @@ -0,0 +1,36 @@ +import React, { useEffect, useState } from 'react'; +import Adminpageunlogin from './Adminpage_unlogin'; +import Adminpagelogin from './Adminpage_login'; +import axios from 'axios'; + +function Adminpage() { + const [isLoggedIn, setIsLoggedIn] = useState(false); + + useEffect(() => { + const checkLoginStatus = async () => { + const token = localStorage.getItem("adminToken"); + if (token) { + try { + const response = await axios.get("/admin/token/check", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (response.status === 200) { + setIsLoggedIn(true); + } + } catch (error) { + console.error("Error fetching data:", error); + setIsLoggedIn(false); + } + } else { + setIsLoggedIn(false); + } + }; + checkLoginStatus(); + }, []); + + return
{isLoggedIn ? : }
; +} + +export default Adminpage; diff --git a/src/pages/Adminpage_login.jsx b/src/pages/Adminpage_login.jsx new file mode 100644 index 0000000..c31b429 --- /dev/null +++ b/src/pages/Adminpage_login.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import AdminNavbar from '../components/Adminnavbar'; + +function Adminpagelogin() { + return ( +
+ +
+ ); +} + +export default Adminpagelogin; diff --git a/src/pages/Adminpage_unlogin.jsx b/src/pages/Adminpage_unlogin.jsx new file mode 100644 index 0000000..211d873 --- /dev/null +++ b/src/pages/Adminpage_unlogin.jsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import '../css/pages/Adminpage.css'; + +function Adminpageunlogin() { + const [passwordVisible, setPasswordVisible] = useState(false); + const [formData, setFormData] = useState({ id: '', password: '' }); + + const togglePasswordVisibility = () => { + setPasswordVisible(!passwordVisible); + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const response = await fetch('/admin/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + // Handle successful response + console.log('Login successful'); + // Redirect to Adminpage_login or change state to show login page + } else { + console.log('Login failed'); + } + } catch (error) { + console.error('Error during login:', error); + } + }; + + return ( +
+
+ Logo +

Partners Page

+
+ +
+ +
+ + +
+ +
+
+ ); +} + +export default Adminpageunlogin;