diff --git a/package-lock.json b/package-lock.json index 57bfc92..aa10319 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "dependencies": { "@sendgrid/mail": "^8.1.3", + "date-fns": "^3.6.0", + "emoji-picker-react": "^4.9.4", "firebase": "^10.12.2", "firebase-admin": "^12.1.1", "node-mailjet": "^6.0.5", @@ -22,7 +24,8 @@ "react-router-dom": "^6.23.1", "react-slider": "^2.0.6", "s": "^1.0.0", - "styled-components": "^6.1.11" + "styled-components": "^6.1.11", + "zustand": "^4.5.2" }, "devDependencies": { "@types/react": "^18.2.66", @@ -2022,7 +2025,7 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true + "devOptional": true }, "node_modules/@types/qs": { "version": "6.9.15", @@ -2038,7 +2041,7 @@ "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2751,6 +2754,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2891,6 +2903,20 @@ "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==", "dev": true }, + "node_modules/emoji-picker-react": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.9.4.tgz", + "integrity": "sha512-u/3IsmMPr3l9Tcsuwpw4VgHb34W0BjC6iE06EXNHNOnNjBtW0BUN8upba8V3piUht00Sa+/L1+hs/xVmdWsswA==", + "dependencies": { + "flairup": "0.0.39" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3624,6 +3650,11 @@ } } }, + "node_modules/flairup": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/flairup/-/flairup-0.0.39.tgz", + "integrity": "sha512-UVPkzZmZeBWBx1+Ovo++kYKk9Wi32Jxt+c7HsxnEY80ExwFV54w+NyquFziqMLS0BnGVE43yGD4OvIwaAm/WiQ==" + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -6533,6 +6564,14 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6846,6 +6885,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 571d3b0..54c99e2 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ }, "dependencies": { "@sendgrid/mail": "^8.1.3", + "date-fns": "^3.6.0", + "emoji-picker-react": "^4.9.4", "firebase": "^10.12.2", "firebase-admin": "^12.1.1", "node-mailjet": "^6.0.5", @@ -24,7 +26,8 @@ "react-router-dom": "^6.23.1", "react-slider": "^2.0.6", "s": "^1.0.0", - "styled-components": "^6.1.11" + "styled-components": "^6.1.11", + "zustand": "^4.5.2" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/src/App.jsx b/src/App.jsx index 2194968..658efd5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,34 +8,38 @@ import LoginSignupForm from "./pages/registration/LoginSignupForm"; import UserProfile from "./pages/userprofile/UserProfile"; import SearchPage from "./pages/search/SearchPage"; import ListingPage from "./pages/listing/Listing"; -import ProductPage from "./pages/viewproduct/ViewProduct"; -import LikesPage from "./pages/likes/Likes"; +import Chat from "./pages/chatapp/ChatApp"; import ReviewPage from "./pages/review/Review"; import NotificationsPage from "./pages/notificaiton/Notificaitons"; +import ProductPage from "./pages/viewproduct/ViewProduct"; +import LikesPage from "./pages/likes/Likes"; const App = () => { - return ( - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - ); +return ( + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + +); } export default App; \ No newline at end of file diff --git a/src/Auth.jsx b/src/Auth.jsx index 9779663..6af317f 100644 --- a/src/Auth.jsx +++ b/src/Auth.jsx @@ -1,23 +1,31 @@ import React, { createContext, useState, useEffect, useContext } from 'react'; import { onAuthStateChanged } from 'firebase/auth'; -import { auth } from './firebase/firebaseConfig'; +import { auth } from './lib/firebaseConfig'; +import { useUserStore } from '/src/lib/userStore'; const AuthContext = createContext(); export const useAuth = () => useContext(AuthContext); export const AuthProvider = ({ children }) => { + const { fetchUserInfo } = useUserStore(); const [currentUser, setCurrentUser] = useState(null); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { - setCurrentUser(user); + if (user) { + setCurrentUser(user); + fetchUserInfo(user.uid); + } else { + setCurrentUser(null); + fetchUserInfo(null); + } }); return unsubscribe; - }, []); + }, [fetchUserInfo]); return ( - + {children} ); diff --git a/src/assets/chat-icons/arrowDown.png b/src/assets/chat-icons/arrowDown.png new file mode 100644 index 0000000..1b2fd66 Binary files /dev/null and b/src/assets/chat-icons/arrowDown.png differ diff --git a/src/assets/chat-icons/arrowUp.png b/src/assets/chat-icons/arrowUp.png new file mode 100644 index 0000000..2c47028 Binary files /dev/null and b/src/assets/chat-icons/arrowUp.png differ diff --git a/src/assets/chat-icons/avatar.png b/src/assets/chat-icons/avatar.png new file mode 100644 index 0000000..115daeb Binary files /dev/null and b/src/assets/chat-icons/avatar.png differ diff --git a/src/assets/chat-icons/camera.png b/src/assets/chat-icons/camera.png new file mode 100644 index 0000000..743ff91 Binary files /dev/null and b/src/assets/chat-icons/camera.png differ diff --git a/src/assets/chat-icons/download.png b/src/assets/chat-icons/download.png new file mode 100644 index 0000000..d14ae60 Binary files /dev/null and b/src/assets/chat-icons/download.png differ diff --git a/src/assets/chat-icons/edit.png b/src/assets/chat-icons/edit.png new file mode 100644 index 0000000..d3176a2 Binary files /dev/null and b/src/assets/chat-icons/edit.png differ diff --git a/src/assets/chat-icons/emoji.png b/src/assets/chat-icons/emoji.png new file mode 100644 index 0000000..6f0b96f Binary files /dev/null and b/src/assets/chat-icons/emoji.png differ diff --git a/src/assets/chat-icons/img.png b/src/assets/chat-icons/img.png new file mode 100644 index 0000000..47017ff Binary files /dev/null and b/src/assets/chat-icons/img.png differ diff --git a/src/assets/chat-icons/info.png b/src/assets/chat-icons/info.png new file mode 100644 index 0000000..81dc9e2 Binary files /dev/null and b/src/assets/chat-icons/info.png differ diff --git a/src/assets/chat-icons/mic.png b/src/assets/chat-icons/mic.png new file mode 100644 index 0000000..48cc58b Binary files /dev/null and b/src/assets/chat-icons/mic.png differ diff --git a/src/assets/chat-icons/minus.png b/src/assets/chat-icons/minus.png new file mode 100644 index 0000000..5c9b91b Binary files /dev/null and b/src/assets/chat-icons/minus.png differ diff --git a/src/assets/chat-icons/more.png b/src/assets/chat-icons/more.png new file mode 100644 index 0000000..3f26cef Binary files /dev/null and b/src/assets/chat-icons/more.png differ diff --git a/src/assets/chat-icons/phone.png b/src/assets/chat-icons/phone.png new file mode 100644 index 0000000..abc1211 Binary files /dev/null and b/src/assets/chat-icons/phone.png differ diff --git a/src/assets/chat-icons/plus.png b/src/assets/chat-icons/plus.png new file mode 100644 index 0000000..fba9bd8 Binary files /dev/null and b/src/assets/chat-icons/plus.png differ diff --git a/src/assets/chat-icons/search.png b/src/assets/chat-icons/search.png new file mode 100644 index 0000000..271677d Binary files /dev/null and b/src/assets/chat-icons/search.png differ diff --git a/src/assets/chat-icons/theme.png b/src/assets/chat-icons/theme.png new file mode 100644 index 0000000..d1d1b3d Binary files /dev/null and b/src/assets/chat-icons/theme.png differ diff --git a/src/assets/chat-icons/video.png b/src/assets/chat-icons/video.png new file mode 100644 index 0000000..28b9f72 Binary files /dev/null and b/src/assets/chat-icons/video.png differ diff --git a/src/components/chatComp/chatApp_chat/Chat.css b/src/components/chatComp/chatApp_chat/Chat.css new file mode 100644 index 0000000..d59be59 --- /dev/null +++ b/src/components/chatComp/chatApp_chat/Chat.css @@ -0,0 +1,186 @@ +.chat { + flex: 2; + border-left: 1px solid #07060635; + border-right: 1px solid #07060635; + height: 100%; + display: flex; + flex-direction: column; + + .top { + padding: 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #07060635; + + .user { + display: flex; + align-items: center; + gap: 20px; + + img { + width: 60px; + height: 60px; + border-radius: 50%; + object-fit: cover; + } + + .texts { + display: flex; + flex-direction: column; + gap: 5px; + + span { + font-size: 18px; + font-weight: bold; + } + + p { + font-size: 14px; + font-weight: 300; + color: #a5a5a5; + } + } + } + + .icons { + display: flex; + gap: 20px; + + img { + width: 20px; + height: 20px; + } + } + } + + .center { + padding: 20px; + flex: 1; + overflow: auto; + display: flex; + flex-direction: column; + gap: 20px; + + .message { + max-width: 70%; + display: flex; + gap: 20px; + + &.own { + align-self: flex-end; + + .texts { + text-align: right; + + p { + background-color: rgba(30, 83, 241, 0.45); + } + } + } + + img { + width: 30px; + height: 30px; + border-radius: 50%; + object-fit: cover; + } + + .texts { + max-width: 100%; + width: auto; + word-break: break-word; + display: inline-block; + vertical-align: top; + + img { + width: 100%; + height: 300px; + border-radius: 10px; + object-fit: cover; + } + + p { + display: inline-block; + padding: 15px; + background-color: rgba(19, 32, 68, 0.45); + border-radius: 10px; + word-break: break-word; + white-space: normal; + max-width: 100%; + } + + span { + display: block; + font-size: 13px; + } + } + } + } + + .bottom { + padding: 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-top: 1px solid #07060635; + gap: 20px; + margin-top: auto; + + .icons { + display: flex; + gap: 20px; + } + + img { + height: 20px; + width: 20px; + cursor: pointer; + } + + input { + flex: 1; + background-color: #aeadad74; + border: none; + outline: none; + color: white; + padding: 20px; + border-radius: 10px; + font-size: 16px; + + &:disabled { + cursor: not-allowed; + } + } + + .emoji { + position: relative; + + img.disabled { + cursor: not-allowed; + } + + .picker { + position: absolute; + bottom: 50px; + left: 0; + + } + + } + + .sendButton { + background-color: #879DE6; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + + &:disabled { + background-color: #97a9e3; + cursor: not-allowed; + } + } + } +} diff --git a/src/components/chatComp/chatApp_chat/Chat.jsx b/src/components/chatComp/chatApp_chat/Chat.jsx new file mode 100644 index 0000000..23a6cd5 --- /dev/null +++ b/src/components/chatComp/chatApp_chat/Chat.jsx @@ -0,0 +1,181 @@ +import { useState, useRef, useEffect } from 'react'; +import './Chat.css'; +import EmojiPicker from 'emoji-picker-react'; +import { arrayUnion, doc, getDoc, onSnapshot, updateDoc } from 'firebase/firestore'; +import { db } from '../../../lib/firebaseConfig'; +import { useChatStore } from '../../../lib/chatStore'; +import { useUserStore } from '../../../lib/userStore'; +import upload from '../../../lib/upload'; +import { formatDistanceToNow, isValid } from 'date-fns'; + +const Chat = () => { + const [chat, setChat] = useState(); + const [open, setOpen] = useState(false); + const [text, setText] = useState(""); + const [img, setImg] = useState({ + file: null, + url: "", + }); + + const { currentUser } = useUserStore(); + const { chatId, user, isCurrentUserBlocked, isReceiverBlocked } = useChatStore(); + + const endRef = useRef(null); + + const scrollToBottom = () => { + if (endRef.current) { + endRef.current.scrollIntoView({ behavior: "smooth" }); + } + }; + + useEffect(() => { + scrollToBottom(); + }, []); + + useEffect(() => { + scrollToBottom(); + }, [chat]); + + useEffect(() => { + const unSub = onSnapshot(doc(db, "Chats", chatId),(res) => { + setChat(res.data()); + }) + + return () => { + unSub(); + }; + },[chatId]); + + const handleEmoji = (e) => { + setText(prev => prev + e.emoji); + setOpen(false); + }; + + const handleImg = (e) => { + if (e.target.files[0]) { + setImg({ + file: e.target.files[0], + url: URL.createObjectURL(e.target.files[0]), + }); + } + }; + + const handleSend = async () => { + if (text === "") return; + + let imgUrl = null; + + try { + if (img.file) { + imgUrl = await upload(img.file, chatId); + } + + await updateDoc(doc(db, "Chats", chatId), { + messages: arrayUnion({ + senderId: currentUser.id, + text, + createdAt: new Date().toString(), + ...(imgUrl && { img: imgUrl }), + }), + }); + + const userIDs = [currentUser.id, user.id]; + + userIDs.forEach(async (id) => { + const userChatsRef = doc(db, "UserChats", id); + const userChatsSnapshot = await getDoc(userChatsRef); + + if (userChatsSnapshot.exists()) { + const userChatsData = userChatsSnapshot.data(); + + const chatIndex = userChatsData.chats.findIndex( + c => c.chatId === chatId + ); + + userChatsData.chats[chatIndex].lastMessage = text; + userChatsData.chats[chatIndex].isSeen = + id === currentUser.id ? true : false; + userChatsData.chats[chatIndex].updatedAt = Date.now(); + + await updateDoc(userChatsRef, { + chats: userChatsData.chats, + }); + } + }); + } catch (err) { + console.log(err); + } + + setImg({ + file: null, + url: "" + }); + + setText(""); + } + + return ( +
+
+
+ +
+ {user?.username} +
+
+
+
+
+
+ {chat?.messages?.map((message) => { + const parsedDate = new Date(message.createdAt); + return ( +
+
+ {message.img && } +

{message.text}

+ {isValid(parsedDate) ? formatDistanceToNow(parsedDate, { addSuffix: true }) : "Invalid date"} +
+
+ ); + })} + {img.url &&
+
+ +
+
} +
+
+
+
+ + +
+ setText(e.target.value)} + disabled={isCurrentUserBlocked || isReceiverBlocked} /> +
+ !isCurrentUserBlocked && !isReceiverBlocked && setOpen((prev) => !prev)} + className={(isCurrentUserBlocked || isReceiverBlocked) ? 'disabled' : ''} + /> +
+ +
+
+ +
+
+ ) +}; + +export default Chat; \ No newline at end of file diff --git a/src/components/chatComp/chatApp_detail/Detail.css b/src/components/chatComp/chatApp_detail/Detail.css new file mode 100644 index 0000000..0c281f8 --- /dev/null +++ b/src/components/chatComp/chatApp_detail/Detail.css @@ -0,0 +1,111 @@ +.detail { + flex: 1; + + .user { + padding: 30px 20px; + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + border-bottom: 1px solid #07060635; + + img { + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; + } + + h2 { + padding: 0; + margin: 0; + font-size: 28px; + } + + p { + padding: 0; + margin: 0; + font-size: 14px; + } + } + + .info { + padding: 20px; + display: flex; + flex-direction: column; + gap: 25px; + + .option { + + .setting { + display: flex; + align-items: center; + justify-content: space-between; + + img { + width: 30px; + height: 30px; + background-color: #09021735; + padding: 10px; + border-radius: 50%; + cursor: pointer; + } + } + + .photos { + display: flex; + flex-direction: column; + gap: 20px; + margin-top: 20px; + + .photoItem { + display: flex; + align-items: center; + justify-content: space-between; + + .photoDetail { + display: flex; + align-items: center; + gap: 20px; + + img { + width: 40px; + height: 40px; + border-radius: 5px; + object-fit: cover; + } + + span { + font-size: 14px; + color: rgba(255, 255, 255, 0.837); + font-weight: 300; + } + } + + .icon { + width: 30px; + height: 30px; + background-color: #09021735; + padding: 10px; + border-radius: 50%; + cursor: pointer; + } + } + } + } + + button { + padding: 16px 20px; + background-color: rgba(189, 6, 6, 0.432); + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 200ms ease-in-out; + + &:hover { + background-color: rgba(189, 6, 6, 0.653); + } + } + } +} \ No newline at end of file diff --git a/src/components/chatComp/chatApp_detail/Detail.jsx b/src/components/chatComp/chatApp_detail/Detail.jsx new file mode 100644 index 0000000..83583a5 --- /dev/null +++ b/src/components/chatComp/chatApp_detail/Detail.jsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from 'react'; +import { arrayRemove, arrayUnion, doc, updateDoc, collection, query, where, onSnapshot } from 'firebase/firestore'; +import { useChatStore } from '../../../lib/chatStore'; +import { useUserStore } from '../../../lib/userStore'; +import { db } from '../../../lib/firebaseConfig'; +import './Detail.css'; + +const Detail = () => { + const { chatId, user, isCurrentUserBlocked, isReceiverBlocked, changeBlock } = useChatStore(); + const { currentUser } = useUserStore(); + const [photos, setPhotos] = useState([]); + const [showPhotos, setShowPhotos] = useState(false); + const [icon, setIcon] = useState("/src/assets/chat-icons/arrowDown.png"); + + useEffect(() => { + if (!chatId) return; + + const photosRef = collection(db, 'photos'); + const q = query(photosRef, where('chatId', '==', chatId)); + + const unsubscribe = onSnapshot(q, (querySnapshot) => { + const photosData = []; + querySnapshot.forEach((doc) => { + photosData.push({ id: doc.id, ...doc.data() }); + }); + setPhotos(photosData); + }); + + return () => unsubscribe(); + }, [chatId]); + + const handleBlock = async () => { + if (!user) return; + + const userDocRef = doc(db, "Users", currentUser.id); + + try { + await updateDoc(userDocRef, { + blocked: isReceiverBlocked ? arrayRemove(user.id) : arrayUnion(user.id), + }); + changeBlock(); + } catch (err) { + console.log(err); + } + } + + const toggleShowPhotos = () => { + setShowPhotos(!showPhotos); + setIcon(showPhotos ? "/src/assets/chat-icons/arrowDown.png" : "/src/assets/chat-icons/arrowUp.png"); + }; + + return ( +
+
+ +

{user?.username}

+
+
+
+
+ Shared Photos + +
+ {showPhotos && ( +
+ {photos.map((photo) => ( +
+
+ + {photo.id} +
+ + Download + +
+ ))} +
+ )} +
+ + +
+
+ ) +}; + +export default Detail; diff --git a/src/components/chatComp/chatApp_list/List.css b/src/components/chatComp/chatApp_list/List.css new file mode 100644 index 0000000..21b150b --- /dev/null +++ b/src/components/chatComp/chatApp_list/List.css @@ -0,0 +1,5 @@ +.list { + flex: 1; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/components/chatComp/chatApp_list/List.jsx b/src/components/chatComp/chatApp_list/List.jsx new file mode 100644 index 0000000..cc9efa7 --- /dev/null +++ b/src/components/chatComp/chatApp_list/List.jsx @@ -0,0 +1,14 @@ +import './List.css'; +import UserInfo from './userInfo/UserInfo'; +import ChatList from './chatList/ChatList'; + +const List = () => { + return ( +
+ + +
+ ) +}; + +export default List; \ No newline at end of file diff --git a/src/components/chatComp/chatApp_list/addUser/addUser.css b/src/components/chatComp/chatApp_list/addUser/addUser.css new file mode 100644 index 0000000..dbeae0d --- /dev/null +++ b/src/components/chatComp/chatApp_list/addUser/addUser.css @@ -0,0 +1,72 @@ +.addUser { + width: max-content; + height: max-content; + padding: 30px; + background-color: rgba(9, 11, 13, 0.696); + border-radius: 10px; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + + form { + display: flex; + gap: 20px; + + input { + padding: 20px; + border-radius: 10px; + border: none; + outline: none; + } + + button { + padding: 20px; + border-radius: 10px; + background-color: #879DE6; + color: white; + border: none; + cursor: pointer; + } + } + + .user { + margin-top: 50px; + display: flex; + align-items: center; + justify-content: space-between; + + .detail { + display: flex; + align-items: center; + gap: 20px; + + img { + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; + } + } + + button { + padding: 10px; + border-radius: 10px; + background-color: #879DE6; + color: white; + border: none; + cursor: pointer; + } + } + + .error { + margin-top: 50px; + padding: 16px; + border-radius: 10px; + color: white; + text-align: center; + font-size: 15px; + } +} \ No newline at end of file diff --git a/src/components/chatComp/chatApp_list/addUser/addUser.jsx b/src/components/chatComp/chatApp_list/addUser/addUser.jsx new file mode 100644 index 0000000..dc2fb1c --- /dev/null +++ b/src/components/chatComp/chatApp_list/addUser/addUser.jsx @@ -0,0 +1,96 @@ +import { arrayUnion, collection, doc, getDocs, query, serverTimestamp, setDoc, updateDoc, where } from "firebase/firestore"; +import "./addUser.css" +import { db } from "../../../../lib/firebaseConfig"; +import { useState } from "react"; +import { useUserStore } from "../../../../lib/userStore"; + +const addUser = () => { + const [user, setUser] = useState(null); + const [error, setError] = useState(""); + const { currentUser } = useUserStore(); + + const handleSearch = async e => { + e.preventDefault(); + const formData = new FormData(e.target); + const username = formData.get("username"); + + try { + const userRef = collection(db, "Users"); + const q = query(userRef, where("username", "==", username)); + const querySnapShot = await getDocs(q); + + if (!querySnapShot.empty) { + setUser(querySnapShot.docs[0].data()); + setError(""); + } else { + setUser(null); + setError("User not found."); + } + } catch (err) { + console.log(err); + setUser(null); + setError("User not found."); + } + }; + + const handleAdd = async () => { + + const chatRef = collection(db, "Chats"); + const userChatsRef = collection(db, "UserChats"); + + try { + const newChatRef = doc(chatRef); + + await setDoc(newChatRef, { + createdAt: serverTimestamp(), + messages:[], + }); + + await updateDoc(doc(userChatsRef, user.id), { + chats: arrayUnion({ + chatId: newChatRef.id, + lastMessage: "", + receiverId: currentUser.id, + updatedAt: Date.now(), + }), + }); + + await updateDoc(doc(userChatsRef, currentUser.id), { + chats: arrayUnion({ + chatId: newChatRef.id, + lastMessage: "", + receiverId: user.id, + updatedAt: Date.now(), + }), + }); + + } catch (err) { + console.log(err); + } + } + + return ( +
+
+ + +
+ {user ? ( +
+
+ + {user.username} +
+ +
+ ) : ( + error && +
+ {error} +
+ )} +
+ ); +}; + +export default addUser; \ No newline at end of file diff --git a/src/components/chatComp/chatApp_list/chatList/ChatList.css b/src/components/chatComp/chatApp_list/chatList/ChatList.css new file mode 100644 index 0000000..afb546f --- /dev/null +++ b/src/components/chatComp/chatApp_list/chatList/ChatList.css @@ -0,0 +1,80 @@ +.chatList { + flex: 1; + overflow: auto; + + .search { + display: flex; + align-items: center; + gap: 20px; + padding: 20px; + + .searchBar { + flex: 1; + background-color: #aeadad74; + display: flex; + align-items: center; + gap: 20px; + border-radius: 10px; + padding: 10px; + + input { + background-color: transparent; + border: none; + outline: none; + color: white; + flex: 1; + } + + img { + width: 20px; + height: 20px; + } + } + + .add { + width: 36px; + height: 36px; + background-color: #aeadad74; + padding: 10px; + border-radius: 10px; + cursor: pointer; + } + } + + .item { + display: flex; + align-items: center; + gap: 20px; + padding: 15px; + cursor: pointer; + border-bottom: 1px solid #07060635; + + img { + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; + } + + .texts { + display: flex; + flex-direction: column; + gap: 10px; + + span { + font-weight: 500; + font-size: 16px; + } + + p { + font-weight: 300; + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + margin: 0; + } + } + } +} \ No newline at end of file diff --git a/src/components/chatComp/chatApp_list/chatList/ChatList.jsx b/src/components/chatComp/chatApp_list/chatList/ChatList.jsx new file mode 100644 index 0000000..e8150df --- /dev/null +++ b/src/components/chatComp/chatApp_list/chatList/ChatList.jsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from 'react'; +import './ChatList.css'; +import AddUser from '/src/components/chatComp/chatApp_list/addUser/addUser.jsx'; +import { useUserStore } from '../../../../lib/userStore'; +import { doc, onSnapshot, getDoc, updateDoc } from 'firebase/firestore'; +import { db } from '../../../../lib/firebaseConfig'; +import { useChatStore } from '../../../../lib/chatStore'; + +const ChatList = () => { + const [chats, setChats] = useState([]); + const [addMode, setAddMode] = useState(false); + const [input, setInput] = useState(""); + + const { currentUser } = useUserStore(); + const { changeChat } = useChatStore(); + + useEffect(() => { + if (!currentUser || !currentUser.id) { + console.error("Current user is not defined"); + return; + } + + const unSub = onSnapshot(doc(db, "UserChats", currentUser.id), async (res) => { + try { + const items = res.data()?.chats || []; + + const promises = items.map(async (item) => { + const userDocRef = doc(db, "Users", item.receiverId); + const userDocSnap = await getDoc(userDocRef); + + const user = userDocSnap.data(); + + return { ...item, user }; + }); + + const chatData = await Promise.all(promises); + + setChats(chatData.sort((a, b) => b.updatedAt - a.updatedAt)); + } catch (error) { + console.error("Error fetching chat data:", error); + } + }); + + return () => { + unSub(); + }; + }, [currentUser]); + + const handleSelect = async (chat) => { + + const userChats = chats.map((item) => { + const { user, ...rest } = item; + return rest; + }); + + const chatIndex = userChats.findIndex(item => item.chatId === chat.chatId); + userChats[chatIndex].isSeen = true; + + const userChatsRef = doc(db, "UserChats", currentUser.id); + + try { + await updateDoc(userChatsRef, { + chats: userChats, + }) + changeChat(chat.chatId, chat.user); + + } catch (err) { + console.log(err); + } + }; + + const filteredChats = chats.filter((c) => + c.user.username.toLowerCase().includes(input.toLowerCase()) + ); + + return ( +
+
+
+ + setInput(e.target.value)}/> +
+ setAddMode((prev) => !prev)} /> +
+ {filteredChats.map((chat) => ( +
handleSelect(chat)} + style= {{ + backgroundColor: chat?.isSeen ? "transparent" : "#5083FE", + }}> + +
+ {chat.user.blocked.includes(currentUser.id) + ? "User" + : chat.user.username} + +

{chat.lastMessage}

+
+
+ ))} + + {addMode && } +
+ ); +}; + +export default ChatList; diff --git a/src/components/chatComp/chatApp_list/userInfo/UserInfo.css b/src/components/chatComp/chatApp_list/userInfo/UserInfo.css new file mode 100644 index 0000000..d39371b --- /dev/null +++ b/src/components/chatComp/chatApp_list/userInfo/UserInfo.css @@ -0,0 +1,40 @@ +.userInfo { + padding: 20px; + display: flex; + align-items: center; + justify-content: space-between; + + .user { + display: flex; + align-items: center; + gap: 20px; + flex-grow: 1; + + h2 { + font-size: 18px; + margin: 0; + white-space: nowrap; + overflow: hidden; + flex-grow: 1; + padding-right: 20px; + } + + img{ + width: 50px; + height: 50px; + border-radius: 50%; + object-fit: cover; + } + } + + .icons { + display: flex; + gap: 20px; + + img { + width: 20px; + height: 20px; + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/src/components/chatComp/chatApp_list/userInfo/UserInfo.jsx b/src/components/chatComp/chatApp_list/userInfo/UserInfo.jsx new file mode 100644 index 0000000..cbf53c0 --- /dev/null +++ b/src/components/chatComp/chatApp_list/userInfo/UserInfo.jsx @@ -0,0 +1,19 @@ +import { useUserStore } from '../../../../lib/userStore'; +import './UserInfo.css'; + +const UserInfo = () => { + const {currentUser } = useUserStore(); + + return ( +
+
+ +

{currentUser.username}

+
+
+
+
+ ) +}; + +export default UserInfo; \ No newline at end of file diff --git a/src/components/header/Header.jsx b/src/components/header/Header.jsx index e5d1b24..ec5748d 100644 --- a/src/components/header/Header.jsx +++ b/src/components/header/Header.jsx @@ -4,7 +4,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { useLikes } from './likecounter/LikeCounter'; import { FaComment, FaHeart, FaUserAlt, FaBell } from 'react-icons/fa'; import { query, collection, where, getDocs } from 'firebase/firestore'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import SearchBar from '../searchbar/Searchbar'; import MechHub_Logo from "../../assets/Logo/MechHub_logo.png"; @@ -54,7 +54,7 @@ function Header() { }; const handleChats = () => { - navigate(`/`); + window.location.href = `/chat/${currentUser.uid}`; }; const handleNotifs = () => { diff --git a/src/components/header/likecounter/LikeCounter.jsx b/src/components/header/likecounter/LikeCounter.jsx index d5fe89f..5639564 100644 --- a/src/components/header/likecounter/LikeCounter.jsx +++ b/src/components/header/likecounter/LikeCounter.jsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; -import { db } from '../../../firebase/firebaseConfig'; +import { db } from '../../../lib/firebaseConfig'; import { doc, getDoc, setDoc, onSnapshot, collection } from 'firebase/firestore'; import { useAuth } from '../../../Auth'; diff --git a/src/components/product cards/FeaturedProduct.css b/src/components/product cards/FeaturedProduct.css new file mode 100644 index 0000000..c1e2dfc --- /dev/null +++ b/src/components/product cards/FeaturedProduct.css @@ -0,0 +1,60 @@ +.product { + display: grid; + margin: 0 40px 40px 40px; + border: 1px solid #eee; + padding: 20px; + width: 250px; + border-radius: 15px; + overflow: hidden; + box-shadow: 5px 5px 10px #ddd; + transition: 0.5s all ease-out; +} + +.product p { + margin-top: 10px; +} + +.product:hover { + transform: translateY(-5px); + } + +.product img { + height: 200px; + justify-self: center; +} + +.product h4 { + margin: 20px 0 0 0; +} + +.product h5 { + margin: 10px 0 0 0; + font-weight: 500; +} + + +h2 { + text-align: center; + color: rgb(232, 139, 45); + margin: 20px 40px; + position: relative; + line-height: 60px; +} + +h2::after { + content: " "; + background: #d55200; + width: 80px; + height: 5px; + border-radius: 5px; + position: absolute; + left: 50%; + transform: translate(-50%); +} + +.products-list { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; +} \ No newline at end of file diff --git a/src/components/productcards/ProductCards.jsx b/src/components/productcards/ProductCards.jsx index 08a8393..14a78e1 100644 --- a/src/components/productcards/ProductCards.jsx +++ b/src/components/productcards/ProductCards.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { FaRegHeart, FaHeart } from "react-icons/fa"; import { useNavigate } from 'react-router-dom'; import { addDoc, collection, doc, getDoc, setDoc, deleteDoc, query, where, getDocs, updateDoc } from 'firebase/firestore'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { useAuth } from '../../Auth'; import { useLikes } from '../header/likecounter/LikeCounter'; diff --git a/src/lib/chatStore.js b/src/lib/chatStore.js new file mode 100644 index 0000000..378e8c8 --- /dev/null +++ b/src/lib/chatStore.js @@ -0,0 +1,57 @@ +import { create } from 'zustand'; +import { useUserStore } from './userStore'; + +export const useChatStore = create((set) => ({ + chatId: null, + user: null, + isCurrentUserBlocked: false, + isReceiverBlocked: false, + changeChat: (chatId, user) => { + const currentUser = useUserStore.getState().currentUser; + console.log('Current User:', currentUser); + console.log('Selected User:', user); + + // Check if currentUser and user are defined and have the blocked property + if (!currentUser || !Array.isArray(currentUser.blocked)) { + console.error('Current user is undefined or does not have a blocked property'); + return; + } + + if (!user || !Array.isArray(user.blocked)) { + console.error('User is undefined or does not have a blocked property'); + return; + } + + + // Check if current user is blocked + if (user.blocked.includes(currentUser.id)) { + return set({ + chatId, + user: null, + isCurrentUserBlocked: true, + isReceiverBlocked: false, + }); + } + // Check if receiver is blocked + else if (currentUser.blocked.includes(user.id)) { + return set({ + chatId, + user: user, + isCurrentUserBlocked: false, + isReceiverBlocked: true, + }); + } else { + return set({ + chatId, + user, + isCurrentUserBlocked: false, + isReceiverBlocked: false, + }); + } + }, + + changeBlock: () => { + set(state => ({...state, isReceiverBlocked: !state.isReceiverBlocked})) + }, + +})); \ No newline at end of file diff --git a/src/firebase/firebaseConfig.jsx b/src/lib/firebaseConfig.js similarity index 65% rename from src/firebase/firebaseConfig.jsx rename to src/lib/firebaseConfig.js index f646e7d..0325caf 100644 --- a/src/firebase/firebaseConfig.jsx +++ b/src/lib/firebaseConfig.js @@ -1,11 +1,8 @@ -// Import the functions you need from the SDKs you need import { initializeApp } from "firebase/app"; -// import { getAnalytics } from "firebase/analytics"; import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; +import { getStorage } from "firebase/storage"; -// Your web app's Firebase configuration -// For Firebase JS SDK v7.20.0 and later, measurementId is optional const firebaseConfig = { apiKey: "AIzaSyDHGTNVbJZlm3XADrOrVc2Yyuo1mve39XU", authDomain: "orbital-mechhub.firebaseapp.com", @@ -16,11 +13,10 @@ const firebaseConfig = { measurementId: "G-ZNS909ZKGD" }; -// Initialize Firebase const app = initializeApp(firebaseConfig); -// const analytics = getAnalytics(app); const auth = getAuth(app); const db = getFirestore(app); +const storage = getStorage(app); -export { auth, db }; \ No newline at end of file +export { auth, db, storage }; \ No newline at end of file diff --git a/src/lib/upload.js b/src/lib/upload.js new file mode 100644 index 0000000..536039b --- /dev/null +++ b/src/lib/upload.js @@ -0,0 +1,34 @@ +import { getDownloadURL, uploadBytesResumable, ref } from 'firebase/storage'; +import { doc, setDoc } from 'firebase/firestore'; +import { storage, db } from './firebaseConfig'; + +const upload = async (file, chatId) => { + const date = new Date().toISOString(); + const storageRef = ref(storage, `images/${date}-${file.name}`); + const uploadTask = uploadBytesResumable(storageRef, file); + + return new Promise((resolve, reject) => { + uploadTask.on( + 'state_changed', + (snapshot) => { + const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + }, + (error) => { + reject("Something went wrong! " + error.code); + }, + async () => { + const downloadURL = await getDownloadURL(uploadTask.snapshot.ref); + const photoDocRef = doc(db, 'photos', `${date}-${file.name}`); + await setDoc(photoDocRef, { + url: downloadURL, + chatId: chatId, + uploadedAt: date + }); + resolve(downloadURL); + } + ); + }); +}; + +export default upload; diff --git a/src/lib/userStore.js b/src/lib/userStore.js new file mode 100644 index 0000000..ba82520 --- /dev/null +++ b/src/lib/userStore.js @@ -0,0 +1,27 @@ +import { create } from 'zustand' +import { db } from './firebaseConfig'; +import { doc, getDoc } from 'firebase/firestore'; + + export const useUserStore = create((set) => ({ + currentUser: null, + isLoading: true, + + fetchUserInfo: async (uid) => { + if (!uid) return set({currentUser:null, isLoading:false}); + + try { + const docRef = doc(db, "Users", uid); + const docSnap = await getDoc(docRef); + + if (docSnap.exists()) { + set({currentUser: docSnap.data(), isLoading: false}); + } else { + set({currentUser: null, isLoading: false}); + } + } catch (err) { + console.log(err); + return set({currentUser:null, isLoading:false}); + } + + }, +})); \ No newline at end of file diff --git a/src/pages/chatapp/ChatApp.css b/src/pages/chatapp/ChatApp.css new file mode 100644 index 0000000..3e8e689 --- /dev/null +++ b/src/pages/chatapp/ChatApp.css @@ -0,0 +1,53 @@ +.chat-page { + display:flex; + flex-direction: column; + flex: 1; + align-items: center; + justify-content: center; + height: 100vh; + color: white; + background-color: #FFF; + margin: 0; + padding: 0; + height: 100%; +} + +.chat-header { + position: fixed; + width: 100%; + top: 0; +} + +.chat-container { + padding: 0; + display: flex; + flex: 1; + margin-top: 64px; + box-sizing: border-box; + font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande", + "Lucida Sans Unicode", Geneva, Verdana, sans-serif; + width: 100%; + justify-content: center; + align-items: center; +} + + +.inner-container { + width: 80vw; + height: 90vh; + background-color: #aeadad74; + backdrop-filter: blur(19px) saturate(180%); + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.076); + display: flex; +} + +.loading { + display: flex; + justify-content: center; + align-items: center; + padding: 50px; + font-size: 36px; + border-radius: 10px; + background-color: rgba(9, 11, 13, 0.696); +} diff --git a/src/pages/chatapp/ChatApp.jsx b/src/pages/chatapp/ChatApp.jsx new file mode 100644 index 0000000..df58161 --- /dev/null +++ b/src/pages/chatapp/ChatApp.jsx @@ -0,0 +1,61 @@ +import './ChatApp.css'; +import { useEffect } from 'react'; +import { onAuthStateChanged } from 'firebase/auth'; +import { auth } from "/src/lib/firebaseConfig"; +import { useUserStore } from '../../lib/userStore.js'; + +import Chat from "../../components/chatComp/chatApp_chat/Chat.jsx"; +import Detail from "../../components/chatComp/chatApp_detail/Detail.jsx"; +import List from "../../components/chatComp/chatApp_list/List.jsx"; +import Header from "../../components/header/Header"; +import LoginSignupForm from "../../pages/registration/LoginSignupForm.jsx"; +import { useChatStore } from '../../lib/chatStore.js'; + +const ChatApp = () => { + const { currentUser, isLoading, fetchUserInfo } = useUserStore(); + const { chatId } = useChatStore(); + + useEffect(() => { + const unSub = onAuthStateChanged(auth, (user) => { + if (user) { + fetchUserInfo(user.uid); + } else { + fetchUserInfo(null); + } + }); + + return () => { + unSub(); + }; + }, [fetchUserInfo]); + + console.log(currentUser); + + if (isLoading) { + // return ; + return
Loading...
+ } + + return ( + <> +
+
+ {currentUser ? ( + <> +
+
+ + {chatId && } + {chatId && } +
+
+ + ) : ( + + )} +
+ + );`` +}; + +export default ChatApp; diff --git a/src/pages/landing/LandingPage.jsx b/src/pages/landing/LandingPage.jsx index f865f27..e09504a 100644 --- a/src/pages/landing/LandingPage.jsx +++ b/src/pages/landing/LandingPage.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { collection, getDocs, query, where, getDoc, doc } from "firebase/firestore"; -import { db } from "../../firebase/firebaseConfig"; +import { db } from "../../lib/firebaseConfig"; import { useAuth } from '../../Auth'; import Header from '../../components/header/Header'; diff --git a/src/pages/likes/Likes.jsx b/src/pages/likes/Likes.jsx index 2b6fcb5..d0a1f0b 100644 --- a/src/pages/likes/Likes.jsx +++ b/src/pages/likes/Likes.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { collection, getDocs, query, where, doc, getDoc } from 'firebase/firestore'; import { useAuth } from '../../Auth'; diff --git a/src/pages/listing/Listing.jsx b/src/pages/listing/Listing.jsx index 29b5e65..7ceaafe 100644 --- a/src/pages/listing/Listing.jsx +++ b/src/pages/listing/Listing.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { collection, addDoc, doc, getDoc, updateDoc, getDocs, query } from 'firebase/firestore'; import { useAuth } from '../../Auth'; import { useNavigate, useParams } from 'react-router-dom'; diff --git a/src/pages/notificaiton/Notificaitons.jsx b/src/pages/notificaiton/Notificaitons.jsx index d2613b2..9c67667 100644 --- a/src/pages/notificaiton/Notificaitons.jsx +++ b/src/pages/notificaiton/Notificaitons.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { collection, query, where, getDocs, updateDoc, doc, getDoc, deleteDoc } from 'firebase/firestore'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { useAuth } from '../../Auth'; import Header from '../../components/header/Header'; diff --git a/src/pages/registration/GoogleAuth.jsx b/src/pages/registration/GoogleAuth.jsx index 843ca05..3edcdad 100644 --- a/src/pages/registration/GoogleAuth.jsx +++ b/src/pages/registration/GoogleAuth.jsx @@ -1,5 +1,5 @@ import { GoogleAuthProvider, signInWithPopup } from "firebase/auth"; -import { auth, db } from "../../firebase/firebaseConfig"; +import { auth, db } from "../../lib/firebaseConfig"; import { doc, setDoc, getDoc } from "firebase/firestore"; import { toast } from 'react-hot-toast'; diff --git a/src/pages/registration/LoginSignupForm.css b/src/pages/registration/LoginSignupForm.css index 36ff031..46ed72b 100644 --- a/src/pages/registration/LoginSignupForm.css +++ b/src/pages/registration/LoginSignupForm.css @@ -1,8 +1,10 @@ @import url('https://fonts.googleapis.com/css?family=Montserrat:400,800'); + * { box-sizing: border-box; } + body { background: #f6f5f7; display: flex; @@ -10,15 +12,19 @@ body { align-items: center; flex-direction: column; font-family: 'Montserrat', sans-serif; - height: 100vh; - margin: -20px 0 50px; +} + +.login-signup-page * { + box-sizing: border-box; } .login-signup-container { + padding-top: 150px; h1 { font-weight: bold; margin: 0; + color: black; } h2 { diff --git a/src/pages/registration/LoginSignupForm.jsx b/src/pages/registration/LoginSignupForm.jsx index 4c70107..662d452 100644 --- a/src/pages/registration/LoginSignupForm.jsx +++ b/src/pages/registration/LoginSignupForm.jsx @@ -3,15 +3,14 @@ import GoogleLogo from '../../assets/google-logo.png'; import defaultProfile from '../../assets/defaultProfile.jpg'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-hot-toast'; - -import './LoginSignupForm.css'; -import Header from '../../components/header/Header'; - -import { auth, db } from "../../firebase/firebaseConfig"; +import { auth, db } from "../../lib/firebaseConfig"; import { signInWithEmailAndPassword, createUserWithEmailAndPassword } from "firebase/auth"; import { query, collection, where, getDocs, doc, setDoc } from "firebase/firestore"; import { signInWithGoogle } from './GoogleAuth'; +import './LoginSignupForm.css'; +import Header from '../../components/header/Header'; + function LoginSignUpForm() { const [rightPanelActive, setRightPanelActive] = useState(false); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); @@ -63,9 +62,17 @@ function LoginSignUpForm() { await setDoc(userDocRef, { username: username, email: user.email, + id : user.uid, profilePic: defaultProfile, + blocked: [], signUpDate: new Date().toISOString(), }); + + const userChatDoc = doc(db, "UserChats", user.uid); + await setDoc(userChatDoc, { + chats: [], + }); + console.log("User ID:", user.uid); toast.success("Account created successfully."); navigate(`/profile/${user.uid}`); diff --git a/src/pages/review/Review.jsx b/src/pages/review/Review.jsx index d218151..150e5bd 100644 --- a/src/pages/review/Review.jsx +++ b/src/pages/review/Review.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { useParams, useNavigate } from 'react-router-dom'; import { collection, doc, getDoc, addDoc } from 'firebase/firestore'; import { useAuth } from '../../Auth'; diff --git a/src/pages/search/SearchPage.jsx b/src/pages/search/SearchPage.jsx index 612df8a..6363021 100644 --- a/src/pages/search/SearchPage.jsx +++ b/src/pages/search/SearchPage.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { collection, getDocs, query, where, getDoc, doc } from 'firebase/firestore'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { useAuth } from '../../Auth'; import { useLocation } from 'react-router-dom'; diff --git a/src/pages/userprofile/UserProfile.css b/src/pages/userprofile/UserProfile.css index d5e24fc..ca59ad6 100644 --- a/src/pages/userprofile/UserProfile.css +++ b/src/pages/userprofile/UserProfile.css @@ -204,4 +204,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/pages/userprofile/UserProfile.jsx b/src/pages/userprofile/UserProfile.jsx index 4ddf41b..5fcdf10 100644 --- a/src/pages/userprofile/UserProfile.jsx +++ b/src/pages/userprofile/UserProfile.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { auth, db } from '../../firebase/firebaseConfig'; +import { auth, db } from '/src/lib/firebaseConfig'; import { useParams, useNavigate } from 'react-router-dom'; import { doc, getDoc, collection, getDocs, query, addDoc, where, setDoc, deleteDoc, updateDoc } from 'firebase/firestore'; import { useAuth } from '../../Auth'; @@ -15,6 +15,8 @@ import ListingButton from '../../components/listingpopup/Button'; import ProductList from '../../components/productcards/ProductList'; import './UserProfile.css'; +const defaultProfilePic = "/src/assets/defaultProfile.jpg"; + function UserProfile() { const { userID } = useParams(); const navigate = useNavigate(); @@ -228,7 +230,7 @@ function UserProfile() {
-
+

@{userInfo.username}

Joined {userInfo.signUpDate}

diff --git a/src/pages/userprofile/editUser/EditPopup.jsx b/src/pages/userprofile/editUser/EditPopup.jsx index a27530a..54cbe91 100644 --- a/src/pages/userprofile/editUser/EditPopup.jsx +++ b/src/pages/userprofile/editUser/EditPopup.jsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from "react"; -import { auth, db } from "../../../firebase/firebaseConfig"; +import { auth, db, storage } from "../../../lib/firebaseConfig"; import { doc, getDoc, updateDoc, collection, query, where, getDocs, writeBatch } from "firebase/firestore"; import { toast } from 'react-hot-toast'; +import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; import './EditPopup.css'; @@ -12,9 +13,12 @@ function EditPopup({ onClose, onSubmit }) { email: "" }); + const [imageFile, setImageFile] = useState(null); + const uploadImage = (e) => { const file = e.target.files[0]; if (file && file.type.includes('image')) { + setImageFile(file); const fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.onload = (fileReaderEvent) => { @@ -34,8 +38,16 @@ function EditPopup({ onClose, onSubmit }) { const userDocRef = doc(db, 'Users', user.uid); try { + let profilePicUrl = formData.image; + + if (imageFile) { + const storageRef = ref(storage, `profilePics/${user.uid}/${imageFile.name}`); + await uploadBytes(storageRef, imageFile); + profilePicUrl = await getDownloadURL(storageRef); + } + await updateDoc(userDocRef, { - profilePic: formData.image, + profilePic: profilePicUrl, username: formData.username, email: formData.email }); @@ -101,7 +113,7 @@ function EditPopup({ onClose, onSubmit }) { name="image" onChange={uploadImage} />
- +
diff --git a/src/pages/userprofile/userReviews/UserReviews.jsx b/src/pages/userprofile/userReviews/UserReviews.jsx index b87d152..6445be4 100644 --- a/src/pages/userprofile/userReviews/UserReviews.jsx +++ b/src/pages/userprofile/userReviews/UserReviews.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { doc, getDoc } from 'firebase/firestore'; -import { db } from '../../../firebase/firebaseConfig'; +import { db } from '../../../lib/firebaseConfig'; import { FaStar } from 'react-icons/fa'; import './UserReviews.css'; diff --git a/src/pages/viewproduct/ViewProduct.jsx b/src/pages/viewproduct/ViewProduct.jsx index cb690c2..644ad34 100644 --- a/src/pages/viewproduct/ViewProduct.jsx +++ b/src/pages/viewproduct/ViewProduct.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { doc, getDoc, addDoc, collection, deleteDoc, query, where, getDocs, updateDoc } from 'firebase/firestore'; -import { db } from '../../firebase/firebaseConfig'; +import { db } from '../../lib/firebaseConfig'; import { useAuth } from '../../Auth'; import { FaRegHeart, FaHeart, FaStar, FaStarHalf } from "react-icons/fa"; import { FaEllipsisVertical } from 'react-icons/fa6'; diff --git a/src/pages/viewproduct/offerPopup/offerPopup.jsx b/src/pages/viewproduct/offerPopup/offerPopup.jsx index 7aee2c0..9c675c4 100644 --- a/src/pages/viewproduct/offerPopup/offerPopup.jsx +++ b/src/pages/viewproduct/offerPopup/offerPopup.jsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { auth, db } from "../../../firebase/firebaseConfig"; +import { auth, db } from "../../../lib/firebaseConfig"; import { doc, addDoc, collection } from "firebase/firestore"; import { toast } from 'react-hot-toast'; diff --git a/src/pages/viewproduct/viewOffers/viewOffers.jsx b/src/pages/viewproduct/viewOffers/viewOffers.jsx index 7a76fb9..43dbeaa 100644 --- a/src/pages/viewproduct/viewOffers/viewOffers.jsx +++ b/src/pages/viewproduct/viewOffers/viewOffers.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { doc, updateDoc, collection, deleteDoc, addDoc, getDoc } from 'firebase/firestore'; -import { db } from '../../../firebase/firebaseConfig'; +import { db } from '../../../lib/firebaseConfig'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-hot-toast'; import { useAuth } from '../../../Auth';