Accord, a clone of Discord, is an instant messaging application where users can send each other direct messages and join and create servers to build Accord communities. This application uses a Ruby on Rails backend with a React frontend.
- ruby 2.5.1
- rails 5.2.6
- node v16.4.2
- npm 7.18.1
- react/redux
- postgresql
- action cable
- twilio
This feature allows users to interact with messages real time, such as sending messages, editing them, deleting them, replying to them or reacting to them.
live-chat-demo.mov
This feature uses the Twilio API to send text messages allowing for real phone number verification.
phone-number-verification-demo.mov
This feature allows users to create public server that can be discovered and joined by other users.
public-servers-demo.mov
This feature allows users to invite friends to servers real time.
server-invite-demo.mov
This feature was implemented to optimize readability of code while also allowing the component to be dynamic. This implementation takes advantage of the React-Redux framework and its single source of truth, allowing the component to access the currently logged in user.
import React from "react";
import Bubble from "../misc/bubble";
const MessageSettings = props => {
const handleReply = () => {
props.reply();
document.querySelector("#message-form > span").focus();
}
return (
<div className="message-settings" style={props.style}>
<div>
<img src={window.reaction} alt="reaction" onClick={e => props.react(e)} />
<Bubble text="Add Reaction" top="-38px" />
</div>
{props.message.senderId === props.currentUserId && !props.message.invitation ?
<div>
<img src={window.edit} alt="edit" onClick={() => props.edit()}/>
<Bubble text="Edit" top="-38px" />
</div>
: null}
<div>
<img src={window.reply} alt="reply" onClick={handleReply}/>
<Bubble text="Reply" top="-38px" />
</div>
{props.message.senderId === props.currentUserId ?
<div>
<img src={window.trash} alt="delete" onClick={() => props.setDeleting()}/>
<Bubble text="Delete" top="-38px" />
</div>
: null}
</div>
);
};
export default MessageSettings;
This feature was implemented so that it can dynamically adapt to the whatever functionality is needed. The hardest part about this implentation was figuring out how to most effectively make the menu dynamic. It was done by passing in options with corresponding functions to list directly as elements in the context menu.
// simplified for demonstration purposes
import React, { useState, useEffect, useRef } from "react";
const ContextMenu = ({ options, top, left, closeMenu }) => {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const containerRef = useRef(null);
// closes context menu on outside click
useEffect(() => {
const handleOutsideClick = e => {
if (containerRef.current && !containerRef.current.contains(e.target)) closeMenu();
};
document.addEventListener("mousedown", handleOutsideClick);
return () => document.removeEventListener("mousedown", handleOutsideClick);
}, []);
// positions context menu within application dimensions
const resize = () => {
const menuDims = document.getElementById("context-menu").getBoundingClientRect();
setX(Math.min(Math.max(800, window.innerWidth) - menuDims.width - 10, left));
setY(Math.min(window.innerHeight - menuDims.height - 10, top));
};
useEffect(() => resize(), [options]);
// maps menu items dynamically
const menu = options.map((option, i) => (
<li key={`option-${i}`} className={ option.disabled ? "disabled" : option.color }
onClick={() => {
option.function();
closeMenu();
}}>{option.text}</li>
));
return (
<ul id="context-menu" ref={containerRef} style={{ top: `${y}px`, left: `${x}px` }}>
{menu}
</ul>
);
};
export default ContextMenu;
- through Action Cable from Ruby on Rails
- through the Twilio API