디자인은 내가 안 하고 전부 gpt한테 시킬 예정 [물론 백,프론트도 그렇지만...]
db도 따로 추가 안 하고 로컬 스토리지로만 진행하려고 하는데,
중간에 막히는 부분이 있다 싶으면 oracle을 사용할 예정이다.
🚀 현재 프로젝트 환경 정리
✅ 네이버 스타일 로그인 & 블로그 시스템 구현 중
✅ 사용된 기술 및 개발 도구 정리
📌 프로젝트 개요
- 프로젝트명: 네이버 스타일 로그인 & 블로그 클론 코딩
- DB: 로컬 스토리지 (LocalStorage) 사용 (별도 백엔드 없이 클라이언트 측 저장)
- 프레임워크: React (JSX 기반 UI 구성)
- 개발 언어: Javascript
- 개발툴: Visual Studio Code
App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./Login";
import Register from "./Register";
import Dashboard from "./Dashboard";
import FindPassword from "./FindPassword";
import Blog from "./Blog";
function App() {
return (
<Router>
<div>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/find-password" element={<FindPassword />} />
<Route path="/blog" element={<Blog />} />
</Routes>
</div>
</Router>
);
}
export default App;

Login.js
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./Login.css"; //
function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [isIpSecurityOn, setIsIpSecurityOn] = useState(true);
const navigate = useNavigate();
const handleLogin = (e) => {
e.preventDefault();
const storedUser = JSON.parse(localStorage.getItem("user"));
if (!storedUser || storedUser.username !== username || storedUser.password !== password) {
alert("아이디 또는 비밀번호가 잘못되었습니다.");
return;
}
alert("로그인 성공!");
navigate("/dashboard");
};
return (
<div className="login-container">
<h1 className="logo">NAVER</h1>
<div className="login-box">
<form onSubmit={handleLogin}>
<input type="text" placeholder="아이디 또는 전화번호" value={username} onChange={(e) => setUsername(e.target.value)} className="login-input" />
<input type="password" placeholder="비밀번호" value={password} onChange={(e) => setPassword(e.target.value)} className="login-input" />
<div className="options">
<label>
<input type="checkbox" /> 로그인 상태 유지
</label>
<span className="ip-security" onClick={() => setIsIpSecurityOn(!isIpSecurityOn)}>
IP보안 <b>{isIpSecurityOn ? "ON" : "OFF"}</b>
</span>
</div>
<button type="submit" className="login-button">로그인</button>
</form>
<div className="links">
<span onClick={() => navigate("/find-password")}>비밀번호 찾기</span> |
<span>아이디 찾기</span> |
<span onClick={() => navigate("/register")}>회원가입</span>
</div>
</div>
</div>
);
}
export default Login;
Login.css
/* 전체 페이지 스타일 */
.login-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #f5f6f7;
}
/* 네이버 로고 스타일 */
.logo {
font-size: 32px;
font-weight: bold;
color: #03c75a;
margin-bottom: 20px;
}
/* 로그인 박스 */
.login-box {
background: white;
padding: 25px;
width: 400px;
border-radius: 10px;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
margin: auto;
}
/* 입력 필드 스타일 */
.login-input {
width: 100%;
max-width: 360px;
padding: 12px;
margin: 8px 0;
border: 1px solid #dadada;
border-radius: 5px;
font-size: 14px;
outline: none;
}
/* 로그인 버튼 스타일 */
.login-button {
width: 100%;
background-color: #03c75a;
color: white;
padding: 12px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
.login-button:hover {
background-color: #029d4e;
}
/* 하단 링크 스타일 */
.links {
margin-top: 15px;
font-size: 13px;
color: #666;
}
.links span {
cursor: pointer;
}
.links span:hover {
text-decoration: underline;
}
/* 비밀번호 찾기 결과 스타일 */
.found-password {
margin-top: 15px;
font-size: 16px;
color: #333;
}
.options {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
max-width: 360px;
font-size: 13px;
margin: 10px 0;
color: #666;
}
/* ✅ IP 보안 ON/OFF 버튼 스타일 */
.ip-security {
cursor: pointer;
background-color: #f5f5f5;
padding: 5px 10px;
border-radius: 12px;
font-weight: bold;
}
.ip-security:hover {
background-color: #e0e0e0;
}
/* ✅ 로그인으로 돌아가기 버튼 (네이버 스타일 적용) */
.back-to-login {
margin-top: 15px;
background: none;
border: none;
font-size: 14px;
color: #0366d6;
cursor: pointer;
font-weight: bold;
}
.back-to-login:hover {
text-decoration: underline;
}

Register.js
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./Register.css"; // ✅ 네이버 스타일 적용
function Register() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const navigate = useNavigate();
// ✅ 회원가입 처리
const handleRegister = (e) => {
e.preventDefault();
// 비밀번호 확인 체크
if (password !== confirmPassword) {
alert("비밀번호가 일치하지 않습니다.");
return;
}
// 로컬 스토리지에 회원 정보 저장 (DB 대체)
const newUser = { username, password, email, phone };
localStorage.setItem("user", JSON.stringify(newUser));
alert("회원가입이 완료되었습니다! 로그인 페이지로 이동합니다.");
navigate("/"); // 로그인 페이지로 이동
};
return (
<div className="register-container">
<h1 className="logo">NAVER</h1>
<div className="register-box">
<h2>회원가입</h2>
<form onSubmit={handleRegister}>
<input type="text" placeholder="아이디" value={username} onChange={(e) => setUsername(e.target.value)} required />
<input type="password" placeholder="비밀번호" value={password} onChange={(e) => setPassword(e.target.value)} required />
<input type="password" placeholder="비밀번호 확인" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} required />
<input type="email" placeholder="이메일" value={email} onChange={(e) => setEmail(e.target.value)} required />
<input type="tel" placeholder="휴대폰 번호" value={phone} onChange={(e) => setPhone(e.target.value)} required />
<button type="submit" className="register-button">가입하기</button>
</form>
{/* 로그인으로 이동 */}
<button onClick={() => navigate("/")} className="back-to-login">로그인으로 돌아가기</button>
</div>
</div>
);
}
export default Register;
Register.css
/* ✅ 전체 회원가입 페이지 스타일 */
.register-container {
display: flex;
flex-direction: column;
align-items: center; /* 👉 가운데 정렬 */
justify-content: center; /* 👉 수직 정렬 */
height: 100vh;
background-color: #f5f6f7;
}
/* ✅ 회원가입 박스 가운데 정렬 */
.register-box {
background: white;
padding: 25px;
width: 400px; /* 👉 박스 크기 조절 */
border-radius: 10px;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
display: flex;
flex-direction: column;
align-items: center; /* 👉 내부 요소 가운데 정렬 */
}
/* ✅ 입력 필드 스타일 */
.register-box input {
width: 100%; /* 👉 가로 너비 조절 */
max-width: 360px; /* 👉 최대 너비 설정 */
padding: 12px;
margin: 8px 0;
border: 1px solid #dadada;
border-radius: 5px;
font-size: 14px;
outline: none;
}
/* ✅ 가입하기 버튼 */
.register-button {
width: 100%;
max-width: 360px;
background-color: #03c75a;
color: white;
padding: 12px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
.register-button:hover {
background-color: #029d4e;
}
/* ✅ 로그인으로 돌아가기 버튼 */
.back-to-login {
margin-top: 10px;
background: none;
border: none;
font-size: 14px;
color: #0366d6;
cursor: pointer;
}
.back-to-login:hover {
text-decoration: underline;
}

Dashboard.js
import React from "react";
import { useNavigate } from "react-router-dom";
import "./Dashboard.css"; //
function Dashboard() {
const navigate = useNavigate();
const storedUser = JSON.parse(localStorage.getItem("user"));
const handleLogout = () => {
alert("로그아웃 되었습니다.");
navigate("/");
};
return (
<div className="dashboard-container">
<div className="nav-bar">
<h1 className="nav-logo">NAVER</h1>
<input type="text" className="search-bar" placeholder="검색어를 입력하세요" />
<button className="search-button">검색</button>
</div>
<div className="main-content">
<div className="user-info">
<h2>{storedUser ? `${storedUser.username}님, 환영합니다!` : "사용자"}</h2>
<button onClick={handleLogout} className="logout-button">로그아웃</button>
</div>
<div className="blog-section">
<h3>내 블로그</h3>
<p>내 블로그에 글을 작성하고 관리하세요.</p>
<button onClick={() => navigate("/blog")} className="blog-button">블로그로 이동</button>
</div>
</div>
</div>
);
}
export default Dashboard;
Dashboard.css
/* ✅ 전체 대시보드 스타일 */
.dashboard-container {
font-family: Arial, sans-serif;
background-color: #f5f6f7;
height: 100vh;
}
/* ✅ 네이버 상단바 */
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #03c75a;
padding: 10px 20px;
color: white;
}
.nav-logo {
font-size: 24px;
font-weight: bold;
}
.search-bar {
width: 400px;
padding: 8px;
border: none;
border-radius: 20px;
font-size: 14px;
}
.search-button {
margin-left: 10px;
padding: 8px 12px;
background-color: white;
color: #03c75a;
border: none;
border-radius: 20px;
font-weight: bold;
cursor: pointer;
}
.search-button:hover {
background-color: #f0f0f0;
}
/* ✅ 메인 콘텐츠 */
.main-content {
padding: 20px;
}
/* ✅ 로그인된 유저 정보 */
.user-info {
display: flex;
align-items: center;
justify-content: space-between;
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.logout-button {
background-color: #d9534f;
color: white;
border: none;
padding: 8px 12px;
border-radius: 5px;
cursor: pointer;
}
.logout-button:hover {
background-color: #c9302c;
}
/* ✅ 블로그 섹션 */
.blog-section {
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
}
.blog-list {
display: flex;
gap: 15px;
margin-top: 10px;
}
.blog-card {
background: #f9f9f9;
padding: 15px;
border-radius: 8px;
width: 30%;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
}
.blog-button {
margin-top: 10px;
padding: 8px 12px;
background-color: #03c75a;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.blog-button:hover {
background-color: #029d4e;
}
.blog-section {
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
margin-top: 20px;
}
.blog-button {
background-color: #03c75a;
color: white;
padding: 10px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
}
.blog-button:hover {
background-color: #029d4e;
}
FindPassword.js -> 미완성
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./Login.css";
function FindPassword() {
const [username, setUsername] = useState("");
const [foundPassword, setFoundPassword] = useState(null);
const navigate = useNavigate();
const handleFindPassword = (e) => {
e.preventDefault();
const storedUser = JSON.parse(localStorage.getItem("user"));
if (!storedUser || storedUser.username !== username) {
alert("아이디가 존재하지 않습니다.");
setFoundPassword(null);
return;
}
setFoundPassword(storedUser.password);
};
return (
<div className="login-container">
<h1 className="logo">NAVER</h1>
<div className="login-box">
<h2>비밀번호 찾기</h2>
<form onSubmit={handleFindPassword}>
<input
type="text"
placeholder="아이디를 입력하세요"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="login-input"
/>
<button type="submit" className="login-button">비밀번호 찾기</button>
</form>
{foundPassword && (
<p className="found-password">
당신의 비밀번호: <strong>{foundPassword}</strong>
</p>
)}
<button className="back-to-login" onClick={() => navigate("/")}>
로그인으로 돌아가기
</button>
</div>
</div>
);
}
export default FindPassword;
FindPassword.css
.find-password-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #f5f6f7;
}
.find-password-input {
width: 300px;
padding: 12px;
margin: 8px 0;
border: 1px solid #dadada;
border-radius: 5px;
font-size: 14px;
outline: none;
}
.find-password-button {
width: 320px;
background-color: #03c75a;
color: white;
padding: 12px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
.find-password-button:hover {
background-color: #029d4e;
}
.found-password {
margin-top: 20px;
font-size: 18px;
color: #333;
}
.back-button {
margin-top: 10px;
background: none;
border: none;
font-size: 14px;
color: #0366d6;
cursor: pointer;
}
.back-button:hover {
text-decoration: underline;
}

Blog.js
import React, { useState, useEffect } from "react";
import "./Blog.css"; // ✅ 네이버 스타일 적용
import { useNavigate } from "react-router-dom";
function Blog() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [posts, setPosts] = useState([]);
const navigate = useNavigate;
// 로컬 스토리지에서 기존 글 불러오기
useEffect(() => {
const savedPosts = JSON.parse(localStorage.getItem("posts")) || [];
setPosts(savedPosts);
}, []);
// 글 저장 함수
const handleSavePost = () => {
if (title.trim() === "" || content.trim() === "") {
alert("제목과 내용을 입력해주세요.");
return;
}
const newPost = { id: Date.now(), title, content };
const updatedPosts = [newPost, ...posts];
setPosts(updatedPosts);
localStorage.setItem("posts", JSON.stringify(updatedPosts));
setTitle("");
setContent("");
};
// 글 삭제 함수
const handleDeletePost = (id) => {
const updatedPosts = posts.filter((post) => post.id !== id);
setPosts(updatedPosts);
localStorage.setItem("posts", JSON.stringify(updatedPosts));
};
return (
<div className="blog-container">
<h2>네이버 블로그</h2>
{/* ✅ 글쓰기 폼 */}
<div className="blog-form">
<input
type="text"
placeholder="제목을 입력하세요"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="blog-input"
/>
<textarea
placeholder="내용을 입력하세요"
value={content}
onChange={(e) => setContent(e.target.value)}
className="blog-textarea"
></textarea>
<button onClick={handleSavePost} className="blog-button">글 작성</button>
</div>
{/* ✅ 글 목록 */}
<div className="blog-list">
{posts.length === 0 ? (
<p className="no-posts">작성된 글이 없습니다.</p>
) : (
posts.map((post) => (
<div key={post.id} className="blog-card">
<h3>{post.title}</h3>
<p>{post.content}</p>
<button onClick={() => handleDeletePost(post.id)} className="delete-button">삭제</button>
</div>
))
)}
</div>
<button onClick={() => navigate(-1)} className="back-button">뒤로가기</button>
</div>
);
}
export default Blog;
Blog.css
/* ✅ 전체 블로그 스타일 */
.blog-container {
max-width: 600px;
margin: auto;
padding: 20px;
font-family: Arial, sans-serif;
}
/* ✅ 글쓰기 폼 */
.blog-form {
display: flex;
flex-direction: column;
gap: 10px;
background: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
}
.blog-input, .blog-textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.blog-textarea {
height: 100px;
}
.blog-button {
background-color: #03c75a;
color: white;
padding: 10px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.blog-button:hover {
background-color: #029d4e;
}
/* ✅ 글 목록 */
.blog-list {
margin-top: 20px;
}
.blog-card {
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
}
.delete-button {
background-color: #d9534f;
color: white;
border: none;
padding: 8px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
.delete-button:hover {
background-color: #c9302c;
}
✅ Blog.js 는 썸머노트(글쓰기 에디터)를 사용하여 실제 에디어로 작성하도록 수정
✅ FindPassword.js 는 권한 및 인증 없이 패스워드를 찾을 수 있으므로 인증 기능 추가
'Project' 카테고리의 다른 글
| [javascript] 네이버 클론 코딩 + 블로그 5: 게시글 수정 (0) | 2025.03.13 |
|---|---|
| [javascript] 네이버 클론 코딩 + 블로그 5: 답글 & 공감 (0) | 2025.03.12 |
| [javascript] 네이버 클론 코딩 + 블로그 4: JWT 발급 (0) | 2025.03.12 |
| [javascript] 네이버 클론 코딩 + 블로그 3: 블로그 댓글 구현 (1) | 2025.03.11 |
| [javascript] 네이버 클론 코딩 + 블로그 2: 글쓰기 에디터 + 경고 메시지 (1) | 2025.03.10 |