├── public ├── robots.txt ├── icons.png ├── phone.png ├── favicon.ico ├── icons-2.png ├── loading.gif ├── Grandista.ttf ├── Grandista.woff ├── ig-mocked.png ├── instakilo.jpg ├── Grandista.woff2 ├── google-play.png ├── favicon-16x16.png ├── favicon-32x32.png ├── microsoft-store.png ├── apple-touch-icon.png ├── complete-loading.gif ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── site.webmanifest ├── home.svg ├── vercel.svg └── instagram-logo-icon.svg ├── screenshots ├── preview.jpg ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png └── screenshot-4.png ├── postcss.config.js ├── atoms └── modalAtom.tsx ├── next.config.js ├── components ├── feed │ ├── Comment.tsx │ ├── Story.tsx │ ├── Feed.tsx │ ├── Profile.tsx │ ├── Stories.tsx │ ├── Posts.tsx │ ├── Suggestions.tsx │ ├── Footer.tsx │ └── Post.tsx ├── sidebar │ ├── SidebarRow.tsx │ ├── Sidebar.tsx │ └── More.tsx └── modal │ ├── DisclaimerModal.tsx │ └── InputModal.tsx ├── pages ├── _app.tsx ├── api │ └── auth │ │ └── [...nextauth].ts ├── index.tsx └── login.tsx ├── .gitignore ├── tailwind.config.js ├── tsconfig.json ├── .env.local.example ├── firebase.js ├── package.json ├── styles └── globals.css ├── README.md └── yarn.lock /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /public/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/icons.png -------------------------------------------------------------------------------- /public/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/phone.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/favicon.ico -------------------------------------------------------------------------------- /public/icons-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/icons-2.png -------------------------------------------------------------------------------- /public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/loading.gif -------------------------------------------------------------------------------- /public/Grandista.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/Grandista.ttf -------------------------------------------------------------------------------- /public/Grandista.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/Grandista.woff -------------------------------------------------------------------------------- /public/ig-mocked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/ig-mocked.png -------------------------------------------------------------------------------- /public/instakilo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/instakilo.jpg -------------------------------------------------------------------------------- /public/Grandista.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/Grandista.woff2 -------------------------------------------------------------------------------- /public/google-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/google-play.png -------------------------------------------------------------------------------- /screenshots/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/screenshots/preview.jpg -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/microsoft-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/microsoft-store.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/complete-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/complete-loading.gif -------------------------------------------------------------------------------- /screenshots/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/screenshots/screenshot-1.png -------------------------------------------------------------------------------- /screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/screenshots/screenshot-2.png -------------------------------------------------------------------------------- /screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/screenshots/screenshot-3.png -------------------------------------------------------------------------------- /screenshots/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/screenshots/screenshot-4.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salimi-my/instagram-clone/main/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /atoms/modalAtom.tsx: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const modalState = atom({ 4 | key: 'modalState', 5 | default: false 6 | }); 7 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | images: { 5 | domains: [ 6 | 'firebasestorage.googleapis.com', 7 | 'cloudflare-ipfs.com', 8 | 'lh3.googleusercontent.com' 9 | ] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /components/feed/Comment.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Comment({ username, comment }: any) { 4 | return ( 5 |

6 | {username} 7 | {comment} 8 |

9 | ); 10 | } 11 | 12 | export default Comment; 13 | -------------------------------------------------------------------------------- /public/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import type { AppProps } from 'next/app'; 3 | import { SessionProvider } from 'next-auth/react'; 4 | import { RecoilRoot } from 'recoil'; 5 | 6 | function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default MyApp; 17 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx}', 5 | './components/**/*.{js,ts,jsx,tsx}', 6 | './app/**/*.{js,ts,jsx,tsx}' 7 | ], 8 | theme: { 9 | extend: { 10 | fontFamily: { 11 | grandista: ['Grandista', 'cursive'] 12 | }, 13 | screens: { 14 | '3xl': '1920px' 15 | } 16 | } 17 | }, 18 | plugins: [ 19 | require('@tailwindcss/line-clamp'), 20 | require('tailwind-scrollbar-hide') 21 | ] 22 | }; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/api/auth/[...nextauth].ts"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import GoogleProvider from 'next-auth/providers/google'; 3 | 4 | export default NextAuth({ 5 | providers: [ 6 | GoogleProvider({ 7 | clientId: process.env.GOOGLE_CLIENT_ID!, 8 | clientSecret: process.env.GOOGLE_CLIENT_SECRET! 9 | }) 10 | ], 11 | secret: process.env.NEXT_PUBLIC_SECRET, 12 | pages: { 13 | signIn: '/login' 14 | }, 15 | callbacks: { 16 | async session({ session, token }:any) { 17 | session.user.username = session.user.name.split(' ').join('').toLocaleLowerCase(); 18 | session.user.uid = token.sub; 19 | return session; 20 | } 21 | } 22 | }); -------------------------------------------------------------------------------- /components/feed/Story.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | function Story({ username, avatar }: any) { 5 | return ( 6 |
7 |
8 |
9 | {username} 16 |
17 |
18 |

{username.toLowerCase()}

19 |
20 | ); 21 | } 22 | 23 | export default Story; 24 | -------------------------------------------------------------------------------- /.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 | GOOGLE_CLIENT_ID=Add your Google Client ID here 11 | GOOGLE_CLIENT_SECRET=Add Google 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 14 | RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /components/feed/Feed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Footer from './Footer'; 3 | import Posts from './Posts'; 4 | import Profile from './Profile'; 5 | import Stories from './Stories'; 6 | import Suggestions from './Suggestions'; 7 | 8 | function Feed({ ssrPosts }: any) { 9 | return ( 10 |
11 |
12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 |
21 |
22 | ); 23 | } 24 | 25 | export default Feed; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "@faker-js/faker": "^7.6.0", 10 | "@headlessui/react": "^1.7.4", 11 | "@heroicons/react": "^2.0.13", 12 | "@tailwindcss/line-clamp": "^0.4.2", 13 | "firebase": "^10.12.2", 14 | "moment": "^2.29.4", 15 | "next": "latest", 16 | "next-auth": "^4.24.10", 17 | "react": "18.2.0", 18 | "react-dom": "18.2.0", 19 | "react-moment": "^1.1.2", 20 | "recoil": "^0.7.6", 21 | "swiper": "^8.4.4", 22 | "tailwind-scrollbar-hide": "^1.1.7" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "18.11.3", 26 | "@types/react": "18.0.21", 27 | "@types/react-dom": "18.0.6", 28 | "autoprefixer": "^10.4.12", 29 | "postcss": "^8.4.31", 30 | "tailwindcss": "^3.2.1", 31 | "typescript": "4.8.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /components/feed/Profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | import { useSession } from 'next-auth/react'; 4 | 5 | function Profile() { 6 | const { data: session }: any = useSession(); 7 | return ( 8 |
9 |
10 | {session?.user?.name 17 |
18 |

{session?.user?.username}

19 |

20 | {session?.user?.name} 21 |

22 |
23 |
24 |

25 | Switch 26 |

27 |
28 | ); 29 | } 30 | 31 | export default Profile; 32 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /components/sidebar/SidebarRow.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import React from 'react'; 3 | 4 | function SidebarRow({ src, path, title, active }: any) { 5 | return ( 6 |
7 | {src && ( 8 | {title} 15 | )} 16 | {path && ( 17 | 27 | {path} 28 | 29 | )} 30 | {active && ( 31 |

32 | {title} 33 |

34 | )} 35 | {!active && ( 36 |

37 | {title} 38 |

39 | )} 40 |
41 | ); 42 | } 43 | 44 | export default SidebarRow; 45 | -------------------------------------------------------------------------------- /components/feed/Stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { faker } from '@faker-js/faker/locale/en_US'; 3 | import { Swiper, SwiperSlide } from 'swiper/react'; 4 | import { Navigation } from 'swiper'; 5 | import 'swiper/css'; 6 | import 'swiper/css/navigation'; 7 | import Story from './Story'; 8 | 9 | function Stories() { 10 | const [users, setUsers] = useState([]); 11 | 12 | useEffect(() => { 13 | const fakers = [...Array(24)].map((_, i) => ({ 14 | id: i, 15 | username: faker.internet.userName(), 16 | avatar: faker.image.avatar() 17 | })); 18 | 19 | setUsers(fakers); 20 | }, []); 21 | 22 | return ( 23 |
24 | 33 | {users.map(({ id, username, avatar }: any) => { 34 | return ( 35 | 36 | 37 | 38 | ); 39 | })} 40 | 41 |
42 | ); 43 | } 44 | 45 | export default Stories; 46 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @font-face { 6 | font-family: 'Grandista'; 7 | src: url('../public/Grandista.woff2') format('woff2'), 8 | url('../public/Grandista.woff') format('woff'), 9 | url('../public/Grandista.ttf') format('truetype'); 10 | } 11 | 12 | :root { 13 | --swiper-navigation-size: 12px !important; 14 | } 15 | 16 | .swiper-button-next, 17 | .swiper-button-prev { 18 | background-image: url('/icons.png'); 19 | border: none !important; 20 | justify-self: center !important; 21 | outline: none !important; 22 | padding: 16px 8px !important; 23 | position: absolute !important; 24 | top: 50% !important; 25 | transform: translateY(-50%) !important; 26 | margin: 0 !important; 27 | } 28 | 29 | .swiper-button-next { 30 | background-repeat: no-repeat; 31 | background-position: -294px -273px; 32 | height: 45px !important; 33 | width: 45px !important; 34 | } 35 | 36 | .swiper-button-prev { 37 | background-repeat: no-repeat; 38 | background-position: -294px -226px; 39 | height: 45px !important; 40 | width: 45px !important; 41 | } 42 | 43 | .swiper-button-next.swiper-button-disabled, 44 | .swiper-button-prev.swiper-button-disabled { 45 | opacity: 0 !important; 46 | cursor: auto; 47 | pointer-events: none; 48 | } 49 | 50 | .swiper-slide { 51 | width: 14.6% !important; 52 | } 53 | 54 | .swiper-button-next:after, 55 | .swiper-button-prev:after { 56 | display: none; 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Instakilo](https://instakilo.salimi.my) · [![Author Salimi](https://img.shields.io/badge/Author-Salimi-%3C%3E)](https://www.linkedin.com/in/mohamad-salimi/) 2 | 3 | This is Instagram clone created using Next.js for educational purposes. User can login using Google login API, make posts with images, like posts and comment on posts. 4 | 5 | ## Exact copy of Instagram UI 6 | 7 | - This is a clone of Instagram app 8 | - Login using NextAuth.js through Google 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://instakilo.salimi.my) to visit. 36 |
37 | Direct link: `https://instakilo.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 new post 54 | 55 | ![Menu](/screenshots/screenshot-4.png) 56 | -------------------------------------------------------------------------------- /components/feed/Posts.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Post from './Post'; 3 | import { db } from '../../firebase'; 4 | import { collection, query, orderBy, onSnapshot } from 'firebase/firestore'; 5 | 6 | function Posts({ ssrPosts }: any) { 7 | const [posts, setPosts] = useState([]); 8 | 9 | useEffect( 10 | () => 11 | onSnapshot( 12 | query(collection(db, 'posts'), orderBy('timestamp', 'desc')), 13 | (snapshot: any) => { 14 | setPosts( 15 | snapshot.docs.map((doc: any) => ({ ...doc.data(), id: doc.id })) 16 | ); 17 | } 18 | ), 19 | [] 20 | ); 21 | 22 | return ( 23 | <> 24 | {posts 25 | ? posts.map((post: any) => ( 26 | 35 | )) 36 | : ssrPosts.map((post: any) => ( 37 | 46 | ))} 47 | 48 | ); 49 | } 50 | 51 | export default Posts; 52 | -------------------------------------------------------------------------------- /public/instagram-logo-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/feed/Suggestions.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { faker } from '@faker-js/faker/locale/en_US'; 3 | import Image from 'next/image'; 4 | 5 | function Suggestions() { 6 | const [users, setUsers] = useState([]); 7 | 8 | useEffect(() => { 9 | const fakers = [...Array(5)].map((_, i) => ({ 10 | id: i, 11 | username: faker.internet.userName(), 12 | avatar: faker.image.avatar(), 13 | firstname: faker.name.firstName() 14 | })); 15 | 16 | setUsers(fakers); 17 | }, []); 18 | return ( 19 |
20 |
21 |

22 | Suggestions For You 23 |

24 |

See All

25 |
26 |
27 | {users.map(({ id, username, avatar, firstname }: any) => ( 28 |
29 |
30 | {username} 37 |
38 |

{username.toLowerCase()}

39 |

40 | Followed by {firstname.toLowerCase()} 41 |

42 |
43 |
44 |

45 | Follow 46 |

47 |
48 | ))} 49 |
50 |
51 | ); 52 | } 53 | 54 | export default Suggestions; 55 | -------------------------------------------------------------------------------- /components/feed/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Footer() { 4 | const currentDate = new Date(); 5 | let year = currentDate.getFullYear(); 6 | return ( 7 | 73 | ); 74 | } 75 | 76 | export default Footer; 77 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { getSession, GetSessionParams } from 'next-auth/react'; 2 | import type { NextPage } from 'next'; 3 | import { Fragment } from 'react'; 4 | import Head from 'next/head'; 5 | import Feed from '../components/feed/Feed'; 6 | import Sidebar from '../components/sidebar/Sidebar'; 7 | import InputModal from '../components/modal/InputModal'; 8 | import { db } from '../firebase'; 9 | import { collection, query, orderBy, getDocs } from 'firebase/firestore'; 10 | 11 | const Home: NextPage = ({ ssrPosts }: any) => { 12 | return ( 13 | 14 | 15 | Instakilo | Login 16 | 17 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 45 | 51 | 57 | 58 | 59 | 60 | 61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 |
69 | ); 70 | }; 71 | 72 | export default Home; 73 | 74 | export async function getServerSideProps( 75 | context: GetSessionParams | undefined 76 | ) { 77 | // Get the user 78 | const session = await getSession(context); 79 | 80 | const posts = await getDocs( 81 | query(collection(db, 'posts'), orderBy('timestamp', 'desc')) 82 | ); 83 | 84 | const docs = posts.docs.map((post) => ({ 85 | id: post.id, 86 | ...post.data(), 87 | timestamp: null 88 | })); 89 | 90 | if (session) { 91 | return { 92 | props: { 93 | session, 94 | ssrPosts: docs 95 | } 96 | }; 97 | } else if (!session) { 98 | return { 99 | redirect: { 100 | permanent: false, 101 | destination: '/login' 102 | } 103 | }; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /components/modal/DisclaimerModal.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from '@headlessui/react'; 2 | import { Fragment, useState } from 'react'; 3 | 4 | export default function MyModal() { 5 | let [isOpen, setIsOpen] = useState(true); 6 | 7 | function closeModal() { 8 | setIsOpen(false); 9 | } 10 | 11 | function openModal() { 12 | setIsOpen(true); 13 | } 14 | 15 | return ( 16 | <> 17 | 18 | 19 | 28 |
29 | 30 | 31 |
32 |
33 | 42 | 43 | 47 | This is NOT REAL Instagram 48 | 49 |
50 |

51 | This site is not the real Instagram. I created this 52 | site for educational purposes only. Under no circumstance 53 | shall I have any liability to you for any loss or damage 54 | of any kind incurred as a result of the use of the site or 55 | reliance on any information provided on the site. Your use 56 | of the site and your reliance on any information on the 57 | site is solely at your own risk. 58 |

59 |
60 | 61 |
62 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 77 | ); 78 | } 79 | 80 | function Alert() { 81 | return ( 82 | 90 | 95 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /components/sidebar/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | import SidebarRow from './SidebarRow'; 4 | import More from './More'; 5 | import { useSession } from 'next-auth/react'; 6 | import { useSetRecoilState } from 'recoil'; 7 | import { modalState } from '../../atoms/modalAtom'; 8 | 9 | function Sidebar() { 10 | const { data: session } = useSession(); 11 | const setOpen = useSetRecoilState(modalState); 12 | 13 | return ( 14 |
15 | 20 |
21 | Instakilo 27 |
28 | 29 |
30 | } title='Home' active={true} /> 31 | } title='Search' /> 32 | } title='Explore' /> 33 | } title='Messages' /> 34 | } title='Notifications' /> 35 | 44 | 45 |
46 | 47 |
48 | ); 49 | } 50 | 51 | export default Sidebar; 52 | 53 | function HomeIcon() { 54 | return ( 55 | 56 | ); 57 | } 58 | 59 | function SearchIcon() { 60 | return ( 61 | <> 62 | 70 | 81 | 82 | ); 83 | } 84 | 85 | function ExploreIcon() { 86 | return ( 87 | <> 88 | 96 | 100 | 110 | 111 | ); 112 | } 113 | 114 | function MessagesIcon() { 115 | return ( 116 | <> 117 | 124 | 128 | 129 | ); 130 | } 131 | 132 | function NotificationsIcon() { 133 | return ( 134 | 135 | ); 136 | } 137 | 138 | function CreateIcon() { 139 | return ( 140 | <> 141 | 149 | 160 | 171 | 172 | ); 173 | } 174 | -------------------------------------------------------------------------------- /components/sidebar/More.tsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from '@headlessui/react'; 2 | import { Fragment } from 'react'; 3 | import { signOut } from 'next-auth/react'; 4 | 5 | export default function More() { 6 | return ( 7 |
8 | 9 |
10 | 11 | 12 |

13 | More 14 |

15 |
16 |
17 | 26 | 27 |
28 | 29 | {({ active }) => ( 30 | 38 | )} 39 | 40 |
41 |
42 | 43 | {({ active }) => ( 44 | 52 | )} 53 | 54 |
55 |
56 | 57 | {({ active }) => ( 58 | 66 | )} 67 | 68 |
69 |
70 | 71 | {({ active }) => ( 72 | 79 | )} 80 | 81 |
82 |
83 | 84 | {({ active }) => ( 85 | 98 | )} 99 | 100 |
101 |
102 |
103 |
104 |
105 | ); 106 | } 107 | 108 | function SettingsIcon() { 109 | return ( 110 | 120 | 130 | 137 | 138 | ); 139 | } 140 | 141 | function SavedIcon() { 142 | return ( 143 | 153 | 161 | 162 | ); 163 | } 164 | 165 | function ReportIcon() { 166 | return ( 167 | 177 | 178 | 179 | ); 180 | } 181 | 182 | function MenuIcon() { 183 | return ( 184 | 194 | 205 | 216 | 227 | 228 | ); 229 | } 230 | -------------------------------------------------------------------------------- /pages/login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { getProviders, signIn } from 'next-auth/react'; 3 | import Head from 'next/head'; 4 | import Image from 'next/image'; 5 | import DisclaimerModal from '../components/modal/DisclaimerModal'; 6 | 7 | export default function login({ providers }: any) { 8 | const [loggingIn, setLoggingIn] = useState(false); 9 | const currentDate = new Date(); 10 | let year = currentDate.getFullYear(); 11 | return ( 12 | <> 13 | 14 | Instakilo 15 | 16 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 44 | 50 | 56 | 57 | 58 | 59 |
60 |
61 |
62 |
63 | phone mocked 70 |
71 |
72 |
73 |
74 | 75 |

Instakilo

76 |
77 | 85 | 93 | {Object.values(providers).map((provider: any) => ( 94 | 130 | ))} 131 |
132 | 133 | 134 | or 135 | 136 | 137 |
138 | 147 | 151 | Forgot password? 152 | 153 |
154 |
155 | Don't have an account? 156 | 157 | Sign up 158 | 159 |
160 |
161 | Get the app 162 |
163 | Get it on Google Play 170 | Get it from Microsoft 177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |

185 | Disclaimer: This site is not the real Instagram. Under no 186 | circumstance shall I have any liability to you for any loss or 187 | damage of any kind incurred as a result of the use of the site 188 | or reliance on any information provided on the site. Your use of 189 | the site and your reliance on any information on the site is 190 | solely at your own risk. 191 |

192 |
193 |

194 | Instagram Clone © {year} Created by{' '} 195 | 196 | Salimi 197 | 198 |

199 |
200 |
201 |
202 | 203 | 204 | ); 205 | } 206 | 207 | export async function getServerSideProps() { 208 | const providers = await getProviders(); 209 | 210 | return { 211 | props: { 212 | providers 213 | } 214 | }; 215 | } 216 | -------------------------------------------------------------------------------- /components/feed/Post.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import { useSession } from 'next-auth/react'; 3 | import Moment from 'react-moment'; 4 | import Image from 'next/image'; 5 | import { db } from '../../firebase'; 6 | import { 7 | addDoc, 8 | collection, 9 | deleteDoc, 10 | doc, 11 | onSnapshot, 12 | orderBy, 13 | query, 14 | setDoc, 15 | serverTimestamp 16 | } from 'firebase/firestore'; 17 | import Comment from './Comment'; 18 | 19 | function Post({ id, username, avatar, image, caption, timestamp }: any) { 20 | const { data: session }: any = useSession(); 21 | const [comments, setComments] = useState([]); 22 | const [likes, setLikes] = useState([]); 23 | const [hasLiked, setHasLiked] = useState(false); 24 | const [buttonEnable, setButtonEnable] = useState(false); 25 | const [showComment, setShowComment] = useState(false); 26 | const commentRef: any = useRef(null); 27 | 28 | useEffect( 29 | () => 30 | onSnapshot( 31 | query( 32 | collection(db, 'posts', id, 'comments'), 33 | orderBy('timestamp', 'desc') 34 | ), 35 | (snapshot) => { 36 | setComments( 37 | snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })) 38 | ); 39 | } 40 | ), 41 | [id] 42 | ); 43 | 44 | useEffect( 45 | () => 46 | onSnapshot(collection(db, 'posts', id, 'likes'), (snapshot) => 47 | setLikes(snapshot.docs) 48 | ), 49 | [id] 50 | ); 51 | 52 | useEffect( 53 | () => 54 | setHasLiked( 55 | likes.findIndex((like) => like.id === session.user.uid) !== -1 56 | ), 57 | [likes, session] 58 | ); 59 | 60 | const buttonHandler = (e: any) => { 61 | if (e.target.value.length > 0) { 62 | setButtonEnable(true); 63 | } else { 64 | setButtonEnable(false); 65 | } 66 | }; 67 | 68 | const sendComment = async (e: any) => { 69 | e.preventDefault(); 70 | 71 | if (!commentRef.current.value.trim()) return; 72 | 73 | const commentToSend = commentRef.current.value; 74 | commentRef.current.value = ''; 75 | 76 | await addDoc(collection(db, 'posts', id, 'comments'), { 77 | comment: commentToSend, 78 | username: session.user.username, 79 | avatar: session.user.image, 80 | timestamp: serverTimestamp() 81 | }); 82 | }; 83 | 84 | const likePost = async () => { 85 | if (hasLiked) { 86 | await deleteDoc(doc(db, 'posts', id, 'likes', session.user.uid)); 87 | } else { 88 | await setDoc(doc(db, 'posts', id, 'likes', session.user.uid), { 89 | username: session.user.username 90 | }); 91 | } 92 | }; 93 | 94 | return ( 95 |
96 |
97 |
98 |
99 |
100 | {username} 107 |
108 |
109 |

{username.toLowerCase()}

110 |
111 | 112 |
113 | {username} 120 |
121 |
122 | 130 | 131 | 132 |
133 | 134 |
135 | {likes.length > 0 && ( 136 |

137 | {likes.length} Likes 138 |

139 | )} 140 |

141 | {username} 142 | {caption} 143 |

144 | {comments.length > 0 && ( 145 | 159 | )} 160 | 161 | {showComment && 162 | comments.length > 0 && 163 | comments.map((comment: any) => ( 164 | 169 | ))} 170 | 171 | {timestamp ? ( 172 |

173 | 174 | {timestamp?.toDate()} 175 | {' '} 176 | AGO 177 |

178 | ) : ( 179 |

180 | LOADING... 181 |

182 | )} 183 | 184 |
185 | 186 |