From 521286cfcc7d8f947f95832d125625154503d6a1 Mon Sep 17 00:00:00 2001 From: Kichiyaki Date: Sun, 7 Mar 2021 16:37:08 +0100 Subject: [PATCH] add .graphqlconfig, fix typo in .prettierrc filename, add a new lib - auth --- .gitignore | 2 + .graphqlconfig | 15 +++++ .prettierc => .prettierrc | 0 package.json | 1 + src/global.d.ts | 4 ++ src/index.tsx | 28 +++++++-- src/libs/auth/Provider.tsx | 116 +++++++++++++++++++++++++++++++++++++ src/libs/auth/context.ts | 18 ++++++ src/libs/auth/index.ts | 5 ++ src/libs/auth/mutations.ts | 19 ++++++ src/libs/auth/queries.ts | 12 ++++ src/libs/auth/types.ts | 21 +++++++ yarn.lock | 43 ++++++++++++++ 13 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 .graphqlconfig rename .prettierc => .prettierrc (100%) create mode 100644 src/global.d.ts create mode 100644 src/libs/auth/Provider.tsx create mode 100644 src/libs/auth/context.ts create mode 100644 src/libs/auth/index.ts create mode 100644 src/libs/auth/mutations.ts create mode 100644 src/libs/auth/queries.ts create mode 100644 src/libs/auth/types.ts diff --git a/.gitignore b/.gitignore index 4d29575..ed58746 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +remote-schema.graphql \ No newline at end of file diff --git a/.graphqlconfig b/.graphqlconfig new file mode 100644 index 0000000..7dff810 --- /dev/null +++ b/.graphqlconfig @@ -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 + } + } + } +} \ No newline at end of file diff --git a/.prettierc b/.prettierrc similarity index 100% rename from .prettierc rename to .prettierrc diff --git a/package.json b/package.json index 381b53e..866780b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..dc76fd5 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,4 @@ +declare enum UserRole { + Admin = "admin", + User = "user", +} diff --git a/src/index.tsx b/src/index.tsx index 2acf2b9..213d5a7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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(, document.getElementById("root")); +const tokenStorage = new TokenStorage(); + +ReactDOM.render( + + + + + + + , + document.getElementById('root') +); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) diff --git a/src/libs/auth/Provider.tsx b/src/libs/auth/Provider.tsx new file mode 100644 index 0000000..d19093b --- /dev/null +++ b/src/libs/auth/Provider.tsx @@ -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(null); + const [loading, setLoading] = useState(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({ + 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({ + 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 ( + + {props.children} + + ); +} diff --git a/src/libs/auth/context.ts b/src/libs/auth/context.ts new file mode 100644 index 0000000..e71aece --- /dev/null +++ b/src/libs/auth/context.ts @@ -0,0 +1,18 @@ +import React from 'react'; +import { AuthContext } from './types'; +import TokenStorage from '../tokenstorage/TokenStorage'; + +const ctx = React.createContext({ + 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 }; diff --git a/src/libs/auth/index.ts b/src/libs/auth/index.ts new file mode 100644 index 0000000..ca8fa75 --- /dev/null +++ b/src/libs/auth/index.ts @@ -0,0 +1,5 @@ +export * from './context'; +export * from './mutations'; +export * from './Provider'; +export * from './queries'; +export * from './types'; diff --git a/src/libs/auth/mutations.ts b/src/libs/auth/mutations.ts new file mode 100644 index 0000000..20e27bd --- /dev/null +++ b/src/libs/auth/mutations.ts @@ -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 + } + } + } +`; diff --git a/src/libs/auth/queries.ts b/src/libs/auth/queries.ts new file mode 100644 index 0000000..332f1e0 --- /dev/null +++ b/src/libs/auth/queries.ts @@ -0,0 +1,12 @@ +import { gql } from "@apollo/client"; + +export const QUERY_ME = gql` + query { + me { + id + displayName + role + email + } + } +`; diff --git a/src/libs/auth/types.ts b/src/libs/auth/types.ts new file mode 100644 index 0000000..fefe801 --- /dev/null +++ b/src/libs/auth/types.ts @@ -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; + signOut: () => Promise; + loading: boolean; +} diff --git a/yarn.lock b/yarn.lock index 66c62ea..c4c2ccf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"