├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── images │ ├── banner.jpg │ └── logos │ ├── tweeter-light.svg │ ├── tweeter-small.svg │ └── tweeter.svg ├── src ├── components │ ├── AuthForm │ │ └── AuthForm.jsx │ ├── Avatar │ │ └── Avatar.jsx │ ├── Banner │ │ └── Banner.jsx │ ├── CommentInput │ │ └── CommentInput.jsx │ ├── Comments │ │ └── Comments.jsx │ ├── ExploreFIlters │ │ └── ExploreFilters.jsx │ ├── Filters │ │ └── Filters.jsx │ ├── FollowButton │ │ └── FollowButton.jsx │ ├── Footer │ │ └── Footer.jsx │ ├── Modal │ │ └── Modal.jsx │ ├── NavBar │ │ └── NavBar.jsx │ ├── Post │ │ └── Post.jsx │ ├── ProfileDropDown │ │ └── ProfileDropDown.jsx │ ├── Suggestions │ │ └── Suggestions.jsx │ ├── Trends │ │ └── Trends.jsx │ ├── TweetInput │ │ └── TweetInput.jsx │ └── UserInfo │ │ └── UserInfo.jsx ├── context │ ├── BookmarksTweetsContext.js │ ├── ExploreTweetsContext.js │ ├── HomeTweetsContext.js │ └── UserContext.js ├── firebase │ └── init.js ├── hooks │ ├── useFollowers.js │ └── useFollowings.js ├── layouts │ └── index.jsx ├── pages │ ├── [username] │ │ ├── index.jsx │ │ └── status │ │ │ └── [tweetId].jsx │ ├── _app.jsx │ ├── _document.jsx │ ├── bookmarks.jsx │ ├── explore.jsx │ ├── home.jsx │ ├── index.jsx │ ├── login.jsx │ └── signup.jsx ├── services │ ├── Authentication.js │ ├── DeleteAccount.js │ ├── DeleteTweet.js │ ├── FetchData.js │ └── PostTweet.js └── styles │ ├── global.css │ └── reset.css └── tailwind.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | # don't lint nyc coverage output 6 | coverage -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShubhamVerma1811/Tweeter/9f5932da65cfc9ec475225e399c1fdca028f6eb5/.eslintrc.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # Created by https://www.toptal.com/developers/gitignore/api/react,firebase 37 | # Edit at https://www.toptal.com/developers/gitignore?templates=react,firebase 38 | 39 | ### Firebase ### 40 | .idea 41 | **/node_modules/* 42 | **/.firebaserc 43 | 44 | ### Firebase Patch ### 45 | .runtimeconfig.json 46 | .firebase/ 47 | 48 | ### react ### 49 | .DS_* 50 | *.log 51 | logs 52 | **/*.backup.* 53 | **/*.back.* 54 | 55 | /testData 56 | 57 | node_modules 58 | bower_components 59 | 60 | *.sublime* 61 | 62 | psd 63 | thumb 64 | sketch 65 | 66 | # End of https://www.toptal.com/developers/gitignore/api/react,firebase 67 | 68 | .env 69 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "jsxBracketSameLine": true 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tweeter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": { 6 | "name": "Shubham Verma" 7 | }, 8 | "scripts": { 9 | "dev": "next dev", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "eslint src/**/* -c .eslintrc.json --ext js,jsx", 13 | "lint-fix": "eslint src/**/* -c .eslintrc.json --fix --ext js,jsx", 14 | "format": "prettier --write src/**/*.{js,jsx,html,css,scss,json}" 15 | }, 16 | "dependencies": { 17 | "@material-ui/core": "^4.11.1", 18 | "@material-ui/icons": "^4.9.1", 19 | "firebase": "^8.1.1", 20 | "next": "10.0.2", 21 | "react": "17.0.1", 22 | "react-dom": "17.0.1", 23 | "shortid": "^2.2.16" 24 | }, 25 | "devDependencies": { 26 | "babel-eslint": "^10.1.0", 27 | "eslint": "^7.14.0", 28 | "eslint-config-airbnb": "^18.2.1", 29 | "eslint-config-prettier": "^6.15.0", 30 | "eslint-plugin-import": "^2.22.1", 31 | "eslint-plugin-jsx-a11y": "^6.4.1", 32 | "eslint-plugin-prettier": "^3.1.4", 33 | "eslint-plugin-react": "^7.21.5", 34 | "eslint-plugin-react-hooks": "^4.2.0", 35 | "prettier": "^2.2.0", 36 | "@fullhuman/postcss-purgecss": "^2.3.0", 37 | "autoprefixer": "^9.8.6", 38 | "tailwindcss": "^1.8.12" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | ...(process.env.NODE_ENV === "production" 6 | ? { 7 | "@fullhuman/postcss-purgecss": { 8 | content: ["./src/**/*.jsx"], 9 | defaultExtractor: (content) => 10 | content.match(/[\w-/:]+(? 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/logos/tweeter-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/logos/tweeter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/AuthForm/AuthForm.jsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from "@material-ui/core/CircularProgress"; 2 | import Link from "next/link"; 3 | import { useState } from "react"; 4 | import shortID from "shortid"; 5 | import { handleSignIn, handleSignUp } from "../../services/Authentication"; 6 | 7 | const AuthForm = ({ type }) => { 8 | const [email, setEmail] = useState(""); 9 | const [password, setPassword] = useState(""); 10 | const [username, setUsername] = useState(""); 11 | const [name, setName] = useState(""); 12 | const [authLoading, setAuthLoading] = useState(false); 13 | const [authErrMsg, setAuthErrMsg] = useState(null); 14 | 15 | const signIn = () => ( 16 |
17 |
{ 19 | e.preventDefault(); 20 | setAuthLoading(true); 21 | const { message } = await handleSignIn(email, password); 22 | setAuthErrMsg(message); 23 | setAuthLoading(false); 24 | }}> 25 |
26 | 44 |
45 |
46 |
47 | 65 |
66 | {authErrMsg && ( 67 |
68 |

69 | {authErrMsg} 70 |

71 |
72 | )} 73 |
74 |
75 | 93 |
94 | 95 | 96 | Create an account? 97 | 98 | 99 |
100 |
101 |
102 |
103 | ); 104 | 105 | const signUp = () => ( 106 |
107 |
108 |
109 |
110 | 128 |
129 |
130 |
131 |
132 | 150 |
151 |
152 |
153 |
154 | 172 |
173 |
174 | 175 |
176 |
177 | 195 |
196 |
197 |
198 |
199 | {authErrMsg && ( 200 |
201 |

202 | {authErrMsg} 203 |

204 |
205 | )} 206 |
207 |
208 | 238 | 245 |
246 |
247 |
248 |
249 | ); 250 | 251 | return ( 252 |
253 | {type === "signIn" && signIn()} 254 | {type === "signUp" && signUp()} 255 |
256 | ); 257 | }; 258 | 259 | export default AuthForm; 260 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | 3 | const Avatar = ({ src }) => { 4 | return ( 5 | user_avatar 6 | ); 7 | }; 8 | 9 | Avatar.propTypes = { 10 | src: PropTypes.string, 11 | }; 12 | 13 | Avatar.defaultProps = { src: "" }; 14 | export default Avatar; 15 | -------------------------------------------------------------------------------- /src/components/Banner/Banner.jsx: -------------------------------------------------------------------------------- 1 | const Banner = () => { 2 | return ( 3 |
8 | banner 13 |
14 | ); 15 | }; 16 | 17 | export default Banner; 18 | -------------------------------------------------------------------------------- /src/components/CommentInput/CommentInput.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import UserContext from "../../context/UserContext"; 3 | import postTweet from "../../services/PostTweet"; 4 | import Avatar from "../Avatar/Avatar"; 5 | 6 | const CommentInput = ({ tweetID }) => { 7 | const { user } = useContext(UserContext); 8 | const [comment, setComment] = useState(""); 9 | 10 | return ( 11 |
12 |
13 |
14 | 15 |
16 |
17 |
{ 20 | e.preventDefault(); 21 | postTweet(user.uid, comment, null, tweetID); 22 | setComment(""); 23 | }}> 24 | setComment(e.target.value)} 30 | /> 31 |
32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default CommentInput; 39 | -------------------------------------------------------------------------------- /src/components/Comments/Comments.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import firebase from "../../firebase/init"; 3 | import { fetchUser } from "../../services/FetchData"; 4 | import Avatar from "../Avatar/Avatar"; 5 | 6 | const Comments = ({ tweetID }) => { 7 | const [comments, setComments] = useState([]); 8 | 9 | useEffect(() => { 10 | const getComments = async () => { 11 | try { 12 | firebase 13 | .firestore() 14 | .collection("tweets") 15 | .where("parentTweet", "==", tweetID) 16 | .onSnapshot(async (tweetsRef) => { 17 | const localComments = []; 18 | 19 | for (let i = 0; i < tweetsRef.size; i++) { 20 | const tweet = tweetsRef.docs[i].data({ 21 | serverTimestamps: "estimate", 22 | }); 23 | const id = tweetsRef.docs[i].id; 24 | const userInfo = await fetchUser({ 25 | userID: tweet.authorId, 26 | }); 27 | localComments.push({ 28 | ...tweet, 29 | id, 30 | createdAt: tweet.createdAt.toDate().toString(), 31 | author: userInfo, 32 | }); 33 | } 34 | setComments(localComments); 35 | }); 36 | } catch (err) { 37 | console.log(err); 38 | } 39 | }; 40 | 41 | getComments(); 42 | }, []); 43 | 44 | return ( 45 |
46 | {comments && 47 | comments.length > 0 && 48 | comments.map((comment) => ( 49 |
50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |

58 | {comment.author.name} 59 |

60 |

61 | {comment.createdAt}{" "} 62 |

63 |
64 |
65 |

{comment.text}

66 |
67 |
68 | {/*
69 | 75 |

.

76 |

77 | 12k Likes 78 |

79 |
*/} 80 |
81 |
82 |
83 | ))} 84 |
85 | ); 86 | }; 87 | 88 | export default Comments; 89 | -------------------------------------------------------------------------------- /src/components/ExploreFIlters/ExploreFilters.jsx: -------------------------------------------------------------------------------- 1 | const ExploreFilters = () => { 2 | return ( 3 |
6 |
  • 7 | Top 8 |
  • 9 |
  • 10 | Latest 11 |
  • 12 |
  • 13 | People 14 |
  • 15 |
  • 16 | Media 17 |
  • 18 |
    19 | ); 20 | }; 21 | 22 | export default ExploreFilters; 23 | -------------------------------------------------------------------------------- /src/components/Filters/Filters.jsx: -------------------------------------------------------------------------------- 1 | const Filters = () => { 2 | return ( 3 |
    6 |
  • 7 | Tweets 8 |
  • 9 |
  • 10 | Tweets & Replies 11 |
  • 12 |
  • 13 | Media 14 |
  • 15 |
  • 16 | Likes 17 |
  • 18 |
    19 | ); 20 | }; 21 | 22 | export default Filters; 23 | -------------------------------------------------------------------------------- /src/components/FollowButton/FollowButton.jsx: -------------------------------------------------------------------------------- 1 | import PersonAddIcon from "@material-ui/icons/PersonAdd"; 2 | import React, { useContext, useEffect, useState } from "react"; 3 | import UserContext from "../../context/UserContext"; 4 | import firebase from "../../firebase/init"; 5 | 6 | const FollowButton = ({ userID }) => { 7 | const { user } = useContext(UserContext); 8 | const [isFollowing, setIsFollowing] = useState(false); 9 | const [connectionDocID, setFollowingDocID] = useState(""); 10 | 11 | const startFollowing = () => { 12 | if (!user) { 13 | alert("You need to sign in for that"); 14 | return; 15 | } 16 | const { id } = firebase.firestore().collection("connections").add({ 17 | followerID: user.uid, 18 | followeeID: userID, 19 | }); 20 | setFollowingDocID(id); 21 | setIsFollowing(true); 22 | }; 23 | 24 | const stopFollowing = () => { 25 | if (!user) { 26 | alert("You need to sign in for that"); 27 | return; 28 | } 29 | firebase 30 | .firestore() 31 | .collection("connections") 32 | .doc(connectionDocID) 33 | .delete(); 34 | setIsFollowing(false); 35 | }; 36 | 37 | useEffect(() => { 38 | if (user) { 39 | async function checkFollowing() { 40 | const result = await firebase 41 | .firestore() 42 | .collection("connections") 43 | .where("followeeID", "==", userID) 44 | .where("followerID", "==", user.uid) 45 | .get(); 46 | if (result.size === 1) { 47 | setIsFollowing(true); 48 | setFollowingDocID(result.docs[0].id); 49 | } 50 | } 51 | checkFollowing(); 52 | } 53 | }, [user]); 54 | 55 | return ( 56 |
    57 | 66 |
    67 | ); 68 | }; 69 | 70 | export default FollowButton; 71 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Footer = () => { 4 | return ( 5 |
    6 |

    7 | Made by{" "} 8 | 13 | Shubham Verma 14 | 15 |

    16 |

    Design was inspired by DevChallnges.io

    17 |
    18 | ); 19 | }; 20 | 21 | export default Footer; 22 | -------------------------------------------------------------------------------- /src/components/Modal/Modal.jsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from "@material-ui/core/CircularProgress"; 2 | import Link from "next/link"; 3 | import { useContext, useState } from "react"; 4 | import UserContext from "../../context/UserContext"; 5 | import Avatar from "../Avatar/Avatar"; 6 | import FollowButton from "../FollowButton/FollowButton"; 7 | 8 | const Modal = ({ users, close, loading }) => { 9 | const { user } = useContext(UserContext); 10 | const [isFollowing, setIsFollowing] = useState(false); 11 | const [followingDocID, setFollowingDocID] = useState(""); 12 | 13 | return ( 14 |
    15 |
    16 |

    17 | {user.name} is following 18 |

    19 |
    20 |

    21 | X 22 |

    23 |
    24 |
    25 | {loading && ( 26 |
    27 | 28 |
    29 | )} 30 |
    31 | {users.map((localUser) => { 32 | return ( 33 |
    34 |
    35 |
    36 | 37 |
    38 |
    39 | 40 |

    41 | {localUser.name} 42 |

    43 | 44 |

    120k Followers

    45 |
    46 |
    47 | 48 |
    49 |
    50 |
    51 |

    52 | {localUser.bio ? localUser.bio : "404 Bio Not Found"} 53 |

    54 |
    55 |
    56 | ); 57 | })} 58 |
    59 | ); 60 | }; 61 | 62 | export default Modal; 63 | -------------------------------------------------------------------------------- /src/components/NavBar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import BookmarkIcon from "@material-ui/icons/Bookmark"; 2 | import ExploreIcon from "@material-ui/icons/Explore"; 3 | import HomeIcon from "@material-ui/icons/Home"; 4 | import Link from "next/link"; 5 | import { useRouter } from "next/router"; 6 | import React, { useContext } from "react"; 7 | import UserContext from "../../context/UserContext"; 8 | import ProfileDropDown from "../ProfileDropDown/ProfileDropDown"; 9 | 10 | const NavBar = () => { 11 | const { user } = useContext(UserContext); 12 | const router = useRouter(); 13 | 14 | return ( 15 |
    16 | 103 |
    104 | ); 105 | }; 106 | 107 | export default NavBar; 108 | -------------------------------------------------------------------------------- /src/components/Post/Post.jsx: -------------------------------------------------------------------------------- 1 | import BookmarkIcon from "@material-ui/icons/Bookmark"; 2 | import BookmarkBorderIcon from "@material-ui/icons/BookmarkBorder"; 3 | import ChatBubbleOutlineIcon from "@material-ui/icons/ChatBubbleOutline"; 4 | import DeleteIcon from "@material-ui/icons/Delete"; 5 | import FavoriteIcon from "@material-ui/icons/Favorite"; 6 | import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder"; 7 | import Link from "next/link"; 8 | import { useContext, useEffect, useState } from "react"; 9 | import UserContext from "../../context/UserContext"; 10 | import firebase from "../../firebase/init"; 11 | import { deleteTweet } from "../../services/DeleteTweet"; 12 | import { fetchTweetLikes, fetchTweetSaves } from "../../services/FetchData"; 13 | import Avatar from "../Avatar/Avatar"; 14 | 15 | const Post = ({ tweet }) => { 16 | const { user } = useContext(UserContext); 17 | const [localTweet, setLocalTweet] = useState(tweet); 18 | 19 | const [likes, setLikes] = useState(0); 20 | const [isLiked, setIsLiked] = useState(false); 21 | const [likeDocID, setLikeDocID] = useState(""); 22 | 23 | const [saves, setSaves] = useState(0); 24 | const [isSaved, setIsSaved] = useState(false); 25 | const [saveDocID, setSaveDocID] = useState(""); 26 | 27 | const [comments, setComments] = useState(0); 28 | 29 | const [myTweet, setMyTweet] = useState(false); 30 | 31 | const likeTweet = async () => { 32 | if (!user) { 33 | alert("You need to sign in for that"); 34 | return; 35 | } 36 | const { id } = await firebase.firestore().collection("likes").add({ 37 | userID: user.uid, 38 | tweetID: tweet.id, 39 | }); 40 | setLikes((prev) => prev + 1); 41 | setLikeDocID(id); 42 | setIsLiked(true); 43 | }; 44 | 45 | const dislikeTweet = () => { 46 | if (!user) { 47 | alert("You need to sign in for that"); 48 | return; 49 | } 50 | firebase.firestore().collection("likes").doc(likeDocID).delete(); 51 | setLikes((prev) => prev - 1); 52 | setIsLiked(false); 53 | }; 54 | 55 | const saveTweets = () => { 56 | if (!user) { 57 | alert("You need to sign in for that"); 58 | return; 59 | } 60 | const { id } = firebase.firestore().collection("saves").add({ 61 | tweetID: tweet.id, 62 | userID: user.uid, 63 | }); 64 | setSaves((prev) => prev + 1); 65 | setSaveDocID(id); 66 | setIsSaved(true); 67 | }; 68 | 69 | const unsaveTweets = () => { 70 | if (!user) { 71 | alert("You need to sign in for that"); 72 | return; 73 | } 74 | firebase.firestore().collection("saves").doc(saveDocID).delete(); 75 | setSaves((prev) => prev - 1); 76 | setIsSaved(false); 77 | }; 78 | 79 | useEffect(async () => { 80 | setLikes((await fetchTweetLikes(localTweet.id)).size); 81 | if (user) { 82 | async function checkForLikes() { 83 | const docs = await firebase 84 | .firestore() 85 | .collection("likes") 86 | .where("userID", "==", user.uid) 87 | .where("tweetID", "==", tweet.id) 88 | .get(); 89 | if (docs.size === 1) { 90 | setIsLiked(true); 91 | setLikeDocID(docs.docs[0].id); 92 | } 93 | } 94 | checkForLikes(); 95 | 96 | async function checkForSaves() { 97 | const docs = await firebase 98 | .firestore() 99 | .collection("saves") 100 | .where("userID", "==", user.uid) 101 | .where("tweetID", "==", tweet.id) 102 | .get(); 103 | if (docs.size === 1) { 104 | setIsSaved(true); 105 | setSaveDocID(docs.docs[0].id); 106 | } 107 | } 108 | checkForSaves(); 109 | 110 | async function getCommentsCount() { 111 | const res = await firebase 112 | .firestore() 113 | .collection("tweets") 114 | .where("parentTweet", "==", tweet.id) 115 | .get(); 116 | setComments(res.size); 117 | } 118 | getCommentsCount(); 119 | if (user.uid === tweet.author.uid) { 120 | setMyTweet(true); 121 | } 122 | } 123 | setSaves((await fetchTweetSaves(localTweet.id)).size); 124 | }, []); 125 | 126 | return ( 127 |
    128 |
    129 |
    130 | 131 |
    132 |
    133 | 134 |

    135 | {localTweet.author.name} 136 |

    137 | 138 |

    139 | @{localTweet.author.username} 140 |

    141 |

    142 | {localTweet.createdAt} 143 |

    144 |
    145 | {myTweet && ( 146 |
    { 149 | e.stopPropagation(); 150 | const answer = confirm( 151 | "Are you sure you want to delete this tweet?" 152 | ); 153 | if (answer) { 154 | deleteTweet(tweet.id); 155 | } 156 | }}> 157 | 158 |
    159 | )} 160 |
    161 | 162 |
    163 | {localTweet.text} 164 |
    165 | {tweet.imgLink && ( 166 | 183 | )} 184 |
    185 |

    186 | {comments} Comments 187 |

    188 |

    189 | {likes} Likes 190 |

    191 |

    192 | {saves} Saved 193 |

    194 |
    195 |
    196 |
    197 |
    198 | 206 | {isLiked ? ( 207 | 219 | ) : ( 220 | 232 | )} 233 | {isSaved ? ( 234 | 246 | ) : ( 247 | 259 | )} 260 |
    261 |
    262 | ); 263 | }; 264 | 265 | export default Post; 266 | -------------------------------------------------------------------------------- /src/components/ProfileDropDown/ProfileDropDown.jsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from "@material-ui/core/CircularProgress"; 2 | import AccountCircleIcon from "@material-ui/icons/AccountCircle"; 3 | import DeleteIcon from "@material-ui/icons/Delete"; 4 | import ExitToAppIcon from "@material-ui/icons/ExitToApp"; 5 | import Link from "next/link"; 6 | import React, { useState } from "react"; 7 | import { handleSignOut } from "../../services/Authentication"; 8 | import { deleteAccount } from "../../services/DeleteAccount"; 9 | import Avatar from "../Avatar/Avatar"; 10 | 11 | const ProfileDropDown = ({ user }) => { 12 | const [dropdown, setDropdown] = useState(false); 13 | const [deletingAccount, setDeletingAccount] = useState(false); 14 | return ( 15 |
    16 |
    17 |
    18 |
    19 | 47 |
    48 | 49 | {dropdown && user && ( 50 |
    51 |
    56 | 57 | 60 | 61 | 62 | 63 | My Profile 64 | 65 | 66 |
    67 | 103 |
    104 | 114 |
    115 |
    116 | )} 117 |
    118 |
    119 |
    120 | ); 121 | }; 122 | 123 | export default ProfileDropDown; 124 | -------------------------------------------------------------------------------- /src/components/Suggestions/Suggestions.jsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from "@material-ui/core/CircularProgress"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import UserContext from "../../context/UserContext"; 4 | import { fetchUser, fetchUserFollowers } from "../../services/FetchData"; 5 | import Avatar from "../Avatar/Avatar"; 6 | import FollowButton from "../FollowButton/FollowButton"; 7 | 8 | const Suggestions = ({ type, userID }) => { 9 | const [user, setUser] = useState(null); 10 | const [loading, setLoading] = useState(true); 11 | const { user: authUser } = useContext(UserContext); 12 | 13 | useEffect(async () => { 14 | if (authUser) { 15 | const localUser = await fetchUser({ 16 | userID, 17 | }); 18 | const followersCount = await fetchUserFollowers(localUser.uid); 19 | setUser({ ...localUser, followersCount: followersCount.size }); 20 | setLoading(false); 21 | } 22 | }, [authUser]); 23 | 24 | return ( 25 |
    26 | {type === "relavant" ? ( 27 |

    28 | Relavant People 29 |

    30 | ) : ( 31 |

    32 | Who to Follow 33 |

    34 | )} 35 |
    36 | {loading && ( 37 |
    38 | 39 |
    40 | )} 41 | {user && ( 42 |
    43 |
    44 |
    45 | 46 |
    47 |
    48 |

    {user.name}

    49 |

    50 | {user.followersCount} Followers 51 |

    52 |
    53 |
    54 | 55 |
    56 |
    57 |
    58 |

    59 | {user.bio} 60 |

    61 |
    62 |
    68 | banner 73 |
    74 |
    75 | )} 76 |
    77 | ); 78 | }; 79 | 80 | export default Suggestions; 81 | -------------------------------------------------------------------------------- /src/components/Trends/Trends.jsx: -------------------------------------------------------------------------------- 1 | const Trends = () => { 2 | return ( 3 |
    6 |

    Trends

    7 |
    8 |
    9 |

    #programming

    10 |

    213k Tweets

    11 |
    12 |
    13 |

    #dev

    14 |

    3k Tweets

    15 |
    16 |
    17 |

    #monday

    18 |

    21.3k Tweets

    19 |
    20 |
    21 |

    #frontend

    22 |

    33k Tweets

    23 |
    24 |
    25 |

    #oneplus

    26 |

    213k Tweets

    27 |
    28 |
    29 |

    #programming

    30 |

    25k Tweets

    31 |
    32 |
    33 |

    #memes

    34 |

    213k Tweets

    35 |
    36 |
    37 | ); 38 | }; 39 | 40 | export default Trends; 41 | -------------------------------------------------------------------------------- /src/components/TweetInput/TweetInput.jsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from "@material-ui/core/CircularProgress"; 2 | import PhotoIcon from "@material-ui/icons/Photo"; 3 | import React, { useContext, useState } from "react"; 4 | import UserContext from "../../context/UserContext"; 5 | import firebase from "../../firebase/init"; 6 | import postTweet from "../../services/PostTweet"; 7 | import Avatar from "../Avatar/Avatar"; 8 | 9 | const TweetInput = () => { 10 | const { user } = useContext(UserContext); 11 | const [tweet, setTweet] = useState(""); 12 | const [imgLink, setImgLink] = useState(null); 13 | const [file, setFile] = useState(null); 14 | const [tweeting, setTweeting] = useState(false); 15 | 16 | const fileInputRef = React.createRef(); 17 | 18 | const uploadFile = async () => { 19 | const storageRef = firebase.storage().ref("tweets/" + file.name); 20 | const task = await storageRef.put(file); 21 | const link = await storageRef.getDownloadURL("tweets/" + file.name); 22 | return link; 23 | }; 24 | 25 | return ( 26 |
    27 |
    28 |
    29 |
    30 | {user && } 31 |
    32 | 33 |
    34 |
    35 |
    { 37 | e.preventDefault(); 38 | async function postTweetandUploadFile() { 39 | setTweeting(true); 40 | let imgLink = null; 41 | if (file) { 42 | imgLink = await uploadFile(); 43 | } 44 | await postTweet(user.uid, tweet.trim(), imgLink); 45 | setTweeting(false); 46 | setFile(null); 47 | setTweet(""); 48 | setImgLink(null); 49 | } 50 | postTweetandUploadFile(); 51 | }}> 52 | 60 |
    61 |
    62 | setFile(e.target.files[0])} 66 | ref={fileInputRef} 67 | /> 68 | 69 | fileInputRef.current.click()} 71 | style={{ color: "#3182ce" }} 72 | /> 73 | 74 | {file && file.name} 75 |
    76 |
    77 | 97 |
    98 |
    99 |
    100 |
    101 |
    102 |
    103 |
    104 |
    105 | ); 106 | }; 107 | 108 | export default TweetInput; 109 | -------------------------------------------------------------------------------- /src/components/UserInfo/UserInfo.jsx: -------------------------------------------------------------------------------- 1 | import PersonAddIcon from "@material-ui/icons/PersonAdd"; 2 | import { useContext, useState } from "react"; 3 | import UserContext from "../../context/UserContext"; 4 | import { useFollowers } from "../../hooks/useFollowers"; 5 | import { useFollowings } from "../../hooks/useFollowings"; 6 | import Avatar from "../Avatar/Avatar"; 7 | import FollowButton from "../FollowButton/FollowButton"; 8 | import Modal from "../Modal/Modal"; 9 | 10 | const UserInfo = ({ fetchedUser }) => { 11 | const { user } = useContext(UserContext); 12 | const [isModalOpen, setIsModalOpen] = useState(false); 13 | const [isModalLoading, setIsModalLoading] = useState(true); 14 | 15 | const { followers, getFollowers } = useFollowers(fetchedUser.uid); 16 | const { followings, getFollowings } = useFollowings(fetchedUser.uid); 17 | 18 | return ( 19 |
    20 |
    21 |
    24 | 25 |
    26 |
    27 | 28 |
    29 |
    30 | 31 |

    32 | {fetchedUser.name} 33 |

    34 |

    { 37 | setIsModalOpen(true); 38 | await getFollowers(); 39 | setIsModalLoading(false); 40 | }}> 41 | {fetchedUser.followersCount - 1} 42 | Followers 43 |

    44 |

    getFollowings()}> 47 | {fetchedUser.followingsCount - 1} 48 | Following 49 |

    50 |
    51 | {user && fetchedUser.username === user.username ? ( 52 | 60 | ) : ( 61 |
    62 | 63 |
    64 | )} 65 |
    66 |
    67 | {fetchedUser.bio ? ( 68 |

    69 | {fetchedUser.bio} 70 |

    71 | ) : ( 72 |

    73 | 404 Bio Not Found 74 |

    75 | )} 76 |
    77 |
    78 | {isModalOpen && ( 79 |
    86 | setIsModalOpen(false)} 89 | loading={isModalLoading} 90 | /> 91 |
    92 | )} 93 |
    94 | ); 95 | }; 96 | 97 | export default UserInfo; 98 | -------------------------------------------------------------------------------- /src/context/BookmarksTweetsContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const BookmarksTweetsContext = createContext([]); 4 | 5 | export default BookmarksTweetsContext; 6 | -------------------------------------------------------------------------------- /src/context/ExploreTweetsContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const ExploreTweetsContext = createContext([]); 4 | 5 | export default ExploreTweetsContext; 6 | -------------------------------------------------------------------------------- /src/context/HomeTweetsContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const HomeTweetsContext = createContext([]); 4 | 5 | export default HomeTweetsContext; 6 | -------------------------------------------------------------------------------- /src/context/UserContext.js: -------------------------------------------------------------------------------- 1 | const { createContext } = require("react"); 2 | 3 | const UserContext = createContext(); 4 | 5 | export default UserContext; 6 | -------------------------------------------------------------------------------- /src/firebase/init.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase"; 2 | 3 | const firebaseConfig = { 4 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_API_KEY, 5 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_AUTH_DOMAIN, 6 | databaseURL: process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_DATABASE_URL, 7 | projectId: process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_PROJECT_ID, 8 | storageBucket: process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_STORAGE_BUCKET, 9 | messagingSenderId: 10 | process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_MESSAGE_SENDER_ID, 11 | appId: process.env.NEXT_PUBLIC_FIREBASE_FIRESTORE_APP_ID, 12 | }; 13 | 14 | !firebase.apps.length && firebase.initializeApp(firebaseConfig); 15 | export default firebase; 16 | -------------------------------------------------------------------------------- /src/hooks/useFollowers.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import UserContext from "../context/UserContext"; 3 | import { fetchUser, fetchUserFollowers } from "../services/FetchData"; 4 | 5 | export function useFollowers(userId, authUserID) { 6 | const { user } = useContext(UserContext); 7 | const [followers, setFollowers] = React.useState([]); 8 | const [isFollowersLoading, setIsFollowersLoading] = React.useState(true); 9 | 10 | const getFollowers = async () => { 11 | const data = []; 12 | setIsFollowersLoading(true); 13 | if (user) { 14 | const followersSnapShot = await fetchUserFollowers(userId, authUserID); 15 | for (let i = 0; i < followersSnapShot.size; i++) { 16 | data.push( 17 | await fetchUser({ 18 | userID: followersSnapShot.docs[i].data().followerID, 19 | }) 20 | ); 21 | } 22 | } 23 | setFollowers(data); 24 | setIsFollowersLoading(false); 25 | }; 26 | 27 | return { followers, isFollowersLoading, getFollowers }; 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useFollowings.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { fetchUser, fetchUserFollowings } from "../services/FetchData"; 3 | 4 | export function useFollowings(userId) { 5 | const [followings, setFollowings] = React.useState([]); 6 | const [isFollowingsLoading, setIsFollowingsLoading] = React.useState(true); 7 | 8 | const getFollowings = async () => { 9 | const data = []; 10 | setIsFollowingsLoading(true); 11 | const followersSnapShot = await fetchUserFollowings(userId); 12 | for (let i = 0; i < followersSnapShot.size; i++) { 13 | data.push( 14 | await fetchUser({ 15 | userID: followersSnapShot.docs[i].data().followeeID, 16 | }) 17 | ); 18 | } 19 | setFollowings(data); 20 | setIsFollowingsLoading(false); 21 | }; 22 | 23 | return { followings, isFollowingsLoading, getFollowings }; 24 | } 25 | -------------------------------------------------------------------------------- /src/layouts/index.jsx: -------------------------------------------------------------------------------- 1 | import Footer from "../components/Footer/Footer"; 2 | import NavBar from "../components/NavBar/NavBar"; 3 | 4 | const Layout = (props) => { 5 | return ( 6 | <> 7 | 8 | {props.children} 9 |