add .graphqlconfig, fix typo in .prettierrc filename, add a new lib - auth

This commit is contained in:
Dawid Wysokiński 2021-03-07 16:37:08 +01:00
parent 0ea3eb8b07
commit 521286cfcc
13 changed files with 279 additions and 5 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
remote-schema.graphql

15
.graphqlconfig Normal file
View File

@ -0,0 +1,15 @@
{
"name": "Zdam Egzamin Zawodowy",
"schemaPath": "remote-schema.graphql",
"extensions": {
"endpoints": {
"Default GraphQL Endpoint": {
"url": "http://localhost:8080/graphql",
"headers": {
"user-agent": "JS GraphQL"
},
"introspect": true
}
}
}
}

View File

@ -56,6 +56,7 @@
"@types/apollo-upload-client": "^14.1.0",
"@types/lodash": "^4.14.168",
"@types/react-router-dom": "^5.1.7",
"babel-plugin-transform-imports": "^2.0.0",
"babel-plugin-transform-modules": "^0.1.1",
"customize-cra": "^1.0.0",
"prettier": "^2.2.1",

4
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare enum UserRole {
Admin = "admin",
User = "user",
}

View File

@ -1,9 +1,27 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./features/App";
import reportWebVitals from "./reportWebVitals";
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { MuiThemeProvider } from '@material-ui/core';
import { ApolloProvider } from '@apollo/client';
import App from './features/App';
import { AuthProvider } from './libs/auth';
import TokenStorage from './libs/tokenstorage/TokenStorage';
import createClient from './libs/graphql/createClient';
import { API_URI } from './config/api';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(<App />, document.getElementById("root"));
const tokenStorage = new TokenStorage();
ReactDOM.render(
<BrowserRouter>
<ApolloProvider client={createClient(API_URI, tokenStorage)}>
<AuthProvider tokenStorage={tokenStorage}>
<App />
</AuthProvider>
</ApolloProvider>
</BrowserRouter>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))

116
src/libs/auth/Provider.tsx Normal file
View File

@ -0,0 +1,116 @@
import React, { useMemo, useState, useEffect } from 'react';
import { useApolloClient } from '@apollo/client';
import { isFunction } from 'lodash';
import { context as Context } from './context';
import { AuthContext, User } from './types';
import TokenStorage from '../tokenstorage/TokenStorage';
import { QUERY_ME } from './queries';
import { MUTATION_SIGN_IN } from './mutations';
export interface AuthProviderProps {
tokenStorage?: TokenStorage;
children?: React.ReactNode;
}
type MeQueryResult = {
me: User | null;
};
type SignInMutationResult = {
token: string;
user: User;
};
type SignInMutationVariables = {
email: string;
staySignedIn: boolean;
password: string;
};
export function AuthProvider(props: AuthProviderProps) {
const client = useApolloClient();
const [user, setUser] = useState<AuthContext['user']>(null);
const [loading, setLoading] = useState<boolean>(true);
const tokenStorage = useMemo(() => {
if (props.tokenStorage) {
return props.tokenStorage;
}
return new TokenStorage();
}, [props.tokenStorage]);
useEffect(() => {
loadUser();
}, []);
const loadUser = async () => {
if (tokenStorage.token) {
try {
const result = await client.query<MeQueryResult>({
query: QUERY_ME,
fetchPolicy: 'network-only',
});
if (result.data.me) {
setUser(result.data.me);
}
} catch (e) {}
}
setLoading(false);
};
const signIn: AuthContext['signIn'] = async (
email: string,
password: string,
staySignedIn: boolean,
validate?: (user: User) => boolean
) => {
const result = await client.mutate<
SignInMutationResult,
SignInMutationVariables
>({
mutation: MUTATION_SIGN_IN,
variables: {
email,
password,
staySignedIn,
},
});
if (result.data?.user) {
if (isFunction(validate) && !validate(result.data?.user)) {
return null;
}
tokenStorage.setToken(result.data.token);
setUser(result.data.user);
client.writeQuery<MeQueryResult>({
query: QUERY_ME,
data: {
me: result.data.user,
},
});
return result.data.user;
}
return null;
};
const signOut = () => {
return client.clearStore().then(() => {
tokenStorage.setToken('');
setUser(null);
});
};
return (
<Context.Provider
value={{
user,
tokenStorage,
signIn,
signOut,
loading,
}}
>
{props.children}
</Context.Provider>
);
}

18
src/libs/auth/context.ts Normal file
View File

@ -0,0 +1,18 @@
import React from 'react';
import { AuthContext } from './types';
import TokenStorage from '../tokenstorage/TokenStorage';
const ctx = React.createContext<AuthContext>({
tokenStorage: new TokenStorage(),
signIn: () => new Promise(resolve => resolve(null)),
signOut: () => new Promise(resolve => resolve()),
loading: true,
user: null,
});
ctx.displayName = 'AuthContext';
const useAuth = (): AuthContext => {
return React.useContext(ctx);
};
export { ctx as context, useAuth };

5
src/libs/auth/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './context';
export * from './mutations';
export * from './Provider';
export * from './queries';
export * from './types';

View File

@ -0,0 +1,19 @@
import { gql } from "@apollo/client";
export const MUTATION_SIGN_IN = gql`
mutation signIn(
$email: String!
$password: String!
$staySignedIn: Boolean!
) {
signIn(email: $email, password: $password, staySignedIn: $staySignedIn) {
token
user {
id
displayName
email
role
}
}
}
`;

12
src/libs/auth/queries.ts Normal file
View File

@ -0,0 +1,12 @@
import { gql } from "@apollo/client";
export const QUERY_ME = gql`
query {
me {
id
displayName
role
email
}
}
`;

21
src/libs/auth/types.ts Normal file
View File

@ -0,0 +1,21 @@
import TokenStorage from '../tokenstorage/TokenStorage';
export type User = {
id: number;
displayName: string;
role: UserRole;
email: string;
};
export interface AuthContext {
user: User | null;
tokenStorage: TokenStorage;
signIn: (
email: string,
password: string,
staySignedIn: boolean,
validate?: (user: User) => boolean
) => Promise<User | null>;
signOut: () => Promise<void>;
loading: boolean;
}

View File

@ -1157,6 +1157,15 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@babel/types@^7.4":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@ -2829,6 +2838,14 @@ babel-plugin-syntax-object-rest-spread@^6.8.0:
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
babel-plugin-transform-imports@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-imports/-/babel-plugin-transform-imports-2.0.0.tgz#9e5f49f751a9d34ba8f4bb988c7e48ed2419c6b6"
integrity sha512-65ewumYJ85QiXdcB/jmiU0y0jg6eL6CdnDqQAqQ8JMOKh1E52VPG3NJzbVKWcgovUR5GBH8IWpCXQ7I8Q3wjgw==
dependencies:
"@babel/types" "^7.4"
is-valid-path "^0.1.1"
babel-plugin-transform-modules@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-modules/-/babel-plugin-transform-modules-0.1.1.tgz#2a72ced69d85613d953196ae9a86d39f19fd7933"
@ -6229,6 +6246,11 @@ is-extendable@^1.0.1:
dependencies:
is-plain-object "^2.0.4"
is-extglob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
is-extglob@^2.1.0, is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@ -6249,6 +6271,13 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
is-glob@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
dependencies:
is-extglob "^1.0.0"
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
@ -6268,6 +6297,13 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3:
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
is-invalid-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34"
integrity sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ=
dependencies:
is-glob "^2.0.0"
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@ -6393,6 +6429,13 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
is-valid-path@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df"
integrity sha1-EQ+f90w39mPh7HkV60UfLbk6yd8=
dependencies:
is-invalid-path "^0.1.0"
is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"