- 프로젝트 배포 링크:
- AWS 의 EC2 로 배포하였습니다.
- 현재 프리티어 계정 만료로 배포 중단했습니다.
- 가입 하기 귀찮다면, 아이디 : test@test.com 비밀번호 : asdf1234! 로 로그인 하시면 됩니다.
- 도메인 미정
- 프로젝트 명: 뜬 구름
- 프로젝트 목적: 커뮤니티 사이트
- 개발 기간: 2024-06-03 ~ 2024-06-27
- 개발 인원: 1명 (프론트엔드/백엔드 모두 담당)
- 백엔드: Repo 링크
- 사용 언어: TypeScript, React
"뜬 구름"은 사용자들이 서로 소통하고 정보를 공유할 수 있는 커뮤니티 사이트입니다. React와 TypeScript를 사용하여 프론트엔드를 구현하였으며, Redux를 통해 상태 관리를 효과적으로 수행하였습니다.
- 프론트엔드: React, TypeScript
- 상태 관리: Redux Toolkit
- HTTP 클라이언트: Axios
- 스타일링: CSS Modules
- 사용자 인증 (로그인/회원가입)
- 게시물 작성, 수정, 삭제
- 댓글 작성, 수정, 삭제
- 사용자 프로필 관리
- 사진 슬라이더 및 모달
프로젝트의 파일 및 디렉토리 구조는 다음과 같습니다:
cloud-story-react/
├── .env
├── .gitignore
├── package.json
├── package-lock.json
├── README.md
├── tsconfig.json
├── public/
│ ├── favicon.ico
│ └── index.html
├── src/
│ ├── App.tsx
│ ├── global.d.ts
│ ├── index.css
│ ├── index.tsx
│ ├── types.ts
│ ├── api/
│ │ ├── api.ts
│ │ └── axios.ts
│ ├── assets/
│ │ ├── cloud-story-logo.png
│ │ └── default-profile.png
│ ├── components/
│ │ ├── EditPostModal.module.css
│ │ ├── EditPostModal.tsx
│ │ ├── Icon.tsx
│ │ ├── ImageModal.module.css
│ │ ├── ImageModal.tsx
│ │ ├── Navbar.module.css
│ │ ├── Navbar.tsx
│ │ ├── PhotoSlider.module.css
│ │ ├── PhotoSlider.tsx
│ │ ├── PostCard.module.css
│ │ ├── PostCard.tsx
│ │ ├── PostCreator.module.css
│ │ ├── PostCreator.tsx
│ │ ├── PostModal.module.css
│ │ ├── PostModal.tsx
│ │ ├── PrivateRoute.tsx
│ │ ├── usePostDetail.ts
│ │ ├── SignUp/
│ │ │ ├── Step1/
│ │ │ │ ├── index.ts
│ │ │ │ └── Step1.tsx
│ │ │ ├── Step2/
│ │ │ │ ├── index.ts
│ │ │ │ └── Step2.tsx
│ │ │ ├── Step3/
│ │ │ │ ├── index.ts
│ │ │ │ └── Step3.tsx
│ │ │ ├── Step4/
│ │ │ │ ├── index.ts
│ │ │ │ └── Step4.tsx
│ ├── features/
│ │ └── auth/
│ │ └── authSlice.ts
│ ├── pages/
│ │ ├── CreateAccountPage.module.css
│ │ ├── CreateAccountPage.tsx
│ │ ├── LoginPage.module.css
│ │ ├── LoginPage.tsx
│ │ ├── MainPage.module.css
│ │ ├── MainPage.tsx
│ │ ├── ProfilePage.module.css
│ │ ├── ProfilePage.tsx
│ │ ├── TodayPage.module.css
│ │ ├── TodayPage.tsx
│ │ ├── WeekPage.tsx
│ └── store/
│ └── index.ts
-
App.tsx
- 프로젝트의 메인 컴포넌트로, 라우팅 및 전역 설정을 담당합니다.
React Router
를 사용하여 페이지 간의 라우팅을 설정하고,PrivateRoute
컴포넌트를 사용하여 인증된 사용자만 접근할 수 있는 경로를 보호합니다.
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import PrivateRoute from './components/PrivateRoute'; import MainPage from './pages/MainPage'; import LoginPage from './pages/LoginPage'; function App() { return ( <Router> <Switch> <Route path="/login" component={LoginPage} /> <PrivateRoute path="/" component={MainPage} /> </Switch> </Router> ); } export default App;
-
authSlice.ts
Redux Toolkit
을 사용하여 사용자 인증 상태를 관리합니다.createSlice
를 통해 간단하게 상태와 액션을 정의할 수 있습니다.
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface AuthState { user: string | null; token: string | null; } const initialState: AuthState = { user: null, token: null, }; const authSlice = createSlice({ name: 'auth', initialState, reducers: { login: (state, action: PayloadAction<{ user: string; token: string }>) => { state.user = action.payload.user; state.token = action.payload.token; }, logout: (state) => { state.user = null; state.token = null; }, }, }); export const { login, logout } = authSlice.actions; export default authSlice.reducer;
-
api.ts 및 axios.ts
- 서버와의 HTTP 통신을 위한 API 설정이 포함되어 있습니다.
axios
인스턴스를 생성하고, 필요한 인터셉터를 설정하여 요청과 응답을 처리합니다.
// src/api/axios.ts import axios from 'axios'; const axiosInstance = axios.create({ baseURL: 'https://api.example.com', timeout: 1000, }); axiosInstance.interceptors.request.use( (config) => { // Do something before request is sent return config; }, (error) => { // Do something with request error return Promise.reject(error); } ); axiosInstance.interceptors.response.use( (response) => { // Do something with response data return response; }, (error) => { // Do something with response error return Promise.reject(error); } ); export default axiosInstance;
// src/api/api.ts import axiosInstance from './axios'; export const fetchPosts = async () => { try { const response = await axiosInstance.get('/posts'); return response.data; } catch (error) { throw error; } }; export const createPost = async (post: any) => { try { const response = await axiosInstance.post('/posts', post); return response.data; } catch (error) { throw error; } };
-
index.tsx
- 프로젝트의 진입점 파일로, ReactDOM을 사용하여 React 컴포넌트를 렌더링합니다.
StrictMode
와Provider
를 사용하여 전체 애플리케이션에 Redux 스토어를 적용합니다.
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { Provider } from 'react-redux'; import store from './store'; ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') );
-
types.ts
- 프로젝트에서 사용되는 타입 정의 파일로, TypeScript의 강력한 타입 시스템을 활용하여 코드의 안전성을 높입니다.
- 예를 들어,
Post
와User
와 같은 주요 객체의 타입을 정의합니다.
export interface Post { id: string; title: string; content: string; author: string; createdAt: Date; updatedAt: Date; } export interface User { id: string; username: string; email: string; profilePicture: string; }
-
PostCard.tsx
- 개별 게시물을 표시하는 카드 컴포넌트입니다.
- 게시물의 제목, 내용, 작성자 및 작성 날짜를 표시하며, 게시물 클릭 시 상세 페이지로 이동합니다.
import React from 'react'; import { Post } from '../types'; interface PostCardProps { post: Post; } const PostCard: React.FC<PostCardProps> = ({ post }) => { return ( <div className="post-card"> <h2>{post.title}</h2> <p>{post.content}</p> <p>By {post.author} on {new Date(post.createdAt).toLocaleDateString()}</p> </div> ); }; export default PostCard;
-
Navbar.tsx
- 네비게이션 바 컴포넌트로, 사용자가 사이트 내에서 쉽게 이동할 수 있도록 도와줍니다.
- 로그인 상태에 따라 다른 메뉴를 표시하며, 프로필 페이지와 로그아웃 기능을 제공합니다.
import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { logout } from '../features/auth/authSlice'; const Navbar: React.FC = () => { const dispatch = useDispatch(); const user = useSelector((state: any) => state.auth.user); return ( <nav className="navbar"> <div className="navbar-logo">뜬 구름</div> {user ? ( <div className="navbar-menu"> <span>{user.username}</span> <button onClick={() => dispatch(logout())}>Logout</button> </div> ) : ( <div className="navbar-menu"> <a href="/login">Login</a> <a href="/signup">Sign Up</a> </div> )} </nav> ); }; export default Navbar;
-
PostCreator.tsx
- 새로운 게시물을 작성할 수 있는 컴포넌트입니다.
- 사용자가 제목과 내용을 입력하고 제출 버튼을 누르면 게시물이 생성됩니다.
import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { createPost } from '../api/api'; const PostCreator: React.FC = () => { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const dispatch = useDispatch(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await createPost({ title, content }); // 추가적인 로직 } catch (error) { console.error('Failed to create post:', error); } }; return ( <form onSubmit={handleSubmit}> <input type="text" placeholder="Title" value={title} onChange={(e) => setTitle(e.target.value)} /> <textarea placeholder="Content" value={content} onChange={(e) => setContent(e.target.value)} /> <button type="submit">Create Post</button> </form> ); }; export default PostCreator;
-
ImageModal.tsx
- 이미지를 확대하여 보여주는 모달 컴포넌트입니다.
- 게시물에 첨부된 이미지를 클릭하면 큰 화면으로 이미지를 볼 수 있습니다.
import React from 'react'; import './ImageModal.module.css'; interface ImageModalProps { imageUrl: string; onClose: () => void; } const ImageModal: React.FC<ImageModalProps> = ({ imageUrl, onClose }) => { return ( <div className="image-modal" onClick={onClose}> <img src={imageUrl} alt="Modal" /> </div> ); }; export default ImageModal;
-
MainPage.tsx
- 메인 페이지 컴포넌트로, 최신 게시물 목록을 표시합니다.
- 사용자는 이 페이지에서 게시물을 클릭하여 상세 페이지로 이동할 수 있습니다.
import React, { useEffect, useState } from 'react';
import { fetchPosts } from '../api/api';
import PostCard from '../components/PostCard';
import { Post } from '../types';
const MainPage: React.FC = () => {
const [posts, setPosts] = useState<Post[]>([]);
useEffect(() => {
const loadPosts = async () => {
try {
const data = await fetchPosts();
setPosts(data);
} catch (error) {
console.error('Failed to fetch posts:', error);
}
};
loadPosts();
}, []);
return (
<div className="main-page">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
);
};
export default MainPage;
서비스 접속 초기화면으로, 사용자가 처음 방문했을 때 나타나는 페이지입니다.
- 로그인이 되어 있지 않은 경우: 로그인 페이지로 리디렉션됩니다.
- 로그인이 되어 있는 경우: 메인 페이지로 리디렉션됩니다.
사용자가 이메일과 비밀번호를 입력하여 로그인할 수 있는 페이지입니다.
- 기능:
- 이메일과 비밀번호 입력
- 이메일과 비밀번호 저장
- 로그인 버튼 클릭 시 인증 처리
- 로그인 실패 시 오류 메시지 표시
새로운 사용자가 이메일과 비밀번호를 입력하여 회원가입할 수 있는 페이지입니다.
- 기능:
- 이메일과 비밀번호 입력
- 입력된 정보에 대한 유효성 검사
- 이메일 중복 검사 및 인증 코드 발송
- 유효성 검사 실패 시 경고 문구 표시
- 유효성 검사 통과 시 다음 버튼 활성화
사용자가 커뮤니티의 최신 게시물을 볼 수 있는 메인 페이지입니다.
- 기능:
- 최신 게시물 목록 표시
- 게시글을 작성할 수 있는 게시글 작성 섹션
- 게시글을 새로 고침(재랜더링) 할 수 있는 버튼
- 게시물 클릭 시 상세 모달 On
사용자의 프로필 정보를 표시하고 편집할 수 있는 페이지입니다.
- 기능:
- 프로필 사진, 사용자 닉네임, 이메일 등 정보 표시
- 프로필 정보(닉네임, 프로필 사진) 수정 가능
- 비밀번호 수정 가능
![image](https://github.com/Re
cyclingbottle/cloud-story-react/assets/101244968/7a257b39-e717-43f3-b57f-40f20c6756b9)
메인 페이지, 오늘의 인기글 페이지, 이번 주의 인기글 페이지 상단에 위치하며, 새로운 게시물을 작성할 수 있는 컴포넌트입니다.
- 기능:
- 텍스트 입력 섹션을 클릭하면 컴포넌트가 열리고 여러 버튼(사진 등록, 취소, 작성)이 숨김 상태에서 사용자에게 보이게 됩니다.
- 사진을 등록할 수 있습니다. 최대 20장을 등록할 수 있습니다.
- 사진을 제거할 수 있습니다.
- 순서는 변경할 수 없습니다.
선택한 게시물의 상세 내용을 볼 수 있는 모달입니다.
- 기능:
- 게시물 내용, 작성자, 작성일 표시
- 게시글의 메타 정보인 좋아요, 싫어요, 댓글, 조회 수 표시
- 댓글 목록과 함께 댓글 작성 및 수정, 삭제 기능 제공
오늘 작성된 게시물 중 인기를 판별하는 수식을 통해 필터링하여 표시하는 페이지입니다.
- 기능:
- 오늘 작성된 게시물 중 인기 있는 5개 글을 순서대로 표시
- 게시물 클릭 시 상세 모달 열기
- 게시글 상세 보기 모달과 동일
한 주의 인기글을 모아 순서대로 표시하는 페이지입니다.
- 기능:
- 이번 주 작성된 인기글 목록 표시
- 게시물 클릭 시 상세 모달 열기
- 게시글 상세 보기 모달과 동일
이번에 처음으로 리액트에서 자바스크립트가 아닌 타입스크립트를 사용하면서 정적 타입에 대한 이해도와 자바스크립트를 더 깊게 이해할 수 있는 기회가 되었습니다. 새로운 시도에 대해 프로젝트를 통해 배우며, 무언가를 깨닫고 도전에서 나오는 성취감을 얻을 수 있어 좋았습니다.