diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx index 6757f54..6d0b020 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx @@ -14,7 +14,7 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => { + key={`bookmark-${bookmark.id}`}> {bookmark.name} ))} diff --git a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx index d299070..728baaa 100644 --- a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx +++ b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx @@ -5,12 +5,14 @@ import ModalForm from '../../UI/Forms/ModalForm/ModalForm'; import InputGroup from '../../UI/Forms/InputGroup/InputGroup'; import { Category, GlobalState, NewBookmark, NewCategory } from '../../../interfaces'; import { FormContentType } from '../Bookmarks'; -import { getCategories } from '../../../store/actions'; +import { getCategories, addCategory, addBookmark } from '../../../store/actions'; interface ComponentProps { modalHandler: () => void; contentType: FormContentType; categories: Category[]; + addCategory: (formData: NewCategory) => void; + addBookmark: (formData: NewBookmark) => void; } const BookmarkForm = (props: ComponentProps): JSX.Element => { @@ -27,8 +29,21 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => { const formSubmitHandler = (e: SyntheticEvent): void => { e.preventDefault(); - if (formData.categoryId === -1) { - alert('select category'); + if (props.contentType === FormContentType.category) { + props.addCategory(categoryName); + setCategoryName({ name: '' }); + } else if (props.contentType === FormContentType.bookmark) { + if (formData.categoryId === -1) { + alert('select category'); + return; + } + + props.addBookmark(formData); + setFormData({ + name: '', + url: '', + categoryId: formData.categoryId + }) } } @@ -129,4 +144,4 @@ const mapStateToProps = (state: GlobalState) => { } } -export default connect(mapStateToProps, { getCategories })(BookmarkForm); \ No newline at end of file +export default connect(mapStateToProps, { getCategories, addCategory, addBookmark })(BookmarkForm); \ No newline at end of file diff --git a/client/src/components/UI/Forms/InputGroup/InputGroup.module.css b/client/src/components/UI/Forms/InputGroup/InputGroup.module.css index d8b9fca..bb4cc3c 100644 --- a/client/src/components/UI/Forms/InputGroup/InputGroup.module.css +++ b/client/src/components/UI/Forms/InputGroup/InputGroup.module.css @@ -8,7 +8,8 @@ display: block; } -.InputGroup input { +.InputGroup input, +.InputGroup select { margin: 8px 0; width: 100%; border: none; diff --git a/client/src/store/actions/actionTypes.ts b/client/src/store/actions/actionTypes.ts index f6ee1f5..b2e46ad 100644 --- a/client/src/store/actions/actionTypes.ts +++ b/client/src/store/actions/actionTypes.ts @@ -1,15 +1,22 @@ import { - GetAppsAction, + // Theme SetThemeAction, + // Apps + GetAppsAction, PinAppAction, AddAppAction, DeleteAppAction, UpdateAppAction, - GetCategoriesAction + // Categories + GetCategoriesAction, + AddCategoryAction, + AddBookmarkAction } from './'; export enum ActionTypes { + // Theme setTheme = 'SET_THEME', + // Apps getApps = 'GET_APPS', getAppsSuccess = 'GET_APPS_SUCCESS', getAppsError = 'GET_APPS_ERROR', @@ -18,9 +25,24 @@ export enum ActionTypes { addAppSuccess = 'ADD_APP_SUCCESS', deleteApp = 'DELETE_APP', updateApp = 'UPDATE_APP', + // Categories getCategories = 'GET_CATEGORIES', getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS', - getCategoriesError = 'GET_CATEGORIES_ERROR' + getCategoriesError = 'GET_CATEGORIES_ERROR', + addCategory = 'ADD_CATEGORY', + addBookmark = 'ADD_BOOKMARK' } -export type Action = GetAppsAction | SetThemeAction | PinAppAction | AddAppAction | DeleteAppAction | UpdateAppAction | GetCategoriesAction; \ No newline at end of file +export type Action = + // Theme + SetThemeAction | + // Apps + GetAppsAction | + PinAppAction | + AddAppAction | + DeleteAppAction | + UpdateAppAction | + // Categories + GetCategoriesAction | + AddCategoryAction | + AddBookmarkAction; \ No newline at end of file diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts index ea074cc..b1bd6f1 100644 --- a/client/src/store/actions/bookmark.ts +++ b/client/src/store/actions/bookmark.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { Dispatch } from 'redux'; import { ActionTypes } from './actionTypes'; -import { Category, ApiResponse } from '../../interfaces'; +import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark } from '../../interfaces'; export interface GetCategoriesAction { type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError; @@ -24,4 +24,40 @@ export const getCategories = () => async (dispatch: Dispatch) => { } catch (err) { console.log(err); } +} + +export interface AddCategoryAction { + type: ActionTypes.addCategory, + payload: Category +} + +export const addCategory = (formData: NewCategory) => async (dispatch: Dispatch) => { + try { + const res = await axios.post>('/api/categories', formData); + + dispatch({ + type: ActionTypes.addCategory, + payload: res.data.data + }) + } catch (err) { + console.log(err); + } +} + +export interface AddBookmarkAction { + type: ActionTypes.addBookmark, + payload: Bookmark +} + +export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch) => { + try { + const res = await axios.post>('/api/bookmarks', formData); + + dispatch({ + type: ActionTypes.addBookmark, + payload: res.data.data + }) + } catch (err) { + console.log(err); + } } \ No newline at end of file diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts index cb8b169..6c4990f 100644 --- a/client/src/store/reducers/bookmark.ts +++ b/client/src/store/reducers/bookmark.ts @@ -27,12 +27,40 @@ const getCategoriesSuccess = (state: State, action: Action): State => { loading: false, categories: action.payload } -} +} + +const addCategory = (state: State, action: Action): State => { + const tmpCategories = [...state.categories, { + ...action.payload, + bookmarks: [] + }]; + + return { + ...state, + categories: tmpCategories + } +} + +const addBookmark = (state: State, action: Action): State => { + const tmpCategories = [...state.categories]; + const tmpCategory = tmpCategories.find((category: Category) => category.id === action.payload.categoryId); + + if (tmpCategory) { + tmpCategory.bookmarks.push(action.payload); + } + + return { + ...state, + categories: tmpCategories + } +} const bookmarkReducer = (state = initialState, action: Action) => { switch (action.type) { case ActionTypes.getCategories: return getCategories(state, action); case ActionTypes.getCategoriesSuccess: return getCategoriesSuccess(state, action); + case ActionTypes.addCategory: return addCategory(state, action); + case ActionTypes.addBookmark: return addBookmark(state, action); default: return state; } } diff --git a/controllers/category.js b/controllers/category.js index 0b47120..5ab26df 100644 --- a/controllers/category.js +++ b/controllers/category.js @@ -23,7 +23,8 @@ exports.getCategories = asyncWrapper(async (req, res, next) => { include: [{ model: Bookmark, as: 'bookmarks' - }] + }], + order: [['name', 'ASC']] }); res.status(200).json({