/otp-ui

Primary LanguageTypeScript

OTP

An app that helps ๐ŸŒˆ Six Siege players find other ๐ŸŒˆ Six Siege players who are also looking for ๐ŸŒˆ Six Siege players...to play ๐ŸŒˆ Six Siege. And make friends.

How it works:

  • โœ” Login via Discord OAuth
  • โš™ In the settings page, setup your profile w/ your rank, your favorite attacker + defender, as well as images, links to your social media accounts, etc.
  • ๐Ÿ‘€ Save and go check out other people who have created profiles on the site!
  • ๐Ÿ‘‹ Swipe right on a profile to wait for a connection, or swipe left to pass.
  • ๐Ÿ˜ If the other person swipes right, its a match!
  • ๐Ÿ’— Once you've matched, they'll show up in your matches tab.
  • ๐Ÿ˜ Send them a discord friend request, or chat with them directly through the chat tab.

Screenshots

Login

Login

Main - where you can swipe profiles

Main

Settings - set your name, rank, operators, social media...etc

Settings

Match + Chat - displays list of matches and open chats
Profile, About...etc

Etc

Tech

  • โญ React
  • โญ Typescript
  • Express
  • TypeORM
  • Postgres
  • Node

APIs

  • ๐ŸŽฎ Discord OAuth
  • โ˜๏ธ Cloudinary

FUNCTIONAL HIGHLIGHTS

โ™ป Redux

To help manage state on a global level, and provide components on nonlinear branches with access to data without passing props around like a hot ๐Ÿฅ”, we used Redux (specifically, Redux Toolkit). This meant that we could store data in our central Redux store and access that data from any component without the need to pass that data in as props.

To oversimplify, like with state, we initialize any variables (like stuff=0) we want in Redux and create a action for it (like setStuff). When we want to update the state, we use the dispatch() function and dispatch the data we want to set inside the set action we want to call. Dispatch will then go to the store where it compares the action we want against all the available actions, and will update the initial data with the dispatched data once a matching set action is found. Then we can call that updated data from the store using useSelector()

In OTP, Redux is used to store things such as the user's profile information upon loading the page, as well as the matches and chats they currently have. This frees us from making multiple similar api calls in different components. Saving information passed back from the socket in the store also allows for automatic responsiveness without having to reload when recording other user's sockets logging on/off when it comes to chat functionality.

const rootReducer = combineReducers({
// the 3 main reducers, each reducer holds data
// at a global level and is organized by what type of data it holds
  profile: profileReducer,
  match: matchReducer,
  chat: chatReducer,
});

const store = configureStore({
// combines all the reducers into one
  reducer: rootReducer,
});

// ...
// disptching
dispatch(initializeProfile(profileResponse));

// selecting
const rank = useSelector((state: RootState) => state.profile.rank);

๐Ÿ”Œ Sockets

WebSockets allow for a persistent and open two-way communication channel between the client and the server. Servers can push information to clients, and so we don't need to constantly ping the API to check if there is any new information. This is especially useful for real-time functionality, like chat. However, since websockets keep a constantly open connection, it can consume large amounts of your server's resources. And too many open connections to your database can โ˜  it.

socket.io is a JS library that uses the WebSocket protocol and allows for real-time, bi-directional connection between clients and the server.

OTP uses socket.io to create open WebSocket connections during while the client is interacting with the page. Each user has their own open socket which we track and identify. with this, we can send chat messages from one user to another specific user, and broadcast online/offline status'.

import io from 'socket.io-client';

// client visits the page and their socket is sent to the server to be logged
    //...
    // conect to socket
      const socket = io(API!);

    // set the socket with the socket instance
      setSocket(socket);

    //socket instance.emit('event name', message being passed back)
      socket.emit('sendMyId', discordId);

// getting the online/offline status of all the sockets that are online
// this is then mapped to a user's chats to see who is online or not
    //...

      socket.on('onlineChats', (onlineChats: string[]) => {
        dispatch(setOnlineChats(onlineChats));
      });

      socket.on('nowOnline', (onlineId: string) => {
        dispatch(setOnlineChat(onlineId));
      });

      socket.on('nowOffline', (offlineId: string) => {
        dispatch(setOfflineChat(offlineId));
      });

๐Ÿ’ขTypescript

  • ๐Ÿ˜  Typescript when you try to write bad code.
  • ๐Ÿฅด Javascript when you write bad code. OTP was first written in JS and then converted to TS. TS is mean but it protects you from yourself and your own bad code.
// from interfaces.ts
export interface OTPRequestInit extends RequestInit {
  headers?: HeadersInit | OTPHeaders;
}

SOME CUTE FEATURE HIGHLIGHTS

Chat Protection ๐Ÿฆ™

You can only enter chats where you have matched ๐Ÿ’• with the other person. Otherwise you get the ๐Ÿ‘ข.

const checkMatch = matches.filter(
      (match, i) => buddyId === match.liker.discordId
    );

Who's Online? ๐ŸŸข

Sockets are leveraged to check who is online and who is offline.

Dynamic buttons based on location ๐Ÿงจ

The buttons wiggle and changed based on where you are. I think its fun. ๐Ÿค—

useEffect(() => {
    setBack(window.location.pathname === '/');
  }, [location]);

Privacy Please ๐Ÿ›‘โœ‹

If you're not logged in, you can't access any of the protected pages.

๐Ÿ”ŽLookup tables๐Ÿ”

Lookup tables for all the att/def operators, and social media sites so we could save ourselves from some typing.