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"