이 글은 저 혼자 " Clever Programmer " 채널의 Clone Coding 영상을 보고 혼자 따라해본 글입니다.
내 TINDER 클론 코딩 깃허브 주소
github.com/junghyeonsu/tinder-clone
구조 파악 (0분 ~ 11분)
import React, { Component } from 'react';
import Header from './Header';
import './App.css';
class App extends Component {
render(){
return (
<div className="App">
<h1>This is awesome TINDER Clone App</h1>
{/* Header */}
<Header />
{/* Tinder Cards */}
{/* Buttons belos tinder cards */}
{/* Chats screen */}
{/* Individual Chat screen */}
</div>
);
}
}
export default App;
Header 컴포넌트 생성(11분 ~ 48분)
App.js
import React, { Component } from 'react';
import Header from './Header';
import './App.css';
import { firestore } from "./firebase";
import { Button } from 'react-bootstrap';
class App extends Component {
render(){
return (
<div className="App">
<Header />
{/* Tinder Cards */}
{/* Buttons belos tinder cards */}
{/* Chats screen */}
{/* Individual Chat screen */}
</div>
);
}
}
export default App;
Header.js
import React from 'react'
import "./Header.css";
import PersonIcon from '@material-ui/icons/Person';
import ForumIcon from '@material-ui/icons/Forum';
import IconButton from "@material-ui/core/IconButton";
function Header() {
return (
<div className="header">
<IconButton>
<PersonIcon className="header__icon" fontSize="large" />
</IconButton>
<img
className="header__logo"
src="https://1000logos.net/wp-content/uploads/2018/07/tinder-logo.png"
alt="tinder logo"
/>
<IconButton>
<ForumIcon className="header__icon" fontSize="large" />
</IconButton>
</div>
)
}
export default Header
Header.css
.header {
/* display to row */
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #f9f9f9;
}
.header__logo {
height: 40px;
/*내용이 종횡비를 유지하면서 요소에 정의된 너비와 높이안에서 가능한한 많이 확대(scale up)시킨다*/
object-fit: contain;
}
.header__icon {
margin: 20px;
}
TinderCards 컴포넌트 생성 및 Router 연결 (48분 ~ 1시간 30분)
App.js
import React, { Component } from 'react';
import Header from './Header';
import TinderCards from './TinderCards';
import { BrowserRouter as Router, Switch, Route, Link} from "react-router-dom";
import './App.css';
import { firestore } from "./firebase";
class App extends Component {
render(){
return (
<div className="App">
<Header />
<Router>
<Switch>
<Route path="/chat">
<h1>I am the Chatpage</h1>
</Route>
<Route path="/">
<TinderCards />
</Route>
</Switch>
{/* Tinder Cards */}
{/* Buttons belos tinder cards */}
{/* Chats screen */}
{/* Individual Chat screen */}
</Router>
</div>
);
}
}
export default App;
TinderCards.js
import React, { useState } from 'react'
import TinderCard from "react-tinder-card";
import './TinderCards.css';
function TinderCards() {
const [people, setPeople] = useState([
{
name: 'steve jobs',
url: 'https://www.biography.com/.image/t_share/MTY2MzU3OTcxMTUwODQxNTM1/steve-jobs--david-paul-morrisbloomberg-via-getty-images.jpg'
},
{
name: 'mark zuckerberg',
url: 'https://api.time.com/wp-content/uploads/2019/04/mark-zuckerberg-time-100-2019.jpg?quality=85&zoom=2'
}
]);
return (
<div>
<h1>Tinder Card</h1>
<div className="tinderCards__cardContainer">
{people.map(person => {
return(
<TinderCard
className="swipe"
key={person.name}
preventSwipe={['up', 'down']}
>
<div
className="card"
style={{ backgroundImage:`url(${person.url})` }}
>
<h3>{person.name}</h3>
</div>
</TinderCard>
);
})}
</div>
</div>
);
}
export default TinderCards;
TinderCards.css
.card {
position: relative;
width: 600px;
padding: 20px;
max-width: 85vw;
height: 50vh;
border-radius: 20px;
background-size: cover;
background-position: center;
box-shadow: 0px 18px 53px 0px rgba(0, 0, 0, 0.3);
}
.card > h3 {
position: absolute;
bottom: 10px;
color: white;
}
.tinderCards__cardContainer {
display: flex;
justify-content: center;
margin-top: 5vh;
}
.swipe {
position: absolute;
}
Firebase 연동 (1시간 30분 ~ 2시간)
firebase에 name과, url의 필드를 갖는 document를 추가해줌으로써
react에서 불러와서 사용할 수 있게 연동을 했습니다.
import database from './firebase';
useEffect(() => {
database.collection('people').onSnapshot(snapshot => {
setPeople(snapshot.docs.map(doc => doc.data()))
})
}, []);
database는 firebase에서 import해오고
collection의 이름은 저희는 people로 지정을 해줬고,
onSnapshot은 말그대로 데이터베이스를 사진을 찍어서 보여준다는 느낌으로 생각하면 됩니다.
그리고 setPeople을 통해서 people state를 snapshot의 document로 저장해줍니다.
그러면 render 부분에서 name과 url을 보고 사진과 이름을 띄우는 형식입니다.
TinderCards.js
import React, { useState, useEffect } from 'react'
import database from './firebase';
import TinderCard from "react-tinder-card";
import './TinderCards.css';
function TinderCards() {
const [people, setPeople] = useState([]);
useEffect(() => {
database.collection('people').onSnapshot(snapshot => {
setPeople(snapshot.docs.map(doc => doc.data()))
})
}, []);
return (
<div>
<h1>Tinder Card</h1>
<div className="tinderCards__cardContainer">
{people.map(person => {
return(
<TinderCard
className="swipe"
key={person.name}
preventSwipe={['up', 'down']}
>
<div
className="card"
style={{ backgroundImage:`url(${person.url})` }}
>
<h3>{person.name}</h3>
</div>
</TinderCard>
);
})}
</div>
</div>
);
}
export default TinderCards;
SwipeButtons 컴포넌트 생성 (2시간 ~ 2시간 20분)
화면 아래의 버튼들을 SwipeButtons 컴포넌트에 넣어주고 스타일링을 해줬습니다.
SwipeButtons.js
import React from 'react'
import ReplayIcon from "@material-ui/icons/Replay";
import CloseIcon from "@material-ui/icons/Close";
import StarRateIcon from "@material-ui/icons/StarRate";
import FavoriteIcon from "@material-ui/icons/Favorite";
import FlashOnIcon from "@material-ui/icons/FlashOn";
import IconButton from "@material-ui/core/IconButton";
import './SwipeButtons.css';
const SwipeButtons = () => {
return (
<div className="swipeButtons">
<IconButton className="swipeButtons__repeat">
<ReplayIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__left">
<CloseIcon fontSize="large" />
</IconButton >
<IconButton className="swipeButtons__star">
<StarRateIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__right">
<FavoriteIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__lightning">
<FlashOnIcon fontSize="large" />
</IconButton>
</div>
)
}
export default SwipeButtons;
SwipeButtons.css
.swipeButtons {
position: fixed;
bottom: 2vh;
width: 100%;
display: flex;
justify-content: space-evenly;
}
.swipeButtons .MuiIconButton-root {
background-color: white;
box-shadow: 0px 10px 53px 0px rgba(0, 0, 0, 0.3) !important;
}
.swipeButtons__repeat {
padding: 3vw !important;
color: #ec5e6f !important;
}
.swipeButtons__left {
padding: 3vw !important;
color: #ec5e6f !important;
}
.swipeButtons__star {
padding: 3vw !important;
color: #62b4f9 !important;
}
.swipeButtons__right {
padding: 3vw !important;
color: #76e2b3 !important;
}
.swipeButtons__lightning {
padding: 3vw !important;
color: #915dd1 !important;
}
메세지 화면 이동 및 메세지 컴포넌트 구현(2시간 20분 ~ 3시간)
홈 화면에서 메세지 버튼 클릭하면 왼쪽 위의 버튼이 뒤로가기 버튼으로 바뀌는 것도 구현
그리고 Chats 컴포넌트에서 Chat 컴포넌트를 추가해서 메세지 화면을 구현
Chats 와 Chat은 다른 컴포넌트입니다!
Chats.js
import React from 'react'
import Chat from './Chat';
import './Chats.css';
function Chats() {
return (
<div className="chats">
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
</div>
)
}
export default Chats
Chat.js
import React from 'react'
import Avatar from "@material-ui/core/Avatar"
import "./Chat.css";
function Chat({ name, message, profilePic, timestamp }) {
return (
<div className="chat">
<Avatar className="chat__image" alt={name} src={profilePic} />
<div className="chat__details">
<h3>{name}</h3>
<p>{message}</p>
</div>
<p className="chat__timestamp">{timestamp}</p>
</div>
)
}
export default Chat
Chat.css
.chat {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
height: 70px;
border-bottom: 1px solid rgb(236, 236, 236);
}
.chat__details{
flex: 1;
}
.chat__details > p {
color: gray;
}
.chat__timestamp {
color: lightgray;
}
.chat__image {
margin-right: 20px;
}
바뀐 Header.js
import React from 'react'
import "./Header.css";
import PersonIcon from '@material-ui/icons/Person';
import ForumIcon from '@material-ui/icons/Forum';
import IconButton from "@material-ui/core/IconButton";
import ArrowBackIosIcon from "@material-ui/icons/ArrowBackIos";
import { Link, useHistory } from "react-router-dom";
function Header({ backButton }) {
const history = useHistory();
return (
<div className="header">
{backButton ? (
<IconButton onClick={() => history.replace(backButton)}>
<ArrowBackIosIcon fontSize="large" className="header__icon" />
</IconButton>
): (
<IconButton>
<PersonIcon className="header__icon" fontSize="large" />
</IconButton>
)}
<Link to="/">
<img
className="header__logo"
src="https://1000logos.net/wp-content/uploads/2018/07/tinder-logo.png"
alt="tinder logo"
/>
</Link>
<Link to="/chat">
<IconButton>
<ForumIcon className="header__icon" fontSize="large" />
</IconButton>
</Link>
</div>
)
}
export default Header
App.js
import React, { Component } from 'react';
import Header from './Header';
import TinderCards from './TinderCards';
import SwipeButtons from './SwipeButtons';
import Chats from './Chats';
import { BrowserRouter as Router, Switch, Route, Link} from "react-router-dom";
import './App.css';
import { firestore } from "./firebase";
class App extends Component {
render(){
return (
<div className="App">
<Router>
<Switch>
<Route path="/chat">
<Header backButton="/" />
<Chats />
</Route>
<Route path="/">
<Header />
<TinderCards />
<SwipeButtons />
</Route>
</Switch>
{/* Tinder Cards */}
{/* Buttons belos tinder cards */}
{/* Chats screen */}
{/* Individual Chat screen */}
</Router>
</div>
);
}
}
export default App;
ChatScreen 컴포넌트 구현 & 라우터 적용 & 채팅 기능 구현 & 마무리 (3시간 00분 ~ 3시간 50분)
CSS가 도중에 안먹는 오류가 있어서 잘안됐습니다.
그대로 따라쳤는데 안됨...
여튼 클론 코딩 마무리 !
전체코드
App.js
import React, { Component } from 'react';
import Header from './Header';
import TinderCards from './TinderCards';
import SwipeButtons from './SwipeButtons';
import Chats from './Chats';
import ChatScreen from './ChatScreen';
import { BrowserRouter as Router, Switch, Route, Link} from "react-router-dom";
import './App.css';
import { firestore } from "./firebase";
class App extends Component {
render(){
return (
<div className="App">
<Router>
<Switch>
<Route path="/chat/:person">
<Header backButton="/chat" />
<ChatScreen />
</Route>
<Route path="/chat">
<Header backButton="/" />
<Chats />
</Route>
<Route path="/">
<Header />
<TinderCards />
<SwipeButtons />
</Route>
</Switch>
</Router>
</div>
);
}
}
export default App;
Chats.js
import React from 'react'
import Chat from './Chat';
import './Chats.css';
function Chats() {
return (
<div className="chats">
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
<Chat
name="Mark"
message="Yo whats up!"
timestamp="40 seconds age"
profilePic="..."
/>
</div>
)
}
export default Chats
Chat.js
import React from 'react'
import Avatar from "@material-ui/core/Avatar"
import "./Chat.css";
import { Link } from 'react-router-dom';
function Chat({ name, message, profilePic, timestamp }) {
return (
<Link to ={`/chat/${name}`}>
<div className="chat">
<Avatar className="chat__image" alt={name} src={profilePic} />
<div className="chat__details">
<h3>{name}</h3>
<p>{message}</p>
</div>
<p className="chat__timestamp">{timestamp}</p>
</div>
</Link>
)
}
export default Chat
Chat.css
.chat {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
height: 70px;
border-bottom: 1px solid rgb(236, 236, 236);
}
a {
text-decoration: none;
color: inherit;
}
.chat__details{
flex: 1;
}
.chat__details > p {
color: gray;
}
.chat__timestamp {
color: lightgray;
}
.chat__image {
margin-right: 20px;
}
ChatScreen.js
import React, { useState } from 'react'
import Avatar from "@material-ui/core/Avatar"
import './ChatScreen.css';
function ChatScreen() {
const [input, setInput] = useState('');
const [messages, setMessages] = useState([
{
name: 'Elien',
image: '...',
message: 'Whats up'
},
{
name: 'Elien',
image: '...',
message: 'hows it going!'
},
{
message: 'hows it going!'
},
])
const handleSend = e => {
e.preventDefault();
setMessages([...messages, { message : input }]);
setInput('');
}
return (
<div className="chatScreen">
<p className="chatScreen__timestamp">YOU MATCHED WITH MARK ON 10/08/20</p>
{messages.map(message =>
message.name ? (
<div className="chatScreen__message">
<Avatar
className="chatScreen__image"
alt={message.name}
src={message.image}
/>
<p className="chatScreen__text"> {message.message} </p>
</div>
) : (
<div className="chatScreen__message">
<p className="chatScreen__textUser"> {message.message} </p>
</div>
)
)}
<div className="chatScreen__input">
<form>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
className="chatScreen__inputField"
placeholder="메세지를 입력하세요"
type="text"
/>
<button
className="chatScreen__inputButton"
onClick={handleSend}
>SEND</button>
</form>
</div>
</div>
)
}
export default ChatScreen
ChatScreen.css
.chatScreen__message {
display: flex;
align-items: center;
padding: 20px;
}
.chatScreen__text {
margin-left: 10px;
background-color: lightgray;
padding: 15px;
border-radius: 20px;
}
.chatScreen__timestamp {
text-align: center;
padding: 20px;
color: gray;
}
.chatScreen__textUser {
margin-left: auto;
background-color: #29b3cd;
padding: 15px;
border-radius: 20px;
color: white;
}
.chatScreen__input {
display: flex;
padding: 5px;
position: fixed;
bottom: 0;
width: 100%;
border-top: 1px solid lightgray;
}
.chatScreen__inputField {
flex: 1;
padding: 10px;
border: none;
}
.chatScreen__inputButton {
border: none;
margin-right: 20px;
background-color: white;
font-weight: bolder;
color: #fe3d71;
}
firebase.js
import firebase from "firebase/app";
import "firebase/firestore";
const firebaseConfig = {
apiKey: "-",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
// 내가 찾은 것
// firebaseConfig 정보로 firebase 시작
// firebase.initializeApp(firebaseConfig);
// firebase의 firestore 인스턴스를 변수에 저장
// const firestore = firebase.firestore();
// 틴더 클론 코딩에서
const firebaseApp = firebase.initializeApp(firebaseConfig);
const database = firebaseApp.firestore();
// 필요한 곳에서 사용할 수 있도록 내보내기
export default database;
Header.js
import React from 'react'
import "./Header.css";
import PersonIcon from '@material-ui/icons/Person';
import ForumIcon from '@material-ui/icons/Forum';
import IconButton from "@material-ui/core/IconButton";
import ArrowBackIosIcon from "@material-ui/icons/ArrowBackIos";
import { Link, useHistory } from "react-router-dom";
function Header({ backButton }) {
const history = useHistory();
return (
<div className="header">
{backButton ? (
<IconButton onClick={() => history.replace(backButton)}>
<ArrowBackIosIcon fontSize="large" className="header__icon" />
</IconButton>
): (
<IconButton>
<PersonIcon className="header__icon" fontSize="large" />
</IconButton>
)}
<Link to="/">
<img
className="header__logo"
src="https://1000logos.net/wp-content/uploads/2018/07/tinder-logo.png"
alt="tinder logo"
/>
</Link>
<Link to="/chat">
<IconButton>
<ForumIcon className="header__icon" fontSize="large" />
</IconButton>
</Link>
</div>
)
}
export default Header
Header.css
.header {
/* display to row */
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #f9f9f9;
}
.header__logo {
height: 40px;
/*내용이 종횡비를 유지하면서 요소에 정의된 너비와 높이안에서 가능한한 많이 확대(scale up)시킨다*/
object-fit: contain;
}
.header__icon {
margin: 20px;
}
SwipeButtons.js
import React from 'react'
import ReplayIcon from "@material-ui/icons/Replay";
import CloseIcon from "@material-ui/icons/Close";
import StarRateIcon from "@material-ui/icons/StarRate";
import FavoriteIcon from "@material-ui/icons/Favorite";
import FlashOnIcon from "@material-ui/icons/FlashOn";
import IconButton from "@material-ui/core/IconButton";
import './SwipeButtons.css';
const SwipeButtons = () => {
return (
<div className="swipeButtons">
<IconButton className="swipeButtons__repeat">
<ReplayIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__left">
<CloseIcon fontSize="large" />
</IconButton >
<IconButton className="swipeButtons__star">
<StarRateIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__right">
<FavoriteIcon fontSize="large" />
</IconButton>
<IconButton className="swipeButtons__lightning">
<FlashOnIcon fontSize="large" />
</IconButton>
</div>
)
}
export default SwipeButtons;
SwipeButtons.css
.swipeButtons {
position: fixed;
bottom: 2vh;
width: 100%;
display: flex;
justify-content: space-evenly;
}
.swipeButtons .MuiIconButton-root {
background-color: white;
box-shadow: 0px 10px 53px 0px rgba(0, 0, 0, 0.3) !important;
}
.swipeButtons__repeat {
padding: 3vw !important;
color: #ec5e6f !important;
}
.swipeButtons__left {
padding: 3vw !important;
color: #ec5e6f !important;
}
.swipeButtons__star {
padding: 3vw !important;
color: #62b4f9 !important;
}
.swipeButtons__right {
padding: 3vw !important;
color: #76e2b3 !important;
}
.swipeButtons__lightning {
padding: 3vw !important;
color: #915dd1 !important;
}
TinderCards.js
import React, { useState, useEffect } from 'react'
import database from './firebase';
import TinderCard from "react-tinder-card";
import './TinderCards.css';
function TinderCards() {
const [people, setPeople] = useState([]);
useEffect(() => {
database.collection('people').onSnapshot(snapshot => {
setPeople(snapshot.docs.map(doc => doc.data()))
})
}, []);
return (
<div>
<div className="tinderCards__cardContainer">
{people.map(person => {
return(
<TinderCard
className="swipe"
key={person.name}
preventSwipe={['up', 'down']}
>
<div
className="card"
style={{ backgroundImage:`url(${person.url})` }}
>
<h3>{person.name}</h3>
</div>
</TinderCard>
);
})}
</div>
</div>
);
}
export default TinderCards;
TinderCards.css
.card {
position: relative;
width: 600px;
padding: 10px;
max-width: 85vw;
height: 55vh;
border-radius: 20px;
background-size: cover;
background-position: center;
box-shadow: 0px 18px 53px 0px rgba(0, 0, 0, 0.3);
}
.card > h3 {
position: absolute;
bottom: 10px;
color: white;
}
.tinderCards__cardContainer {
display: flex;
justify-content: center;
margin-top: 5vh;
}
.swipe {
position: absolute;
}
'React > React Clone Coding' 카테고리의 다른 글
[Amazon Clone Coding]리액트로 만드는 아마존 웹사이트 - 1 (0) | 2020.09.13 |
---|---|
React + Firebase 애플리케이션 구축하기 (0) | 2020.08.22 |