├── .eslintrc.json
├── public
├── favicon.ico
└── vercel.svg
├── postcss.config.js
├── next.config.js
├── pages
├── api
│ ├── hello.js
│ └── auth
│ │ └── [...nextauth].js
├── _app.js
├── index.js
└── auth
│ └── signin.js
├── atom
├── userAtom.js
└── modalAtom.js
├── styles
└── globals.css
├── tailwind.config.js
├── components
├── Story.js
├── Posts.js
├── Feed.js
├── MiniProfile.js
├── Stories.js
├── Suggestions.js
├── Header.js
├── UploadModal.js
└── Post.js
├── .gitignore
├── package.json
├── firebase.js
└── README.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahandghavidel/insta-v4/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | images: {
4 | domains: ["www.jennexplores.com", "upload.wikimedia.org"],
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/atom/userAtom.js:
--------------------------------------------------------------------------------
1 | import { atom } from "recoil";
2 |
3 | export const userState = atom({
4 | key: "userState", // unique ID (with respect to other atoms/selectors)
5 | default: null, // default value (aka initial value)
6 | });
7 |
--------------------------------------------------------------------------------
/atom/modalAtom.js:
--------------------------------------------------------------------------------
1 | import { atom } from "recoil";
2 |
3 | export const modalState = atom({
4 | key: "modalState", // unique ID (with respect to other atoms/selectors)
5 | default: false, // default value (aka initial value)
6 | });
7 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer components {
6 | .btn {
7 | @apply h-7 hover:scale-125 transition-transform duration-200 ease-out cursor-pointer
8 | }
9 | }
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./pages/**/*.{js,ts,jsx,tsx}",
4 | "./components/**/*.{js,ts,jsx,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [require('@tailwindcss/forms'), require('tailwind-scrollbar')],
10 | }
--------------------------------------------------------------------------------
/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: { session, ...pageProps } }) {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default MyApp;
16 |
--------------------------------------------------------------------------------
/components/Story.js:
--------------------------------------------------------------------------------
1 | import {PlusIcon} from "@heroicons/react/solid"
2 | export default function Story({img, username, isUser}) {
3 | return
4 |
5 | {isUser &&
}
6 |
{username}
7 |
;
8 | }
9 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import Feed from "../components/Feed";
3 | import Header from "../components/Header";
4 | import UploadModal from "../components/UploadModal";
5 |
6 | export default function Home() {
7 | return (
8 |
9 |
10 |
Instagram App
11 |
12 |
13 |
14 |
15 | {/* Header */}
16 |
17 |
18 |
19 | {/* Feed */}
20 |
21 |
22 |
23 | {/* Modal */}
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].js:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth";
2 | import GoogleProvider from "next-auth/providers/google";
3 |
4 | export default NextAuth({
5 | // Configure one or more authentication providers
6 | providers: [
7 | GoogleProvider({
8 | clientId: process.env.GOOGLE_CLIENT_ID,
9 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
10 | }),
11 | // ...add more providers here
12 | ],
13 | secret: process.env.SECRET,
14 | pages: {
15 | signIn: "/auth/signin",
16 | },
17 |
18 | callbacks: {
19 | async session({ session, token, user }) {
20 | session.user.username = session.user.name
21 | .split(" ")
22 | .join("")
23 | .toLocaleLowerCase();
24 | session.user.uid = token.sub;
25 | return session;
26 | },
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "insta-v4",
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 | "@heroicons/react": "^1.0.5",
12 | "@tailwindcss/forms": "^0.4.0",
13 | "firebase": "^9.6.6",
14 | "minifaker": "^1.34.0",
15 | "moment": "^2.29.1",
16 | "next": "12.0.9",
17 | "next-auth": "^4.2.1",
18 | "react": "17.0.2",
19 | "react-dom": "17.0.2",
20 | "react-modal": "^3.14.4",
21 | "react-moment": "^1.1.1",
22 | "recoil": "^0.6.1"
23 | },
24 | "devDependencies": {
25 | "autoprefixer": "^10.4.2",
26 | "eslint": "8.8.0",
27 | "eslint-config-next": "12.0.9",
28 | "postcss": "^8.4.5",
29 | "tailwind-scrollbar": "^1.3.1",
30 | "tailwindcss": "^3.0.18"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/components/Posts.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import Post from "./Post";
3 | import { collection, onSnapshot, orderBy, query } from "firebase/firestore";
4 | import { db } from "../firebase";
5 |
6 | export default function Posts() {
7 | const [posts, setPosts] = useState([]);
8 | useEffect(() => {
9 | const unsubscribe = onSnapshot(
10 | query(collection(db, "posts"), orderBy("timestamp", "desc")),
11 | (snapshot) => {
12 | setPosts(snapshot.docs);
13 | }
14 | );
15 | return unsubscribe;
16 | }, [db]);
17 | return (
18 |
19 | {posts.map((post) => (
20 |
28 | ))}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/components/Feed.js:
--------------------------------------------------------------------------------
1 | import { useRecoilState } from "recoil";
2 | import { userState } from "../atom/userAtom";
3 | import MiniProfile from "./MiniProfile";
4 | import Posts from "./Posts";
5 | import Stories from "./Stories";
6 | import Suggestions from "./Suggestions";
7 |
8 | export default function Feed() {
9 | const [currentUser] = useRecoilState(userState)
10 | return (
11 |
12 |
13 | {/* Stories */}
14 |
15 |
16 | {/* Posts */}
17 |
18 |
19 |
20 |
21 |
22 | {/* Mini Profile */}
23 |
24 |
25 | {/* Suggestions */}
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/components/MiniProfile.js:
--------------------------------------------------------------------------------
1 | import { getAuth, signOut } from "firebase/auth";
2 | import { useRecoilState } from "recoil";
3 | import { userState } from "../atom/userAtom";
4 |
5 | export default function MiniProfile() {
6 | const [currentUser, setCurrentUser] = useRecoilState(userState);
7 | const auth = getAuth();
8 | function onSignOut() {
9 | signOut(auth);
10 | setCurrentUser(null);
11 | }
12 | return (
13 |
14 |
19 |
20 |
{currentUser?.username}
21 | Welcome to instagram
22 |
23 |
27 | Sign out
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/firebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp, getApp, getApps } from "firebase/app";
3 | import { getFirestore } from "firebase/firestore";
4 | import { getStorage } from "firebase/storage";
5 | // TODO: Add SDKs for Firebase products that you want to use
6 | // https://firebase.google.com/docs/web/setup#available-libraries
7 |
8 | // Your web app's Firebase configuration
9 | const firebaseConfig = {
10 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
11 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
12 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
13 | storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
14 | messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGE_SENDER_ID,
15 | appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
16 | };
17 |
18 | // Initialize Firebase
19 | const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
20 | const db = getFirestore();
21 | const storage = getStorage();
22 |
23 | export { app, db, storage };
24 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/components/Stories.js:
--------------------------------------------------------------------------------
1 | import minifaker from "minifaker";
2 | import "minifaker/locales/en";
3 | import { useEffect, useState } from "react";
4 | import { useRecoilState } from "recoil";
5 | import { userState } from "../atom/userAtom";
6 | import Story from "./Story";
7 |
8 | export default function Stories() {
9 | const [storyUsers, setSoryUsers] = useState([]);
10 | const [currentUser] = useRecoilState(userState)
11 | useEffect(() => {
12 | const storyUsers = minifaker.array(20, (i) => ({
13 | username: minifaker.username({ locale: "en" }).toLowerCase(),
14 | img: `https://i.pravatar.cc/150?img=${Math.ceil(Math.random() * 70)}`,
15 | id: i,
16 | }));
17 | setSoryUsers(storyUsers);
18 | console.log(storyUsers);
19 | }, []);
20 | return (
21 |
22 | {currentUser && (
23 |
24 | )}
25 | {storyUsers.map((user) => (
26 |
27 | ))}
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/components/Suggestions.js:
--------------------------------------------------------------------------------
1 | import minifaker from "minifaker";
2 | import "minifaker/locales/en";
3 | import { useEffect, useState } from "react";
4 |
5 | export default function Suggestions() {
6 | const [suggestions, setSuggestions] = useState([]);
7 | useEffect(() => {
8 | const suggestions = minifaker.array(5, (i) => ({
9 | username: minifaker.username({ locale: "en" }).toLowerCase(),
10 | jobTitle: minifaker.jobTitle(),
11 | id: i,
12 | }));
13 | setSuggestions(suggestions);
14 | }, []);
15 | return (
16 |
17 |
18 |
Suggestion for you
19 | See all
20 |
21 | {suggestions.map((suggestion) => (
22 |
26 |
33 |
34 |
{suggestion.username}
35 |
36 | {suggestion.jobTitle}
37 |
38 |
39 |
40 | Follow
41 |
42 |
43 | ))}
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/pages/auth/signin.js:
--------------------------------------------------------------------------------
1 | import Header from "../../components/Header";
2 | import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
3 | import { db } from "../../firebase";
4 | import { doc, getDoc, serverTimestamp, setDoc } from "firebase/firestore";
5 | import { useRouter } from "next/router";
6 | export default function Signin() {
7 | const router = useRouter();
8 | async function onGoogleClick() {
9 | try {
10 | const auth = getAuth();
11 | const provider = new GoogleAuthProvider();
12 | await signInWithPopup(auth, provider);
13 | const user = auth.currentUser.providerData[0];
14 | const docRef = doc(db, "users", user.uid);
15 | const docSnap = await getDoc(docRef);
16 | if (!docSnap.exists()) {
17 | await setDoc(docRef, {
18 | name: user.displayName,
19 | email: user.email,
20 | userImg: user.photoURL,
21 | uid: user.uid,
22 | timestamp: serverTimestamp(),
23 | username: user.displayName.split(" ").join("").toLocaleLowerCase(),
24 | });
25 | }
26 | router.push("/");
27 | } catch (error) {
28 | console.log(error);
29 | }
30 | }
31 | return (
32 | <>
33 |
34 |
35 |
40 |
41 |
42 |
47 |
48 | This app is created for learning purposes
49 |
50 |
54 | Sign in with Google
55 |
56 |
57 |
58 |
59 | >
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { useEffect } from "react";
3 | import { SearchIcon, PlusCircleIcon } from "@heroicons/react/outline";
4 | import { HomeIcon } from "@heroicons/react/solid";
5 | import { useRecoilState } from "recoil";
6 | import { modalState } from "../atom/modalAtom";
7 | import { useRouter } from "next/router";
8 | import { getAuth, onAuthStateChanged, signOut } from "firebase/auth";
9 | import { doc, getDoc } from "firebase/firestore";
10 | import { userState } from "../atom/userAtom";
11 | import { db } from "../firebase";
12 | export default function Header() {
13 | const [open, setOpen] = useRecoilState(modalState);
14 | const [currentUser, setCurrentUser] = useRecoilState(userState);
15 | const router = useRouter();
16 | const auth = getAuth();
17 |
18 | useEffect(() => {
19 | onAuthStateChanged(auth, (user) => {
20 | if (user) {
21 | const fetchUser = async () => {
22 | console.log(user);
23 | const docRef = doc(
24 | db,
25 | "users",
26 | user.auth.currentUser.providerData[0].uid
27 | );
28 | const docSnap = await getDoc(docRef);
29 | if (docSnap.exists()) {
30 | setCurrentUser(docSnap.data());
31 | }
32 | };
33 | fetchUser();
34 | }
35 | });
36 | }, []);
37 |
38 | function onSignOut() {
39 | signOut(auth);
40 | setCurrentUser(null);
41 | }
42 | return (
43 |
44 |
45 | {/* Left */}
46 |
47 | router.push("/")}
52 | />
53 |
54 |
55 | router.push("/")}
60 | />
61 |
62 | {/* Middle */}
63 |
64 |
74 |
75 | {/* Right */}
76 |
77 |
78 |
router.push("/")}
80 | className="hidden md:inline-flex h-6 cursor-pointer hover:scale-125 transition-tranform duration-200 ease-out"
81 | />
82 | {currentUser ? (
83 | <>
84 | setOpen(true)}
86 | className="h-6 cursor-pointer hover:scale-125 transition-tranform duration-200 ease-out"
87 | />
88 |
94 | >
95 | ) : (
96 | router.push("/auth/signin")}>Sign in
97 | )}
98 |
99 |
100 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/components/UploadModal.js:
--------------------------------------------------------------------------------
1 | import { useRecoilState } from "recoil";
2 | import { modalState } from "../atom/modalAtom";
3 | import Modal from "react-modal";
4 | import { CameraIcon } from "@heroicons/react/outline";
5 | import { useRef, useState } from "react";
6 | import {
7 | addDoc,
8 | collection,
9 | doc,
10 | serverTimestamp,
11 | updateDoc,
12 | } from "firebase/firestore";
13 | import { db, storage } from "../firebase";
14 | import { getDownloadURL, ref, uploadString } from "firebase/storage";
15 | import { userState } from "../atom/userAtom";
16 |
17 | export default function UploadModal() {
18 | const [open, setOpen] = useRecoilState(modalState);
19 | const [selectedFile, setSelectedFile] = useState(null);
20 | const [loading, setLoading] = useState(false);
21 | const [currentUser] = useRecoilState(userState)
22 | async function uploadPost() {
23 | if (loading) return;
24 |
25 | setLoading(true);
26 |
27 | const docRef = await addDoc(collection(db, "posts"), {
28 | caption: captionRef.current.value,
29 | username: currentUser?.username,
30 | profileImg: currentUser.userImg,
31 | timestamp: serverTimestamp(),
32 | });
33 |
34 | const imageRef = ref(storage, `posts/${docRef.id}/image`);
35 | await uploadString(imageRef, selectedFile, "data_url").then(
36 | async (snapshot) => {
37 | const downloadURL = await getDownloadURL(imageRef);
38 | await updateDoc(doc(db, "posts", docRef.id), {
39 | image: downloadURL,
40 | });
41 | }
42 | );
43 | setOpen(false);
44 | setLoading(false);
45 | setSelectedFile(null);
46 | }
47 |
48 | function addImageToPost(event) {
49 | const reader = new FileReader();
50 | if (event.target.files[0]) {
51 | reader.readAsDataURL(event.target.files[0]);
52 | }
53 |
54 | reader.onload = (readerEvent) => {
55 | setSelectedFile(readerEvent.target.result);
56 | };
57 | }
58 | const filePickerRef = useRef(null);
59 | const captionRef = useRef(null);
60 | return (
61 |
62 | {open && (
63 |
{
67 | setOpen(false);
68 | setSelectedFile(null);
69 | }}
70 | >
71 |
107 |
108 | )}
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/components/Post.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Moment from "react-moment";
3 | import { db } from "../firebase";
4 | import {
5 | DotsHorizontalIcon,
6 | HeartIcon,
7 | ChatIcon,
8 | BookmarkIcon,
9 | EmojiHappyIcon,
10 | } from "@heroicons/react/outline";
11 | import { HeartIcon as HeartIconFilled } from "@heroicons/react/solid";
12 | import {
13 | addDoc,
14 | collection,
15 | deleteDoc,
16 | doc,
17 | onSnapshot,
18 | orderBy,
19 | query,
20 | serverTimestamp,
21 | setDoc,
22 | } from "firebase/firestore";
23 | import { useRecoilState } from "recoil";
24 | import { userState } from "../atom/userAtom";
25 | export default function Post({ img, userImg, caption, username, id }) {
26 | const [comment, setComment] = useState("");
27 | const [comments, setComments] = useState([]);
28 | const [likes, setLikes] = useState([]);
29 | const [hasLiked, setHasLiked] = useState(false);
30 | const [currentUser] = useRecoilState(userState)
31 | useEffect(() => {
32 | const unsubscribe = onSnapshot(
33 | query(
34 | collection(db, "posts", id, "comments"),
35 | orderBy("timestamp", "desc")
36 | ),
37 | (snapshot) => {
38 | setComments(snapshot.docs);
39 | }
40 | );
41 | }, [db, id]);
42 | useEffect(() => {
43 | const unsubscribe = onSnapshot(
44 | collection(db, "posts", id, "likes"),
45 | (snapshot) => setLikes(snapshot.docs)
46 | );
47 | }, [db]);
48 | useEffect(() => {
49 | setHasLiked(
50 | likes.findIndex((like) => like.id === currentUser?.uid) !== -1
51 | );
52 | }, [likes]);
53 | async function likePost() {
54 | if (hasLiked) {
55 | await deleteDoc(doc(db, "posts", id, "likes", currentUser?.uid));
56 | } else {
57 | await setDoc(doc(db, "posts", id, "likes", currentUser?.uid), {
58 | username: currentUser?.username,
59 | });
60 | }
61 | }
62 | async function sendComment(event) {
63 | event.preventDefault();
64 | const commentToSend = comment;
65 | setComment("");
66 | await addDoc(collection(db, "posts", id, "comments"), {
67 | comment: commentToSend,
68 | username: currentUser?.username,
69 | userImage: currentUser?.userImg,
70 | timestamp: serverTimestamp(),
71 | });
72 | }
73 | return (
74 |
75 | {/* Post Header */}
76 |
77 |
78 |
83 |
{username}
84 |
85 |
86 |
87 | {/* Post Image */}
88 |
89 |
90 |
91 | {/* Post Buttons */}
92 |
93 | {currentUser && (
94 |
95 |
96 | {hasLiked ? (
97 |
101 | ) : (
102 |
103 | )}
104 |
105 |
106 |
107 |
108 |
109 | )}
110 |
111 | {/* Post comments */}
112 |
113 |
114 | {likes.length > 0 && (
115 |
{likes.length} likes
116 | )}
117 |
{username}
118 | {caption}
119 |
120 | {comments.length > 0 && (
121 |
122 | {comments.map((comment) => (
123 |
127 |
132 |
{comment.data().username}
133 |
{comment.data().comment}
134 |
{comment.data().timestamp?.toDate()}
135 |
136 | ))}
137 |
138 | )}
139 |
140 | {/* Post input box */}
141 | {currentUser && (
142 |
160 | )}
161 |
162 | );
163 | }
164 |
--------------------------------------------------------------------------------