├── src ├── components │ ├── themeColor.js │ ├── Loading.jsx │ ├── PrivateRoute.jsx │ ├── Theme.jsx │ ├── Singup.jsx │ ├── ProfileOption.jsx │ ├── OAuth.jsx │ ├── SingIn.jsx │ ├── SocketConnection.jsx │ ├── mobileMenu.jsx │ ├── Contact.jsx │ ├── ListingCard.jsx │ ├── PostCard.jsx │ ├── RentListing.jsx │ ├── SaleListing.jsx │ ├── OfferedListing.jsx │ ├── Conversations.jsx │ ├── Header.jsx │ ├── Chat.jsx │ ├── SkletonLoading.jsx │ └── Footer.jsx ├── redux │ ├── search │ │ └── searchSlice.js │ ├── saveListing │ │ └── saveListingSlice.js │ ├── notifications │ │ └── notificationSlice.js │ ├── store.js │ └── user │ │ └── userSlice.js ├── main.jsx ├── firebase.js ├── App.jsx ├── index.css └── pages │ ├── SaveListing.jsx │ ├── Login.jsx │ ├── Home.jsx │ ├── Message.jsx │ ├── Profile.jsx │ ├── Search.jsx │ └── ListingPage.jsx ├── utils └── apiHooks.js ├── postcss.config.js ├── dist ├── assets │ ├── ajax-loader-e7b44c86.gif │ └── slick-12459f22.svg └── index.html ├── .gitignore ├── vite.config.js ├── index.html ├── .eslintrc.cjs ├── tailwind.config.js ├── README.md └── package.json /src/components/themeColor.js: -------------------------------------------------------------------------------- 1 | export const themeColor = '313A67' 2 | 3 | -------------------------------------------------------------------------------- /utils/apiHooks.js: -------------------------------------------------------------------------------- 1 | export const baseUrl = "https://property-sale-backend.onrender.com/api"; 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /dist/assets/ajax-loader-e7b44c86.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emoncr/Property-Sale/HEAD/dist/assets/ajax-loader-e7b44c86.gif -------------------------------------------------------------------------------- /src/components/Loading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 |

7 |
8 | ) 9 | } 10 | 11 | export default Loading -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | *.local 12 | 13 | # Editor directories and files 14 | .vscode/* 15 | !.vscode/extensions.json 16 | .idea 17 | .DS_Store 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | .env -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { defineConfig } from "vite"; 3 | import react from "@vitejs/plugin-react"; 4 | 5 | export default defineConfig({ 6 | server: { 7 | proxy: { 8 | "/api": { 9 | target: "http://localhost:3000", 10 | changeOrigin: true, 11 | secure: false, 12 | }, 13 | }, 14 | }, 15 | plugins: [react()], 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { Navigate, Outlet } from 'react-router-dom' 4 | 5 | const PrivateRoute = () => { 6 | const { currentUser } = useSelector(state => state.user) 7 | 8 | return ( 9 | currentUser ? : 10 | ) 11 | } 12 | 13 | export default PrivateRoute -------------------------------------------------------------------------------- /src/redux/search/searchSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | searchTermState: "", 5 | }; 6 | 7 | const searchSlice = createSlice({ 8 | name: "search", 9 | initialState, 10 | reducers: { 11 | setSearchTermState: (state, action) => { 12 | state.searchTermState = action.payload; 13 | }, 14 | }, 15 | }); 16 | 17 | export const { setSearchTermState, } = searchSlice.actions; 18 | 19 | export default searchSlice.reducer; 20 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | import { Provider } from 'react-redux' 6 | import { persistor, store } from './redux/store.js' 7 | import { PersistGate } from 'redux-persist/integration/react' 8 | 9 | ReactDOM.createRoot(document.getElementById('root')).render( 10 | 11 | 12 | 13 | 14 | , 15 | ) 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | Property Sale 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/Theme.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { AiOutlineSetting } from 'react-icons/ai'; 3 | 4 | 5 | 6 | 7 | const Theme = () => { 8 | 9 | return ( 10 |
11 |
12 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default Theme; 21 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:react/jsx-runtime", 8 | "plugin:react-hooks/recommended", 9 | ], 10 | ignorePatterns: ["dist", ".eslintrc.cjs"], 11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" }, 12 | settings: { react: { version: "18.2" } }, 13 | plugins: ["react-refresh"], 14 | rules: { 15 | "react-refresh/only-export-components": [ 16 | "warn", 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | Property Sale 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/redux/saveListing/saveListingSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | saveListings: [], 5 | }; 6 | 7 | const saveSlice = createSlice({ 8 | 9 | name: "saved_listings", 10 | initialState, 11 | 12 | reducers: { 13 | handleSave: (state, action) => { 14 | state.saveListings.push(action.payload); 15 | }, 16 | handleLisingRemove: (state, action) => { 17 | state.saveListings = action.payload; 18 | }, 19 | clearSavedListing: (state) => { 20 | state.saveListings = []; 21 | }, 22 | }, 23 | }); 24 | 25 | export const { handleSave, handleLisingRemove,clearSavedListing } = saveSlice.actions; 26 | 27 | export default saveSlice.reducer; 28 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | import { themeColor } from "./src/components/themeColor"; 4 | 5 | export default { 6 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 7 | theme: { 8 | extend: { 9 | fontFamily: { 10 | heading: ["KoHo", "sans-serif"], 11 | content: ["Open Sans", "sans-serif"], 12 | oswald: ["Oswald", "sans-serif"], 13 | }, 14 | colors: { 15 | "brand-blue": `#${themeColor}`, 16 | "ui-bg": "#f1f5f1", 17 | }, 18 | boxShadow: { 19 | brand: "0 2px 5px", 20 | }, 21 | }, 22 | }, 23 | darkMode: "media", 24 | plugins: [require("daisyui"), "prettier-plugin-tailwindcss"], 25 | }; 26 | -------------------------------------------------------------------------------- /src/redux/notifications/notificationSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | notificationsDB: [], 5 | conversationActive: { chatId: "" }, 6 | }; 7 | 8 | const notificationSlice = createSlice({ 9 | name: "notification_slice", 10 | initialState, 11 | reducers: { 12 | setNotification: (state, action) => { 13 | state.notificationsDB = action.payload; 14 | }, 15 | deleteNotification: (state, action) => { 16 | state.notificationsDB.pop(action.payload); 17 | }, 18 | setSingleNotification: (state, action) => { 19 | state.notificationsDB.push(action.payload); 20 | }, 21 | }, 22 | }); 23 | 24 | export const { 25 | setNotification, 26 | deleteNotification, 27 | setSingleNotification, 28 | setConversationActive, 29 | } = notificationSlice.actions; 30 | 31 | export default notificationSlice.reducer; 32 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | import { getAnalytics } from "firebase/analytics"; 4 | // TODO: Add SDKs for Firebase products that you want to use 5 | // https://firebase.google.com/docs/web/setup#available-libraries 6 | 7 | // Your web app's Firebase configuration 8 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 9 | const firebaseConfig = { 10 | apiKey: import.meta.env.VITE_FIRIBASE_API_KEY , 11 | authDomain: "property-sell-401819.firebaseapp.com", 12 | projectId: "property-sell-401819", 13 | storageBucket: "property-sell-401819.appspot.com", 14 | messagingSenderId: "588716927912", 15 | appId: "1:588716927912:web:a6d64c89172800b05a4b9d", 16 | measurementId: "G-NQ79YSEFBT" 17 | }; 18 | 19 | // Initialize Firebase 20 | export const firebaseApp = initializeApp(firebaseConfig); 21 | // const analytics = getAnalytics(firebaseApp); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hi There ! 2 | 3 | This is a real estate application 4 | ======= 5 | LIVE_URL: https://property-sell.vercel.app/ 6 | 7 | Excited to announce the completion of my latest project, 'Property Sale'! 🏡 Built on MERN stack with real-time chat functionality using Socket.io. A platform where you can buy, sell, or rent properties. 8 | 9 | Here is the overview of the project. 10 | 11 | Technology: 🚀 12 | Backend - Node Js, Express Js, Socket Io, Mongoose, Jwt. 13 | 14 | Database - Mongodb. 15 | 16 | Fronted - React Js, React Router Dom, React Hook Form, Firebase, Tailwind Css, Socket Io Client. 17 | 18 | 19 | Features: 🚀 20 | 1. Real time conversation and local notification. 21 | 2. Search and sort result. 22 | 3. Buyer and saler role management. 23 | 4. Authentication with google or email. 24 | 5. Property Image upload facilities. 25 | 6. Pagination for all posts. 26 | 27 | Ahhh😪...there are many more features here is live url and don't forget to check it out. 28 | 29 | Live URL - https://property-sell.vercel.app/ -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import userReducer from "../redux/user/userSlice"; 2 | import { combineReducers, configureStore } from "@reduxjs/toolkit"; 3 | import { persistReducer } from "redux-persist"; 4 | import persistStore from "redux-persist/es/persistStore"; 5 | import storage from "redux-persist/lib/storage"; 6 | import searchSlice from "./search/searchSlice"; 7 | import saveListingSlice from "./saveListing/saveListingSlice"; 8 | import notificationSlice from "./notifications/notificationSlice"; 9 | 10 | //===== Redux Persist's Code ======// 11 | const rootReducer = combineReducers({ 12 | user: userReducer, 13 | search: searchSlice, 14 | notification: notificationSlice, 15 | savedListing: saveListingSlice, 16 | }); 17 | 18 | const persistConfig = { 19 | key: "root", 20 | storage, 21 | whitelist: ["user", "savedListing"], 22 | }; 23 | const persistedReducer = persistReducer(persistConfig, rootReducer); 24 | 25 | //===== Redux Store ======// 26 | export const store = configureStore({ 27 | reducer: persistedReducer, 28 | 29 | //==== Middlware for serializable check =====// 30 | middleware: (getDefaultMiddleware) => 31 | getDefaultMiddleware({ 32 | serializableCheck: false, 33 | }), 34 | }); 35 | 36 | export const persistor = persistStore(store); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-sell", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "start": "serve -s dist -l $PORT" 12 | }, 13 | "dependencies": { 14 | "@preact/signals-react": "^1.3.7", 15 | "@reduxjs/toolkit": "^1.9.7", 16 | "dotenv": "^16.3.1", 17 | "firebase": "^10.5.0", 18 | "js-cookie": "^3.0.5", 19 | "postcss-plugin": "^1.0.0", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "react-hook-form": "^7.47.0", 23 | "react-icons": "^4.11.0", 24 | "react-redux": "^8.1.3", 25 | "react-router-dom": "^6.16.0", 26 | "react-slick": "^0.29.0", 27 | "react-toastify": "^9.1.3", 28 | "redux-persist": "^6.0.0", 29 | "serve": "^14.2.4", 30 | "slick-carousel": "^1.8.1", 31 | "socket.io-client": "^4.7.2" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.15", 35 | "@types/react-dom": "^18.2.7", 36 | "@vitejs/plugin-react": "^4.0.3", 37 | "autoprefixer": "^10.4.16", 38 | "daisyui": "^4.0.3", 39 | "eslint": "^8.45.0", 40 | "eslint-plugin-react": "^7.32.2", 41 | "eslint-plugin-react-hooks": "^4.6.0", 42 | "eslint-plugin-react-refresh": "^0.4.3", 43 | "postcss": "^8.2.9", 44 | "prettier": "^3.1.0", 45 | "prettier-plugin-tailwindcss": "^0.5.7", 46 | "tailwindcss": "^3.3.3", 47 | "vite": "^4.4.5" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from 'react-router-dom' 2 | import Home from './pages/Home' 3 | import Login from './pages/Login' 4 | import Header from './components/Header' 5 | import Theme from './components/Theme' 6 | import Profile from './pages/Profile' 7 | import PrivateRoute from './components/PrivateRoute' 8 | import CreatePost from './pages/CreatePost' 9 | import UpdatePost from './pages/UpdatePost' 10 | import ListingPage from './pages/ListingPage' 11 | import SaveListing from './pages/SaveListing' 12 | import Search from './pages/Search' 13 | import Message from './pages/Message' 14 | import SocketConnection from './components/SocketConnection' 15 | 16 | 17 | function App() { 18 | return ( 19 | <> 20 | 21 | 22 |
23 | {/* */} 24 | 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | 31 | {/* /---------Private Routes-----------/ */} 32 | }> 33 | } /> 34 | } /> 35 | } /> 36 | } /> 37 | } /> 38 | 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | export default App 46 | -------------------------------------------------------------------------------- /dist/assets/slick-12459f22.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/redux/user/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | currentUser: null, 5 | loading: false, 6 | signinError: null, 7 | error: null, 8 | }; 9 | 10 | const userSlice = createSlice({ 11 | name: "user", 12 | initialState, 13 | reducers: { 14 | loddingStart: (state) => { 15 | state.loading = true; 16 | }, 17 | signinSuccess: (state, action) => { 18 | state.currentUser = action.payload; 19 | state.loading = false; 20 | state.signinError = false; 21 | }, 22 | signinFailed: (state, action) => { 23 | state.signinError = action.payload; 24 | state.loading = false; 25 | }, 26 | userUpdateSuccess: (state, action) => { 27 | state.currentUser = action.payload; 28 | state.loading = false; 29 | state.error = false; 30 | }, 31 | userUpdateFailed: (state, action) => { 32 | state.loading = false; 33 | state.error = action.payload; 34 | }, 35 | 36 | //=====Handlle User Delete State =====// 37 | 38 | userDeleteSuccess: (state) => { 39 | state.loading = false; 40 | state.currentUser = null; 41 | state.error = false; 42 | }, 43 | userDeleteFail: (state, action) => { 44 | state.loading = false; 45 | state.error = action.payload; 46 | }, 47 | 48 | // Handle Sign Out 49 | signoutSuccess: (state) => { 50 | state.loading = false; 51 | state.currentUser = null; 52 | state.error = false; 53 | }, 54 | signoutFailed: (state, action) => { 55 | state.loading = false; 56 | state.error = action.payload; 57 | }, 58 | 59 | // HANDLE lISTINS SAVED ITEMS 60 | handleSave: (state, action) => { 61 | state.savedListing.push(action.payload); 62 | }, 63 | handleLisingRemove: (state, action) => { 64 | state.savedListing = action.payload; 65 | }, 66 | }, 67 | }); 68 | 69 | export const { 70 | loddingStart, 71 | signinSuccess, 72 | signinFailed, 73 | userUpdateFailed, 74 | userUpdateSuccess, 75 | userDeleteStart, 76 | userDeleteSuccess, 77 | userDeleteFail, 78 | signoutSuccess, 79 | signoutFailed, 80 | handleSave, 81 | handleLisingRemove, 82 | } = userSlice.actions; 83 | 84 | export default userSlice.reducer; 85 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=KoHo:wght@700&family=Open+Sans:wght@400;500;600;700&display=swap"); 2 | 3 | body { 4 | @apply bg-[#f1f5f1] min-h-screen scroll-smooth; 5 | } 6 | 7 | #root { 8 | @apply bg-[#f1f5f1] h-full relative; 9 | } 10 | 11 | .container { 12 | @apply max-w-[1400px] mx-auto px-4 sm:px-10; 13 | } 14 | .search { 15 | @apply input w-full h-10 mx-auto border-2 rounded-md border-brand-blue max-w-full sm:max-w-sm focus:outline-0 text-sm font-semibold bg-transparent text-slate-900 placeholder:text-brand-blue placeholder:font-semibold px-3; 16 | } 17 | .search_btn { 18 | @apply absolute right-0 bg-brand-blue h-full w-10 sm:w-14 sm:max-w-[56px] rounded-e-md max-w-[40px] flex items-center justify-center hover:bg-brand-blue/80 duration-300; 19 | } 20 | 21 | /*=================Form styles start here =========== */ 22 | .form_input { 23 | @apply py-2 border-0 border-b-[1px] border-slate-300 placeholder:font-content placeholder:font-medium placeholder:text-sm focus:border-brand-blue focus:outline-none px-2 w-full text-sm text-slate-900 font-content font-semibold bg-white; 24 | } 25 | 26 | /*========tostify design==========*/ 27 | .Toastify__toast { 28 | @apply bg-brand-blue; 29 | } 30 | .Toastify__toast-body { 31 | @apply text-white; 32 | } 33 | .Toastify__close-button > svg { 34 | @apply text-white; 35 | } 36 | 37 | .scrollbar-hide::-webkit-scrollbar { 38 | display: none; 39 | } 40 | 41 | .scrollbar-hide { 42 | -ms-overflow-style: none; 43 | scrollbar-width: none; 44 | } 45 | .slick-dots li { 46 | @apply bg-white rounded-full; 47 | } 48 | .post_container .slick-list { 49 | @apply pb-11 sm:pb-16; 50 | } 51 | .post_container .slick-slide { 52 | @apply p-3; 53 | } 54 | 55 | .chats_container { 56 | height: calc(100vh - 75px); 57 | } 58 | .message_container { 59 | height: calc(100vh - 200px); 60 | } 61 | 62 | /* Universal Styles */ 63 | h1, 64 | h2, 65 | h3, 66 | h4, 67 | h5, 68 | h6 { 69 | @apply text-[#1f2937]; 70 | } 71 | p, 72 | label, 73 | li, 74 | span { 75 | @apply text-[#1f2937]; 76 | } 77 | .label-text { 78 | @apply !text-[#1f2937]; 79 | } 80 | button > span { 81 | @apply text-white; 82 | } 83 | input { 84 | @apply !text-[#1f2937]; 85 | } 86 | 87 | @tailwind base; 88 | @tailwind components; 89 | @tailwind utilities; 90 | -------------------------------------------------------------------------------- /src/components/Singup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useForm } from 'react-hook-form' 3 | const API_BASE = import.meta.env.VITE_API_BASE_URL; 4 | 5 | 6 | 7 | const Singup = ({ userState }) => { 8 | const { setResponseData, setIsformSubmit } = userState; 9 | 10 | const [loading, setLoading] = useState(false) 11 | const { 12 | register, 13 | handleSubmit, 14 | formState: { errors }, 15 | } = useForm(); 16 | 17 | 18 | 19 | //======handling form submting function =====// 20 | const onSubmit = async (formData) => { 21 | setLoading(true) 22 | const res = await fetch(`${API_BASE}/api/auth/signup`, { 23 | method: 'POST', 24 | headers: { 25 | 'Content-Type': 'application/json' 26 | }, 27 | body: JSON.stringify(formData) 28 | }); 29 | const data = await res.json(); 30 | 31 | setIsformSubmit(true) 32 | setResponseData(data) 33 | setLoading(false) 34 | }; 35 | 36 | 37 | return ( 38 | <> 39 |
42 | 43 | {errors.username && This field is required} 44 | 45 | 46 | 47 | {errors.email && This field is required} 48 | 49 | 50 | 51 | {errors.password && This field is required} 52 | 53 | 54 | 62 |
63 | 64 | 65 | ) 66 | } 67 | 68 | export default Singup -------------------------------------------------------------------------------- /src/components/ProfileOption.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDispatch } from 'react-redux'; 3 | import { Link } from 'react-router-dom' 4 | import { signoutFailed, signoutSuccess } from '../redux/user/userSlice'; 5 | import { ToastContainer, toast } from 'react-toastify'; 6 | import { clearSavedListing } from '../redux/saveListing/saveListingSlice'; 7 | import { FaBookmark, FaSignOutAlt, FaUser } from 'react-icons/fa'; 8 | const API_BASE = import.meta.env.VITE_API_BASE_URL; 9 | 10 | const ProfileOption = ({ user }) => { 11 | const dispatch = useDispatch(); 12 | 13 | const handleLogOut = async () => { 14 | try { 15 | const res = await fetch(`${API_BASE}/api/auth/signout`); 16 | const data = await res.json(); 17 | if (data.success === false) { 18 | useDispatch(signoutFailed(data.message)) 19 | toast.error(data.message, { 20 | autoClose: 2000, 21 | }) 22 | } 23 | else { 24 | dispatch(signoutSuccess()) 25 | dispatch(clearSavedListing()) 26 | } 27 | } catch (error) { 28 | dispatch(signoutFailed(error.message)) 29 | toast.error(error.message, { 30 | autoClose: 2000, 31 | }) 32 | } 33 | } 34 | 35 | 36 | 37 | 38 | return ( 39 |
40 |
41 | 46 |
    47 |
  • 48 | 49 | Profile 50 | 51 |
  • 52 |
  • 53 | 54 | Saved Listings 55 | 56 |
  • 57 |
  • 58 | 59 | Logout 60 | 61 |
  • 62 |
63 |
64 | 65 |
66 | ) 67 | }; 68 | 69 | export default ProfileOption; -------------------------------------------------------------------------------- /src/components/OAuth.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { GoogleAuthProvider, getAuth, signInWithPopup } from "firebase/auth"; 3 | import { firebaseApp } from '../firebase.js'; 4 | import { useDispatch } from 'react-redux'; 5 | import { signinFailed, signinSuccess } from '../redux/user/userSlice.js'; 6 | import { ToastContainer, toast } from 'react-toastify'; 7 | import 'react-toastify/dist/ReactToastify.css'; 8 | import { useNavigate } from 'react-router-dom'; 9 | 10 | const API_BASE = import.meta.env.VITE_API_BASE_URL; 11 | 12 | 13 | const OAuth = () => { 14 | const dispatch = useDispatch(); 15 | const navigate = useNavigate(); 16 | const handleGoogleSignIn = async () => { 17 | try { 18 | const auth = getAuth(firebaseApp); 19 | const provider = new GoogleAuthProvider(); 20 | const result = await signInWithPopup(auth, provider) 21 | const { displayName, email, photoURL } = result.user; 22 | 23 | //=====Fetch The Data To Backend====// 24 | const res = await fetch(`${API_BASE}/api/auth/google`, { 25 | method: "POST", 26 | headers: { 27 | "Content-Type": 'application/json' 28 | }, 29 | body: JSON.stringify({ 30 | name: displayName, 31 | email, 32 | photo: photoURL, 33 | }) 34 | }) 35 | const userData = await res.json(); 36 | if (userData.success === false) { 37 | dispatch(signinFailed(userData.message)); 38 | toast(userData.message); 39 | } 40 | else { 41 | dispatch(signinSuccess(userData)); 42 | navigate('/home'); 43 | } 44 | 45 | } catch (error) { 46 | console.log(error); 47 | }; 48 | }; 49 | 50 | 51 | 52 | 53 | 54 | 55 | return ( 56 | <> 57 |
58 |

OR

59 | 60 | 72 | 73 |
74 | 75 | 76 | ) 77 | } 78 | 79 | export default OAuth -------------------------------------------------------------------------------- /src/components/SingIn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useForm } from 'react-hook-form' 3 | import { useNavigate } from 'react-router-dom'; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { loddingStart, signinSuccess, signinFailed } from '../redux/user/userSlice'; 6 | import { ToastContainer, toast } from 'react-toastify'; 7 | import 'react-toastify/dist/ReactToastify.css'; 8 | const API_BASE = import.meta.env.VITE_API_BASE_URL; 9 | 10 | 11 | const SingIn = () => { 12 | const { 13 | register, 14 | handleSubmit, 15 | formState: { errors }, 16 | } = useForm(); 17 | const navigate = useNavigate(); 18 | const dispatch = useDispatch(); 19 | 20 | const { loading } = useSelector((state) => state.user) 21 | 22 | 23 | 24 | 25 | //======handling form submting function =====// 26 | const onSubmit = async (formData) => { 27 | dispatch(loddingStart()) 28 | try { 29 | const res = await fetch(`${API_BASE}/api/auth/signin`, { 30 | method: 'POST', 31 | headers: { 32 | 'Content-Type': 'application/json' 33 | }, 34 | body: JSON.stringify(formData) 35 | }); 36 | const userData = await res.json(); 37 | 38 | //===checking reqest success or not ===// 39 | if (userData.success === false) { 40 | dispatch(signinFailed(userData.message)) 41 | 42 | //===showing error in tostify====// 43 | toast.error(userData.message, { 44 | autoClose: 2000, 45 | }) 46 | } 47 | else { 48 | dispatch(signinSuccess(userData)) 49 | navigate('/home') 50 | } 51 | } 52 | catch (error) { 53 | dispatch(signinFailed(error.message)) 54 | toast.error(userData.message, { 55 | autoClose: 2000, 56 | }) 57 | } 58 | }; 59 | 60 | 61 | 62 | 63 | 64 | return ( 65 | <> 66 |
69 | 70 | {errors.email && This field is required} 71 | 72 | 73 | 74 | {errors.password && This field is required} 75 | 76 | 77 | 85 |
86 | 87 | 88 | ) 89 | } 90 | 91 | export default SingIn -------------------------------------------------------------------------------- /src/components/SocketConnection.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { io } from "socket.io-client"; 4 | import { 5 | setNotification, 6 | setSingleNotification, 7 | } from "../redux/notifications/notificationSlice"; 8 | import { activeChatId } from "./Conversations"; 9 | import { signal } from "@preact/signals-react"; 10 | const API_BASE = import.meta.env.VITE_API_BASE_URL; 11 | 12 | //production 13 | // const Node_Env = "local" 14 | export const socket = io("https://thunder-scarlet-wizard.glitch.me/", { 15 | headers: { 16 | "user-agent": "chrome", 17 | }, 18 | }); 19 | 20 | export const notifySignal = signal({ 21 | notifications: [], 22 | }); 23 | 24 | const SocketConnection = () => { 25 | const { currentUser } = useSelector((state) => state.user); 26 | const dispatch = useDispatch(); 27 | 28 | //======== Get Notification From DB =========// 29 | useEffect(() => { 30 | const loadPrevNotification = async () => { 31 | try { 32 | const unseenNotificaton = await fetch( 33 | `${API_BASE}/api/notification/${currentUser._id}` 34 | ); 35 | const res = await unseenNotificaton.json(); 36 | if (res.success === false) { 37 | console.log(res); 38 | } else { 39 | notifySignal.value.notifications = res; 40 | dispatch(setNotification(res)); 41 | } 42 | } catch (error) { 43 | console.log(error.message); 44 | } 45 | }; 46 | currentUser && loadPrevNotification(); 47 | }, []); 48 | 49 | //=========== Store notificaions to DB =============// 50 | 51 | const sendNotificationToDB = async (notificationData) => { 52 | try { 53 | const sendNotification = await fetch( 54 | `${API_BASE}/api/notification/create`, 55 | { 56 | method: "POST", 57 | headers: { "Content-Type": "application/json" }, 58 | body: JSON.stringify(notificationData), 59 | } 60 | ); 61 | const response = await sendNotification.json(); 62 | if (response.success === false) { 63 | console.log(response); 64 | } 65 | } catch (error) { 66 | console.log(error); 67 | } 68 | }; 69 | 70 | //----- Get Notification from socket and setNotification---------// 71 | useEffect(() => { 72 | socket.on(`${currentUser?._id}`, (socketNotification) => { 73 | const currentPath = window.location.pathname; 74 | 75 | if (currentPath !== "/message") { 76 | activeChatId.value.chatId = ""; 77 | } 78 | 79 | if (socketNotification.chatId !== activeChatId.value.chatId) { 80 | const isNotificationExist = notifySignal.value.notifications.some( 81 | (notify) => notify.chatId === socketNotification.chatId 82 | ); 83 | 84 | if (!isNotificationExist) { 85 | notifySignal.value.notifications = [ 86 | ...notifySignal.value.notifications, 87 | socketNotification, 88 | ]; 89 | dispatch(setSingleNotification(socketNotification)); 90 | sendNotificationToDB(socketNotification); 91 | } 92 | } 93 | }); 94 | }); 95 | 96 | return <>; 97 | }; 98 | 99 | export default SocketConnection; 100 | -------------------------------------------------------------------------------- /src/pages/SaveListing.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import ListingCard from '../components/ListingCard' 4 | import { FaArrowRight } from 'react-icons/fa' 5 | import { useNavigate } from 'react-router-dom' 6 | import Footer from '../components/Footer' 7 | 8 | const SaveListing = () => { 9 | const { saveListings } = useSelector(state => state.savedListing) 10 | const navigate = useNavigate() 11 | return ( 12 | <> 13 |
14 |
15 |
16 |

Your saved Listing

17 | 42 |
43 |
44 | { 45 | saveListings.length === 0 ? 46 |
47 | 48 |

49 | Your saved listings are currently empty. 50 |

51 | 52 |
53 | : 54 |
55 | { 56 | saveListings && saveListings.map(listing => ) 57 | } 58 |
59 | } 60 | 61 |
62 |
63 |
64 | <> 65 |