├── public ├── robots.txt ├── Aa.png ├── jobs.png ├── watch.png ├── fakebook.jpg ├── favicon.ico ├── friends.png ├── groups.png ├── icons-1.png ├── icons-2.png ├── icons-3.png ├── icons-4.png ├── icons-5.png ├── facebook-logo.png ├── marketplace.png ├── down.svg ├── plus.svg ├── grids.svg ├── messenger.svg ├── notification.svg ├── public.svg ├── like.svg ├── three-dots.svg └── settings.svg ├── .eslintrc.json ├── screenshots ├── preview.jpg ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png └── screenshot-4.png ├── postcss.config.js ├── next.config.js ├── atoms └── modalAtom.js ├── tailwind.config.js ├── pages ├── _app.js ├── api │ └── auth │ │ └── [...nextauth].js ├── index.js ├── login.js └── privacy-policy.js ├── hooks └── use-window-size.js ├── components ├── left-sidebar │ ├── Shortcut.js │ ├── SidebarRow.js │ └── LeftSidebar.js ├── feed │ ├── Feed.js │ ├── Friend.js │ ├── AddCommentIcon.js │ ├── Posts.js │ ├── Comment.js │ ├── CreateRoom.js │ ├── StoryCard.js │ ├── Stories.js │ ├── AddComment.js │ ├── InputBox.js │ └── Post.js ├── right-sidebar │ ├── Contact.js │ ├── Sponsor.js │ └── RightSidebar.js ├── header │ ├── HeaderIcon.js │ ├── Header.js │ └── Dropdown.js └── modal │ ├── PostIcon.js │ ├── DisclaimerModal.js │ └── InputModal.js ├── .gitignore ├── .env.local.example ├── firebase.js ├── styles └── globals.css ├── package.json └── README.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/Aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/Aa.png -------------------------------------------------------------------------------- /public/jobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/jobs.png -------------------------------------------------------------------------------- /public/watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/watch.png -------------------------------------------------------------------------------- /public/fakebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/fakebook.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/favicon.ico -------------------------------------------------------------------------------- /public/friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/friends.png -------------------------------------------------------------------------------- /public/groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/groups.png -------------------------------------------------------------------------------- /public/icons-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/icons-1.png -------------------------------------------------------------------------------- /public/icons-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/icons-2.png -------------------------------------------------------------------------------- /public/icons-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/icons-3.png -------------------------------------------------------------------------------- /public/icons-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/icons-4.png -------------------------------------------------------------------------------- /public/icons-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/icons-5.png -------------------------------------------------------------------------------- /public/facebook-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/facebook-logo.png -------------------------------------------------------------------------------- /public/marketplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/public/marketplace.png -------------------------------------------------------------------------------- /screenshots/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/screenshots/preview.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/screenshots/screenshot-1.png -------------------------------------------------------------------------------- /screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/screenshots/screenshot-2.png -------------------------------------------------------------------------------- /screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/screenshots/screenshot-3.png -------------------------------------------------------------------------------- /screenshots/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/facebook-clone/main/screenshots/screenshot-4.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | images: { 3 | domains: [ 4 | 'links.salimi.my', 5 | 'platform-lookaside.fbsbx.com', 6 | 'firebasestorage.googleapis.com' 7 | ] 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /atoms/modalAtom.js: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const inputModalState = atom({ 4 | key: 'inputModalState', 5 | default: false 6 | }); 7 | 8 | export const photoModalState = atom({ 9 | key: 'photoModalState', 10 | default: false 11 | }); 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx}', 5 | './components/**/*.{js,ts,jsx,tsx}' 6 | ], 7 | theme: { 8 | extend: {} 9 | }, 10 | plugins: [require('tailwind-scrollbar-hide')] 11 | }; 12 | -------------------------------------------------------------------------------- /public/down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/grids.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import { SessionProvider } from 'next-auth/react'; 3 | import { RecoilRoot } from 'recoil'; 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | export default MyApp; 16 | -------------------------------------------------------------------------------- /hooks/use-window-size.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | function useWindowSize() { 4 | const [windowSize, setWindowSize] = useState(undefined); 5 | useEffect(() => { 6 | function handleResize() { 7 | setWindowSize(window.innerWidth); 8 | } 9 | window.addEventListener('resize', handleResize); 10 | handleResize(); 11 | return () => window.removeEventListener('resize', handleResize); 12 | }, []); 13 | return windowSize; 14 | } 15 | 16 | export default useWindowSize; 17 | -------------------------------------------------------------------------------- /components/left-sidebar/Shortcut.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | function Shortcut({ src, name }) { 4 | return ( 5 |
6 | 15 |

{name}

16 |
17 | ); 18 | } 19 | 20 | export default Shortcut; 21 | -------------------------------------------------------------------------------- /.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 | #vscode 37 | /.vscode/ 38 | -------------------------------------------------------------------------------- /public/messenger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/feed/Feed.js: -------------------------------------------------------------------------------- 1 | import CreateRoom from './CreateRoom'; 2 | import InputBox from './InputBox'; 3 | import Posts from './Posts'; 4 | import Stories from './Stories'; 5 | 6 | function Feed({ posts }) { 7 | return ( 8 |
9 |
10 | 11 | 12 | 13 | 14 |
15 |
16 | ); 17 | } 18 | 19 | export default Feed; 20 | -------------------------------------------------------------------------------- /components/right-sidebar/Contact.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | function Contact({ src, name }) { 4 | return ( 5 |
6 | 15 |

{name}

16 |
17 |
18 | ); 19 | } 20 | 21 | export default Contact; 22 | -------------------------------------------------------------------------------- /components/left-sidebar/SidebarRow.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | function SidebarRow({ src, Icon, title }) { 4 | return ( 5 |
6 | {src && ( 7 | 15 | )} 16 | {Icon && } 17 |

{title}

18 |
19 | ); 20 | } 21 | 22 | export default SidebarRow; 23 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | # Firebase Configurations 2 | NEXT_PUBLIC_FIREBASE_API_KEY=Add your API key here 3 | NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=Add your Auth Domain here 4 | NEXT_PUBLIC_FIREBASE_PROJECT_ID=Add your Project ID here 5 | NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=Add your Storage Bucket here 6 | NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=Add your Messenging Sender ID here 7 | NEXT_PUBLIC_FIREBASE_APP_ID=Add your APP ID here 8 | 9 | # NextAuth Configurations 10 | FACEBOOK_CLIENT_ID=Add your Facebook Client ID here 11 | FACEBOOK_CLIENT_SECRET=Add Facebook Client Secret here 12 | NEXTAUTH_URL=Add your url here | exmple: http://localhost:3000 13 | NEXT_PUBLIC_SECRET=Add your created secret here | To create secret run this command: openssl rand -base64 32 -------------------------------------------------------------------------------- /components/header/HeaderIcon.js: -------------------------------------------------------------------------------- 1 | function HeaderIcon({ path, active }) { 2 | return ( 3 |
4 |
9 | 15 | {path} 16 | 17 |
18 | {active &&
} 19 |
20 | ); 21 | } 22 | 23 | export default HeaderIcon; 24 | -------------------------------------------------------------------------------- /public/notification.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApp, getApps } from 'firebase/app'; 2 | import { getFirestore } from 'firebase/firestore'; 3 | import { getStorage } from 'firebase/storage'; 4 | 5 | const firebaseConfig = { 6 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, 7 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, 8 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 9 | storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, 10 | messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, 11 | appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID 12 | }; 13 | 14 | const app = !getApps().length ? initializeApp(firebaseConfig) : getApp(); 15 | const db = getFirestore(app); 16 | const storage = getStorage(app); 17 | 18 | export { app, db, storage }; 19 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .icon { 7 | @apply inline-flex p-2 h-10 w-10 bg-gray-200 rounded-full text-gray-700 cursor-pointer hover:bg-gray-300; 8 | } 9 | 10 | .icon-svg { 11 | @apply inline-flex p-[0.6rem] h-10 w-10 bg-gray-200 rounded-full text-gray-700 cursor-pointer hover:bg-gray-300; 12 | } 13 | 14 | .inputIcon { 15 | @apply flex items-center space-x-1 hover:bg-gray-100 flex-grow justify-center p-1 rounded-lg cursor-pointer; 16 | } 17 | } 18 | 19 | @layer base { 20 | ul { 21 | display: block; 22 | list-style-type: disc; 23 | margin-block-start: 1em; 24 | margin-block-end: 1em; 25 | margin-inline-start: 0px; 26 | margin-inline-end: 0px; 27 | padding-inline-start: 40px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /components/right-sidebar/Sponsor.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | function Sponsor(props) { 4 | const urlWithoutProtocol = new URL(props.link).host; 5 | 6 | return ( 7 | 13 | 22 |
23 |

{props.title}

24 |

{urlWithoutProtocol}

25 |
26 |
27 | ); 28 | } 29 | 30 | export default Sponsor; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "facebook-clone", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "@headlessui/react": "^1.4.2", 12 | "@heroicons/react": "^1.0.5", 13 | "@tippyjs/react": "^4.2.6", 14 | "dotenv": "^10.0.0", 15 | "firebase": "^10.12.2", 16 | "moment": "^2.29.1", 17 | "next": "^12.3.4", 18 | "next-auth": "^4.24.10", 19 | "react": "17.0.2", 20 | "react-dom": "17.0.2", 21 | "react-firebase-hooks": "^4.0.1", 22 | "react-moment": "^1.1.1", 23 | "react-textarea-autosize": "^8.3.3", 24 | "recoil": "^0.5.2", 25 | "sharp": "^0.32.6", 26 | "tailwind-scrollbar-hide": "^1.1.7" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^10.4.1", 30 | "eslint": "8.6.0", 31 | "eslint-config-next": "12.0.7", 32 | "postcss": "^8.4.31", 33 | "tailwindcss": "^3.0.8" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /components/feed/Friend.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Tippy from '@tippyjs/react'; 3 | 4 | function Friend({ src, name }) { 5 | return ( 6 | 14 |

{name}

15 |

Say hi

16 | 17 | } 18 | > 19 |
20 | 29 |
30 |
31 |
32 | ); 33 | } 34 | 35 | export default Friend; 36 | -------------------------------------------------------------------------------- /components/feed/AddCommentIcon.js: -------------------------------------------------------------------------------- 1 | import Tippy from '@tippyjs/react/headless'; 2 | 3 | function AddCommentIcon({ url, position, title }) { 4 | return ( 5 | ( 7 |
12 | {title} 13 |
14 | )} 15 | > 16 |
{ 18 | if (title === 'Photo/Video') setPhotoPost(true); 19 | }} 20 | className='flex items-center rounded-full cursor-pointer p-2 hover:bg-gray-200' 21 | > 22 | 34 |
35 |
36 | ); 37 | } 38 | 39 | export default AddCommentIcon; 40 | -------------------------------------------------------------------------------- /public/public.svg: -------------------------------------------------------------------------------- 1 | Shared with Public -------------------------------------------------------------------------------- /components/modal/PostIcon.js: -------------------------------------------------------------------------------- 1 | import Tippy from '@tippyjs/react/headless'; 2 | import { useRecoilState } from 'recoil'; 3 | import { photoModalState } from '../../atoms/modalAtom'; 4 | 5 | function PostIcon({ url, position, title }) { 6 | const [photoPost, setPhotoPost] = useRecoilState(photoModalState); 7 | return ( 8 | ( 10 |
15 | {title} 16 |
17 | )} 18 | > 19 |
{ 21 | if (title === 'Photo/Video') setPhotoPost(true); 22 | }} 23 | className={`flex items-center rounded-full cursor-pointer 24 | p-[0.3rem] ${ 25 | title === 'Photo/Video' && photoPost 26 | ? 'bg-green-100' 27 | : 'hover:bg-gray-200' 28 | }`} 29 | > 30 | 41 |
42 |
43 | ); 44 | } 45 | 46 | export default PostIcon; 47 | -------------------------------------------------------------------------------- /public/like.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Fakebook](https://fakebook.salimi.my) · [![Author Salimi](https://img.shields.io/badge/Author-Salimi-%3C%3E)](https://www.linkedin.com/in/mohamad-salimi/) 2 | 3 | This is Facebook clone created using Next.js for educational purposes. User can login using Facebook login API, make posts with images, like posts and comment on posts. 4 | 5 | ## Exact copy of Facebook UI 6 | 7 | - This is a clone of Facebook app 8 | - Login using NextAuth.js through Facebook login api 9 | - Realtime database using Firebase 10 | - NextJS Server-side Rendering 11 | - Hosted in Vercel 12 | 13 | ## Tech/framework used 14 | 15 | - Next.js 16 | - NextAuth.js 17 | - Tailwind CSS 18 | - Tailwind UI 19 | - Firebase 20 | - Recoil 21 | - Vercel 22 | 23 | ## Starting the project 24 | 25 | Open the [.env.local.example](/.env.local.example) and fill in your Firebase Configurations & NextAuth Configurations then save it as .env.local the run the following command: 26 | 27 | ```bash 28 | npm run dev 29 | # or 30 | yarn run dev 31 | ``` 32 | 33 | ## Demo 34 | 35 | The app hosted on Vercel. [Click here](https://fakebook.salimi.my) to visit. 36 |
37 | Direct link: `https://fakebook.salimi.my` 38 | 39 | ## Screenshots 40 | 41 | #### Login 42 | 43 | ![Login](/screenshots/screenshot-1.png) 44 | 45 | #### Feed 46 | 47 | ![Feed](/screenshots/screenshot-2.png) 48 | 49 | #### Menu 50 | 51 | ![Menu](/screenshots/screenshot-3.png) 52 | 53 | #### Create post 54 | 55 | ![Menu](/screenshots/screenshot-4.png) 56 | -------------------------------------------------------------------------------- /public/three-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | 13 | 17 | 21 | 22 | 23 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /components/feed/Posts.js: -------------------------------------------------------------------------------- 1 | import { collection, query, orderBy, onSnapshot } from 'firebase/firestore'; 2 | import { db } from '../../firebase'; 3 | import Post from './Post'; 4 | import { useEffect, useState } from 'react'; 5 | 6 | function Posts({ posts }) { 7 | const [realtimePost, setRealtimePost] = useState(); 8 | 9 | useEffect( 10 | () => 11 | onSnapshot( 12 | query(collection(db, 'posts'), orderBy('timestamp', 'desc')), 13 | (snapshot) => { 14 | setRealtimePost( 15 | snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })) 16 | ); 17 | } 18 | ), 19 | [] 20 | ); 21 | 22 | return ( 23 |
24 | {realtimePost 25 | ? realtimePost.map((post) => ( 26 | 36 | )) 37 | : posts.map((post) => ( 38 | 48 | ))} 49 |
50 | ); 51 | } 52 | 53 | export default Posts; 54 | -------------------------------------------------------------------------------- /components/feed/Comment.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Moment from 'react-moment'; 3 | 4 | function Comment({ image, name, comment, timestamp }) { 5 | return ( 6 |
7 | 14 |
15 |
16 |

{name}

17 |

{comment}

18 |
19 |
20 |

21 | Like 22 |

23 | · 24 |

25 | Reply 26 |

27 | · 28 |

29 | Share 30 |

31 | · 32 |

33 | 34 | {timestamp?.toDate()} 35 | 36 |

37 |
38 |
39 |
40 | ); 41 | } 42 | 43 | export default Comment; 44 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import FacebookProvider from 'next-auth/providers/facebook'; 3 | 4 | export default NextAuth({ 5 | // Configure one or more authentication providers 6 | providers: [ 7 | FacebookProvider({ 8 | clientId: process.env.FACEBOOK_CLIENT_ID, 9 | clientSecret: process.env.FACEBOOK_CLIENT_SECRET, 10 | authorization: 'https://www.facebook.com/v11.0/dialog/oauth?scope=email', 11 | userinfo: { 12 | url: 'https://graph.facebook.com/me', 13 | params: { 14 | fields: 15 | 'id,name,first_name,last_name,email,picture.width(200).height(200)' 16 | } 17 | } 18 | }) 19 | ], 20 | secret: process.env.NEXT_PUBLIC_SECRET, 21 | pages: { 22 | signIn: '/login' 23 | }, 24 | callbacks: { 25 | async signIn({ user, account, profile, email, credentials }) { 26 | const isAllowedToSignIn = true; 27 | if (isAllowedToSignIn) { 28 | return true; 29 | } else { 30 | // Return false to display a default error message 31 | return false; 32 | // Or you can return a URL to redirect to: 33 | // return '/unauthorized' 34 | } 35 | }, 36 | async jwt({ token, account, profile }) { 37 | // Persist the OAuth access_token to the token right after signin 38 | if (account) { 39 | token.profile = profile; 40 | } 41 | return token; 42 | }, 43 | async session({ session, token, user }) { 44 | // Send properties to the client, like an access_token from a provider. 45 | session.user.first_name = token.profile.first_name; 46 | session.user.last_name = token.profile.last_name; 47 | return session; 48 | } 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /components/left-sidebar/LeftSidebar.js: -------------------------------------------------------------------------------- 1 | import { ChevronDownIcon } from '@heroicons/react/solid'; 2 | import { useSession } from 'next-auth/react'; 3 | import Shortcut from './Shortcut'; 4 | import SidebarRow from './SidebarRow'; 5 | 6 | const shortcuts = [ 7 | { src: 'https://links.salimi.my/reactgroup', name: 'React Developer' }, 8 | { src: 'https://links.salimi.my/nextgroup', name: 'Nextjs Developer' }, 9 | { src: 'https://links.salimi.my/jsgroup', name: 'JavaScript Programmer' }, 10 | { src: 'https://links.salimi.my/tailwindgroup', name: 'Tailwind CSS' }, 11 | { src: 'https://links.salimi.my/webdevgroup', name: 'Web Developer' } 12 | ]; 13 | 14 | function LeftSidebar() { 15 | const { data: session } = useSession(); 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |

Your Shortcuts

28 |
29 | {shortcuts.map((shortcut) => ( 30 | 31 | ))} 32 | 33 |
34 | ); 35 | } 36 | 37 | export default LeftSidebar; 38 | -------------------------------------------------------------------------------- /components/feed/CreateRoom.js: -------------------------------------------------------------------------------- 1 | import { ChevronRightIcon } from '@heroicons/react/outline'; 2 | import Friend from './Friend'; 3 | 4 | const friends = [ 5 | { src: 'https://links.salimi.my/jeffbezosprofile', name: 'Jeff Bezos' }, 6 | { src: 'https://links.salimi.my/elonmuskprofile', name: 'Elon Musk' }, 7 | { src: 'https://links.salimi.my/billgatesprofile', name: 'Bill Gates' }, 8 | { 9 | src: 'https://links.salimi.my/markzuckerbergprofile', 10 | name: 'Mark Zuckerberg' 11 | }, 12 | { src: 'https://links.salimi.my/timcookprofile', name: 'Tim Cook' }, 13 | { src: 'https://links.salimi.my/sundarpichaiprofile', name: 'Sundar Pichai' }, 14 | { 15 | src: 'https://links.salimi.my/rayroslanskyprofile', 16 | name: 'Ryan Roslansky' 17 | }, 18 | { src: 'https://links.salimi.my/paragagrawalprofile', name: 'Parag Agrawal' } 19 | ]; 20 | 21 | function CreateRoom() { 22 | return ( 23 |
24 |
25 |
26 | 39 |
40 |

Create room

41 |
42 | {friends.map((friend) => ( 43 | 44 | ))} 45 |
46 | 47 |
48 |
49 | ); 50 | } 51 | 52 | export default CreateRoom; 53 | -------------------------------------------------------------------------------- /components/feed/StoryCard.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import { PlusIcon } from '@heroicons/react/solid'; 3 | 4 | function StoryCard({ user, name, story, profile }) { 5 | let otherStory = ( 6 |
7 |

8 | {name} 9 |

10 | 15 | 23 |
24 | ); 25 | 26 | let createStory = ( 27 |
28 |
29 | 36 |
37 |
38 | 39 |
40 |

41 | Create Story 42 |

43 |
44 |
45 | ); 46 | 47 | return <>{user ? createStory : otherStory}; 48 | } 49 | 50 | export default StoryCard; 51 | -------------------------------------------------------------------------------- /components/feed/Stories.js: -------------------------------------------------------------------------------- 1 | import StoryCard from './StoryCard'; 2 | import useWindowSize from '../../hooks/use-window-size'; 3 | import { useSession } from 'next-auth/react'; 4 | import { ArrowRightIcon } from '@heroicons/react/outline'; 5 | 6 | const stories = [ 7 | { 8 | id: 's1', 9 | name: 'Elon Musk', 10 | profile: 'https://links.salimi.my/elonmuskprofile', 11 | story: 'https://links.salimi.my/elonmuskstory' 12 | }, 13 | { 14 | id: 's2', 15 | name: 'Jack Ma', 16 | profile: 'https://links.salimi.my/jackmaprofile', 17 | story: 'https://links.salimi.my/jackmastory' 18 | }, 19 | { 20 | id: 's4', 21 | name: 'Bill Gates', 22 | profile: 'https://links.salimi.my/billgatesprofile', 23 | story: 'https://links.salimi.my/billgatesstory' 24 | }, 25 | { 26 | id: 's3', 27 | name: 'Mark Zuckerberg', 28 | profile: 'https://links.salimi.my/markzuckerbergprofile', 29 | story: 'https://links.salimi.my/markzuckerbergstory' 30 | } 31 | ]; 32 | 33 | function Stories() { 34 | const windowSize = useWindowSize(); 35 | const { data: session } = useSession(); 36 | return ( 37 |
38 | 45 | {windowSize > 767 46 | ? stories 47 | .slice(0, 4) 48 | .map((story) => ( 49 | 56 | )) 57 | : stories 58 | .slice(0, 3) 59 | .map((story) => ( 60 | 67 | ))} 68 |
69 | 70 |
71 |
72 | ); 73 | } 74 | 75 | export default Stories; 76 | -------------------------------------------------------------------------------- /public/settings.svg: -------------------------------------------------------------------------------- 1 | Shared with Each photo has its own privacy setting -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { getSession } from 'next-auth/react'; 2 | import Head from 'next/head'; 3 | import Feed from '../components/feed/Feed'; 4 | import Header from '../components/header/Header'; 5 | import LeftSidebar from '../components/left-sidebar/LeftSidebar'; 6 | import RightSidebar from '../components/right-sidebar/RightSidebar'; 7 | import { collection, query, orderBy, getDocs } from 'firebase/firestore'; 8 | import { db } from '../firebase'; 9 | import { Fragment } from 'react'; 10 | import { useRouter } from 'next/router'; 11 | import InputModal from '../components/modal/InputModal'; 12 | 13 | export default function Home({ posts }) { 14 | const router = useRouter(); 15 | if (router.asPath === '/#_=_') { 16 | window.history.pushState('', document.title, window.location.pathname); 17 | } 18 | 19 | return ( 20 | 21 | 22 | Fakebook 23 | 24 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 | 53 |
54 | 55 | 56 | 57 | ); 58 | } 59 | 60 | export async function getServerSideProps(context) { 61 | // Get the user 62 | const session = await getSession(context); 63 | 64 | const posts = await getDocs( 65 | query(collection(db, 'posts'), orderBy('timestamp', 'desc')) 66 | ); 67 | 68 | const docs = posts.docs.map((post) => ({ 69 | id: post.id, 70 | ...post.data(), 71 | timestamp: null 72 | })); 73 | 74 | if (session) { 75 | return { 76 | props: { 77 | session, 78 | posts: docs 79 | } 80 | }; 81 | } else if (!session) { 82 | return { 83 | redirect: { 84 | permanent: false, 85 | destination: '/login' 86 | } 87 | }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /components/feed/AddComment.js: -------------------------------------------------------------------------------- 1 | import { addDoc, collection, serverTimestamp } from 'firebase/firestore'; 2 | import { useSession } from 'next-auth/react'; 3 | import Image from 'next/image'; 4 | import { useRef, useState } from 'react'; 5 | import TextareaAutosize from 'react-textarea-autosize'; 6 | import { db } from '../../firebase'; 7 | import useWindowSize from '../../hooks/use-window-size'; 8 | import AddCommentIcon from './AddCommentIcon'; 9 | 10 | function AddComment({ id }) { 11 | const windowSize = useWindowSize(); 12 | const commentRef = useRef(null); 13 | const { data: session } = useSession(); 14 | const [commentIconBottom, setCommentIconBottom] = useState(false); 15 | 16 | const commentChangeHandler = (e) => { 17 | let charCount; 18 | if (windowSize > 767) { 19 | charCount = 55; 20 | } else if (windowSize > 424 && windowSize < 768) { 21 | charCount = 20; 22 | } else if (windowSize > 374 && windowSize < 425) { 23 | charCount = 15; 24 | } 25 | if (e.target.value.length > charCount) { 26 | setCommentIconBottom(true); 27 | } else { 28 | setCommentIconBottom(false); 29 | } 30 | }; 31 | 32 | const sendComment = async () => { 33 | if (!commentRef.current.value.trim()) return; 34 | 35 | const commentToSend = commentRef.current.value; 36 | commentRef.current.value = ''; 37 | 38 | await addDoc(collection(db, 'posts', id, 'comments'), { 39 | comment: commentToSend, 40 | name: session.user.name, 41 | image: session.user.image, 42 | timestamp: serverTimestamp() 43 | }); 44 | }; 45 | 46 | const onEnterPress = (e) => { 47 | if (e.keyCode == 13 && e.shiftKey == false) { 48 | e.preventDefault(); 49 | sendComment(); 50 | } 51 | }; 52 | 53 | return ( 54 |
55 |
56 | 65 |
66 |
67 |
71 | 79 | {commentIconBottom &&
} 80 | {windowSize > 374 && ( 81 |
82 | 87 | 92 | 97 | 102 |
103 | )} 104 | 105 |
106 | ); 107 | } 108 | 109 | export default AddComment; 110 | -------------------------------------------------------------------------------- /components/modal/DisclaimerModal.js: -------------------------------------------------------------------------------- 1 | /* This example requires Tailwind CSS v2.0+ */ 2 | import { Fragment, useRef, useState } from 'react'; 3 | import { Dialog, Transition } from '@headlessui/react'; 4 | import { ExclamationIcon } from '@heroicons/react/outline'; 5 | 6 | export default function DisclaimerModal() { 7 | const [open, setOpen] = useState(true); 8 | 9 | return ( 10 | 11 | 16 |
17 | 26 | 27 | 28 | 29 | {/* This element is to trick the browser into centering the modal contents. */} 30 | 36 | 45 |
46 |
47 |
48 |
49 |
54 |
55 | 59 | This is NOT REAL Facebook 60 | 61 |
62 |

63 | This site is not the real Facebook. I created 64 | this site for educational purposes only. Under no 65 | circumstance shall I have any liability to you for any 66 | loss or damage of any kind incurred as a result of the 67 | use of the site or reliance on any information provided 68 | on the site. Your use of the site and your reliance on 69 | any information on the site is solely at your own risk. 70 |

71 |
72 |
73 |
74 |
75 |
76 | 83 |
84 |
85 |
86 |
87 |
88 |
89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /components/feed/InputBox.js: -------------------------------------------------------------------------------- 1 | import { useSession } from 'next-auth/react'; 2 | import Image from 'next/image'; 3 | import { useSetRecoilState } from 'recoil'; 4 | import { inputModalState, photoModalState } from '../../atoms/modalAtom'; 5 | 6 | function InputBox() { 7 | const { data: session } = useSession(); 8 | const setOpen = useSetRecoilState(inputModalState); 9 | const setPhotoPost = useSetRecoilState(photoModalState); 10 | 11 | return ( 12 |
13 |
14 | 22 |
23 | { 25 | setOpen(true); 26 | setPhotoPost(false); 27 | }} 28 | className='rounded-full h-10 bg-gray-100 flex-grow px-3 focus:outline-none text-lg placeholder-gray-500 hover:bg-gray-200 cursor-pointer' 29 | type='type' 30 | placeholder={`What's on your mind, ${session.user.first_name}?`} 31 | readOnly 32 | /> 33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 |

Live Video

45 |
46 |
{ 48 | setPhotoPost(true); 49 | setOpen(true); 50 | }} 51 | className='inputIcon py-3' 52 | > 53 | 54 | 55 | 56 | 60 | 61 | 62 |

Photo/Video

63 |
64 |
65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 |

Feeling/Activity

76 |
77 |
78 |
79 | ); 80 | } 81 | 82 | export default InputBox; 83 | -------------------------------------------------------------------------------- /components/right-sidebar/RightSidebar.js: -------------------------------------------------------------------------------- 1 | import Contact from './Contact'; 2 | import Sponsor from './Sponsor'; 3 | 4 | const contacts = [ 5 | { src: 'https://links.salimi.my/jeffbezosprofile', name: 'Jeff Bezos' }, 6 | { src: 'https://links.salimi.my/elonmuskprofile', name: 'Elon Musk' }, 7 | { src: 'https://links.salimi.my/billgatesprofile', name: 'Bill Gates' }, 8 | { 9 | src: 'https://links.salimi.my/markzuckerbergprofile', 10 | name: 'Mark Zuckerberg' 11 | }, 12 | { src: 'https://links.salimi.my/timcookprofile', name: 'Tim Cook' }, 13 | { src: 'https://links.salimi.my/sundarpichaiprofile', name: 'Sundar Pichai' }, 14 | { 15 | src: 'https://links.salimi.my/rayroslanskyprofile', 16 | name: 'Ryan Roslansky' 17 | }, 18 | { src: 'https://links.salimi.my/paragagrawalprofile', name: 'Parag Agrawal' } 19 | ]; 20 | 21 | const sponsors = [ 22 | { 23 | src: 'https://links.salimi.my/react', 24 | title: 'React: A JavaScript library', 25 | link: 'https://reactjs.org/' 26 | }, 27 | { 28 | src: 'https://links.salimi.my/nextjs', 29 | title: 'Next.js: The React Framework', 30 | link: 'https://nextjs.org/' 31 | } 32 | ]; 33 | 34 | function RightSidebar() { 35 | return ( 36 |
37 |
38 |

Sponsored

39 |
40 | 41 | {sponsors.map((sponsor) => ( 42 | 48 | ))} 49 | 50 |
51 | 52 |
53 |

Contacts

54 |
55 |
56 | 60 | 64 | 65 |
66 |
67 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 |
80 | 84 | 88 | 89 |
90 |
91 |
92 | 93 | {contacts.map((contact) => ( 94 | 95 | ))} 96 |
97 | ); 98 | } 99 | 100 | export default RightSidebar; 101 | -------------------------------------------------------------------------------- /components/header/Header.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import { SearchIcon } from '@heroicons/react/outline'; 3 | import HeaderIcon from './HeaderIcon'; 4 | import { useSession } from 'next-auth/react'; 5 | import Dropdown from './Dropdown'; 6 | 7 | function Header() { 8 | const { data: session } = useSession(); 9 | return ( 10 |
11 |
12 | 19 |
20 | 21 | 26 |
27 |
28 | 29 |
30 |
31 | 35 | } 36 | /> 37 | 40 | } 41 | /> 42 | 45 | } 46 | /> 47 | 50 | } 51 | /> 52 |
53 |
54 | 55 |
56 |
57 | 65 |

66 | {session.user.first_name} 67 |

68 |
69 |
70 | 71 |
72 |
73 | 74 |
75 |
76 | 83 |
84 |
85 | 92 |
93 | 94 |
95 |
96 | ); 97 | } 98 | 99 | export default Header; 100 | -------------------------------------------------------------------------------- /components/header/Dropdown.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react'; 2 | import { Menu, Transition } from '@headlessui/react'; 3 | import { 4 | ChatAltIcon, 5 | CogIcon, 6 | MoonIcon, 7 | QuestionMarkCircleIcon 8 | } from '@heroicons/react/solid'; 9 | import { useSession, signOut } from 'next-auth/react'; 10 | import Image from 'next/image'; 11 | import { ChevronRightIcon, LogoutIcon } from '@heroicons/react/outline'; 12 | 13 | function classNames(...classes) { 14 | return classes.filter(Boolean).join(' '); 15 | } 16 | 17 | export default function Dropdown() { 18 | const { data: session } = useSession(); 19 | return ( 20 | 21 |
22 | 23 | 30 | 31 |
32 | 33 | 42 | 43 |
44 | 45 | {({ active }) => ( 46 | 53 | 61 |
62 |

63 | {session.user.name} 64 |

65 |

66 | See your profile 67 |

68 |
69 |
70 | )} 71 |
72 |
73 | 74 | {({ active }) => ( 75 | 82 |
83 | 84 |
85 |

86 | Give feedback 87 |

88 |

89 | Help us improve the new Facebook. 90 |

91 |
92 |
93 |
94 | )} 95 |
96 |
97 | 98 | {({ active }) => ( 99 | 106 |
107 | 108 |
109 |

110 | Settings & privacy 111 |

112 |
113 | 114 |
115 |
116 | )} 117 |
118 | 119 | {({ active }) => ( 120 | 127 |
128 | 129 |
130 |

131 | Help & Support 132 |

133 |
134 | 135 |
136 |
137 | )} 138 |
139 | 140 | {({ active }) => ( 141 | 148 |
149 | 150 |
151 |

152 | Display & Accessibility 153 |

154 |
155 | 156 |
157 |
158 | )} 159 |
160 | 161 | {({ active }) => ( 162 | { 169 | e.preventDefault(); 170 | signOut({ 171 | callbackUrl: '/login' 172 | }); 173 | }} 174 | > 175 |
176 | 177 |
178 |

179 | Log Out 180 |

181 |
182 |
183 |
184 | )} 185 |
186 |
187 |
188 |
189 |
190 | ); 191 | } 192 | -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import { getProviders, signIn } from 'next-auth/react'; 2 | import Head from 'next/head'; 3 | import Link from 'next/link'; 4 | import { useState } from 'react'; 5 | import DisclaimerModal from '../components/modal/DisclaimerModal'; 6 | 7 | function Login({ providers }) { 8 | const [loggingIn, setLoggingIn] = useState(false); 9 | 10 | return ( 11 | <> 12 | 13 | Fakebook | Login 14 | 15 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 |
45 |

46 | fakebook 47 |

48 |

49 | This is not real Facebook and it cannot connect with real 50 | people. 51 |

52 |
53 |
54 |
55 | 64 |
65 |
66 | 75 |
76 | {Object.values(providers).map((provider) => ( 77 |
78 | 113 |
114 | ))} 115 | 119 | Forgotten password? 120 | 121 |
122 |
123 | 129 |
130 |
131 |
132 |

133 | 134 | Create a Page 135 | 136 |  for a celebrity, band or business 137 |

138 |
139 |
140 |
141 |
142 | 143 |
144 |

145 | This is just a clone of Facebook created using Next.js, Tailwind CSS 146 | and Firebase. 147 |

148 |
149 |

150 | Disclaimer: This site is not the real Facebook. Under no 151 | circumstance shall I have any liability to you for any loss or 152 | damage of any kind incurred as a result of the use of the site or 153 | reliance on any information provided on the site. Your use of the 154 | site and your reliance on any information on the site is solely at 155 | your own risk.{' '} 156 | 157 | 158 | Privacy Policy 159 | 160 | 161 |

162 |
163 |
164 |

165 | Facebook Clone © 2022 Created by{' '} 166 | 170 | Salimi 171 | 172 |

173 |
174 |
175 |
176 |
177 | 178 | 179 | ); 180 | } 181 | 182 | export default Login; 183 | 184 | export async function getServerSideProps() { 185 | const providers = await getProviders(); 186 | 187 | return { 188 | props: { 189 | providers 190 | } 191 | }; 192 | } 193 | -------------------------------------------------------------------------------- /components/feed/Post.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import { useEffect, useState } from 'react'; 3 | import AddComment from './AddComment'; 4 | import Comment from './Comment'; 5 | import Moment from 'react-moment'; 6 | import { 7 | collection, 8 | deleteDoc, 9 | doc, 10 | onSnapshot, 11 | orderBy, 12 | query, 13 | setDoc 14 | } from 'firebase/firestore'; 15 | import { db } from '../../firebase'; 16 | import { useSession } from 'next-auth/react'; 17 | 18 | function Post({ id, name, message, postImage, image, timestamp }) { 19 | const { data: session } = useSession(); 20 | const [showComment, setShowComment] = useState(false); 21 | const [comments, setComments] = useState([]); 22 | const [likes, setLikes] = useState([]); 23 | const [hasLiked, setHasLiked] = useState(false); 24 | 25 | useEffect( 26 | () => 27 | onSnapshot( 28 | query( 29 | collection(db, 'posts', id, 'comments'), 30 | orderBy('timestamp', 'desc') 31 | ), 32 | (snapshot) => { 33 | setComments( 34 | snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })) 35 | ); 36 | } 37 | ), 38 | [id] 39 | ); 40 | 41 | useEffect( 42 | () => 43 | onSnapshot(collection(db, 'posts', id, 'likes'), (snapshot) => 44 | setLikes(snapshot.docs) 45 | ), 46 | [id] 47 | ); 48 | 49 | useEffect( 50 | () => 51 | setHasLiked( 52 | likes.findIndex((like) => like.id === session.user.email) !== -1 53 | ), 54 | [likes, session] 55 | ); 56 | 57 | const likePost = async () => { 58 | if (hasLiked) { 59 | await deleteDoc(doc(db, 'posts', id, 'likes', session.user.email)); 60 | } else { 61 | await setDoc(doc(db, 'posts', id, 'likes', session.user.email), { 62 | email: session.user.email 63 | }); 64 | } 65 | }; 66 | 67 | return ( 68 |
69 |
70 |
71 | 78 |
79 |

{name}

80 |
81 | {timestamp ? ( 82 |

83 | 84 | {timestamp?.toDate()} 85 | 86 |

87 | ) : ( 88 |

89 | Loading 90 |

91 | )} 92 | · 93 |
94 | 95 | 96 | 97 | 98 | 102 | 103 | 104 |
105 |
106 |
107 |
108 |

{message}

109 |
110 | {postImage && ( 111 |
112 | 113 |
114 | )} 115 | {(likes.length > 0 || comments.length > 0) && ( 116 |
117 | {likes.length > 0 ? ( 118 |
119 | 120 |

{likes.length}

121 |
122 | ) : ( 123 |
124 | )} 125 | {comments.length > 0 ? ( 126 |
setShowComment(true)} 128 | className='flex items-center space-x-1 cursor-pointer hover:underline text-gray-500' 129 | > 130 |

{comments.length} Comments

131 |
132 | ) : ( 133 |
134 | )} 135 |
136 | )} 137 | {(likes.length > 0 || comments.length > 0) && ( 138 |
139 |
140 |
141 | )} 142 |
147 |
148 | {hasLiked ? ( 149 | 163 | ) : ( 164 | 176 | )} 177 |

182 | Like 183 |

184 |
185 |
setShowComment(true)} 187 | className='inputIcon rounded-md' 188 | > 189 | 201 |

Comment

202 |
203 |
204 | 216 |

Share

217 |
218 |
219 | {showComment && ( 220 |
221 |
222 |
223 | )} 224 | {showComment && ( 225 |
226 |
227 |

All comments

228 | 229 | 230 | 231 |
232 | {comments.length > 0 && 233 | comments.map((comment) => ( 234 | 241 | ))} 242 | 243 |
244 | )} 245 |
246 | ); 247 | } 248 | 249 | export default Post; 250 | -------------------------------------------------------------------------------- /components/modal/InputModal.js: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from 'react'; 2 | import { Dialog, Transition } from '@headlessui/react'; 3 | import { XIcon } from '@heroicons/react/outline'; 4 | import { useRecoilState } from 'recoil'; 5 | import { inputModalState, photoModalState } from '../../atoms/modalAtom'; 6 | import { useSession } from 'next-auth/react'; 7 | import Image from 'next/image'; 8 | import TextareaAutosize from 'react-textarea-autosize'; 9 | import PostIcon from './PostIcon'; 10 | import { db, storage } from '../../firebase'; 11 | import { 12 | addDoc, 13 | collection, 14 | doc, 15 | serverTimestamp, 16 | updateDoc 17 | } from 'firebase/firestore'; 18 | import { ref, getDownloadURL, uploadString } from 'firebase/storage'; 19 | 20 | export default function InputModal() { 21 | const { data: session } = useSession(); 22 | const [open, setOpen] = useRecoilState(inputModalState); 23 | const [smalText, setSmallText] = useState(false); 24 | const [enableButton, setEnableButton] = useState(false); 25 | const [photoPost, setPhotoPost] = useRecoilState(photoModalState); 26 | const [imageToPost, setImageToPost] = useState(null); 27 | const [posting, setPosting] = useState(false); 28 | const inputRef = useRef(null); 29 | const filePickerRef = useRef(null); 30 | 31 | const inputChangeHandler = (e) => { 32 | if (e.target.value.length > 0) { 33 | setEnableButton(true); 34 | } else { 35 | setEnableButton(false); 36 | } 37 | if (e.target.value.length > 85) { 38 | setSmallText(true); 39 | } else { 40 | setSmallText(false); 41 | } 42 | }; 43 | 44 | const sendPost = async (e) => { 45 | e.preventDefault(); 46 | 47 | if (!inputRef.current.value) return; 48 | 49 | setPosting(true); 50 | 51 | const docRef = await addDoc(collection(db, 'posts'), { 52 | message: inputRef.current.value, 53 | name: session.user.name, 54 | email: session.user.email, 55 | image: session.user.image, 56 | timestamp: serverTimestamp() 57 | }); 58 | 59 | if (imageToPost) { 60 | const imageRef = ref(storage, `post/${docRef.id}`); 61 | 62 | await uploadString(imageRef, imageToPost, 'data_url').then( 63 | async (snapshot) => { 64 | const downloadURL = await getDownloadURL(imageRef); 65 | 66 | await updateDoc(doc(db, 'posts', docRef.id), { 67 | postImage: downloadURL 68 | }); 69 | } 70 | ); 71 | 72 | removeImage(); 73 | } 74 | 75 | setPosting(false); 76 | setOpen(false); 77 | setEnableButton(false); 78 | inputRef.current.value = ''; 79 | }; 80 | 81 | const addImageToPost = (e) => { 82 | const reader = new FileReader(); 83 | if (e.target.files[0]) { 84 | reader.readAsDataURL(e.target.files[0]); 85 | } 86 | 87 | reader.onload = (readerEvent) => { 88 | setImageToPost(readerEvent.target.result); 89 | }; 90 | }; 91 | 92 | const removeImage = () => { 93 | setImageToPost(null); 94 | }; 95 | 96 | return ( 97 | 98 | 103 |
104 | 113 | 114 | 115 | 116 | {/* This element is to trick the browser into centering the modal contents. */} 117 | 123 | 132 |
133 | {posting && ( 134 |
135 |

Posting

136 | 144 |
145 | )} 146 |
147 | 151 | Create post 152 | 153 | setOpen(false)} 155 | className='icon absolute right-4 top-2' 156 | /> 157 |
158 |
159 | 167 |
168 |

169 | {session.user.name} 170 |

171 |
172 |
173 | 181 |

Public

182 | 190 |
191 |
192 |
193 |
194 |
199 | 210 | {photoPost && ( 211 |
212 | 224 |
225 | )} 226 |
227 | {photoPost && ( 228 |
229 | { 231 | removeImage(); 232 | setPhotoPost(false); 233 | }} 234 | className='inline-flex z-20 p-1 h-8 w-8 bg-white rounded-full text-gray-500 cursor-pointer hover:bg-gray-200 absolute right-4 top-4' 235 | /> 236 | {photoPost && !imageToPost && ( 237 | 259 | )} 260 | {photoPost && imageToPost && ( 261 | selected image 266 | )} 267 | 273 |
274 | )} 275 | {!photoPost && ( 276 |
277 | 285 | 297 |
298 | )} 299 |
300 |

Add to your post

301 |
302 | 307 | 312 | 317 | 322 | 327 | 332 |
333 |
334 |
335 | 346 |
347 |
348 |
349 |
350 |
351 |
352 | ); 353 | } 354 | -------------------------------------------------------------------------------- /pages/privacy-policy.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Link from 'next/link'; 3 | 4 | function PrivacyPolicy() { 5 | return ( 6 | <> 7 | 8 | Fakebook | Privacy Policy 9 | 10 | 14 | 15 | 16 | 20 | 21 | 25 | 26 | 27 | 28 | 32 | 33 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | 44 |

45 | {'<'} Back 46 |

47 | 48 |

49 | Privacy Policy 50 |

51 |

Last updated: January 29, 2023

52 |

53 | This Privacy Policy describes Our policies and procedures on the 54 | collection, use and disclosure of Your information when You use the 55 | Service and tells You about Your privacy rights and how the law 56 | protects You. 57 |

58 |

59 | We use Your Personal data to provide and improve the Service. By 60 | using the Service, You agree to the collection and use of 61 | information in accordance with this Privacy Policy. 62 |

63 |

64 | Interpretation and Definitions 65 |

66 |

67 | Interpretation 68 |

69 |

70 | The words of which the initial letter is capitalized have meanings 71 | defined under the following conditions. The following definitions 72 | shall have the same meaning regardless of whether they appear in 73 | singular or in plural. 74 |

75 |

76 | Definitions 77 |

78 |

For the purposes of this Privacy Policy:

79 |
    80 |
  • 81 |

    82 | Account means a unique account created for You 83 | to access our Service or parts of our Service. 84 |

    85 |
  • 86 |
  • 87 |

    88 | Affiliate means an entity that controls, is 89 | controlled by or is under common control with a party, where 90 | "control" means ownership of 50% or more of the 91 | shares, equity interest or other securities entitled to vote for 92 | election of directors or other managing authority. 93 |

    94 |
  • 95 |
  • 96 |

    97 | Company (referred to as either "the 98 | Company", "We", "Us" or "Our" 99 | in this Agreement) refers to Fakebook. 100 |

    101 |
  • 102 |
  • 103 |

    104 | Cookies are small files that are placed on Your 105 | computer, mobile device or any other device by a website, 106 | containing the details of Your browsing history on that website 107 | among its many uses. 108 |

    109 |
  • 110 |
  • 111 |

    112 | Country refers to: Malaysia 113 |

    114 |
  • 115 |
  • 116 |

    117 | Device means any device that can access the 118 | Service such as a computer, a cellphone or a digital tablet. 119 |

    120 |
  • 121 |
  • 122 |

    123 | Personal Data is any information that relates 124 | to an identified or identifiable individual. 125 |

    126 |
  • 127 |
  • 128 |

    129 | Service refers to the Website. 130 |

    131 |
  • 132 |
  • 133 |

    134 | Service Provider means any natural or legal 135 | person who processes the data on behalf of the Company. It 136 | refers to third-party companies or individuals employed by the 137 | Company to facilitate the Service, to provide the Service on 138 | behalf of the Company, to perform services related to the 139 | Service or to assist the Company in analyzing how the Service is 140 | used. 141 |

    142 |
  • 143 |
  • 144 |

    145 | Third-party Social Media Service refers to any 146 | website or any social network website through which a User can 147 | log in or create an account to use the Service. 148 |

    149 |
  • 150 |
  • 151 |

    152 | Usage Data refers to data collected 153 | automatically, either generated by the use of the Service or 154 | from the Service infrastructure itself (for example, the 155 | duration of a page visit). 156 |

    157 |
  • 158 |
  • 159 |

    160 | Website refers to Fakebook, accessible from{' '} 161 | 166 | https://fakebook.salimi.my 167 | 168 |

    169 |
  • 170 |
  • 171 |

    172 | You means the individual accessing or using the 173 | Service, or the company, or other legal entity on behalf of 174 | which such individual is accessing or using the Service, as 175 | applicable. 176 |

    177 |
  • 178 |
179 |

180 | Collecting and Using Your Personal Data 181 |

182 |

183 | Types of Data Collected 184 |

185 |

186 | Personal Data 187 |

188 |

189 | While using Our Service, We may ask You to provide Us with certain 190 | personally identifiable information that can be used to contact or 191 | identify You. Personally identifiable information may include, but 192 | is not limited to: 193 |

194 |
    195 |
  • 196 |

    Email address

    197 |
  • 198 |
  • 199 |

    First name and last name

    200 |
  • 201 |
  • 202 |

    Usage Data

    203 |
  • 204 |
205 |

206 | Usage Data 207 |

208 |

209 | Usage Data is collected automatically when using the Service. 210 |

211 |

212 | Usage Data may include information such as Your Device's 213 | Internet Protocol address (e.g. IP address), browser type, browser 214 | version, the pages of our Service that You visit, the time and date 215 | of Your visit, the time spent on those pages, unique device 216 | identifiers and other diagnostic data. 217 |

218 |

219 | When You access the Service by or through a mobile device, We may 220 | collect certain information automatically, including, but not 221 | limited to, the type of mobile device You use, Your mobile device 222 | unique ID, the IP address of Your mobile device, Your mobile 223 | operating system, the type of mobile Internet browser You use, 224 | unique device identifiers and other diagnostic data. 225 |

226 |

227 | We may also collect information that Your browser sends whenever You 228 | visit our Service or when You access the Service by or through a 229 | mobile device. 230 |

231 |

232 | Information from Third-Party Social Media Services 233 |

234 |

235 | The Company allows You to create an account and log in to use the 236 | Service through the following Third-party Social Media Services: 237 |

238 |
    239 |
  • Google
  • 240 |
  • Facebook
  • 241 |
  • Twitter
  • 242 |
  • LinkedIn
  • 243 |
244 |

245 | If You decide to register through or otherwise grant us access to a 246 | Third-Party Social Media Service, We may collect Personal data that 247 | is already associated with Your Third-Party Social Media 248 | Service's account, such as Your name, Your email address, Your 249 | activities or Your contact list associated with that account. 250 |

251 |

252 | You may also have the option of sharing additional information with 253 | the Company through Your Third-Party Social Media Service's 254 | account. If You choose to provide such information and Personal 255 | Data, during registration or otherwise, You are giving the Company 256 | permission to use, share, and store it in a manner consistent with 257 | this Privacy Policy. 258 |

259 |

260 | Tracking Technologies and Cookies 261 |

262 |

263 | We use Cookies and similar tracking technologies to track the 264 | activity on Our Service and store certain information. Tracking 265 | technologies used are beacons, tags, and scripts to collect and 266 | track information and to improve and analyze Our Service. The 267 | technologies We use may include: 268 |

269 |
    270 |
  • 271 | Cookies or Browser Cookies. A cookie is a small 272 | file placed on Your Device. You can instruct Your browser to 273 | refuse all Cookies or to indicate when a Cookie is being sent. 274 | However, if You do not accept Cookies, You may not be able to use 275 | some parts of our Service. Unless you have adjusted Your browser 276 | setting so that it will refuse Cookies, our Service may use 277 | Cookies. 278 |
  • 279 |
  • 280 | Web Beacons. Certain sections of our Service and 281 | our emails may contain small electronic files known as web beacons 282 | (also referred to as clear gifs, pixel tags, and single-pixel 283 | gifs) that permit the Company, for example, to count users who 284 | have visited those pages or opened an email and for other related 285 | website statistics (for example, recording the popularity of a 286 | certain section and verifying system and server integrity). 287 |
  • 288 |
289 |

290 | Cookies can be "Persistent" or "Session" 291 | Cookies. Persistent Cookies remain on Your personal computer or 292 | mobile device when You go offline, while Session Cookies are deleted 293 | as soon as You close Your web browser. 294 |

295 |

296 | We use both Session and Persistent Cookies for the purposes set out 297 | below: 298 |

299 |
    300 |
  • 301 |

    302 | Necessary / Essential Cookies 303 |

    304 |

    Type: Session Cookies

    305 |

    Administered by: Us

    306 |

    307 | Purpose: These Cookies are essential to provide You with 308 | services available through the Website and to enable You to use 309 | some of its features. They help to authenticate users and 310 | prevent fraudulent use of user accounts. Without these Cookies, 311 | the services that You have asked for cannot be provided, and We 312 | only use these Cookies to provide You with those services. 313 |

    314 |
  • 315 |
  • 316 |

    317 | Cookies Policy / Notice Acceptance Cookies 318 |

    319 |

    Type: Persistent Cookies

    320 |

    Administered by: Us

    321 |

    322 | Purpose: These Cookies identify if users have accepted the use 323 | of cookies on the Website. 324 |

    325 |
  • 326 |
  • 327 |

    328 | Functionality Cookies 329 |

    330 |

    Type: Persistent Cookies

    331 |

    Administered by: Us

    332 |

    333 | Purpose: These Cookies allow us to remember choices You make 334 | when You use the Website, such as remembering your login details 335 | or language preference. The purpose of these Cookies is to 336 | provide You with a more personal experience and to avoid You 337 | having to re-enter your preferences every time You use the 338 | Website. 339 |

    340 |
  • 341 |
342 |

343 | For more information about the cookies we use and your choices 344 | regarding cookies, please visit our Cookies Policy or the Cookies 345 | section of our Privacy Policy. 346 |

347 |

348 | Use of Your Personal Data 349 |

350 |

351 | The Company may use Personal Data for the following purposes: 352 |

353 |
    354 |
  • 355 |

    356 | To provide and maintain our Service, including 357 | to monitor the usage of our Service. 358 |

    359 |
  • 360 |
  • 361 |

    362 | To manage Your Account: to manage Your 363 | registration as a user of the Service. The Personal Data You 364 | provide can give You access to different functionalities of the 365 | Service that are available to You as a registered user. 366 |

    367 |
  • 368 |
  • 369 |

    370 | For the performance of a contract: the 371 | development, compliance and undertaking of the purchase contract 372 | for the products, items or services You have purchased or of any 373 | other contract with Us through the Service. 374 |

    375 |
  • 376 |
  • 377 |

    378 | To contact You: To contact You by email, 379 | telephone calls, SMS, or other equivalent forms of electronic 380 | communication, such as a mobile application's push 381 | notifications regarding updates or informative communications 382 | related to the functionalities, products or contracted services, 383 | including the security updates, when necessary or reasonable for 384 | their implementation. 385 |

    386 |
  • 387 |
  • 388 |

    389 | To provide You with news, special offers and 390 | general information about other goods, services and events which 391 | we offer that are similar to those that you have already 392 | purchased or enquired about unless You have opted not to receive 393 | such information. 394 |

    395 |
  • 396 |
  • 397 |

    398 | To manage Your requests: To attend and manage 399 | Your requests to Us. 400 |

    401 |
  • 402 |
  • 403 |

    404 | For business transfers: We may use Your 405 | information to evaluate or conduct a merger, divestiture, 406 | restructuring, reorganization, dissolution, or other sale or 407 | transfer of some or all of Our assets, whether as a going 408 | concern or as part of bankruptcy, liquidation, or similar 409 | proceeding, in which Personal Data held by Us about our Service 410 | users is among the assets transferred. 411 |

    412 |
  • 413 |
  • 414 |

    415 | For other purposes: We may use Your information 416 | for other purposes, such as data analysis, identifying usage 417 | trends, determining the effectiveness of our promotional 418 | campaigns and to evaluate and improve our Service, products, 419 | services, marketing and your experience. 420 |

    421 |
  • 422 |
423 |

424 | We may share Your personal information in the following situations: 425 |

426 |
    427 |
  • 428 | With Service Providers: We may share Your 429 | personal information with Service Providers to monitor and analyze 430 | the use of our Service, to contact You. 431 |
  • 432 |
  • 433 | For business transfers: We may share or transfer 434 | Your personal information in connection with, or during 435 | negotiations of, any merger, sale of Company assets, financing, or 436 | acquisition of all or a portion of Our business to another 437 | company. 438 |
  • 439 |
  • 440 | With Affiliates: We may share Your information 441 | with Our affiliates, in which case we will require those 442 | affiliates to honor this Privacy Policy. Affiliates include Our 443 | parent company and any other subsidiaries, joint venture partners 444 | or other companies that We control or that are under common 445 | control with Us. 446 |
  • 447 |
  • 448 | With business partners: We may share Your 449 | information with Our business partners to offer You certain 450 | products, services or promotions. 451 |
  • 452 |
  • 453 | With other users: when You share personal 454 | information or otherwise interact in the public areas with other 455 | users, such information may be viewed by all users and may be 456 | publicly distributed outside. If You interact with other users or 457 | register through a Third-Party Social Media Service, Your contacts 458 | on the Third-Party Social Media Service may see Your name, 459 | profile, pictures and description of Your activity. Similarly, 460 | other users will be able to view descriptions of Your activity, 461 | communicate with You and view Your profile. 462 |
  • 463 |
  • 464 | With Your consent: We may disclose Your personal 465 | information for any other purpose with Your consent. 466 |
  • 467 |
468 |

469 | Retention of Your Personal Data 470 |

471 |

472 | The Company will retain Your Personal Data only for as long as is 473 | necessary for the purposes set out in this Privacy Policy. We will 474 | retain and use Your Personal Data to the extent necessary to comply 475 | with our legal obligations (for example, if we are required to 476 | retain your data to comply with applicable laws), resolve disputes, 477 | and enforce our legal agreements and policies. 478 |

479 |

480 | The Company will also retain Usage Data for internal analysis 481 | purposes. Usage Data is generally retained for a shorter period of 482 | time, except when this data is used to strengthen the security or to 483 | improve the functionality of Our Service, or We are legally 484 | obligated to retain this data for longer time periods. 485 |

486 |

487 | Transfer of Your Personal Data 488 |

489 |

490 | Your information, including Personal Data, is processed at the 491 | Company's operating offices and in any other places where the 492 | parties involved in the processing are located. It means that this 493 | information may be transferred to — and maintained on — computers 494 | located outside of Your state, province, country or other 495 | governmental jurisdiction where the data protection laws may differ 496 | than those from Your jurisdiction. 497 |

498 |

499 | Your consent to this Privacy Policy followed by Your submission of 500 | such information represents Your agreement to that transfer. 501 |

502 |

503 | The Company will take all steps reasonably necessary to ensure that 504 | Your data is treated securely and in accordance with this Privacy 505 | Policy and no transfer of Your Personal Data will take place to an 506 | organization or a country unless there are adequate controls in 507 | place including the security of Your data and other personal 508 | information. 509 |

510 |

511 | Delete Your Personal Data 512 |

513 |

514 | You have the right to delete or request that We assist in deleting 515 | the Personal Data that We have collected about You. 516 |

517 |

518 | Our Service may give You the ability to delete certain information 519 | about You from within the Service. 520 |

521 |

522 | You may update, amend, or delete Your information at any time by 523 | signing in to Your Account, if you have one, and visiting the 524 | account settings section that allows you to manage Your personal 525 | information. You may also contact Us to request access to, correct, 526 | or delete any personal information that You have provided to Us. 527 |

528 |

529 | Please note, however, that We may need to retain certain information 530 | when we have a legal obligation or lawful basis to do so. 531 |

532 |

533 | Disclosure of Your Personal Data 534 |

535 |

536 | Business Transactions 537 |

538 |

539 | If the Company is involved in a merger, acquisition or asset sale, 540 | Your Personal Data may be transferred. We will provide notice before 541 | Your Personal Data is transferred and becomes subject to a different 542 | Privacy Policy. 543 |

544 |

545 | Law enforcement 546 |

547 |

548 | Under certain circumstances, the Company may be required to disclose 549 | Your Personal Data if required to do so by law or in response to 550 | valid requests by public authorities (e.g. a court or a government 551 | agency). 552 |

553 |

554 | Other legal requirements 555 |

556 |

557 | The Company may disclose Your Personal Data in the good faith belief 558 | that such action is necessary to: 559 |

560 |
    561 |
  • Comply with a legal obligation
  • 562 |
  • 563 | Protect and defend the rights or property of the Company 564 |
  • 565 |
  • 566 | Prevent or investigate possible wrongdoing in connection with the 567 | Service 568 |
  • 569 |
  • 570 | Protect the personal safety of Users of the Service or the public 571 |
  • 572 |
  • Protect against legal liability
  • 573 |
574 |

575 | Security of Your Personal Data 576 |

577 |

578 | The security of Your Personal Data is important to Us, but remember 579 | that no method of transmission over the Internet, or method of 580 | electronic storage is 100% secure. While We strive to use 581 | commercially acceptable means to protect Your Personal Data, We 582 | cannot guarantee its absolute security. 583 |

584 |

585 | Children's Privacy 586 |

587 |

588 | Our Service does not address anyone under the age of 13. We do not 589 | knowingly collect personally identifiable information from anyone 590 | under the age of 13. If You are a parent or guardian and You are 591 | aware that Your child has provided Us with Personal Data, please 592 | contact Us. If We become aware that We have collected Personal Data 593 | from anyone under the age of 13 without verification of parental 594 | consent, We take steps to remove that information from Our servers. 595 |

596 |

597 | If We need to rely on consent as a legal basis for processing Your 598 | information and Your country requires consent from a parent, We may 599 | require Your parent's consent before We collect and use that 600 | information. 601 |

602 |

603 | Links to Other Websites 604 |

605 |

606 | Our Service may contain links to other websites that are not 607 | operated by Us. If You click on a third party link, You will be 608 | directed to that third party's site. We strongly advise You to 609 | review the Privacy Policy of every site You visit. 610 |

611 |

612 | We have no control over and assume no responsibility for the 613 | content, privacy policies or practices of any third party sites or 614 | services. 615 |

616 |

617 | Changes to this Privacy Policy 618 |

619 |

620 | We may update Our Privacy Policy from time to time. We will notify 621 | You of any changes by posting the new Privacy Policy on this page. 622 |

623 |

624 | We will let You know via email and/or a prominent notice on Our 625 | Service, prior to the change becoming effective and update the 626 | "Last updated" date at the top of this Privacy Policy. 627 |

628 |

629 | You are advised to review this Privacy Policy periodically for any 630 | changes. Changes to this Privacy Policy are effective when they are 631 | posted on this page. 632 |

633 |

634 | Contact Us 635 |

636 |

637 | If you have any questions about this Privacy Policy, You can contact 638 | us: 639 |

640 |
    641 |
  • By email: contact@salimi.my
  • 642 |
643 |
644 |
645 | 646 | ); 647 | } 648 | 649 | export default PrivacyPolicy; 650 | --------------------------------------------------------------------------------