VIDEO
auto format onsave:
.vscode/settings.json
{
"editor.formatOnSave": true,
}
Source dari hasil part2 -> https://github.com/ponkcoding/ponk-chat-app/tree/v0.1.0 Install material-ui
npm install @material-ui/core
npm install @material-ui/icons
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" />
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;
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;
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;
install apollo grahpql client
npm install @apollo/client graphql
npm install subscriptions-transport-ws
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]),
});
.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
Posting Komentar