basic items page, with user permissioning :)
(the permissioning happens on the backend in the prev change! but yeah we send the auth token in the headers, so the backend knows who you are and whether to show you private data) (also it is just owned items not in any list!)
This commit is contained in:
parent
e2b5486168
commit
70d3b06742
5 changed files with 163 additions and 27 deletions
|
@ -5,11 +5,13 @@ import { CSSReset, ChakraProvider } from "@chakra-ui/core";
|
|||
import defaultTheme from "@chakra-ui/theme";
|
||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||
import loadable from "@loadable/component";
|
||||
import { useAuth0 } from "@auth0/auth0-react";
|
||||
|
||||
import apolloClient from "./apolloClient";
|
||||
import buildApolloClient from "./apolloClient";
|
||||
|
||||
const WardrobePage = loadable(() => import("./WardrobePage"));
|
||||
const ItemsPage = loadable(() => import("./ItemsPage"));
|
||||
const HomePage = loadable(() => import("./HomePage"));
|
||||
const WardrobePage = loadable(() => import("./WardrobePage"));
|
||||
|
||||
const theme = {
|
||||
...defaultTheme,
|
||||
|
@ -39,22 +41,40 @@ function App() {
|
|||
audience="https://impress-2020.openneo.net/api"
|
||||
scope=""
|
||||
>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<ApolloProviderWithAuth0>
|
||||
<ChakraProvider theme={theme}>
|
||||
<CSSReset />
|
||||
<Switch>
|
||||
<Route path="/outfits/new">
|
||||
<WardrobePage />
|
||||
</Route>
|
||||
<Route path="/user/:userId/items">
|
||||
<ItemsPage />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<HomePage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</ChakraProvider>
|
||||
</ApolloProvider>
|
||||
</ApolloProviderWithAuth0>
|
||||
</Auth0Provider>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
function ApolloProviderWithAuth0({ children }) {
|
||||
const auth0 = useAuth0();
|
||||
const auth0Ref = React.useRef(auth0);
|
||||
|
||||
React.useEffect(() => {
|
||||
auth0Ref.current = auth0;
|
||||
}, [auth0]);
|
||||
|
||||
const client = React.useMemo(
|
||||
() => buildApolloClient(() => auth0Ref.current),
|
||||
[]
|
||||
);
|
||||
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -13,12 +13,13 @@ import {
|
|||
useToast,
|
||||
} from "@chakra-ui/core";
|
||||
import { MoonIcon, SunIcon } from "@chakra-ui/icons";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { useAuth0 } from "@auth0/auth0-react";
|
||||
|
||||
import { Heading1, usePageTitle } from "./util";
|
||||
import OutfitPreview from "./components/OutfitPreview";
|
||||
import useCurrentUser from "./components/useCurrentUser";
|
||||
|
||||
import HomepageSplashImg from "../images/homepage-splash.png";
|
||||
import HomepageSplashImg2x from "../images/homepage-splash@2x.png";
|
||||
|
@ -76,32 +77,21 @@ function HomePage() {
|
|||
}
|
||||
|
||||
function UserLoginLogout() {
|
||||
const {
|
||||
isLoading,
|
||||
user,
|
||||
isAuthenticated,
|
||||
loginWithRedirect,
|
||||
logout,
|
||||
} = useAuth0();
|
||||
const { isLoading, isAuthenticated, loginWithRedirect, logout } = useAuth0();
|
||||
const { id, username } = useCurrentUser();
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAuthenticated) {
|
||||
// NOTE: Users created correctly should have these attributes... but I'm
|
||||
// coding defensively, because third-party integrations are always a
|
||||
// bit of a thing, and I don't want failures to crash us!
|
||||
const username = user["https://oauth.impress-2020.openneo.net/username"];
|
||||
const id = user.sub?.match(/^auth0\|impress-([0-9]+)$/)?.[1];
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
{username && <Box fontSize="sm">Hi, {username}!</Box>}
|
||||
{id && (
|
||||
<Button
|
||||
as="a"
|
||||
href={`https://impress.openneo.net/user/${id}-${username}/closet`}
|
||||
as={Link}
|
||||
to={`/user/${id}/items`}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
marginLeft="2"
|
||||
|
|
73
src/app/ItemsPage.js
Normal file
73
src/app/ItemsPage.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import React from "react";
|
||||
import { Box, Image, Wrap } from "@chakra-ui/core";
|
||||
import gql from "graphql-tag";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useQuery } from "@apollo/client";
|
||||
|
||||
import HangerSpinner from "./components/HangerSpinner";
|
||||
import { Heading1 } from "./util";
|
||||
import useCurrentUser from "./components/useCurrentUser";
|
||||
|
||||
function ItemsPage() {
|
||||
const { userId } = useParams();
|
||||
const currentUser = useCurrentUser();
|
||||
const isCurrentUser = currentUser.id === userId;
|
||||
|
||||
const { loading, error, data } = useQuery(
|
||||
gql`
|
||||
query ItemsPage($userId: ID!) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
username
|
||||
itemsTheyOwn {
|
||||
id
|
||||
name
|
||||
thumbnailUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ variables: { userId } }
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box padding="6" display="flex" justifyContent="center">
|
||||
<HangerSpinner boxSize="48px" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Box padding="6" color="red.400">
|
||||
{error.message}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box padding="6" maxWidth="800px" margin="0 auto">
|
||||
<Heading1 marginBottom="6">
|
||||
{isCurrentUser ? "Items you own" : `Items ${data.user.username} owns`}
|
||||
</Heading1>
|
||||
<Wrap justify="center">
|
||||
{data.user.itemsTheyOwn.map((item) => (
|
||||
<Box key={item.id} width="100px" textAlign="center">
|
||||
<Image
|
||||
src={item.thumbnailUrl}
|
||||
alt=""
|
||||
height="80px"
|
||||
width="80px"
|
||||
boxShadow="md"
|
||||
margin="0 auto"
|
||||
/>
|
||||
{item.name}
|
||||
</Box>
|
||||
))}
|
||||
</Wrap>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ItemsPage;
|
|
@ -1,4 +1,5 @@
|
|||
import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
|
||||
import { setContext } from "@apollo/client/link/context";
|
||||
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
|
@ -55,7 +56,7 @@ const typePolicies = {
|
|||
// don't love escape-hatching to the client like this, but...
|
||||
let cachedData;
|
||||
try {
|
||||
cachedData = client.readQuery({
|
||||
cachedData = hackyEscapeHatchClient.readQuery({
|
||||
query: gql`
|
||||
query CacheLookupForItemAppearanceReader(
|
||||
$speciesId: ID!
|
||||
|
@ -105,6 +106,32 @@ const persistedQueryLink = createPersistedQueryLink({
|
|||
useGETForHashedQueries: true,
|
||||
});
|
||||
const httpLink = createHttpLink({ uri: "/api/graphql" });
|
||||
const buildAuthLink = (getAuth0) =>
|
||||
setContext(async (_, { headers }) => {
|
||||
// Wait for auth0 to stop loading, so we can maybe get a token! We'll do
|
||||
// this hackily by checking every 100ms until it's true.
|
||||
await new Promise((resolve) => {
|
||||
function check() {
|
||||
if (getAuth0().isLoading) {
|
||||
setTimeout(check, 100);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
check();
|
||||
});
|
||||
|
||||
const { isAuthenticated, getAccessTokenSilently } = getAuth0();
|
||||
if (isAuthenticated) {
|
||||
const token = await getAccessTokenSilently();
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : "",
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const initialCache = {};
|
||||
for (const zone of cachedZones) {
|
||||
|
@ -115,10 +142,17 @@ for (const zone of cachedZones) {
|
|||
* apolloClient is the global Apollo Client instance we use for GraphQL
|
||||
* queries. This is how we communicate with the server!
|
||||
*/
|
||||
const client = new ApolloClient({
|
||||
link: persistedQueryLink.concat(httpLink),
|
||||
cache: new InMemoryCache({ typePolicies }).restore(initialCache),
|
||||
connectToDevTools: true,
|
||||
});
|
||||
let hackyEscapeHatchClient = null;
|
||||
const buildClient = (getAuth0) => {
|
||||
const client = new ApolloClient({
|
||||
link: buildAuthLink(getAuth0).concat(persistedQueryLink).concat(httpLink),
|
||||
cache: new InMemoryCache({ typePolicies }).restore(initialCache),
|
||||
connectToDevTools: true,
|
||||
});
|
||||
|
||||
export default client;
|
||||
hackyEscapeHatchClient = client;
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export default buildClient;
|
||||
|
|
19
src/app/components/useCurrentUser.js
Normal file
19
src/app/components/useCurrentUser.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { useAuth0 } from "@auth0/auth0-react";
|
||||
|
||||
function useCurrentUser() {
|
||||
const { isLoading, isAuthenticated, user } = useAuth0();
|
||||
|
||||
if (isLoading || !isAuthenticated) {
|
||||
return { id: null, username: null };
|
||||
}
|
||||
|
||||
// NOTE: Users created correctly should have these attributes... but I'm
|
||||
// coding defensively, because third-party integrations are always a
|
||||
// bit of a thing, and I don't want failures to crash us!
|
||||
const id = user.sub?.match(/^auth0\|impress-([0-9]+)$/)?.[1];
|
||||
const username = user["https://oauth.impress-2020.openneo.net/username"];
|
||||
|
||||
return { id, username };
|
||||
}
|
||||
|
||||
export default useCurrentUser;
|
Loading…
Reference in a new issue