diff --git a/package.json b/package.json index b2be3b5..731a0c4 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "autoprefixer": "10.4.20", + "lucide-react": "0.469.0", "postcss": "8.4.49", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/src/App.tsx b/src/App.tsx index 47b5285..1fdb2d8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,31 +1,50 @@ import './index.css'; -import { useState } from 'react'; +import { createContext, useState } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import ExplorePage from './pages/ExplorePage'; -import HomePage from './pages/HomePage'; import LoginPage from './pages/LoginPage'; +import MainPage from './pages/MainPage'; + +export type LoginContextType = { + isLoggedIn: boolean; + handleIsLoggedIn: (value: boolean) => void; +}; + +export const LoginContext = createContext(null); export const App = () => { - const [isLoggedIn, setIsLoggedIn] = useState(false); + // 임시 로그인 상태 + // TODO: 로그인 API 관리 로직 구현 + const [isLoggedIn, setIsLoggedIn] = useState(() => { + const saved = localStorage.getItem('isLoggedIn'); + return saved === 'true'; + }); + + const handleIsLoggedIn = (value: boolean) => { + setIsLoggedIn(value); + localStorage.setItem('isLoggedIn', String(value)); + }; return ( - - - ) : ( - - ) - } - > - : } - > - + + + + ) : ( + + ) + } + > + : } + > + + ); }; diff --git a/src/components/MobileBar.tsx b/src/components/MobileBar.tsx new file mode 100644 index 0000000..11c4169 --- /dev/null +++ b/src/components/MobileBar.tsx @@ -0,0 +1,26 @@ +import { Heart, Home, PlusSquare, Search, User } from 'lucide-react'; +import React from 'react'; + +interface NavItemProps { + icon: React.ReactNode; +} + +const NavItem = ({ icon }: NavItemProps) => ( + + {icon} + +); + +const BottomBar = () => { + return ( + + ); +}; + +export default BottomBar; diff --git a/src/components/MobileHeader.tsx b/src/components/MobileHeader.tsx new file mode 100644 index 0000000..1dd1551 --- /dev/null +++ b/src/components/MobileHeader.tsx @@ -0,0 +1,15 @@ +import { Heart, MessageCircle } from 'lucide-react'; + +const MobileHeader = () => { + return ( +
+ Instagram +
+ + +
+
+ ); +}; + +export default MobileHeader; diff --git a/src/components/Post.tsx b/src/components/Post.tsx new file mode 100644 index 0000000..03f5e0b --- /dev/null +++ b/src/components/Post.tsx @@ -0,0 +1,46 @@ +import { Heart, MessageCircle, Send } from 'lucide-react'; + +type PostProps = { + username: string; + imageUrl: string; + caption: string; + likes: number; + comments: number; + timestamp: string; +}; + +const Post = ({ + username, + imageUrl, + caption, + likes, + comments, + timestamp, +}: PostProps) => ( +
+
+ {username} + {username} +
+ Post +
+
+ + + +
+

{likes.toLocaleString()} likes

+

+ {username} {caption} +

+

View all {comments} comments

+

{timestamp}

+
+
+); + +export default Post; diff --git a/src/components/Posts.tsx b/src/components/Posts.tsx new file mode 100644 index 0000000..4d0b647 --- /dev/null +++ b/src/components/Posts.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; + +import Post from './Post'; + +interface PostData { + id: number; + username: string; + imageUrl: string; + caption: string; + likes: number; + comments: number; + timestamp: string; +} + +type PostsProps = { + posts: PostData[]; + postsPerPage: number; +}; + +const Posts = ({ posts, postsPerPage }: PostsProps) => { + const [currentPage, setCurrentPage] = useState(1); + + const indexOfLastPost = currentPage * postsPerPage; + const indexOfFirstPost = indexOfLastPost - postsPerPage; + const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost); + + const totalPages = Math.ceil(posts.length / postsPerPage); + + return ( +
+ {currentPosts.map((post) => ( + + ))} +
+ + + Page {currentPage} of {totalPages} + + +
+
+ ); +}; + +export default Posts; diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx new file mode 100644 index 0000000..107eb18 --- /dev/null +++ b/src/components/SideBar.tsx @@ -0,0 +1,81 @@ +import { + Compass, + Heart, + Home, + Menu, + PlusSquare, + Search, + User, +} from 'lucide-react'; +import React, { useContext } from 'react'; + +import { LoginContext, type LoginContextType } from '../App'; + +interface NavItemProps { + icon: React.ReactNode; + label: string; + active: boolean; +} + +const NavItem = ({ icon, label, active }: NavItemProps) => ( + + {icon} + {{label}} + +); + +const Sidebar = () => { + const [isMenuOpen, setIsMenuOpen] = React.useState(false); + + const toggleMenu = () => { + setIsMenuOpen(!isMenuOpen); + }; + + const context = useContext(LoginContext) as LoginContextType; + const handleIsLoggedIn = context.handleIsLoggedIn; + + return ( +
+
+ Instagram +
+
+ } label="Home" active /> + } label="Search" active={false} /> + } label="Explore" active={false} /> + } label="Notifications" active={false} /> + } label="Create" active={false} /> + } label="Profile" active={false} /> +
+
+ + {isMenuOpen && ( +
+
+ +
+
+ )} +
+
+ ); +}; + +export default Sidebar; diff --git a/src/components/Stories.tsx b/src/components/Stories.tsx new file mode 100644 index 0000000..3ef98b7 --- /dev/null +++ b/src/components/Stories.tsx @@ -0,0 +1,22 @@ +const Story = () => ( +
+
+
+ Story +
+
+

username

+
+); + +const Stories = () => { + return ( +
+ {[1, 2, 3, 4, 5].map((_, i) => ( + + ))} +
+ ); +}; + +export default Stories; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index a2340b3..b011a3b 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,16 +1,16 @@ import React, { useState } from 'react'; -type Props = { - setIsLoggedIn: (isLoggedIn: boolean) => void; +type LoginPageProps = { + handleIsLoggedIn: (value: boolean) => void; }; -const LoginPage = ({ setIsLoggedIn }: Props) => { +const LoginPage = ({ handleIsLoggedIn }: LoginPageProps) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - setIsLoggedIn(true); + handleIsLoggedIn(true); //TODO : 로그인 로직 }; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx new file mode 100644 index 0000000..81d3d4c --- /dev/null +++ b/src/pages/MainPage.tsx @@ -0,0 +1,86 @@ +import BottomBar from '../components/MobileBar'; +import MobileHeader from '../components/MobileHeader'; +import Posts from '../components/Posts'; +import SideBar from '../components/SideBar'; +import Stories from '../components/Stories'; + +const dummyPosts = [ + { + id: 1, + username: 'user1', + imageUrl: '/placeholder.svg', + caption: 'This is a sample post', + likes: 1000, + comments: 100, + timestamp: '1 HOUR AGO', + }, + { + id: 2, + username: 'user2', + imageUrl: '/placeholder.svg', + caption: 'Another sample post', + likes: 1500, + comments: 150, + timestamp: '2 HOURS AGO', + }, + { + id: 3, + username: 'user3', + imageUrl: '/placeholder.svg', + caption: 'Yet another sample post', + likes: 2000, + comments: 200, + timestamp: '3 HOURS AGO', + }, + { + id: 4, + username: 'user4', + imageUrl: '/placeholder.svg', + caption: 'Fourth sample post', + likes: 2500, + comments: 250, + timestamp: '4 HOURS AGO', + }, + { + id: 5, + username: 'user5', + imageUrl: '/placeholder.svg', + caption: 'Fifth sample post', + likes: 3000, + comments: 300, + timestamp: '5 HOURS AGO', + }, + { + id: 6, + username: 'user6', + imageUrl: '/placeholder.svg', + caption: 'Sixth sample post', + likes: 3500, + comments: 350, + timestamp: '6 HOURS AGO', + }, +]; + +const MainPage = () => { + return ( +
+
+
+ + + +
+

© INSTAGRAM

+
+
+
+ +
+ + +
+
+ ); +}; + +export default MainPage; diff --git a/yarn.lock b/yarn.lock index 36e8a93..6d80602 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2837,6 +2837,15 @@ __metadata: languageName: node linkType: hard +"lucide-react@npm:0.469.0": + version: 0.469.0 + resolution: "lucide-react@npm:0.469.0" + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/0ee78d110550579b848a01446a440f733fd17e1bf1850aa01551b61f415e03e5f5ab7f26b56f1c648edc93856177f5476ff9f8f6f5d80e8fe45913d208f49961 + languageName: node + linkType: hard + "make-fetch-happen@npm:^13.0.0": version: 13.0.1 resolution: "make-fetch-happen@npm:13.0.1" @@ -3761,6 +3770,7 @@ __metadata: autoprefixer: "npm:10.4.20" eslint: "npm:9.12.0" knip: "npm:5.33.3" + lucide-react: "npm:0.469.0" postcss: "npm:8.4.49" prettier: "npm:3.3.3" react: "npm:18.3.1"