Twitter-Clone (12~13)
➰Create Profile & Image Upload
1. 📝Create 📁web ➡ 📁src ➡📁component➡📃CreateProfile.tsx
import { gql, useMutation } from '@apollo/client';
import React, { useState } from 'react';
import { ME_QUERY } from '../pages/Profile';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import Modal from 'react-modal';
import CustomStyles from '../styles/CustomModalStyles';
const CREATE_PROFILE_MUTATION = gql`
mutation createProfile (
$bio: String
$location: String
$website: String
$avatar: String
){
createProfile(
bio : $bio
location : $location
website : $website
avatar : $avatar
){
id
}
}
`
interface ProfileValue {
bio: string
location: string
website: string
avatar: string
}
const CreateProfile = () => {
const [createProfile] = useMutation(CREATE_PROFILE_MUTATION, {
🚗 refetchQueries: [{query: ME_QUERY}]
}) // second argument is refetch queries
const [modalIsOpen, setIsOpen] = useState(false);
const initialValues: ProfileValue = {
bio: "",
location: "",
website: "",
avatar: ""
}
const openModal = () => {
setIsOpen(true)
}
const closeModal = () => {
setIsOpen(false)
}
return (
<div>
<button onClick={openModal} >Create Profile</button>
🚓 <Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Modal"
style={CustomStyles}
>
<Formik
initialValues={initialValues}
// validationSchema={validationSchema}
onSubmit={async (value, { setSubmitting }) => {
setSubmitting(true)
await createProfile({
variables: value
})
setSubmitting(false)
setIsOpen(false)
}}
>
<Form>
<Field name="bio" type="text" as="textarea" placeholder="Bio" />
<ErrorMessage name="bio" component={'div'} />
<Field name="location" type="location" placeholder="Location" />
<ErrorMessage name="location" component={'div'} />
<Field name="website" type="website" placeholder="Website" />
<ErrorMessage name="website" component={'div'} />
<button type="submit" className="login-button" >
<span>Create Profile</span>
</button>
</Form>
</Formik>
</Modal>
</div>
);
}
export default CreateProfile
📋Memo(CreateProfile.tsx)
import { gql, useMutation } from '@apollo/client';
import React, { useState } from 'react';
import { ME_QUERY } from '../pages/Profile';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import Modal from 'react-modal';
import CustomStyles from '../styles/CustomModalStyles';
const CREATE_PROFILE_MUTATION = gql`
mutation createProfile (
$bio: String
$location: String
$website: String
$avatar: String
){
createProfile(
bio : $bio
location : $location
website : $website
avatar : $avatar
){
id
}
}
`
interface ProfileValue {
bio: string
location: string
website: string
avatar: string
}
const CreateProfile = () => {
const [createProfile] = useMutation(CREATE_PROFILE_MUTATION, {
🚗 refetchQueries: [{query: ME_QUERY}]
}) // second argument is refetch queries
const [modalIsOpen, setIsOpen] = useState(false);
const initialValues: ProfileValue = {
bio: "",
location: "",
website: "",
avatar: ""
}
const openModal = () => {
setIsOpen(true)
}
const closeModal = () => {
setIsOpen(false)
}
return (
<div>
<button onClick={openModal} >Create Profile</button>
🚓 <Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Modal"
style={CustomStyles}
>
<Formik
initialValues={initialValues}
// validationSchema={validationSchema}
onSubmit={async (value, { setSubmitting }) => {
setSubmitting(true)
await createProfile({
variables: value
})
setSubmitting(false)
setIsOpen(false)
}}
>
<Form>
<Field name="bio" type="text" as="textarea" placeholder="Bio" />
<ErrorMessage name="bio" component={'div'} />
<Field name="location" type="location" placeholder="Location" />
<ErrorMessage name="location" component={'div'} />
<Field name="website" type="website" placeholder="Website" />
<ErrorMessage name="website" component={'div'} />
<button type="submit" className="login-button" >
<span>Create Profile</span>
</button>
</Form>
</Formik>
</Modal>
</div>
);
}
export default CreateProfile
🚗 refetchQueries: [{query: ME_QUERY}
1. Refetch queries
🚓 <Modal isOpen={modalIsOpen} ... style={CustomStyles} >
1. Open window
called Modal
2. 📝Create 📁web ➡ 📁src ➡📁component➡📃UpdateProfile.tsx
import { gql, useMutation, useQuery } from '@apollo/client';
import React, { MutableRefObject, useRef, useState } from 'react';
import { ME_QUERY } from '../pages/Profile';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import Modal from 'react-modal';
import CustomStyles from '../styles/CustomModalStyles';
import '../styles/UpdateProfile.css'
const UPDATE_PROFILE_MUTATION = gql`
mutation updateProfile (
$id: Int!
$bio: String
$location: String
$website: String
$avatar: String
){
updateProfile(
id : $id
bio : $bio
location : $location
website : $website
avatar : $avatar
){
id
}
}
`
interface ProfileValue {
id : number
bio: string
location: string
website: string
avatar: string
}
const UpdateProfile = () => {
🚗 const inputFile = useRef() as MutableRefObject<HTMLInputElement>
const [image, setImage] = useState("")
const [imageLoading, setImageLoading] = useState(false)
const { loading, error, data } = useQuery(ME_QUERY)
const [updateProfile] = useMutation(UPDATE_PROFILE_MUTATION, {
refetchQueries: [{query: ME_QUERY}]
}) // second argument is refetch queries
const [modalIsOpen, setIsOpen] = useState(false);
if(loading) return <p>Loading...</p>
if (error) return <p>{error.message}</p>
const initialValues: ProfileValue = {
id: data.me.Profile.id,
bio: data.me.Profile.bio,
location: data.me.Profile.location,
website: data.me.Profile.website,
avatar:data.me.Profile.avatar
}
const openModal = () => {
setIsOpen(true)
}
const closeModal = () => {
setIsOpen(false)
}
const uploadImage = async (e: any) => {
🚓 const files = e.target.files
const data = new FormData()
data.append('file', files[0])
data.append("upload_preset", "Coaspe")
setImageLoading(true)
const res = await fetch("API_ADDRESS", {
method: "POST",
body: data
})
const file = await res.json()
setImage(file.secure_url)
setImageLoading(false)
}
return (
<div>
<button onClick={openModal} className="edit-button">Edit Profile</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Modal"
style={CustomStyles}
ariaHideApp={false}
>
<input
type="file"
name="file"
placeholder="upload file"
onChange={uploadImage}
ref={inputFile}
style={{display:"none"}}
/>
{/* velog 정리 */}
{imageLoading ? (
<h3>Loading...</h3>
) : (
<>
{data.me.Profile.avatar ? (
// span 클릭하면 위에 input을 클릭한 것과 같다
🚕 <span onClick={() => inputFile.current.click()}>
<img className="profile_img"
src={data.me.Profile.avatar}
style={{width:"150px", borderRadius: "30%"}}
alt="avatar"
/>
</span>
) : (
<span onClick={() => inputFile.current.click()}>
<i className="fa fa-user fa-5x" aria-hidden="true"></i>
</span>
)
}
</>
)}
<Formik
initialValues={initialValues}
// validationSchema={validationSchema}
onSubmit={async (value, { setSubmitting }) => {
setSubmitting(true)
await updateProfile({
variables: {...value, avatar: image}
})
setSubmitting(false)
setIsOpen(false)
}}
>
<Form>
<Field name="bio" type="text" as="textarea" placeholder="Bio" />
<ErrorMessage name="bio" component={'div'} />
<Field name="location" type="location" placeholder="Location" />
<ErrorMessage name="location" component={'div'} />
<Field name="website" type="website" placeholder="Website" />
<ErrorMessage name="website" component={'div'} />
<button type="submit" className="login-button" >
<span>Update Profile</span>
</button>
</Form>
</Formik>
</Modal>
</div>
);
}
export default UpdateProfile
📋Memo(UpdateProfile.tsx)
🚗 const inputFile = useRef() as MutableRefObject<HTMLInputElement>
💥 First i did const inputFile = useRef(null)
but it has type issue
🚓 const files = e.target.files
🚕 <span onClick={() => inputFile.current.click()}>
1. Use useRef()
3. 📝Create 📁web ➡ 📁src ➡📁pages➡📃Profile.tsx
import { gql, useQuery } from '@apollo/client';
import { Link, useHistory } from 'react-router-dom';
import CreateProfile from '../components/CreateProfile';
import UpdateProfile from '../components/UpdateProfile';
import "../styles/profile.css"
import "../styles/primary.css"
import '../styles/UpdateProfile.css'
import LeftNav from '../components/LeftNav';
export const ME_QUERY = gql`
query me {
me {
id
name
Profile {
id
bio
location
website
avatar
}
}
}
`
const Profile = () => {
// npm install react-modal
// npm install -D @types/react-modal
const history = useHistory();
const { loading, error, data } = useQuery(ME_QUERY);
if (loading) {
return <p>Loading...</p>
}
if (error) {
return <p>{error.message}</p>
}
return (
<>
<div className="primary">
<div className="left"><LeftNav /></div>
<div className="profile">
<div className="profile-info">
<div className="profile-head">
<span className="back-arrow" onClick={() => history.goBack()}>
<i className="fa fa-arrow-left" aria-hidden="true"></i>
</span>
<span className="nickname">
<h3>{data.me.name}</h3>
</span>
</div>
<div className="avatar">
{data.me.Profile.avatar ? (
<img className="profile_img"
src={data.me.Profile.avatar}
style={{width:"150px", borderRadius: "30%"}}
alt="avatar"
/>
) : (<i className="fa fa-user fa-5x" aria-hidden="true"></i>) }
</div>
<div className="make-profile">
{data.me.Profile ? <UpdateProfile /> : <CreateProfile />}
</div>
<h3 className="name">{data.me.name}</h3>
{data.me.Profile ?
<p>{data.me.Profile.bio}</p>
: null}
{data.me.Profile ?
<p>{data.me.Profile.location}</p>
: null}
{data.me.Profile ? (
<p>
<i className="fas fa-link"> </i>{" "}
<Link
to={{ pathname: `http://${data.me.Profile.website}` }}
target="_blank"
>
{data.me.Profile.website}
</Link>
</p>
) : null}
<div className="followers">
<p>200 following</p>
<p>384 followers</p>
</div>
</div>
</div>
<div className="right">
Right
</div>
</div>
</>
);
}
export default Profile;
4. 📝Create 📁web ➡ 📁src ➡📁components➡📃LeftNav.tsx
import { Link } from 'react-router-dom'
import favicon from "../styles/assets/Twitter-Logo.png"
import "../styles/leftNav.css"
const LeftNav = () => {
return (
<div>
<Link to='/users'>
<img src={favicon} alt="logo" style={{width:'40px'}} />
</Link>
<Link to='/users'>
<h2>
<i className="fa fa-home" aria-hidden="true"></i>{" "}
<span className="title">Home</span>
</h2>
</Link>
<Link to='/profile'>
<h2>
<i className="fa fa-user" aria-hidden="true"></i>{" "}
<span className="title">Profile</span>
</h2>
</Link>
<Link to='/users'>
<h2>
<i className="fa fa-envelope" aria-hidden="true"></i>{" "}
<span className="title">Messages</span>
</h2>
</Link>
<Link to='/users'>
<h2>
<i className="fa fa-bell" aria-hidden="true"></i>{" "}
<span className="title">Notification</span>
</h2>
</Link>
<Link to='/users'>
<h2>
<i className="fa fa-ellipsis-h" aria-hidden="true"></i>{" "}
<span className="title">More</span>
</h2>
</Link>
<button style={{ marginRight: "10px", marginTop: "30px" }}>
<span style={{padding : "15px 70px 15px 70px"}}>Tweet</span>
</button>
</div>
);
}
export default LeftNav;
Author And Source
이 문제에 관하여(Twitter-Clone (12~13)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@aspalt85/Twitter-Clone-705cqbsy저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)