├── assets ├── icon.png ├── splash.png ├── favicon.png └── adaptive-icon.png ├── babel.config.js ├── app ├── (tabs) │ ├── bio │ │ ├── _layout.js │ │ └── index.js │ ├── profile │ │ ├── _layout.js │ │ └── index.js │ ├── chat │ │ ├── _layout.js │ │ ├── index.js │ │ ├── chatroom.js │ │ └── select.js │ └── _layout.js ├── index.js └── (authenticate) │ ├── _layout.js │ ├── select.js │ ├── login.js │ └── register.js ├── api ├── models │ ├── message.js │ └── user.js ├── package.json ├── index.js └── yarn.lock ├── .gitignore ├── App.js ├── app.json ├── package.json ├── components ├── UserChat.js └── Profile.js └── data.js /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSA-988/dating-app/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSA-988/dating-app/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSA-988/dating-app/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SSA-988/dating-app/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: ['expo-router/babel'] 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /app/(tabs)/bio/_layout.js: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app/(tabs)/profile/_layout.js: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, View } from 'react-native' 2 | import React from 'react' 3 | import { Redirect } from 'expo-router' 4 | 5 | const index = () => { 6 | return ( 7 | 8 | ) 9 | } 10 | 11 | export default index 12 | 13 | const styles = StyleSheet.create({}) 14 | 15 | // -> "/" -------------------------------------------------------------------------------- /app/(authenticate)/_layout.js: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /app/(tabs)/chat/_layout.js: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /api/models/message.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const chatSchema = new mongoose.Schema({ 4 | senderId: { 5 | type: String, 6 | required: true, 7 | }, 8 | receiverId: { 9 | type: String, 10 | required: true, 11 | }, 12 | message: { 13 | type: String, 14 | required: true, 15 | }, 16 | timestamp: { 17 | type: Date, 18 | default: Date.now, 19 | }, 20 | }); 21 | 22 | const Chat = mongoose.model("Chat", chatSchema); 23 | 24 | module.exports = Chat; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | 4 | export default function App() { 5 | return ( 6 | 7 | Open up App.js to start working on your app! 8 | 9 | 10 | ); 11 | } 12 | 13 | const styles = StyleSheet.create({ 14 | container: { 15 | flex: 1, 16 | backgroundColor: '#fff', 17 | alignItems: 'center', 18 | justifyContent: 'center', 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "backend", 5 | "main": "index.js", 6 | "scripts": { 7 | "start":"nodemon index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.20.2", 14 | "cors": "^2.8.5", 15 | "express": "^4.18.2", 16 | "jsonwebtoken": "^9.0.2", 17 | "mongoose": "^8.0.3", 18 | "nodemailer": "^6.9.8", 19 | "nodemon": "^3.0.2", 20 | "socket.io": "^4.7.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "dating-app-yt", 4 | "slug": "dating-app-yt", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "scheme": "dating-app-yt", 27 | "web": { 28 | "favicon": "./assets/favicon.png" 29 | }, 30 | "plugins": [ 31 | "expo-router" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dating-app-yt", 3 | "version": "1.0.0", 4 | "main": "expo-router/entry", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@react-native-async-storage/async-storage": "1.18.2", 13 | "@react-navigation/native": "^6.1.9", 14 | "axios": "^1.6.5", 15 | "core-js": "^3.35.0", 16 | "expo": "~49.0.15", 17 | "expo-constants": "~14.4.2", 18 | "expo-linking": "~5.0.2", 19 | "expo-router": "^2.0.0", 20 | "expo-status-bar": "~1.6.0", 21 | "jwt-decode": "^4.0.0", 22 | "react": "18.2.0", 23 | "react-native": "0.72.6", 24 | "react-native-animatable": "^1.4.0", 25 | "react-native-safe-area-context": "4.6.3", 26 | "react-native-screens": "~3.22.0", 27 | "react-native-snap-carousel": "^3.9.1", 28 | "socket.io-client": "^4.7.3" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.20.0" 32 | }, 33 | "private": true 34 | } 35 | -------------------------------------------------------------------------------- /api/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | }, 8 | email: { 9 | type: String, 10 | required: true, 11 | unique: true, 12 | }, 13 | password: { 14 | type: String, 15 | required: true, 16 | }, 17 | gender: { 18 | type: String, 19 | enum: ["male", "female", "other"], 20 | }, 21 | verified: { 22 | type: Boolean, 23 | default: false, 24 | }, 25 | verificationToken: String, 26 | crushes: [ 27 | { 28 | type: mongoose.Schema.Types.ObjectId, 29 | ref: "User", 30 | }, 31 | ], 32 | recievedLikes: [ 33 | { 34 | type: mongoose.Schema.Types.ObjectId, 35 | ref: "User", 36 | }, 37 | ], 38 | matches: [ 39 | { 40 | type: mongoose.Schema.Types.ObjectId, 41 | ref: "User", 42 | }, 43 | ], 44 | profileImages: [ 45 | { 46 | type: String, 47 | }, 48 | ], 49 | description: { 50 | type: String, 51 | }, 52 | turnOns: [ 53 | { 54 | type: String, //Array of string for turn ons 55 | }, 56 | ], 57 | lookingFor: [ 58 | { 59 | type: String, // Array of strings for what they are looking for 60 | }, 61 | ], 62 | }); 63 | 64 | 65 | const User = mongoose.model("User",userSchema); 66 | 67 | module.exports = User -------------------------------------------------------------------------------- /app/(tabs)/_layout.js: -------------------------------------------------------------------------------- 1 | import { Tabs } from "expo-router"; 2 | import { Feather } from "@expo/vector-icons"; 3 | import { Ionicons } from "@expo/vector-icons"; 4 | import { MaterialCommunityIcons } from "@expo/vector-icons"; 5 | 6 | export default function Layout() { 7 | return ( 8 | 9 | 15 | focused ? ( 16 | 17 | ) : ( 18 | 19 | ), 20 | }} 21 | /> 22 | 23 | 29 | focused ? ( 30 | 35 | ) : ( 36 | 41 | ), 42 | }} 43 | /> 44 | 45 | 51 | focused ? ( 52 | 57 | ) : ( 58 | 63 | ), 64 | }} 65 | /> 66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /app/(tabs)/profile/index.js: -------------------------------------------------------------------------------- 1 | import { FlatList, StyleSheet, Text, View } from "react-native"; 2 | import React, { useState, useEffect } from "react"; 3 | import "core-js/stable/atob"; 4 | import { jwtDecode } from "jwt-decode"; 5 | import AsyncStorage from "@react-native-async-storage/async-storage"; 6 | import axios from "axios"; 7 | import Profile from "../../../components/Profile"; 8 | 9 | const index = () => { 10 | const [userId, setUserId] = useState(""); 11 | const [user, setUser] = useState(); 12 | const [profiles, setProfiles] = useState([]); 13 | useEffect(() => { 14 | const fetchUser = async () => { 15 | const token = await AsyncStorage.getItem("auth"); 16 | const decodedToken = jwtDecode(token); 17 | const userId = decodedToken.userId; 18 | setUserId(userId); 19 | }; 20 | 21 | fetchUser(); 22 | }, []); 23 | const fetchUserDescription = async () => { 24 | try { 25 | const response = await axios.get(`http://localhost:3000/users/${userId}`); 26 | console.log(response); 27 | const user = response.data; 28 | setUser(user?.user); 29 | } catch (error) { 30 | console.log("Error fetching user description", error); 31 | } 32 | }; 33 | 34 | const fetchProfiles = async () => { 35 | try { 36 | const response = await axios.get("http://localhost:3000/profiles", { 37 | params: { 38 | userId: userId, 39 | gender: user?.gender, 40 | turnOns: user?.turnOns, 41 | lookingFor: user?.lookingFor, 42 | }, 43 | }); 44 | 45 | setProfiles(response.data.profiles); 46 | } catch (error) { 47 | console.log("error", error); 48 | } 49 | }; 50 | useEffect(() => { 51 | if (userId) { 52 | fetchUserDescription(); 53 | } 54 | }, [userId]); 55 | useEffect(() => { 56 | if (userId && user) { 57 | fetchProfiles(); 58 | } 59 | }, [userId, user]); 60 | console.log("profiles", profiles); 61 | return ( 62 | 63 | item.id} 66 | renderItem={({ item, index }) => ( 67 | 74 | )} 75 | /> 76 | 77 | ); 78 | }; 79 | 80 | export default index; 81 | 82 | const styles = StyleSheet.create({}); 83 | -------------------------------------------------------------------------------- /components/UserChat.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, View, Pressable, Image } from "react-native"; 2 | import React ,{useEffect,useState} from "react"; 3 | import { useRouter } from "expo-router"; 4 | import axios from "axios" 5 | 6 | const UserChat = ({ item, userId }) => { 7 | const router = useRouter(); 8 | const [messages,setMessages] = useState([]); 9 | const getLastMessage = () => { 10 | const n = messages.length; 11 | 12 | 13 | return messages[n-1]; 14 | } 15 | const lastMessage = getLastMessage(); 16 | useEffect(() => { 17 | fetchMessages(); 18 | }, []); 19 | 20 | const fetchMessages = async () => { 21 | try { 22 | const senderId = userId; 23 | const receiverId = item?._id; 24 | const response = await axios.get("http://localhost:3000/messages", { 25 | params: { senderId, receiverId }, 26 | }); 27 | 28 | // Assuming messages are stored in state to display in the UI 29 | setMessages(response.data); 30 | } catch (error) { 31 | console.error("Error fetching messages:", error); 32 | // Handle error scenarios 33 | } 34 | }; 35 | return ( 36 | 38 | router.push({ 39 | pathname: "/chat/chatroom", 40 | params: { 41 | image: item?.profileImages[0], 42 | name: item?.name, 43 | receiverId: item?._id, 44 | senderId: userId, 45 | }, 46 | }) 47 | } 48 | style={{ 49 | flexDirection: "row", 50 | alignItems: "center", 51 | gap: 12, 52 | marginVertical: 12, 53 | }} 54 | > 55 | 56 | 60 | 61 | 62 | 63 | 71 | {item?.name} 72 | 73 | 81 | {lastMessage ? lastMessage?.message : `Start Chat with ${item?.name}`} 82 | 83 | 84 | 85 | ); 86 | }; 87 | 88 | export default UserChat; 89 | 90 | const styles = StyleSheet.create({}); 91 | -------------------------------------------------------------------------------- /app/(tabs)/chat/index.js: -------------------------------------------------------------------------------- 1 | import { Pressable, StyleSheet, Text, View } from "react-native"; 2 | import React, { useState, useEffect,useCallback } from "react"; 3 | import { Ionicons } from "@expo/vector-icons"; 4 | import { Feather } from "@expo/vector-icons"; 5 | import { MaterialIcons } from "@expo/vector-icons"; 6 | import "core-js/stable/atob"; 7 | import { jwtDecode } from "jwt-decode"; 8 | import AsyncStorage from "@react-native-async-storage/async-storage"; 9 | import axios from "axios"; 10 | import { useRouter } from "expo-router"; 11 | import { useFocusEffect } from "@react-navigation/native"; 12 | import UserChat from "../../../components/UserChat"; 13 | 14 | const index = () => { 15 | const router = useRouter(); 16 | const [userId, setUserId] = useState(""); 17 | const [profiles, setProfiles] = useState([]); 18 | const [matches, setMatches] = useState([]); 19 | useEffect(() => { 20 | const fetchUser = async () => { 21 | const token = await AsyncStorage.getItem("auth"); 22 | const decodedToken = jwtDecode(token); 23 | const userId = decodedToken.userId; 24 | setUserId(userId); 25 | }; 26 | 27 | fetchUser(); 28 | }, []); 29 | const fetchRecievedLikesDetails = async () => { 30 | try { 31 | const response = await axios.get( 32 | `http://localhost:3000/received-likes/${userId}/details` 33 | ); 34 | 35 | console.log(response); 36 | 37 | const receivedLikesDetails = response.data.receivedLikesDetails; 38 | 39 | setProfiles(receivedLikesDetails); 40 | } catch (error) { 41 | console.log("error fetching the details", error); 42 | } 43 | }; 44 | const fetchUserMatches = async () => { 45 | try { 46 | const response = await axios.get( 47 | `http://localhost:3000/users/${userId}/matches` 48 | ); 49 | 50 | const userMatches = response.data.matches; 51 | 52 | setMatches(userMatches); 53 | } catch (error) { 54 | console.log("Error", error); 55 | } 56 | }; 57 | useEffect(() => { 58 | if (userId) { 59 | fetchRecievedLikesDetails(); 60 | } 61 | }, [userId]); 62 | useEffect(() => { 63 | if (userId) { 64 | fetchUserMatches(); 65 | } 66 | }, [userId]); 67 | useFocusEffect( 68 | useCallback(() => { 69 | if (userId) { 70 | fetchUserMatches(); 71 | } 72 | }, []) 73 | ); 74 | console.log("matches",matches) 75 | return ( 76 | 77 | 84 | CHATS 85 | 86 | 87 | 89 | router.push({ 90 | pathname: "/chat/select", 91 | params: { 92 | profiles: JSON.stringify(profiles), 93 | userId: userId, 94 | }, 95 | }) 96 | } 97 | style={{ 98 | marginVertical: 12, 99 | flexDirection: "row", 100 | alignItems: "center", 101 | }} 102 | > 103 | 113 | 114 | 115 | 116 | You have got {profiles?.length} likes 117 | 118 | 119 | 120 | 121 | 122 | {matches?.map((item,index) => ( 123 | 124 | ))} 125 | 126 | 127 | ); 128 | }; 129 | 130 | export default index; 131 | 132 | const styles = StyleSheet.create({}); 133 | -------------------------------------------------------------------------------- /app/(authenticate)/select.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, View, Pressable, Image } from "react-native"; 2 | import React, { useState, useEffect } from "react"; 3 | import AsyncStorage from "@react-native-async-storage/async-storage"; 4 | import "core-js/stable/atob"; 5 | import {jwtDecode} from "jwt-decode"; 6 | import axios from "axios" 7 | import { useRouter } from "expo-router"; 8 | 9 | const select = () => { 10 | const router = useRouter(); 11 | const [option, setOption] = useState(""); 12 | const [userId,setUserId] = useState(""); 13 | useEffect(() => { 14 | const fetchUser = async() => { 15 | const token = await AsyncStorage.getItem("auth"); 16 | const decodedToken = jwtDecode(token); 17 | const userId = decodedToken.userId; 18 | setUserId(userId) 19 | } 20 | 21 | fetchUser(); 22 | },[]) 23 | const updateUserGender = async () => { 24 | try{ 25 | const response = await axios.put(`http://localhost:3000/users/${userId}/gender`,{ 26 | gender:option 27 | }); 28 | 29 | console.log(response.data); 30 | 31 | if(response.status == 200){ 32 | router.replace("(tabs)/bio") 33 | } 34 | } catch(error){ 35 | console.log("error",error) 36 | } 37 | } 38 | return ( 39 | 40 | setOption("male")} 42 | style={{ 43 | backgroundColor: "#F0F0F0", 44 | padding: 12, 45 | justifyContent: "space-between", 46 | flexDirection: "row", 47 | alignItems: "center", 48 | marginTop: 25, 49 | borderRadius: 5, 50 | borderColor: option == "male" ? "#D0D0D0" : "transparent", 51 | borderWidth: option == "male" ? 1 : 0, 52 | }} 53 | > 54 | I am a Man 55 | 61 | 62 | 63 | setOption("female")} 65 | style={{ 66 | backgroundColor: "#F0F0F0", 67 | padding: 12, 68 | justifyContent: "space-between", 69 | flexDirection: "row", 70 | alignItems: "center", 71 | marginTop: 25, 72 | borderRadius: 5, 73 | borderColor: option == "female" ? "#D0D0D0" : "transparent", 74 | borderWidth: option == "female" ? 1 : 0, 75 | }} 76 | > 77 | I am a Woman 78 | 84 | 85 | 86 | setOption("nonbinary")} 88 | style={{ 89 | backgroundColor: "#F0F0F0", 90 | padding: 12, 91 | justifyContent: "space-between", 92 | flexDirection: "row", 93 | alignItems: "center", 94 | marginTop: 25, 95 | borderRadius: 5, 96 | borderColor: option == "nonbinary" ? "#D0D0D0" : "transparent", 97 | borderWidth: option == "nonbinary" ? 1 : 0, 98 | }} 99 | > 100 | I am Non-Binary 101 | 107 | 108 | 109 | {option && ( 110 | 119 | 122 | Done 123 | 124 | 125 | )} 126 | 127 | ); 128 | }; 129 | 130 | export default select; 131 | 132 | const styles = StyleSheet.create({}); 133 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | //login screen images 2 | <> 3 | 9 | 15 | select screen images 16 | 22 | 28 | ; 29 | 30 | const profileImages = [ 31 | { 32 | image: 33 | "https://images.pexels.com/photos/1042140/pexels-photo-1042140.jpeg?auto=compress&cs=tinysrgb&w=800", 34 | }, 35 | { 36 | image: 37 | "https://images.pexels.com/photos/1215695/pexels-photo-1215695.jpeg?auto=compress&cs=tinysrgb&w=800", 38 | }, 39 | { 40 | image: 41 | "https://images.pexels.com/photos/7580971/pexels-photo-7580971.jpeg?auto=compress&cs=tinysrgb&w=800", 42 | }, 43 | ]; 44 | const turnons = [ 45 | { 46 | id: "0", 47 | name: "Music", 48 | description: "Pop Rock-Indie pick our sound track", 49 | }, 50 | { 51 | id: "10", 52 | name: "Kissing", 53 | description: 54 | " It's a feeling of closeness, where every touch of lips creates a symphony of emotions.", 55 | }, 56 | { 57 | id: "1", 58 | name: "Fantasies", 59 | description: 60 | "Fantasies can be deeply personal, encompassing diverse elements such as romance", 61 | }, 62 | { 63 | id: "2", 64 | name: "Nibbling", 65 | description: 66 | "playful form of biting or taking small, gentle bites, typically done with the teeth", 67 | }, 68 | { 69 | id: "3", 70 | name: "Desire", 71 | description: "powerful emotion or attainment of a particular person.", 72 | }, 73 | ]; 74 | const data = [ 75 | { 76 | id: "0", 77 | name: "Casual", 78 | description: "Let's keep it easy and see where it goes", 79 | }, 80 | { 81 | id: "1", 82 | name: "Long Term", 83 | description: "How about a one life stand", 84 | }, 85 | { 86 | id: "2", 87 | name: "Virtual", 88 | description: "Let's have some virtual fun", 89 | }, 90 | { 91 | id: "3", 92 | name: "Open for Anything", 93 | description: "Let's Vibe and see where it goes", 94 | }, 95 | ]; 96 | 97 | const profiles = [ 98 | { 99 | id: "0", 100 | name: "Shreya Rao", 101 | description: 102 | "Hello, I want to chat and if everything goes well, meet probably during the weekend,we can go to the cinema feed the birds and hangout sometime ", 103 | profileImages: [ 104 | { 105 | id: "0", 106 | image: 107 | "https://images.pexels.com/photos/3236651/pexels-photo-3236651.jpeg?auto=compress&cs=tinysrgb&w=800", 108 | }, 109 | ], 110 | }, 111 | { 112 | id: "1", 113 | name: "Aditi K", 114 | description: 115 | "Hello! I'm all for engaging conversations and if vibes align, how about a weekend meet-up? We could do something fun like catching a movie, feeding the birds at the park, and just enjoying each other's company", 116 | profileImages: [ 117 | { 118 | id: "0", 119 | image: 120 | "https://images.pexels.com/photos/1485031/pexels-photo-1485031.jpeg?auto=compress&cs=tinysrgb&w=800", 121 | }, 122 | ], 123 | }, 124 | { 125 | id: "10", 126 | name: "Rheaa Thakur", 127 | description: 128 | "Hi! I'm here for intriguing conversations and if we hit it off, why not plan a weekend adventure? I'm into outdoor photography, trying new coffee shops, or maybe even a spontaneous road trip. Any ideas?", 129 | profileImages: [ 130 | { 131 | id: "0", 132 | image: 133 | "https://images.pexels.com/photos/1844012/pexels-photo-1844012.jpeg?auto=compress&cs=tinysrgb&w=800", 134 | }, 135 | ], 136 | }, 137 | { 138 | id: "3", 139 | name: "Manisha Singha", 140 | description: 141 | "Hello! I'm all about deep talks and discovering common passions. If we vibe, how about planning something unique—like attending a live music gig, exploring a farmer's market, or taking a dance class together?", 142 | profileImages: [ 143 | { 144 | id: "0", 145 | image: 146 | "https://images.pexels.com/photos/1642228/pexels-photo-1642228.jpeg?auto=compress&cs=tinysrgb&w=800", 147 | }, 148 | ], 149 | }, 150 | { 151 | id: "4", 152 | name: "Nisha J", 153 | description: 154 | "Hey! I'm into meaningful connections and if it feels right, let's plan something exciting—like attending a book reading, going to a comedy show, or even trying out a new adventurous cuisine together. What do you think about that?", 155 | profileImages: [ 156 | { 157 | id: "0", 158 | image: 159 | "https://images.pexels.com/photos/1152994/pexels-photo-1152994.jpeg?auto=compress&cs=tinysrgb&w=800", 160 | }, 161 | ], 162 | }, 163 | ]; 164 | 165 | //chat - > select 166 | 167 | ; 173 | 174 | //bio tab top image 175 | ; 181 | -------------------------------------------------------------------------------- /app/(tabs)/chat/chatroom.js: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | Text, 4 | View, 5 | Image, 6 | KeyboardAvoidingView, 7 | ScrollView, 8 | TextInput, 9 | Pressable, 10 | } from "react-native"; 11 | import React, { useLayoutEffect, useState, useEffect } from "react"; 12 | import { useNavigation } from "@react-navigation/native"; 13 | import { Ionicons } from "@expo/vector-icons"; 14 | import { useLocalSearchParams } from "expo-router"; 15 | import { Entypo, Feather } from "@expo/vector-icons"; 16 | import { MaterialCommunityIcons } from "@expo/vector-icons"; 17 | import { io } from "socket.io-client"; 18 | import axios from "axios"; 19 | 20 | const chatroom = () => { 21 | const navigation = useNavigation(); 22 | const [message, setMessage] = useState(""); 23 | const params = useLocalSearchParams(); 24 | const [messages, setMessages] = useState([]); 25 | const socket = io("http://localhost:8000"); 26 | socket.on("connect", () => { 27 | console.log("Connected to the Socket.IO server"); 28 | }); 29 | socket.on("receiveMessage", (newMessage) => { 30 | console.log("new Message", newMessage); 31 | 32 | //update the state to include new message 33 | setMessages((prevMessages) => [...prevMessages, newMessage]); 34 | }); 35 | const sendMessage = async (senderId, receiverId) => { 36 | socket.emit("sendMessage", { senderId, receiverId, message }); 37 | 38 | setMessage(""); 39 | 40 | // call the fetchMessages() function to see the UI update 41 | setTimeout(() => { 42 | fetchMessages(); 43 | },200) 44 | }; 45 | useLayoutEffect(() => { 46 | navigation.setOptions({ 47 | headerTitle: "", 48 | headerLeft: () => ( 49 | 50 | 51 | 52 | 61 | 62 | {params?.name} 63 | 64 | 65 | 66 | ), 67 | headerRight: () => ( 68 | 69 | 74 | 75 | 76 | ), 77 | }); 78 | }, []); 79 | const fetchMessages = async () => { 80 | try { 81 | const senderId = params?.senderId; 82 | const receiverId = params?.receiverId; 83 | 84 | const response = await axios.get("http://localhost:3000/messages", { 85 | params: { senderId, receiverId }, 86 | }); 87 | 88 | setMessages(response.data); 89 | } catch (error) { 90 | console.log("Error fetching the messages", error); 91 | } 92 | }; 93 | useEffect(() => { 94 | fetchMessages(); 95 | }, []); 96 | const formatTime = (time) => { 97 | const options = { hour: "numeric", minute: "numeric" }; 98 | return new Date(time).toLocaleString("en-US", options); 99 | }; 100 | return ( 101 | 102 | 103 | {messages?.map((item, index) => ( 104 | 125 | 126 | {item?.message} 127 | 128 | {formatTime(item?.timestamp)} 129 | 130 | ))} 131 | 132 | 133 | 144 | 150 | setMessage(text)} 153 | style={{ 154 | flex: 1, 155 | height: 40, 156 | borderWidth: 1, 157 | borderColor: "#dddddd", 158 | borderRadius: 20, 159 | paddingHorizontal: 10, 160 | }} 161 | placeholder="Type your message..." 162 | /> 163 | 164 | 172 | 173 | 174 | 175 | 176 | 177 | sendMessage(params?.senderId, params?.receiverId)} 179 | style={{ 180 | backgroundColor: "#007bff", 181 | paddingHorizontal: 12, 182 | paddingVertical: 8, 183 | borderRadius: 20, 184 | }} 185 | > 186 | Send 187 | 188 | 189 | 190 | ); 191 | }; 192 | 193 | export default chatroom; 194 | 195 | const styles = StyleSheet.create({}); 196 | -------------------------------------------------------------------------------- /app/(authenticate)/login.js: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | Text, 4 | View, 5 | SafeAreaView, 6 | Image, 7 | KeyboardAvoidingView, 8 | TextInput, 9 | Pressable, 10 | } from "react-native"; 11 | import React, { useState,useEffect } from "react"; 12 | import { MaterialIcons } from "@expo/vector-icons"; 13 | import { AntDesign } from "@expo/vector-icons"; 14 | import { useRouter } from "expo-router"; 15 | import axios from "axios"; 16 | import AsyncStorage from "@react-native-async-storage/async-storage"; 17 | 18 | 19 | const login = () => { 20 | const [email, setEmail] = useState(""); 21 | const [password, setPassword] = useState(""); 22 | const router = useRouter(); 23 | useEffect(() => { 24 | const checkLoginStatus = async () => { 25 | try{ 26 | const token = await AsyncStorage.getItem("auth"); 27 | if(token){ 28 | router.replace("/(tabs)/profile") 29 | } 30 | } catch(error){ 31 | console.log("Error",error) 32 | } 33 | } 34 | checkLoginStatus() 35 | },[]) 36 | const handleLogin = () => { 37 | const user = { 38 | email: email, 39 | password: password, 40 | }; 41 | axios.post("http://localhost:3000/login",user).then((response) => { 42 | console.log(response); 43 | const token = response.data.token; 44 | AsyncStorage.setItem("auth",token); 45 | router.replace("/(authenticate)/select") 46 | }) 47 | }; 48 | return ( 49 | 52 | 53 | 60 | 66 | 67 | 75 | Match Mate 76 | 77 | 78 | 79 | 80 | 81 | 89 | Log in to your Account 90 | 91 | 92 | 93 | 100 | 106 | 107 | 108 | 109 | 120 | 126 | setEmail(text)} 129 | placeholder="Enter your email" 130 | placeholderTextColor={"white"} 131 | style={{ 132 | color: "white", 133 | marginVertical: 10, 134 | width: 300, 135 | fontSize: password ? 17 : 17, 136 | }} 137 | /> 138 | 139 | 140 | 141 | 152 | 158 | setPassword(text)} 161 | secureTextEntry={true} 162 | placeholder="Enter your password" 163 | style={{ 164 | color: "white", 165 | marginVertical: 10, 166 | width: 300, 167 | fontSize: password ? 17 : 17, 168 | }} 169 | placeholderTextColor="white" 170 | /> 171 | 172 | 173 | 174 | 182 | Keep me logged in 183 | 184 | 185 | Forgot Password 186 | 187 | 188 | 189 | 190 | 191 | 202 | 210 | Login 211 | 212 | 213 | 214 | router.replace("/register")} 216 | style={{ marginTop: 12 }} 217 | > 218 | 219 | Don't have an account? Sign Up 220 | 221 | 222 | 223 | 224 | 225 | ); 226 | }; 227 | 228 | export default login; 229 | 230 | const styles = StyleSheet.create({}); 231 | -------------------------------------------------------------------------------- /app/(authenticate)/register.js: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | Text, 4 | View, 5 | SafeAreaView, 6 | Image, 7 | Pressable, 8 | TextInput, 9 | KeyboardAvoidingView, 10 | Alert, 11 | } from "react-native"; 12 | import React, { useState } from "react"; 13 | import { MaterialIcons, Ionicons } from "@expo/vector-icons"; 14 | import { AntDesign } from "@expo/vector-icons"; 15 | import { useRouter } from "expo-router"; 16 | import axios from "axios"; 17 | 18 | const register = () => { 19 | const [name, setName] = useState(""); 20 | const [email, setEmail] = useState(""); 21 | const [password, setPassword] = useState(""); 22 | const router = useRouter(); 23 | const handleRegister = () => { 24 | const user = { 25 | name: name, 26 | email: email, 27 | password: password, 28 | }; 29 | 30 | // send a POST request to the backend API to register the user 31 | axios 32 | .post("http://localhost:3000/register", user) 33 | .then((response) => { 34 | console.log(response); 35 | Alert.alert( 36 | "Registration successful", 37 | "You have been registered Successfully" 38 | ); 39 | setName(""); 40 | setEmail(""); 41 | setPassword(""); 42 | }) 43 | .catch((error) => { 44 | Alert.alert( 45 | "Registration Error", 46 | "An error occurred while registering" 47 | ); 48 | console.log("registration failed", error); 49 | }); 50 | }; 51 | return ( 52 | 55 | 56 | 63 | 69 | 70 | 78 | Match Mate 79 | 80 | 81 | 82 | 83 | 84 | 92 | Register to your Account 93 | 94 | 95 | 96 | 103 | 109 | 110 | 111 | 112 | 123 | 129 | setName(text)} 132 | placeholder="Enter your name" 133 | placeholderTextColor={"white"} 134 | style={{ 135 | color: "white", 136 | marginVertical: 10, 137 | width: 300, 138 | fontSize: name ? 17 : 17, 139 | }} 140 | /> 141 | 142 | 143 | 154 | 160 | setEmail(text)} 163 | placeholder="Enter your email" 164 | placeholderTextColor={"white"} 165 | style={{ 166 | color: "white", 167 | marginVertical: 10, 168 | width: 300, 169 | fontSize: password ? 17 : 17, 170 | }} 171 | /> 172 | 173 | 174 | 175 | 186 | 192 | setPassword(text)} 195 | secureTextEntry={true} 196 | placeholder="Enter your password" 197 | style={{ 198 | color: "white", 199 | marginVertical: 10, 200 | width: 300, 201 | fontSize: password ? 17 : 17, 202 | }} 203 | placeholderTextColor="white" 204 | /> 205 | 206 | 207 | 208 | 209 | 210 | 221 | 229 | Register 230 | 231 | 232 | 233 | router.replace("/login")} 235 | style={{ marginTop: 12 }} 236 | > 237 | 238 | Already have an account? Sign In 239 | 240 | 241 | 242 | 243 | 244 | ); 245 | }; 246 | 247 | export default register; 248 | 249 | const styles = StyleSheet.create({}); 250 | -------------------------------------------------------------------------------- /app/(tabs)/chat/select.js: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | Text, 4 | View, 5 | ScrollView, 6 | Image, 7 | Pressable, 8 | } from "react-native"; 9 | import React from "react"; 10 | import { useLocalSearchParams, useRouter } from "expo-router"; 11 | import { AntDesign } from "@expo/vector-icons"; 12 | import { FontAwesome, Entypo } from "@expo/vector-icons"; 13 | import axios from "axios"; 14 | 15 | const select = () => { 16 | const router = useRouter(); 17 | const params = useLocalSearchParams(); 18 | const profiles = JSON.parse(params?.profiles); 19 | 20 | const userId = params?.userId; 21 | 22 | const handleMatch = async (selectedUserId) => { 23 | try { 24 | await axios.post("http://localhost:3000/create-match", { 25 | currentUserId: userId, 26 | selectedUserId: selectedUserId, 27 | }); 28 | 29 | setTimeout(() => { 30 | router.push("/chat"); 31 | }, 500); 32 | } catch (error) { 33 | console.log("error", error); 34 | } 35 | }; 36 | return ( 37 | 38 | 39 | 42 | 43 | NearBy 🔥 44 | 45 | 46 | 49 | 50 | Looking for 💓 51 | 52 | 53 | 56 | 57 | Turn-Ons 💌 58 | 59 | 60 | 61 | {profiles?.length > 0 ? ( 62 | 63 | {profiles?.map((item, index) => ( 64 | 65 | 66 | 73 | 74 | 75 | {item?.name} 76 | 77 | 87 | {item?.description?.length > 160 88 | ? item?.description 89 | : item?.description.substr(0, 160)} 90 | 91 | 92 | 93 | {item?.profileImages?.slice(0, 1).map((item, index) => ( 94 | 103 | ))} 104 | 105 | 106 | 107 | 108 | 116 | 117 | 124 | 134 | 135 | 136 | 137 | handleMatch(item._id)} 139 | style={{ 140 | width: 50, 141 | height: 50, 142 | borderRadius: 25, 143 | backgroundColor: "#E0E0E0", 144 | justifyContent: "center", 145 | alignItems: "center", 146 | }} 147 | > 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | Turn Ons 💓 156 | 164 | {item?.turnOns?.map((item, index) => ( 165 | 172 | 180 | {item} 181 | 182 | 183 | ))} 184 | 185 | 186 | 187 | 188 | Looking For 👀 189 | 197 | {item?.lookingFor?.map((item, index) => ( 198 | 205 | 213 | {item} 214 | 215 | 216 | ))} 217 | 218 | 219 | 220 | ))} 221 | 222 | ) : ( 223 | 231 | 237 | 238 | 239 | 246 | UH - OH{" "} 247 | 254 | No likes yet 255 | 256 | 257 | 258 | 259 | Improve your AD to get better likes 260 | 261 | 262 | 263 | )} 264 | 265 | ); 266 | }; 267 | 268 | export default select; 269 | 270 | const styles = StyleSheet.create({}); 271 | -------------------------------------------------------------------------------- /components/Profile.js: -------------------------------------------------------------------------------- 1 | import { 2 | ScrollView, 3 | StyleSheet, 4 | Text, 5 | View, 6 | Image, 7 | Pressable, 8 | } from "react-native"; 9 | import React, { useState } from "react"; 10 | import { Entypo } from "@expo/vector-icons"; 11 | import { AntDesign } from "@expo/vector-icons"; 12 | import { FontAwesome } from "@expo/vector-icons"; 13 | import axios from "axios"; 14 | import * as Animatable from "react-native-animatable"; 15 | 16 | const Profile = ({ item, isEven, userId, setProfiles }) => { 17 | const colors = ["#F0F8FF", "#FFFFFF"]; 18 | const [liked, setLiked] = useState(false); 19 | const [selected, setSelcted] = useState(false); 20 | const handleLike = async (selectedUserId) => { 21 | try { 22 | setLiked(true); 23 | await axios.post("http://localhost:3000/send-like", { 24 | currentUserId: userId, 25 | selectedUserId: selectedUserId, 26 | }); 27 | 28 | setTimeout(() => { 29 | setProfiles((prevProfiles) => 30 | prevProfiles.filter((profile) => profile._id !== selectedUserId) 31 | ); 32 | setLiked(false); 33 | }, 200); 34 | } catch (error) { 35 | console.log("error liking", error); 36 | } 37 | }; 38 | const handleLikeOther = async (selectedUserId) => { 39 | try { 40 | setSelcted(true); 41 | await axios.post("http://localhost:3000/send-like", { 42 | currentUserId: userId, 43 | selectedUserId: selectedUserId, 44 | }); 45 | 46 | setTimeout(() => { 47 | setProfiles((prevProfiles) => 48 | prevProfiles.filter((profile) => profile._id !== selectedUserId) 49 | ); 50 | setSelcted(false); 51 | }, 200); 52 | 53 | // Handle success: Perform any UI updates or navigate to another screen 54 | } catch (error) { 55 | console.error("Error liking user:", error); 56 | // Handle error scenarios 57 | } 58 | }; 59 | if (isEven) { 60 | return ( 61 | 62 | 63 | 64 | 65 | 66 | {item?.name} 67 | 68 | 78 | {item?.description?.length > 160 79 | ? item?.description 80 | : item?.description.substr(0, 160)} 81 | 82 | 83 | 84 | {item?.profileImages?.slice(0, 1).map((item, index) => ( 85 | 94 | ))} 95 | 96 | 97 | 98 | 99 | 107 | 108 | 111 | 121 | 122 | 123 | 124 | {liked ? ( 125 | 135 | 140 | 141 | 142 | 143 | ) : ( 144 | handleLike(item?._id)} 146 | style={{ 147 | width: 50, 148 | height: 50, 149 | borderRadius: 25, 150 | backgroundColor: "#E0E0E0", 151 | justifyContent: "center", 152 | alignItems: "center", 153 | }} 154 | > 155 | 156 | 157 | )} 158 | 159 | 160 | 161 | 162 | 163 | 164 | ); 165 | } else { 166 | return ( 167 | 168 | 169 | 170 | {item?.profileImages?.slice(0, 1).map((item, index) => ( 171 | 180 | ))} 181 | 182 | 183 | {item?.name} 184 | 185 | 196 | {item?.description} 197 | 198 | 199 | 200 | 201 | 202 | 210 | 211 | 214 | 224 | 225 | 226 | 227 | {selected ? ( 228 | 238 | 243 | 244 | 245 | 246 | ) : ( 247 | handleLikeOther(item._id)} 249 | style={{ 250 | width: 50, 251 | height: 50, 252 | borderRadius: 25, 253 | backgroundColor: "#6699CC", 254 | justifyContent: "center", 255 | alignItems: "center", 256 | }} 257 | > 258 | 259 | 260 | )} 261 | 262 | 263 | 264 | 265 | 266 | ); 267 | } 268 | }; 269 | 270 | export default Profile; 271 | 272 | const styles = StyleSheet.create({}); 273 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const mongoose = require("mongoose"); 4 | const crypto = require("crypto"); 5 | const nodemailer = require("nodemailer"); 6 | 7 | const app = express(); 8 | const port = 3000; 9 | const cors = require("cors"); 10 | 11 | const http = require("http").createServer(app); 12 | const io = require("socket.io")(http); 13 | 14 | app.use(cors()); 15 | 16 | app.use(bodyParser.urlencoded({ extended: false })); 17 | app.use(bodyParser.json()); 18 | 19 | const jwt = require("jsonwebtoken"); 20 | const User = require("./models/user"); 21 | const Chat = require("./models/message"); 22 | 23 | mongoose 24 | .connect("mongodb+srv://sujan:sujan@cluster0.ye1ucer.mongodb.net/") 25 | .then(() => { 26 | console.log("Connected to MongoDB"); 27 | }) 28 | .catch((error) => { 29 | console.log("Error connecting to MongoDB"); 30 | }); 31 | 32 | app.listen(port, () => { 33 | console.log("Server is running on 3000"); 34 | }); 35 | 36 | //endpoint to register a user to the backend 37 | app.post("/register", async (req, res) => { 38 | try { 39 | const { name, email, password } = req.body; 40 | 41 | //check if the email is already registered 42 | const existingUser = await User.findOne({ email }); 43 | if (existingUser) { 44 | console.log("Email already registered"); 45 | return res.status(400).json({ message: "Email already registered" }); 46 | } 47 | 48 | //create a new User 49 | const newUser = new User({ 50 | name, 51 | email, 52 | password, 53 | }); 54 | 55 | //generate the verification token 56 | newUser.verificationToken = crypto.randomBytes(20).toString("hex"); 57 | 58 | //save the user to the database 59 | await newUser.save(); 60 | 61 | //send the verification email to the registered user 62 | sendVerificationEmail(newUser.email, newUser.verificationToken); 63 | 64 | res 65 | .status(200) 66 | .json({ message: "User registered successfully", userId: newUser._id }); 67 | } catch (error) { 68 | console.log("Error registering user", error); 69 | res.status(500).json({ message: "Registration failed" }); 70 | } 71 | }); 72 | const sendVerificationEmail = async (email, verificationToken) => { 73 | const transpoter = nodemailer.createTransport({ 74 | service: "gmail", 75 | auth: { 76 | user: "sujananand0@gmail.com", 77 | pass: "rnzcugnscqtqiefs", 78 | }, 79 | }); 80 | 81 | const mailOptions = { 82 | from: "matchmake.com", 83 | to: email, 84 | subject: "Email verification", 85 | text: `Please click on the following link to verify your email : http://localhost:3000/verify/${verificationToken}`, 86 | }; 87 | 88 | //send the mail 89 | try { 90 | await transpoter.sendMail(mailOptions); 91 | } catch (error) { 92 | console.log("Error sending the verification email"); 93 | } 94 | }; 95 | 96 | //verify the user 97 | app.get("/verify/:token", async (req, res) => { 98 | try { 99 | const token = req.params.token; 100 | 101 | const user = await User.findOne({ verificationToken: token }); 102 | if (!user) { 103 | return res.status(404).json({ message: "Invalid verification token" }); 104 | } 105 | 106 | //mark the user as verified 107 | user.verified = true; 108 | user.verificationToken = undefined; 109 | 110 | await user.save(); 111 | 112 | res.status(200).json({ message: "Email verified Sucesfully" }); 113 | } catch (error) { 114 | console.log("errror", error); 115 | res.status(500).json({ message: "Email verification failed" }); 116 | } 117 | }); 118 | 119 | const generateSecretKey = () => { 120 | const secretKey = crypto.randomBytes(32).toString("hex"); 121 | 122 | return secretKey; 123 | }; 124 | 125 | const secretKey = generateSecretKey(); 126 | 127 | //endpoint to login 128 | app.post("/login", async (req, res) => { 129 | try { 130 | const { email, password } = req.body; 131 | 132 | //check if the user exists already 133 | const user = await User.findOne({ email }); 134 | if (!user) { 135 | return res.status(401).json({ message: "Invalid email or password" }); 136 | } 137 | 138 | //check in password is correct 139 | if (user.password !== password) { 140 | return res.status(401).json({ message: "Invalide password" }); 141 | } 142 | 143 | const token = jwt.sign({ userId: user._id }, secretKey); 144 | 145 | res.status(200).json({ token }); 146 | } catch (error) { 147 | res.status(500).json({ message: "login failed" }); 148 | } 149 | }); 150 | 151 | //endpoint to change or select the gender for a particular user profile 152 | app.put("/users/:userId/gender", async (req, res) => { 153 | try { 154 | const { userId } = req.params; 155 | const { gender } = req.body; 156 | 157 | const user = await User.findByIdAndUpdate( 158 | userId, 159 | { gender: gender }, 160 | { new: true } 161 | ); 162 | 163 | if (!user) { 164 | return res.status(404).json({ message: "User not found" }); 165 | } 166 | 167 | return res.status(200).json({ message: "User gender updated Succesfully" }); 168 | } catch (error) { 169 | res.status(500).json({ message: "Error updating user gender", error }); 170 | } 171 | }); 172 | 173 | //endpoint to update the user description 174 | app.put("/users/:userId/description", async (req, res) => { 175 | try { 176 | const { userId } = req.params; 177 | const { description } = req.body; 178 | 179 | const user = await User.findByIdAndUpdate( 180 | userId, 181 | { 182 | description: description, 183 | }, 184 | { new: true } 185 | ); 186 | 187 | if (!user) { 188 | return res.status(404).json({ message: "User not found" }); 189 | } 190 | 191 | return res 192 | .status(200) 193 | .json({ message: "User description updated succesfully" }); 194 | } catch (error) { 195 | res.status(500).json({ message: "Error updating user description" }); 196 | } 197 | }); 198 | 199 | //fetch users data 200 | app.get("/users/:userId", async (req, res) => { 201 | try { 202 | const { userId } = req.params; 203 | 204 | const user = await User.findById(userId); 205 | 206 | if (!user) { 207 | return res.status(500).json({ message: "User not found" }); 208 | } 209 | 210 | return res.status(200).json({ user }); 211 | } catch (error) { 212 | res.status(500).json({ message: "Error fetching the user details" }); 213 | } 214 | }); 215 | 216 | //end point to add a turnon for a user in the backend 217 | app.put("/users/:userId/turn-ons/add", async (req, res) => { 218 | try { 219 | const { userId } = req.params; 220 | const { turnOn } = req.body; 221 | 222 | const user = await User.findByIdAndUpdate( 223 | userId, 224 | { $addToSet: { turnOns: turnOn } }, 225 | { new: true } 226 | ); 227 | 228 | if (!user) { 229 | return res.status(404).json({ message: "User not found" }); 230 | } 231 | 232 | return res 233 | .status(200) 234 | .json({ message: "Turn on updated succesfully", user }); 235 | } catch (error) { 236 | res.status(500).json({ message: "Error adding the turn on" }); 237 | } 238 | }); 239 | 240 | //endpoint to remove a particular turn on for the user 241 | app.put("/users/:userId/turn-ons/remove", async (req, res) => { 242 | try { 243 | const { userId } = req.params; 244 | 245 | const { turnOn } = req.body; 246 | 247 | const user = await User.findByIdAndUpdate( 248 | userId, 249 | { $pull: { turnOns: turnOn } }, 250 | { new: true } 251 | ); 252 | 253 | if (!user) { 254 | return res.status(404).json({ message: "User not found" }); 255 | } 256 | 257 | return res 258 | .status(200) 259 | .json({ message: "Turn on removed succesfully", user }); 260 | } catch (error) { 261 | return res.status(500).json({ message: "Error removing turn on" }); 262 | } 263 | }); 264 | 265 | //end point to add a lookingFor for a user in the backend 266 | app.put("/users/:userId/looking-for", async (req, res) => { 267 | try { 268 | const { userId } = req.params; 269 | const { lookingFor } = req.body; 270 | 271 | const user = await User.findByIdAndUpdate( 272 | userId, 273 | { 274 | $addToSet: { lookingFor: lookingFor }, 275 | }, 276 | { new: true } 277 | ); 278 | 279 | if (!user) { 280 | return res.status(404).json({ message: "No user" }); 281 | } 282 | 283 | return res 284 | .status(200) 285 | .json({ message: "Looking for updated succesfully".user }); 286 | } catch (error) { 287 | res.status(500).json({ message: "Error updating looking for", error }); 288 | } 289 | }); 290 | 291 | //endpoint to remove looking for in the backend 292 | app.put("/users/:userId/looking-for/remove", async (req, res) => { 293 | try { 294 | const { userId } = req.params; 295 | const { lookingFor } = req.body; 296 | 297 | const user = await User.findByIdAndUpdate( 298 | userId, 299 | { 300 | $pull: { lookingFor: lookingFor }, 301 | }, 302 | { new: true } 303 | ); 304 | 305 | if (!user) { 306 | return res.status(404).json({ message: "No user" }); 307 | } 308 | 309 | return res 310 | .status(200) 311 | .json({ message: "Looking for updated succesfully".user }); 312 | } catch (error) { 313 | res.status(500).json({ message: "Error removing looking for", error }); 314 | } 315 | }); 316 | 317 | app.post("/users/:userId/profile-images", async (req, res) => { 318 | try { 319 | const { userId } = req.params; 320 | const { imageUrl } = req.body; 321 | 322 | const user = await User.findById(userId); 323 | 324 | if (!user) { 325 | return res.status(404).json({ message: "User not found" }); 326 | } 327 | 328 | user.profileImages.push(imageUrl); 329 | 330 | await user.save(); 331 | 332 | return res.status(200).json({ message: "Image has been added", user }); 333 | } catch (error) { 334 | res.status(500).json({ message: "Error addding the profile images" }); 335 | } 336 | }); 337 | 338 | //endpoint to fetch all the profiles for a particular user 339 | app.get("/profiles", async (req, res) => { 340 | const { userId, gender, turnOns, lookingFor } = req.query; 341 | 342 | try { 343 | let filter = { gender: gender === "male" ? "female" : "male" }; // For gender filtering 344 | 345 | // Add filtering based on turnOns and lookingFor arrays 346 | if (turnOns) { 347 | filter.turnOns = { $in: turnOns }; 348 | } 349 | 350 | if (lookingFor) { 351 | filter.lookingFor = { $in: lookingFor }; 352 | } 353 | 354 | const currentUser = await User.findById(userId) 355 | .populate("matches", "_id") 356 | .populate("crushes", "_id"); 357 | 358 | // Extract IDs of friends 359 | const friendIds = currentUser.matches.map((friend) => friend._id); 360 | 361 | // Extract IDs of crushes 362 | const crushIds = currentUser.crushes.map((crush) => crush._id); 363 | 364 | const profiles = await User.find(filter) 365 | .where("_id") 366 | .nin([userId, ...friendIds, ...crushIds]); 367 | 368 | return res.status(200).json({ profiles }); 369 | } catch (error) { 370 | return res.status(500).json({ message: "Error fetching profiles", error }); 371 | } 372 | }); 373 | 374 | app.post("/send-like", async (req, res) => { 375 | const { currentUserId, selectedUserId } = req.body; 376 | 377 | try { 378 | //update the recepient's friendRequestsArray! 379 | await User.findByIdAndUpdate(selectedUserId, { 380 | $push: { recievedLikes: currentUserId }, 381 | }); 382 | //update the sender's sentFriendRequests array 383 | await User.findByIdAndUpdate(currentUserId, { 384 | $push: { crushes: selectedUserId }, 385 | }); 386 | 387 | res.sendStatus(200); 388 | } catch (error) { 389 | res.sendStatus(500); 390 | } 391 | }); 392 | 393 | //ednpoint to get the details of the received Likes 394 | app.get("/received-likes/:userId/details", async (req, res) => { 395 | const { userId } = req.params; 396 | 397 | try { 398 | // Find the user by ID 399 | const user = await User.findById(userId); 400 | 401 | if (!user) { 402 | return res.status(404).json({ message: "User not found" }); 403 | } 404 | 405 | // Fetch details of users who liked the current user 406 | const receivedLikesDetails = []; 407 | for (const likedUserId of user.recievedLikes) { 408 | const likedUser = await User.findById(likedUserId); 409 | if (likedUser) { 410 | receivedLikesDetails.push(likedUser); 411 | } 412 | } 413 | 414 | res.status(200).json({ receivedLikesDetails }); 415 | } catch (error) { 416 | res.status(500).json({ 417 | message: "Error fetching received likes details", 418 | error: error.message, 419 | }); 420 | } 421 | }); 422 | 423 | //endpoint to create a match betweeen two people 424 | app.post("/create-match", async (req, res) => { 425 | try { 426 | const { currentUserId, selectedUserId } = req.body; 427 | 428 | //update the selected user's crushes array and the matches array 429 | await User.findByIdAndUpdate(selectedUserId, { 430 | $push: { matches: currentUserId }, 431 | $pull: { crushes: currentUserId }, 432 | }); 433 | 434 | //update the current user's matches array recievedlikes array 435 | await User.findByIdAndUpdate(currentUserId, { 436 | $push: { matches: selectedUserId }, 437 | $pull: { recievedLikes: selectedUserId }, 438 | }); 439 | 440 | res.sendStatus(200); 441 | } catch (error) { 442 | res.status(500).json({ message: "Error creating a match", error }); 443 | } 444 | }); 445 | 446 | //endpoint to get all the matches of the particular user 447 | app.get("/users/:userId/matches", async (req, res) => { 448 | try { 449 | const { userId } = req.params; 450 | 451 | const user = await User.findById(userId); 452 | 453 | if (!user) { 454 | return res.status(404).json({ message: "User not found" }); 455 | } 456 | 457 | const matchIds = user.matches; 458 | 459 | const matches = await User.find({ _id: { $in: matchIds } }); 460 | 461 | res.status(200).json({ matches }); 462 | } catch (error) { 463 | res.status(500).json({ message: "Error retrieving the matches", error }); 464 | } 465 | }); 466 | 467 | io.on("connection", (socket) => { 468 | console.log("a user is connected"); 469 | 470 | socket.on("sendMessage", async (data) => { 471 | try { 472 | const { senderId, receiverId, message } = data; 473 | 474 | console.log("data", data); 475 | 476 | const newMessage = new Chat({ senderId, receiverId, message }); 477 | await newMessage.save(); 478 | 479 | //emit the message to the receiver 480 | io.to(receiverId).emit("receiveMessage", newMessage); 481 | } catch (error) { 482 | console.log("Error handling the messages"); 483 | } 484 | socket.on("disconnet", () => { 485 | console.log("user disconnected"); 486 | }); 487 | }); 488 | }); 489 | 490 | http.listen(8000, () => { 491 | console.log("Socket.IO server running on port 8000"); 492 | }); 493 | 494 | app.get("/messages", async (req, res) => { 495 | try { 496 | const { senderId, receiverId } = req.query; 497 | 498 | console.log(senderId); 499 | console.log(receiverId); 500 | 501 | const messages = await Chat.find({ 502 | $or: [ 503 | { senderId: senderId, receiverId: receiverId }, 504 | { senderId: receiverId, receiverId: senderId }, 505 | ], 506 | }).populate("senderId", "_id name"); 507 | 508 | res.status(200).json(messages); 509 | } catch (error) { 510 | res.status(500).json({ message: "Error in getting messages", error }); 511 | } 512 | }); 513 | 514 | 515 | //endpoint to delete the messages; 516 | 517 | app.post("/delete",async(req,res) => { 518 | try{ 519 | const {messages} = req.body; 520 | 521 | if(!Array.isArray(messages) || messages.length == 0){ 522 | return res.status(400).json({message:"Invalid request body"}) 523 | }; 524 | 525 | for(const messageId of messages){ 526 | await Chat.findByIdAndDelete(messageId); 527 | } 528 | 529 | res.status(200).json({message:"Messages delted successfully!"}) 530 | } catch(error){ 531 | res.status(500).json({message:"Internal server error",error}) 532 | } 533 | }) -------------------------------------------------------------------------------- /app/(tabs)/bio/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | Text, 4 | View, 5 | ScrollView, 6 | Image, 7 | Pressable, 8 | TextInput, 9 | Button, 10 | FlatList, 11 | Alert, 12 | } from "react-native"; 13 | import React, { useState, useEffect } from "react"; 14 | import { Entypo, AntDesign } from "@expo/vector-icons"; 15 | import Carousel from "react-native-snap-carousel"; 16 | import axios from "axios"; 17 | import "core-js/stable/atob"; 18 | import { jwtDecode } from "jwt-decode"; 19 | import AsyncStorage from "@react-native-async-storage/async-storage"; 20 | 21 | const index = () => { 22 | const [option, setOption] = useState("AD"); 23 | const [description, setDescription] = useState(""); 24 | const [activeSlide, setActiveSlide] = React.useState(0); 25 | const [userId, setUserId] = useState(""); 26 | const [selectedTurnOns, setSelectedTurnOns] = useState([]); 27 | const [lookingOptions, setLookingOptions] = useState([]); 28 | const [imageUrl, setImageUrl] = useState(""); 29 | const [images, setImages] = useState([]); 30 | const profileImages = [ 31 | { 32 | image: 33 | "https://images.pexels.com/photos/1042140/pexels-photo-1042140.jpeg?auto=compress&cs=tinysrgb&w=800", 34 | }, 35 | { 36 | image: 37 | "https://images.pexels.com/photos/1215695/pexels-photo-1215695.jpeg?auto=compress&cs=tinysrgb&w=800", 38 | }, 39 | { 40 | image: 41 | "https://images.pexels.com/photos/7580971/pexels-photo-7580971.jpeg?auto=compress&cs=tinysrgb&w=800", 42 | }, 43 | ]; 44 | const turnons = [ 45 | { 46 | id: "0", 47 | name: "Music", 48 | description: "Pop Rock-Indie pick our sound track", 49 | }, 50 | { 51 | id: "10", 52 | name: "Kissing", 53 | description: 54 | " It's a feeling of closeness, where every touch of lips creates a symphony of emotions.", 55 | }, 56 | { 57 | id: "1", 58 | name: "Fantasies", 59 | description: 60 | "Fantasies can be deeply personal, encompassing diverse elements such as romance", 61 | }, 62 | { 63 | id: "2", 64 | name: "Nibbling", 65 | description: 66 | "playful form of biting or taking small, gentle bites, typically done with the teeth", 67 | }, 68 | { 69 | id: "3", 70 | name: "Desire", 71 | description: "powerful emotion or attainment of a particular person.", 72 | }, 73 | ]; 74 | const data = [ 75 | { 76 | id: "0", 77 | name: "Casual", 78 | description: "Let's keep it easy and see where it goes", 79 | }, 80 | { 81 | id: "1", 82 | name: "Long Term", 83 | description: "How about a one life stand", 84 | }, 85 | { 86 | id: "2", 87 | name: "Virtual", 88 | description: "Let's have some virtual fun", 89 | }, 90 | { 91 | id: "3", 92 | name: "Open for Anything", 93 | description: "Let's Vibe and see where it goes", 94 | }, 95 | ]; 96 | useEffect(() => { 97 | const fetchUser = async () => { 98 | const token = await AsyncStorage.getItem("auth"); 99 | const decodedToken = jwtDecode(token); 100 | const userId = decodedToken.userId; 101 | setUserId(userId); 102 | }; 103 | 104 | fetchUser(); 105 | }, []); 106 | const fetchUserDescription = async () => { 107 | try { 108 | const response = await axios.get(`http://localhost:3000/users/${userId}`); 109 | console.log(response); 110 | const user = response.data; 111 | 112 | setDescription(user?.user?.description); 113 | setSelectedTurnOns(user.user?.turnOns); 114 | setImages(user?.user.profileImages); 115 | setLookingOptions(user?.user.lookingFor) 116 | } catch (error) { 117 | console.log("Error fetching user description", error); 118 | } 119 | }; 120 | useEffect(() => { 121 | if (userId) { 122 | fetchUserDescription(); 123 | } 124 | }, [userId]); 125 | const updateUserDescription = async () => { 126 | try { 127 | const response = await axios.put( 128 | `http://localhost:3000/users/${userId}/description`, 129 | { 130 | description: description, 131 | } 132 | ); 133 | 134 | console.log(response.data); 135 | 136 | if (response.status === 200) { 137 | Alert.alert("Success", "Description updated successfully"); 138 | } 139 | } catch (error) { 140 | console.log("Error updating the user Description"); 141 | } 142 | }; 143 | const handleToggleTurnOn = (turnOn) => { 144 | if (selectedTurnOns.includes(turnOn)) { 145 | removeTurnOn(turnOn); 146 | } else { 147 | addTurnOn(turnOn); 148 | } 149 | }; 150 | const handleOption = (lookingFor) => { 151 | if (lookingOptions.includes(lookingFor)) { 152 | removeLookingFor(lookingFor); 153 | } else { 154 | addLookingFor(lookingFor); 155 | } 156 | }; 157 | const addLookingFor = async (lookingFor) => { 158 | try { 159 | const response = await axios.put( 160 | `http://localhost:3000/users/${userId}/looking-for`, 161 | { 162 | lookingFor: lookingFor, 163 | } 164 | ); 165 | 166 | console.log(response.data); 167 | 168 | if (response.status == 200) { 169 | setLookingOptions([...lookingOptions, lookingFor]); 170 | } 171 | } catch (error) { 172 | console.log("Error addding looking for", error); 173 | } 174 | }; 175 | const removeLookingFor = async (lookingFor) => { 176 | try { 177 | const response = await axios.put( 178 | `http://localhost:3000/users/${userId}/looking-for/remove`, 179 | { 180 | lookingFor: lookingFor, 181 | } 182 | ); 183 | 184 | console.log(response.data); // Log the response for confirmation 185 | 186 | // Handle success or update your app state accordingly 187 | if (response.status === 200) { 188 | setLookingOptions(lookingOptions.filter((item) => item !== lookingFor)); 189 | } 190 | } catch (error) { 191 | console.error("Error removing looking for:", error); 192 | // Handle error scenarios 193 | } 194 | }; 195 | 196 | const addTurnOn = async (turnOn) => { 197 | try { 198 | const response = await axios.put( 199 | `http://localhost:3000/users/${userId}/turn-ons/add`, 200 | { 201 | turnOn: turnOn, 202 | } 203 | ); 204 | 205 | console.log(response.data); 206 | 207 | if (response.status == 200) { 208 | setSelectedTurnOns([...selectedTurnOns, turnOn]); 209 | } 210 | } catch (error) { 211 | console.log("Error adding turn on", error); 212 | } 213 | }; 214 | const removeTurnOn = async (turnOn) => { 215 | try { 216 | const response = await axios.put( 217 | `http://localhost:3000/users/${userId}/turn-ons/remove`, 218 | { 219 | turnOn: turnOn, 220 | } 221 | ); 222 | 223 | console.log(response.data); 224 | 225 | if (response.status == 200) { 226 | setSelectedTurnOns(selectedTurnOns.filter((item) => item !== turnOn)); 227 | } 228 | } catch (error) { 229 | console.log("error removing turn on", error); 230 | } 231 | }; 232 | const renderImageCarousel = ({ item }) => ( 233 | 236 | 246 | 249 | {activeSlide + 1}/{images.length} 250 | 251 | 252 | ); 253 | const handleAddImage = async () =>{ 254 | try{ 255 | const response = await axios.post(`http://localhost:3000/users/${userId}/profile-images`,{ 256 | imageUrl:imageUrl 257 | }); 258 | 259 | console.log(response); 260 | 261 | setImageUrl(""); 262 | } catch(error){ 263 | console.log("error",error) 264 | } 265 | } 266 | const getRandomImage = () => { 267 | const randomIndex = Math.floor(Math.random() * images.length); 268 | return images[randomIndex] 269 | } 270 | const randomImage = getRandomImage() 271 | return ( 272 | 273 | 274 | 280 | 281 | 282 | 298 | 309 | 310 | Bangalore 311 | 312 | 313 | 22 years 110 days 314 | 315 | 316 | 317 | 318 | 319 | 320 | 330 | setOption("AD")}> 331 | 338 | AD 339 | 340 | 341 | setOption("Photos")}> 342 | 349 | Photos 350 | 351 | 352 | setOption("Turn-ons")}> 353 | 360 | Turn-ons 361 | 362 | 363 | setOption("Looking For")}> 364 | 371 | Looking For 372 | 373 | 374 | 375 | 376 | 377 | {option == "AD" && ( 378 | 387 | setDescription(text)} 391 | style={{ 392 | fontFamily: "Helvetica", 393 | fontSize: description ? 17 : 17, 394 | }} 395 | placeholder="Write your AD for people to like you" 396 | // placeholderTextColor={"black"} 397 | /> 398 | 411 | 419 | Publish in feed 420 | 421 | 422 | 423 | 424 | )} 425 | 426 | 427 | 428 | {option == "Photos" && ( 429 | 430 | setActiveSlide(index)} 436 | /> 437 | 438 | 439 | Add a picture of yourself 440 | 451 | 457 | setImageUrl(text)} 460 | style={{ color: "gray", marginVertical: 10, width: 300 }} 461 | placeholder="enter your image url" 462 | /> 463 | 464 |