chat app #3a - Bikin aplikasi chat pakai ReactJS




auto format onsave: .vscode/settings.json

{
    "editor.formatOnSave": true,
}


  1. Source dari hasil part2 -> https://github.com/ponkcoding/ponk-chat-app/tree/v0.1.0
  2. Install material-ui
npm install @material-ui/core
npm install @material-ui/icons
  1. import font untuk material ui

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
  1. pages/Main/index.js

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import CssBaseline from "@material-ui/core/CssBaseline";
import Drawer from "@material-ui/core/Drawer";
import Hidden from "@material-ui/core/Hidden";
import IconButton from "@material-ui/core/IconButton";
import List from "@material-ui/core/List";
import MenuIcon from "@material-ui/icons/Menu";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import { makeStyles, useTheme } from "@material-ui/core/styles";
import { TextField } from "@material-ui/core";

const drawerWidth = 300;

const useStyles = makeStyles((theme) => ({
  root: {
    display: "flex",
    height: "calc(100% - 40px)",
  },
  drawer: {
    [theme.breakpoints.up("sm")]: {
      width: drawerWidth,
      flexShrink: 0,
    },
  },
  appBar: {
    [theme.breakpoints.up("sm")]: {
      width: `calc(100% - ${drawerWidth}px)`,
      marginLeft: drawerWidth,
    },
    background: "#f5f5f5",
    color: "rgba(0,0,0,.87)",
    boxShadow: "unset",
    borderBottom: "1px solid rgba(0,0,0,.12)",
  },
  menuButton: {
    marginRight: theme.spacing(2),
    [theme.breakpoints.up("sm")]: {
      display: "none",
    },
  },
  // necessary for content to be below app bar
  toolbar: theme.mixins.toolbar,
  drawerPaper: {
    width: drawerWidth,
  },
  content: {
    flexGrow: 1,
    padding: theme.spacing(2),
    display: "flex",
    marginTop: "40px",
    flexWrap: "wrap",
    height: "100%",
  },
  chatFooter: {
    flexBasis: "100%",
    height: "100px",
    background: "#f6f6f6",
    borderRadius: "15px",
  },
  chatContent: {
    width: "100%",
    height: "calc(100% - 100px)",
    paddingLeft: theme.spacing(4),
    overflowY: "scroll",
  },
  messageForm: {
    overflow: "hidden",
    margin: "20px",
    padding: "0",
  },
}));

const Main = (props) => {
  const { window } = props;
  const classes = useStyles();
  const theme = useTheme();
  const [mobileOpen, setMobileOpen] = React.useState(false);

  const handleDrawerToggle = () => {
    setMobileOpen(!mobileOpen);
  };

  const drawer = (
    <div>
      <div className={classes.toolbar} />
      <List>
        contact disini
      </List>
    </div>
  );

  const container =
    window !== undefined ? () => window().document.body : undefined;

  return (
    <div className={classes.root}>
      <CssBaseline />
      <AppBar position="fixed" className={classes.appBar}>
        <Toolbar>
          <IconButton
            color="inherit"
            aria-label="open drawer"
            edge="start"
            onClick={handleDrawerToggle}
            className={classes.menuButton}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" noWrap>
            Armin
          </Typography>
        </Toolbar>
      </AppBar>
      <nav className={classes.drawer} aria-label="mailbox folders">
        {/* The implementation can be swapped with js to avoid SEO duplication of links. */}
        <Hidden smUp implementation="css">
          <Drawer
            container={container}
            variant="temporary"
            anchor={theme.direction === "rtl" ? "right" : "left"}
            open={mobileOpen}
            onClose={handleDrawerToggle}
            classes={{
              paper: classes.drawerPaper,
            }}
            ModalProps={{
              keepMounted: true, // Better open performance on mobile.
            }}
          >
            {drawer}
          </Drawer>
        </Hidden>
        <Hidden xsDown implementation="css">
          <Drawer
            classes={{
              paper: classes.drawerPaper,
            }}
            variant="permanent"
            open
          >
            {drawer}
          </Drawer>
        </Hidden>
      </nav>
      <main className={classes.content}>
        <div className={classes.toolbar} />

        <div className={classes.chatContent}>
            pesan disini
        </div>

        <div className={classes.chatFooter}>
          <form className={classes.messageForm} noValidate autoComplete="off">
            <TextField
              id="input-message"
              className={classes.messageForm.input}
              variant="outlined"
              placeholder="type your message..."
              fullWidth={true}
              style={{ background: "#fff" }}
            />
          </form>
        </div>
      </main>
    </div>
  );
};

export default Main;

  1. components/MessageBubble/index.js

import { makeStyles } from "@material-ui/core";
import React from "react";

const useStyles = makeStyles((theme) => ({
  root: {
    position: "relative",
    display: "flex",
    flexDirection: "column",
    alignItems: "flex-start",
    justifyContent: "flex-end",
    padding: "0 16px 4px",
    paddingLeft: props => props.isMe ? "40px" : "16px",
    marginTop: "40px",
  },

  img: {
    position: "absolute",
    left: "-32px",
    margin: "0",
    height: "40px",
    width: "40px",
    top: "0",
  },

  bubble: {
    position: "relative",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: "12px",
    maxWidth: "100%",
    borderRadius: "20px",
    backgroundColor: props => props.isMe ? "#e0e0e0" : "#3c4252",
    color: props => props.isMe ? "rgba(0,0,0,.87)" : "#fff",
    marginLeft: props => props.isMe ? "auto" : "initial",
  },

  timestamp: {
    position: "absolute",
    width: "100%",
    fontSize: "11px",
    marginTop: "8px",
    top: "100%",
    left: "0",
    whiteSpace: "nowrap",
    color: "#999",
    textAlign: props => props.isMe ? "right" : "left",
  }
}));

const MessageBubble = (props) => {
  const classes = useStyles(props);
  const { isMe, message } = props;

  return (
    <div className={classes.root} >
      {!isMe &&
        <img className={classes.img} alt="" src="https://avataaars.io/?avatarStyle=Circle&topType=ShortHairFrizzle&accessoriesType=Prescription02&hairColor=Black&facialHairType=BeardMedium&facialHairColor=BrownDark&clotheType=CollarSweater&clotheColor=Gray01&eyeType=Wink&eyebrowType=FlatNatural&mouthType=Vomit&skinColor=DarkBrown" />
      }
      <div className={classes.bubble}>
        <div>
          {message.message}
        </div>
        <div className={classes.timestamp}>{message.createdAt}</div>
      </div>
    </div>
  );
};

export default MessageBubble;

  1. components/ContactList/index.js

import React from "react";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import { makeStyles } from "@material-ui/core";

const useStyles = makeStyles((theme) => ({
  root: {},
  img: {
    height: "40px",
    marginRight: theme.spacing(2),
  }
}));

const ContactList = (props) => {
  const classes = useStyles(props);
  const { user } = props;

  return (
    <ListItem button key={user.id} className={classes.root}>
      <img alt="" src={user.img} className={classes.img}></img>
      <ListItemText primary={user.name} />
    </ListItem>
  );
};

export default ContactList;

  1. install apollo grahpql client

npm install @apollo/client graphql
npm install subscriptions-transport-ws
  1. Apollo code snippet di App.js

import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
import { setContext } from "@apollo/client/link/context";
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  split,
  HttpLink,
  ApolloLink,
} from "@apollo/client";

const wsLink = new WebSocketLink({
    uri: process.env.REACT_APP_GRAPHQL_WEBSOCKET,
    options: {
      reconnect: true,
      connectionParams: {
        headers: {
          Authorization: token ? `Bearer ${token}` : "",
        },
      },
    },
  });
  const httpLink = new HttpLink({
    uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
  });
  const authLink = setContext((_, { headers }) => {
    // get the authentication token from local cookie if it exists
    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
      },
    };
  });

  const splitLink = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === "OperationDefinition" &&
        definition.operation === "subscription"
      );
    },
    wsLink,
    httpLink
  );

  const client = new ApolloClient({
    cache: new InMemoryCache(),
    link: ApolloLink.from([authLink, splitLink]),
  });
  1. .env

REACT_APP_BASE_URL=http://localhost:3000
REACT_APP_GRAPHQL_ENDPOINT=https://ponk-chat-app.herokuapp.com/v1/graphql
REACT_APP_GRAPHQL_WEBSOCKET=wss://ponk-chat-app.herokuapp.com/v1/graphql
REACT_APP_AUTH_DOMAIN=ponkcoding.au.auth0.com
REACT_APP_AUTH_CLIENTID=Hn3L6XL41BeNam3ZSPe5fTuT6WdACtwb

Komentar

postingan lainnya

Cara GAMPANG bikin syntax/code highlighter di blogger/blogspot TERBARU 2021

gopls not working at apple m1 visual studio code 1.54.3

chat app #3b - menghubungkan UI dengan graphql API

Cara Lapor SPT Tahunan dengan E-Form bagi freelancer 2021