src/pages/app.tsx
import React, { useEffect } from "react"
import ms from "ms"
import { useLazyQuery, gql } from "@apollo/client"
import { navigate, PageProps, Link } from "gatsby"
import { Router } from "@reach/router"
import Avatar from "@material-ui/core/Avatar"
import Button from "@material-ui/core/Button"
import ButtonGroup from "@material-ui/core/ButtonGroup"
import Box from "@material-ui/core/Box"
import { useTheme, makeStyles } from "@material-ui/core/styles"
import useMediaQuery from "@material-ui/core/useMediaQuery"
import styled, { createGlobalStyle } from "styled-components"
import { LayoutManager } from "components/layoutManager"
import { LoadingPage } from "components/LoadingPage"
import SEO from "components/seo"
import {
Profile,
Settings,
Discussion,
Discussions,
Submit,
Default,
} from "components/App"
import { useVerifyTokenSet } from "utils"
import { Query, QueryGetOrCreateUserArgs, UserInput } from "types"
const ACTIVE_LINK = "ACTIVE_LINK"
const useStyles = makeStyles((theme) => {
return {
paperRoot: {
background: "var(--geist-background)",
boxShadow: "var(--shadow-medium)",
display: "flex",
flexDirection: "column",
},
groupedOutlined: {
borderColor: "var(--accents-2)",
"&:hover": {
background: "var(--accents-1)",
},
},
buttonRoot: {
fontFamily: "var(--font-sans)",
fontWeight: "normal",
color: "var(--accents-5)",
transition: theme.transitions.create(["color"], {
easing: theme.transitions.easing.easeInOut,
}),
"&:hover": {
color: "var(--geist-foreground)",
},
/** @see https://stackoverflow.com/a/53772369/9823455 */
[`&.${ACTIVE_LINK}`]: {
color: "var(--geist-foreground)",
"&::before": {
content: '""',
display: "block",
position: "absolute",
height: 0,
left: "9px",
right: "9px",
bottom: 0,
borderBottom: "2px solid",
},
},
},
divider: {
background: "var(--accents-2)",
},
avatarRoot: {
height: "var(--geist-space-24x)",
width: "var(--geist-space-24x)",
border: "1px solid var(--accents-2)",
},
}
})
const AppOverrideStyles = createGlobalStyle`
h1, h2, h3, h4, h5, h6, p, pre {
margin: unset;
}
p {
margin: var(--geist-space-gap-half) 0;
}
img {
/* override typography.js */
margin: unset;
}
`
const AppHeader = styled.header`
background: var(--geist-background);
border-bottom: 1px solid var(--accents-2);
`
const AppBody = styled.div`
background: var(--accents-1);
`
export const GET_OR_CREATE_USER = gql`
query GetOrCreateUser($userInput: UserInput!) {
user: getOrCreateUser(userInput: $userInput) {
id
created
identities {
providerName
dateCreated
}
name
email
family_name
given_name
preferred_username
avatar_url
}
}
`
const GET_USERS = gql`
query GetUsers {
users: getUsers {
id
created
# identities {
# providerName
# }
name
family_name
given_name
preferred_username
avatar_url
}
}
`
/**
* Anything at `/app/*` requires the user to be authenticated
*/
const App = ({ location }: PageProps) => {
const { isLoggedIn, idTokenPayload, accessTokenPayload } = useVerifyTokenSet()
const classes = useStyles()
const theme = useTheme()
const xsDown = useMediaQuery(theme.breakpoints.down("xs"))
const userInput: UserInput = {
id: idTokenPayload?.["cognito:username"] || idTokenPayload?.sub,
email: idTokenPayload?.email,
identities: idTokenPayload?.identities,
name: idTokenPayload?.name,
family_name: idTokenPayload?.family_name,
given_name: idTokenPayload?.given_name,
}
/**
* This is the input for the `GET_OR_CREATE_USER` query and
* it is also passed to <Settings/> so that another mutation
* may update the Apollo InMemoryCache
*/
const getOrCreateUserVariables: QueryGetOrCreateUserArgs = {
userInput,
}
const [getUsers, { data: usersData, loading: usersLoading }] = useLazyQuery<{
users: Query["getUsers"]
}>(GET_USERS)
const users = usersData?.users
const [
getOrCreateUser,
{ data: userData, loading: userLoading },
] = useLazyQuery<{
user: Query["getOrCreateUser"]
}>(GET_OR_CREATE_USER, {
variables: getOrCreateUserVariables,
onCompleted: async (res) => {
await getUsers()
},
onError: (err) => {},
})
const user = userData?.user
useEffect(() => {
if (isLoggedIn === true) getOrCreateUser()
}, [isLoggedIn])
if (isLoggedIn === null) {
return <LoadingPage />
}
if (isLoggedIn === false) {
navigate("/auth/login")
return null
}
return (
<>
<SEO title="App" />
<AppOverrideStyles />
<LayoutManager location={location}>
<Box
py={2}
px="var(--geist-gap)"
mx="auto"
mt={xsDown ? 0 : "var(--header-height)"}
maxWidth="var(--geist-page-width-with-margin)"
>
<ButtonGroup
disableRipple
variant="outlined"
classes={{
groupedOutlined: classes.groupedOutlined,
}}
>
<Button
classes={{ root: classes.buttonRoot }}
component={Link}
to="/app/profile"
activeClassName={ACTIVE_LINK}
>
Profile
</Button>
<Button
classes={{ root: classes.buttonRoot }}
component={Link}
to="/app/settings"
activeClassName={ACTIVE_LINK}
>
Settings
</Button>
<Button
classes={{ root: classes.buttonRoot }}
component={Link}
to="/app/discussions"
activeClassName={ACTIVE_LINK}
>
Discussions
</Button>
<Button
classes={{ root: classes.buttonRoot }}
component={Link}
to="/app/post"
activeClassName={ACTIVE_LINK}
>
Post
</Button>
</ButtonGroup>
</Box>
<AppHeader>
<Box
pt={6}
pb={10.5}
px="var(--geist-gap)"
mx="auto"
maxWidth="var(--geist-page-width-with-margin)"
display="flex"
>
<Box mr={2}>
<Avatar
src={user?.avatar_url}
classes={{ root: classes.avatarRoot }}
></Avatar>
</Box>
<Box display="flex" flexDirection="column">
<h1>{idTokenPayload?.name ?? accessTokenPayload?.username}</h1>
<p>
Logged in:
{ms(
+new Date() - +new Date(accessTokenPayload?.auth_time * 1000)
)}
</p>
</Box>
</Box>
</AppHeader>
<AppBody>
<Box
px="var(--geist-gap)"
mx="auto"
maxWidth="var(--geist-page-width-with-margin)"
style={{
transform: "translateY(var(--geist-space-small-negative))",
}}
>
<Router basepath="/app">
<Profile
path="/profile"
user={user}
userLoading={userLoading}
users={users}
usersLoading={usersLoading}
/>
<Settings
path="/settings"
user={user}
variablesForCacheUpdate={getOrCreateUserVariables}
/>
<Submit path="/post" />
<Discussions path="/discussions" />
<Discussion path="/discussions/:discussionId" />
<Default path="/*" />
</Router>
</Box>
<div
style={{
paddingTop: "56px", // match the SimpleBottomNavigation height
}}
/>
</AppBody>
</LayoutManager>
</>
)
}
export default App