Skip to main content

Homies

Social networking and friend connections for action sports riders.

Overview

Homies is TrickBook's social feature that allows users to connect with other riders, follow their progress, and communicate directly. Users can send homie requests, exchange direct messages, and see their friends' activity in the feed.

Frontend Implementation

Website (Next.js)

Location: /pages/

FilePurpose
homies/index.jsHomies list and discovery page
profile/[id].jsUser profile with homie status
messages/index.jsConversation list (DMs)
messages/[conversationId].jsIndividual chat view

Components:

ComponentLocationPurpose
HomieCard.js/components/HomieCard.jsUser preview card with action buttons
HomieRequestCard.js/components/HomieRequestCard.jsPending request with accept/decline
UserSearchBar.js/components/UserSearchBar.jsSearch for users to add as homies
ChatBubble.js/components/messages/ChatBubble.jsMessage bubble (sent/received)
ConversationItem.js/components/messages/ConversationItem.jsConversation preview in list
TypingIndicator.js/components/messages/TypingIndicator.jsShows when other user is typing

Key Features:

  • Homie request system (send, accept, decline)
  • User search and discovery
  • Direct messaging between homies
  • Real-time message delivery (Socket.IO)
  • Typing indicators
  • Read receipts
  • Unread message badges
  • Online/offline status

Mobile App (React Native)

Location: /TrickList/screens/

ScreenPurpose
HomiesScreen.jsList of homies with search
HomieRequestsScreen.jsPending homie requests
UserProfileScreen.jsView other user's profile
MessagesScreen.jsConversation list
ChatScreen.jsIndividual chat view

Push Notifications:

  • New homie request received
  • Homie request accepted
  • New message received

Backend Implementation

Database Schema

Collection: users (Homies stored as array)

{
_id: ObjectId,
name: String,
email: String,
imageUri: String,
// ... other user fields

// Homies system
homies: [ObjectId], // Accepted homie user IDs
homieRequests: {
sent: [ObjectId], // Requests sent by this user
received: [ObjectId] // Requests received by this user
},

// Privacy settings
privacy: {
profileVisibility: String, // "public", "homies", "private"
allowMessages: String, // "everyone", "homies", "none"
showOnlineStatus: Boolean
},

// Activity
lastSeen: Date,
isOnline: Boolean
}

Collection: conversations

{
_id: ObjectId,
participants: [ObjectId, ObjectId], // Sorted user IDs for consistent lookup
lastMessage: {
content: String,
senderId: ObjectId,
createdAt: Date
},
unreadCount: {
[userId]: Number // Unread count per participant
},
createdAt: Date,
updatedAt: Date
}

Indexes:

  • { participants: 1 } - Find conversations by users
  • { updatedAt: -1 } - Sort by recent activity

Collection: messages

{
_id: ObjectId,
conversationId: ObjectId,
senderId: ObjectId,
content: String,
status: String, // "sent", "delivered", "read"
readAt: Date, // When recipient read message
createdAt: Date
}

Indexes:

  • { conversationId: 1, createdAt: 1 } - Messages in conversation order

API Endpoints

Base URL: https://api.thetrickbook.com/api

Homies

MethodEndpointDescriptionAuth
GET/user/homiesGet current user's homiesJWT
GET/user/homie-requestsGet pending requestsJWT
POST/user/homie-request/:userIdSend homie requestJWT
PUT/user/homie-request/:userId/acceptAccept requestJWT
PUT/user/homie-request/:userId/declineDecline requestJWT
DELETE/user/homie/:userIdRemove homieJWT
GET/users/searchSearch users by name/emailJWT
GET/users/:id/profileGet user's public profileJWT

Direct Messages

MethodEndpointDescriptionAuth
GET/dm/conversationsList user's conversationsJWT
GET/dm/conversations/:idGet conversation detailsJWT
GET/dm/conversations/:id/messagesGet messages (paginated)JWT
POST/dm/conversationsStart new conversationJWT
POST/dm/conversations/:id/messagesSend messageJWT
PUT/dm/conversations/:id/readMark as readJWT
GET/dm/unread-countGet total unread countJWT

Routes Implementation

File: /Backend/routes/user.js (Homies)

// Send homie request
router.post("/homie-request/:userId", auth, async (req, res) => {
const targetUser = await User.findById(req.params.userId);
if (!targetUser) return res.status(404).json({ error: "User not found" });

// Check if already homies
if (req.user.homies.includes(req.params.userId)) {
return res.status(400).json({ error: "Already homies" });
}

// Check if request already sent
if (req.user.homieRequests.sent.includes(req.params.userId)) {
return res.status(400).json({ error: "Request already sent" });
}

// Add to sent/received arrays
await User.findByIdAndUpdate(req.user._id, {
$addToSet: { "homieRequests.sent": req.params.userId }
});

await User.findByIdAndUpdate(req.params.userId, {
$addToSet: { "homieRequests.received": req.user._id }
});

// Send push notification
await sendPushNotification(targetUser.expoPushToken, {
title: "New Homie Request",
body: `${req.user.name} wants to be your homie!`
});

res.json({ success: true });
});

// Accept homie request
router.put("/homie-request/:userId/accept", auth, async (req, res) => {
// Add to both users' homies arrays
await User.findByIdAndUpdate(req.user._id, {
$addToSet: { homies: req.params.userId },
$pull: { "homieRequests.received": req.params.userId }
});

await User.findByIdAndUpdate(req.params.userId, {
$addToSet: { homies: req.user._id },
$pull: { "homieRequests.sent": req.user._id }
});

res.json({ success: true });
});

File: /Backend/routes/dm.js (Direct Messages)

// Start or get existing conversation
router.post("/conversations", auth, async (req, res) => {
const { targetUserId } = req.body;

// Verify users are homies (only homies can message)
const user = await User.findById(req.user._id);
if (!user.homies.includes(targetUserId)) {
return res.status(403).json({ error: "Can only message homies" });
}

// Sort participant IDs for consistent lookup
const participants = [req.user._id, targetUserId].sort();

// Find existing or create new
let conversation = await Conversation.findOne({ participants });

if (!conversation) {
conversation = await Conversation.create({
participants,
unreadCount: { [req.user._id]: 0, [targetUserId]: 0 }
});
}

res.json(conversation);
});

// Send message
router.post("/conversations/:id/messages", auth, async (req, res) => {
const { content } = req.body;
const conversation = await Conversation.findById(req.params.id);

// Create message
const message = await Message.create({
conversationId: req.params.id,
senderId: req.user._id,
content,
status: "sent"
});

// Update conversation
const otherUserId = conversation.participants.find(
p => p.toString() !== req.user._id.toString()
);

await Conversation.findByIdAndUpdate(req.params.id, {
lastMessage: { content, senderId: req.user._id, createdAt: new Date() },
$inc: { [`unreadCount.${otherUserId}`]: 1 },
updatedAt: new Date()
});

// Emit via Socket.IO for real-time delivery
io.to(`user:${otherUserId}`).emit("message:new", message);

res.json(message);
});

Real-Time Communication (Socket.IO)

Server Setup

File: /Backend/socket/index.js

const socketIO = require("socket.io");
const jwt = require("jsonwebtoken");

function initializeSocket(server) {
const io = socketIO(server, {
cors: {
origin: ["https://thetrickbook.com", "http://localhost:3000"],
credentials: true
}
});

// JWT authentication middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.userId = decoded.user.id;
next();
} catch (err) {
next(new Error("Authentication error"));
}
});

io.on("connection", (socket) => {
// Join user's personal room
socket.join(`user:${socket.userId}`);

// Update online status
User.findByIdAndUpdate(socket.userId, { isOnline: true, lastSeen: new Date() });

// Handle typing indicators
socket.on("typing:start", ({ conversationId }) => {
socket.to(`conversation:${conversationId}`).emit("typing:start", {
userId: socket.userId
});
});

socket.on("typing:stop", ({ conversationId }) => {
socket.to(`conversation:${conversationId}`).emit("typing:stop", {
userId: socket.userId
});
});

// Join conversation room
socket.on("join:conversation", ({ conversationId }) => {
socket.join(`conversation:${conversationId}`);
});

socket.on("disconnect", () => {
User.findByIdAndUpdate(socket.userId, { isOnline: false, lastSeen: new Date() });
});
});

return io;
}

Client Setup (Next.js)

File: /lib/socket.js

import { io } from "socket.io-client";

let socket = null;

export function connectSocket(token) {
if (socket?.connected) return socket;

socket = io(process.env.NEXT_PUBLIC_SOCKET_URL, {
auth: { token },
transports: ["websocket", "polling"]
});

return socket;
}

export function disconnectSocket() {
socket?.disconnect();
socket = null;
}

export function getSocket() {
return socket;
}

Socket Events

EventDirectionDescription
message:newServer → ClientNew message received
typing:startBidirectionalUser started typing
typing:stopBidirectionalUser stopped typing
messages:readServer → ClientMessages marked as read
homie:requestServer → ClientNew homie request
homie:acceptedServer → ClientHomie request accepted

Privacy & Security

Message Privacy

  • Only homies can send messages
  • Messages encrypted in transit (TLS)
  • No message content logged on server

Blocking (Future)

  • Block users from sending requests
  • Block from viewing profile
  • Remove from homies list

Reporting (Future)

  • Report inappropriate messages
  • Report user profiles
  • Admin review queue

Mobile Considerations

Push Notifications

  • New homie request
  • Request accepted
  • New message (when app backgrounded)
  • Badge count for unread messages

Background Message Sync

  • Sync messages when app opens
  • Show notification badges
  • Local notification for new messages

Offline Mode

  • Queue outgoing messages
  • Show pending status
  • Sync when connection restored

UI/UX Patterns

Homie Request Flow

  1. User searches for rider
  2. Taps "Add Homie" button
  3. Request sent, button shows "Pending"
  4. Target user sees request in notifications
  5. Accept/Decline options
  6. Both users now see each other as homies

Message Read Receipts

  • Single checkmark: Sent
  • Double checkmark: Delivered
  • Yellow checkmarks: Read