From 6fd696b440fd216f27bab4ead711a85248f974f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Malak?= Date: Thu, 21 Oct 2021 14:02:35 +0200 Subject: [PATCH] Split SnippetsContext into separate files --- client/src/store/SnippetsContext.tsx | 178 ------------------ .../src/store/auth/actions/getUserProfile.ts | 33 ++++ client/src/store/auth/actions/index.ts | 1 + client/src/store/auth/actions/loginUser.ts | 6 +- client/src/store/auth/actions/registerUser.ts | 4 +- client/src/store/auth/index.tsx | 30 +-- client/src/store/contexts.ts | 33 ++++ client/src/store/index.ts | 3 +- .../src/store/snippets/actions/countTags.ts | 29 +++ .../store/snippets/actions/createSnippet.ts | 34 ++++ .../store/snippets/actions/deleteSnippet.ts | 38 ++++ .../src/store/snippets/actions/getSnippet.ts | 27 +++ .../src/store/snippets/actions/getSnippets.ts | 26 +++ client/src/store/snippets/actions/index.ts | 7 + .../store/snippets/actions/searchSnippets.ts | 31 +++ .../store/snippets/actions/updateSnippet.ts | 42 +++++ client/src/store/snippets/index.tsx | 141 ++++++++++++++ 17 files changed, 452 insertions(+), 211 deletions(-) delete mode 100644 client/src/store/SnippetsContext.tsx create mode 100644 client/src/store/auth/actions/getUserProfile.ts create mode 100644 client/src/store/contexts.ts create mode 100644 client/src/store/snippets/actions/countTags.ts create mode 100644 client/src/store/snippets/actions/createSnippet.ts create mode 100644 client/src/store/snippets/actions/deleteSnippet.ts create mode 100644 client/src/store/snippets/actions/getSnippet.ts create mode 100644 client/src/store/snippets/actions/getSnippets.ts create mode 100644 client/src/store/snippets/actions/index.ts create mode 100644 client/src/store/snippets/actions/searchSnippets.ts create mode 100644 client/src/store/snippets/actions/updateSnippet.ts create mode 100644 client/src/store/snippets/index.tsx diff --git a/client/src/store/SnippetsContext.tsx b/client/src/store/SnippetsContext.tsx deleted file mode 100644 index 261224a..0000000 --- a/client/src/store/SnippetsContext.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { useState, createContext, ReactNode } from 'react'; -import { useHistory } from 'react-router-dom'; -import axios from 'axios'; - -import { - SnippetsContext as Context, - Snippet, - Response, - TagCount, - NewSnippet, - SearchQuery -} from '../typescript/interfaces'; - -export const SnippetsContext = createContext({ - snippets: [], - searchResults: [], - currentSnippet: null, - tagCount: [], - getSnippets: () => {}, - getSnippetById: (id: number) => {}, - setSnippet: (id: number) => {}, - createSnippet: (snippet: NewSnippet) => {}, - updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {}, - deleteSnippet: (id: number) => {}, - toggleSnippetPin: (id: number) => {}, - countTags: () => {}, - searchSnippets: (query: SearchQuery) => {} -}); - -interface Props { - children: ReactNode; -} - -export const SnippetsContextProvider = (props: Props): JSX.Element => { - const [snippets, setSnippets] = useState([]); - const [searchResults, setSearchResults] = useState([]); - const [currentSnippet, setCurrentSnippet] = useState(null); - const [tagCount, setTagCount] = useState([]); - - const history = useHistory(); - - const redirectOnError = () => { - history.push('/'); - }; - - const getSnippets = (): void => { - axios - .get>('/api/snippets') - .then(res => setSnippets(res.data.data)) - .catch(err => redirectOnError()); - }; - - const getSnippetById = (id: number): void => { - axios - .get>(`/api/snippets/${id}`) - .then(res => setCurrentSnippet(res.data.data)) - .catch(err => redirectOnError()); - }; - - const setSnippet = (id: number): void => { - if (id < 0) { - setCurrentSnippet(null); - return; - } - - getSnippetById(id); - - const snippet = snippets.find(s => s.id === id); - - if (snippet) { - setCurrentSnippet(snippet); - } - }; - - const createSnippet = (snippet: NewSnippet): void => { - axios - .post>('/api/snippets', snippet) - .then(res => { - setSnippets([...snippets, res.data.data]); - setCurrentSnippet(res.data.data); - history.push({ - pathname: `/snippet/${res.data.data.id}`, - state: { from: '/snippets' } - }); - }) - .catch(err => redirectOnError()); - }; - - const updateSnippet = ( - snippet: NewSnippet, - id: number, - isLocal?: boolean - ): void => { - axios - .put>(`/api/snippets/${id}`, snippet) - .then(res => { - const oldSnippetIdx = snippets.findIndex(s => s.id === id); - setSnippets([ - ...snippets.slice(0, oldSnippetIdx), - res.data.data, - ...snippets.slice(oldSnippetIdx + 1) - ]); - setCurrentSnippet(res.data.data); - - if (!isLocal) { - history.push({ - pathname: `/snippet/${res.data.data.id}`, - state: { from: '/snippets' } - }); - } - }) - .catch(err => redirectOnError()); - }; - - const deleteSnippet = (id: number): void => { - if (window.confirm('Are you sure you want to delete this snippet?')) { - axios - .delete>(`/api/snippets/${id}`) - .then(res => { - const deletedSnippetIdx = snippets.findIndex(s => s.id === id); - setSnippets([ - ...snippets.slice(0, deletedSnippetIdx), - ...snippets.slice(deletedSnippetIdx + 1) - ]); - setSnippet(-1); - history.push('/snippets'); - }) - .catch(err => redirectOnError()); - } - }; - - const toggleSnippetPin = (id: number): void => { - const snippet = snippets.find(s => s.id === id); - - if (snippet) { - updateSnippet({ ...snippet, isPinned: !snippet.isPinned }, id, true); - } - }; - - const countTags = (): void => { - axios - .get>('/api/snippets/statistics/count') - .then(res => setTagCount(res.data.data)) - .catch(err => redirectOnError()); - }; - - const searchSnippets = (query: SearchQuery): void => { - axios - .post>('/api/snippets/search', query) - .then(res => { - setSearchResults(res.data.data); - console.log(res.data.data); - }) - .catch(err => console.log(err)); - }; - - const context: Context = { - snippets, - searchResults, - currentSnippet, - tagCount, - getSnippets, - getSnippetById, - setSnippet, - createSnippet, - updateSnippet, - deleteSnippet, - toggleSnippetPin, - countTags, - searchSnippets - }; - - return ( - - {props.children} - - ); -}; diff --git a/client/src/store/auth/actions/getUserProfile.ts b/client/src/store/auth/actions/getUserProfile.ts new file mode 100644 index 0000000..d92719b --- /dev/null +++ b/client/src/store/auth/actions/getUserProfile.ts @@ -0,0 +1,33 @@ +import { User, UserWithRole, Response } from '../../../typescript/interfaces'; +import React from 'react'; +import axios from 'axios'; +import { authErrorHandler } from '../../../utils'; + +interface Params { + token: string; + setIsAuthenticated: (v: React.SetStateAction) => void; + setUser: (v: React.SetStateAction) => void; +} + +export const getUserProfile = async (params: Params) => { + const { token, setIsAuthenticated, setUser } = params; + + try { + const res = await axios.get>('/api/auth/me', { + headers: { + Authorization: `Bearer ${token}` + } + }); + + const { ...user } = res.data.data; + + setUser({ + ...user, + isAdmin: user.role === 'admin' + }); + + setIsAuthenticated(true); + } catch (err) { + authErrorHandler({ err, setIsAuthenticated, setUser }); + } +}; diff --git a/client/src/store/auth/actions/index.ts b/client/src/store/auth/actions/index.ts index d15769f..4e1226a 100644 --- a/client/src/store/auth/actions/index.ts +++ b/client/src/store/auth/actions/index.ts @@ -1,3 +1,4 @@ export * from './registerUser'; export * from './loginUser'; export * from './logoutUser'; +export * from './getUserProfile'; diff --git a/client/src/store/auth/actions/loginUser.ts b/client/src/store/auth/actions/loginUser.ts index 2bd10c1..b66e3bc 100644 --- a/client/src/store/auth/actions/loginUser.ts +++ b/client/src/store/auth/actions/loginUser.ts @@ -1,5 +1,5 @@ import { User, Response, UserWithRole } from '../../../typescript/interfaces'; -import { errorHandler } from '../../../utils'; +import { authErrorHandler } from '../../../utils'; import axios from 'axios'; import React from 'react'; @@ -31,9 +31,7 @@ export const loginUser = async (params: Params) => { localStorage.setItem('token', resToken); setIsAuthenticated(true); - - // redirect to snippets? / home? } catch (err) { - errorHandler(err); + authErrorHandler({ err, setIsAuthenticated, setUser }); } }; diff --git a/client/src/store/auth/actions/registerUser.ts b/client/src/store/auth/actions/registerUser.ts index deedbc9..f28678f 100644 --- a/client/src/store/auth/actions/registerUser.ts +++ b/client/src/store/auth/actions/registerUser.ts @@ -1,5 +1,5 @@ import { User, Response, UserWithRole } from '../../../typescript/interfaces'; -import { errorHandler } from '../../../utils'; +import { authErrorHandler } from '../../../utils'; import axios from 'axios'; import React from 'react'; @@ -32,6 +32,6 @@ export const registerUser = async (params: Params) => { setIsAuthenticated(true); } catch (err) { - errorHandler(err); + authErrorHandler({ err, setIsAuthenticated, setUser }); } }; diff --git a/client/src/store/auth/index.tsx b/client/src/store/auth/index.tsx index 46536f8..d6aff10 100644 --- a/client/src/store/auth/index.tsx +++ b/client/src/store/auth/index.tsx @@ -1,24 +1,12 @@ -import { createContext, ReactNode, useState } from 'react'; -import axios from 'axios'; +import { ReactNode, useState } from 'react'; +import { AuthContext } from '..'; -import { loginUser, logoutUser, registerUser } from './actions'; +import { getUserProfile, loginUser, logoutUser, registerUser } from './actions'; import { AuthContext as Context, - Response, - User, UserWithRole } from '../../typescript/interfaces'; -import { errorHandler } from '../../utils'; - -export const AuthContext = createContext({ - isAuthenticated: false, - user: null, - autoLogin: () => {}, - login: () => {}, - logout: () => {}, - register: () => {} -}); interface Props { children: ReactNode; @@ -47,17 +35,7 @@ export const AuthContextProvider = (props: Props): JSX.Element => { }; const getProfile = async (token: string) => { - try { - const res = await axios.get>('/api/auth/me', { - headers: { - Authorization: `Bearer ${token}` - } - }); - - console.log(res.data.data); - } catch (err) { - errorHandler(err); - } + await getUserProfile({ token, setIsAuthenticated, setUser }); }; const context: Context = { diff --git a/client/src/store/contexts.ts b/client/src/store/contexts.ts new file mode 100644 index 0000000..3d1fa2a --- /dev/null +++ b/client/src/store/contexts.ts @@ -0,0 +1,33 @@ +import { createContext } from 'react'; + +import { + SnippetsContext as SnippetsContextInterface, + AuthContext as AuthContextInterface, + NewSnippet, + SearchQuery +} from '../typescript/interfaces'; + +export const SnippetsContext = createContext({ + snippets: [], + searchResults: [], + currentSnippet: null, + tagCount: [], + getSnippets: () => {}, + getSnippetById: (id: number) => {}, + setSnippet: (id: number) => {}, + createSnippet: (snippet: NewSnippet) => {}, + updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {}, + deleteSnippet: (id: number) => {}, + toggleSnippetPin: (id: number) => {}, + countTags: () => {}, + searchSnippets: (query: SearchQuery) => {} +}); + +export const AuthContext = createContext({ + isAuthenticated: false, + user: null, + autoLogin: () => {}, + login: () => {}, + logout: () => {}, + register: () => {} +}); diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 8442e7b..e3daf10 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -1,2 +1,3 @@ -export * from './SnippetsContext'; +export * from './snippets'; export * from './auth'; +export * from './contexts'; diff --git a/client/src/store/snippets/actions/countTags.ts b/client/src/store/snippets/actions/countTags.ts new file mode 100644 index 0000000..d28161d --- /dev/null +++ b/client/src/store/snippets/actions/countTags.ts @@ -0,0 +1,29 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { Response, TagCount } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + setTagCount: (v: SetStateAction) => void; +} + +export const countTagsAction = async (params: Params) => { + const { setTagCount } = params; + + const token = `Bearer ${localStorage.token}`; + + try { + const res = await axios.get>( + '/api/snippets/statistics/count', + { + headers: { + Authorization: token + } + } + ); + + setTagCount(res.data.data); + } catch (err) { + errorHandler(err); + } +}; diff --git a/client/src/store/snippets/actions/createSnippet.ts b/client/src/store/snippets/actions/createSnippet.ts new file mode 100644 index 0000000..8acb003 --- /dev/null +++ b/client/src/store/snippets/actions/createSnippet.ts @@ -0,0 +1,34 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { NewSnippet, Response, Snippet } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + snippet: NewSnippet; + snippets: Snippet[]; + setSnippets: (v: SetStateAction) => void; + setCurrentSnippet: (v: SetStateAction) => void; +} + +export const createSnippetAction = async ( + params: Params +): Promise => { + const { snippet, snippets, setSnippets, setCurrentSnippet } = params; + + const token = `Bearer ${localStorage.token}`; + + try { + const res = await axios.post>('/api/snippets', snippet, { + headers: { + Authorization: token + } + }); + + setSnippets([...snippets, res.data.data]); + setCurrentSnippet(res.data.data); + + return res.data.data.id; + } catch (err) { + errorHandler(err); + } +}; diff --git a/client/src/store/snippets/actions/deleteSnippet.ts b/client/src/store/snippets/actions/deleteSnippet.ts new file mode 100644 index 0000000..b0f9142 --- /dev/null +++ b/client/src/store/snippets/actions/deleteSnippet.ts @@ -0,0 +1,38 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { Response, Snippet } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + id: number; + snippets: Snippet[]; + setSnippets: (v: SetStateAction) => void; + setSnippet: (id: number) => void; +} + +export const deleteSnippetAction = async (params: Params) => { + const { id, snippets, setSnippets, setSnippet } = params; + + const token = `Bearer ${localStorage.token}`; + + if (window.confirm('Are you sure you want to delete this snippet?')) { + try { + await axios.delete>(`/api/snippets/${id}`, { + headers: { + Authorization: token + } + }); + + const deletedSnippetIdx = snippets.findIndex(s => s.id === id); + + setSnippets([ + ...snippets.slice(0, deletedSnippetIdx), + ...snippets.slice(deletedSnippetIdx + 1) + ]); + + setSnippet(-1); + } catch (err) { + errorHandler(err); + } + } +}; diff --git a/client/src/store/snippets/actions/getSnippet.ts b/client/src/store/snippets/actions/getSnippet.ts new file mode 100644 index 0000000..9a527c7 --- /dev/null +++ b/client/src/store/snippets/actions/getSnippet.ts @@ -0,0 +1,27 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { Response, Snippet } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + id: number; + setCurrentSnippet: (v: SetStateAction) => void; +} + +export const getSnippetByIdAction = async (params: Params) => { + const { id, setCurrentSnippet } = params; + + const token = `Bearer ${localStorage.token}`; + + try { + const res = await axios.get>(`/api/snippets/${id}`, { + headers: { + Authorization: token + } + }); + + setCurrentSnippet(res.data.data); + } catch (err) { + errorHandler(err); + } +}; diff --git a/client/src/store/snippets/actions/getSnippets.ts b/client/src/store/snippets/actions/getSnippets.ts new file mode 100644 index 0000000..8641ada --- /dev/null +++ b/client/src/store/snippets/actions/getSnippets.ts @@ -0,0 +1,26 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { Response, Snippet } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + setSnippets: (v: SetStateAction) => void; +} + +export const getSnippetsAction = async (params: Params) => { + const { setSnippets } = params; + + const token = `Bearer ${localStorage.token}`; + + try { + const res = await axios.get>('/api/snippets', { + headers: { + Authorization: token + } + }); + + setSnippets(res.data.data); + } catch (err) { + errorHandler(err); + } +}; diff --git a/client/src/store/snippets/actions/index.ts b/client/src/store/snippets/actions/index.ts new file mode 100644 index 0000000..3ee332b --- /dev/null +++ b/client/src/store/snippets/actions/index.ts @@ -0,0 +1,7 @@ +export * from './getSnippet'; +export * from './createSnippet'; +export * from './countTags'; +export * from './searchSnippets'; +export * from './updateSnippet'; +export * from './getSnippets'; +export * from './deleteSnippet'; diff --git a/client/src/store/snippets/actions/searchSnippets.ts b/client/src/store/snippets/actions/searchSnippets.ts new file mode 100644 index 0000000..869a2b6 --- /dev/null +++ b/client/src/store/snippets/actions/searchSnippets.ts @@ -0,0 +1,31 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { SearchQuery, Response, Snippet } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + query: SearchQuery; + setSearchResults: (v: SetStateAction) => void; +} + +export const searchSnippetsAction = async (params: Params) => { + const { query, setSearchResults } = params; + + const token = `Bearer ${localStorage.token}`; + + try { + const res = await axios.post>( + '/api/snippets/search', + query, + { + headers: { + Authorization: `Bearer ${localStorage.token}` + } + } + ); + + setSearchResults(res.data.data); + } catch (err) { + errorHandler(err); + } +}; diff --git a/client/src/store/snippets/actions/updateSnippet.ts b/client/src/store/snippets/actions/updateSnippet.ts new file mode 100644 index 0000000..eeda510 --- /dev/null +++ b/client/src/store/snippets/actions/updateSnippet.ts @@ -0,0 +1,42 @@ +import { SetStateAction } from 'react'; +import { errorHandler } from '../../../utils'; +import { Response, Snippet, NewSnippet } from '../../../typescript/interfaces'; +import axios from 'axios'; + +interface Params { + id: number; + snippet: NewSnippet; + snippets: Snippet[]; + setSnippets: (v: SetStateAction) => void; + setCurrentSnippet: (v: SetStateAction) => void; +} + +export const updateSnippetAction = async (params: Params) => { + const { id, snippet, snippets, setSnippets, setCurrentSnippet } = params; + + const token = `Bearer ${localStorage.token}`; + + try { + const res = await axios.put>( + `/api/snippets/${id}`, + snippet, + { + headers: { + Authorization: token + } + } + ); + + const oldSnippetIdx = snippets.findIndex(s => s.id === id); + + setSnippets([ + ...snippets.slice(0, oldSnippetIdx), + res.data.data, + ...snippets.slice(oldSnippetIdx + 1) + ]); + + setCurrentSnippet(res.data.data); + } catch (err) { + errorHandler(err); + } +}; diff --git a/client/src/store/snippets/index.tsx b/client/src/store/snippets/index.tsx new file mode 100644 index 0000000..2afe2da --- /dev/null +++ b/client/src/store/snippets/index.tsx @@ -0,0 +1,141 @@ +import { useState, ReactNode } from 'react'; +import { useHistory } from 'react-router-dom'; +import { SnippetsContext } from '..'; + +import { + countTagsAction, + createSnippetAction, + deleteSnippetAction, + getSnippetByIdAction, + getSnippetsAction, + searchSnippetsAction, + updateSnippetAction +} from './actions'; + +import { + SnippetsContext as Context, + Snippet, + TagCount, + NewSnippet, + SearchQuery +} from '../../typescript/interfaces'; + +interface Props { + children: ReactNode; +} + +export const SnippetsContextProvider = (props: Props): JSX.Element => { + const [snippets, setSnippets] = useState([]); + const [searchResults, setSearchResults] = useState([]); + const [currentSnippet, setCurrentSnippet] = useState(null); + const [tagCount, setTagCount] = useState([]); + + const history = useHistory(); + + const getSnippets = async () => { + await getSnippetsAction({ setSnippets }); + }; + + const getSnippetById = async (id: number) => { + await getSnippetByIdAction({ id, setCurrentSnippet }); + }; + + const setSnippet = async (id: number) => { + if (id < 0) { + setCurrentSnippet(null); + return; + } + + await getSnippetById(id); + + const snippet = snippets.find(s => s.id === id); + + if (snippet) { + setCurrentSnippet(snippet); + } + }; + + const createSnippet = async (snippet: NewSnippet) => { + const id = await createSnippetAction({ + snippet, + snippets, + setCurrentSnippet, + setSnippets + }); + + if (id) { + history.push({ + pathname: `/snippet/${id}`, + state: { from: '/snippets' } + }); + } + }; + + const updateSnippet = async ( + snippet: NewSnippet, + id: number, + isLocal?: boolean + ) => { + await updateSnippetAction({ + id, + snippet, + snippets, + setSnippets, + setCurrentSnippet + }); + + if (!isLocal) { + history.push({ + pathname: `/snippet/${id}`, + state: { from: '/snippets' } + }); + } + }; + + const deleteSnippet = async (id: number) => { + await deleteSnippetAction({ id, snippets, setSnippets, setSnippet }); + history.push('/snippets'); + }; + + const countTags = async () => { + await countTagsAction({ setTagCount }); + }; + + const searchSnippets = async (query: SearchQuery) => { + await searchSnippetsAction({ query, setSearchResults }); + }; + + const toggleSnippetPin = async (id: number) => { + const snippet = snippets.find(s => s.id === id); + + if (snippet) { + await updateSnippet( + { ...snippet, isPinned: !snippet.isPinned }, + id, + true + ); + } + }; + + const context: Context = { + snippets, + searchResults, + currentSnippet, + tagCount, + getSnippets, + getSnippetById, + setSnippet, + createSnippet, + updateSnippet, + deleteSnippet, + toggleSnippetPin, + countTags, + searchSnippets + }; + + return ( + + {props.children} + + ); +};