diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx index 388b4d8..4387afc 100644 --- a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx +++ b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx @@ -1,5 +1,8 @@ import { ContentType } from '../Bookmarks'; import classes from './BookmarkTable.module.css'; +import { connect } from 'react-redux'; +import { pinCategory, deleteCategory } from '../../../store/actions'; +import { KeyboardEvent } from 'react'; import Table from '../../UI/Table/Table'; import { Bookmark, Category } from '../../../interfaces'; @@ -8,9 +11,25 @@ import Icon from '../../UI/Icons/Icon/Icon'; interface ComponentProps { contentType: ContentType; categories: Category[]; + pinCategory: (category: Category) => void; + deleteCategory: (id: number) => void; } const BookmarkTable = (props: ComponentProps): JSX.Element => { + const deleteCategoryHandler = (category: Category): void => { + const proceed = window.confirm(`Are you sure you want to delete ${category.name}? It will delete ALL assigned bookmarks`); + + if (proceed) { + props.deleteCategory(category.id); + } + } + + const keyboardActionHandler = (e: KeyboardEvent, category: Category, handler: Function) => { + if (e.key === 'Enter') { + handler(category); + } + } + if (props.contentType === ContentType.category) { return ( {
deleteAppHandler(app)} - // onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)} + onClick={() => deleteCategoryHandler(category)} + onKeyDown={(e) => keyboardActionHandler(e, category, deleteCategoryHandler)} tabIndex={0}>
@@ -38,8 +57,8 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
props.pinApp(app)} - // onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)} + onClick={() => props.pinCategory(category)} + onKeyDown={(e) => keyboardActionHandler(e, category, props.pinCategory)} tabIndex={0}> {category.isPinned ? @@ -100,4 +119,4 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { } } -export default BookmarkTable; \ No newline at end of file +export default connect(null, { pinCategory, deleteCategory })(BookmarkTable); \ No newline at end of file diff --git a/client/src/components/Bookmarks/Bookmarks.module.css b/client/src/components/Bookmarks/Bookmarks.module.css index 09810e1..4c17197 100644 --- a/client/src/components/Bookmarks/Bookmarks.module.css +++ b/client/src/components/Bookmarks/Bookmarks.module.css @@ -1,4 +1,13 @@ .ActionsContainer { display: flex; align-items: center; +} + +.BookmarksMessage { + color: var(--color-primary); +} + +.BookmarksMessage a { + color: var(--color-accent); + font-weight: 600; } \ No newline at end of file diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx index ba14b5e..6e39cc3 100644 --- a/client/src/components/Bookmarks/Bookmarks.tsx +++ b/client/src/components/Bookmarks/Bookmarks.tsx @@ -99,7 +99,9 @@ const Bookmarks = (props: ComponentProps): JSX.Element => { {props.loading ? : (!isInEdit - ? + ? props.categories.length > 0 + ? + :

You don't have any bookmarks. You can a new one from /bookmarks menu

: ) } diff --git a/client/src/store/actions/actionTypes.ts b/client/src/store/actions/actionTypes.ts index 153e3fa..db5f344 100644 --- a/client/src/store/actions/actionTypes.ts +++ b/client/src/store/actions/actionTypes.ts @@ -11,6 +11,8 @@ import { GetCategoriesAction, AddCategoryAction, AddBookmarkAction, + PinCategoryAction, + DeleteCategoryAction, // Notifications CreateNotificationAction, ClearNotificationAction @@ -34,6 +36,8 @@ export enum ActionTypes { getCategoriesError = 'GET_CATEGORIES_ERROR', addCategory = 'ADD_CATEGORY', addBookmark = 'ADD_BOOKMARK', + pinCategory = 'PIN_CATEGORY', + deleteCategory = 'DELETE_CATEGORY', // Notifications createNotification = 'CREATE_NOTIFICATION', clearNotification = 'CLEAR_NOTIFICATION' @@ -52,6 +56,8 @@ export type Action = GetCategoriesAction | AddCategoryAction | AddBookmarkAction | + PinCategoryAction | + DeleteCategoryAction | // Notifications CreateNotificationAction | ClearNotificationAction; \ No newline at end of file diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts index 7c121dc..933be1b 100644 --- a/client/src/store/actions/bookmark.ts +++ b/client/src/store/actions/bookmark.ts @@ -4,6 +4,9 @@ import { ActionTypes } from './actionTypes'; import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark } from '../../interfaces'; import { CreateNotificationAction } from './notification'; +/** + * GET CATEGORIES + */ export interface GetCategoriesAction { type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError; payload: T; @@ -27,6 +30,9 @@ export const getCategories = () => async (dispatch: Dispatch) => { } } +/** + * ADD CATEGORY + */ export interface AddCategoryAction { type: ActionTypes.addCategory, payload: Category @@ -53,6 +59,9 @@ export const addCategory = (formData: NewCategory) => async (dispatch: Dispatch) } } +/** + * ADD BOOKMARK + */ export interface AddBookmarkAction { type: ActionTypes.addBookmark, payload: Bookmark @@ -77,4 +86,65 @@ export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch) } catch (err) { console.log(err); } +} + +/** + * PIN CATEGORY + */ +export interface PinCategoryAction { + type: ActionTypes.pinCategory, + payload: Category +} + +export const pinCategory = (category: Category) => async (dispatch: Dispatch) => { + try { + const { id, isPinned, name } = category; + const res = await axios.put>(`/api/categories/${id}`, { isPinned: !isPinned }); + + const status = isPinned ? 'unpinned from Homescreen' : 'pinned to Homescreen'; + + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Category ${name} ${status}` + } + }) + + dispatch({ + type: ActionTypes.pinCategory, + payload: res.data.data + }) + } catch (err) { + console.log(err); + } +} + +/** + * DELETE CATEGORY + */ +export interface DeleteCategoryAction { + type: ActionTypes.deleteCategory, + payload: number +} + +export const deleteCategory = (id: number) => async (dispatch: Dispatch) => { + try { + const res = await axios.delete>(`/api/categories/${id}`); + + dispatch({ + type: ActionTypes.createNotification, + payload: { + title: 'Success', + message: `Category deleted` + } + }) + + dispatch({ + type: ActionTypes.deleteCategory, + payload: id + }) + } 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 6c4990f..997d880 100644 --- a/client/src/store/reducers/bookmark.ts +++ b/client/src/store/reducers/bookmark.ts @@ -55,12 +55,37 @@ const addBookmark = (state: State, action: Action): State => { } } +const pinCategory = (state: State, action: Action): State => { + const tmpCategories = [...state.categories]; + const changedCategory = tmpCategories.find((category: Category) => category.id === action.payload.id); + + if (changedCategory) { + changedCategory.isPinned = action.payload.isPinned; + } + + return { + ...state, + categories: tmpCategories + } +} + +const deleteCategory = (state: State, action: Action): State => { + const tmpCategories = [...state.categories].filter((category: Category) => category.id !== 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); + case ActionTypes.pinCategory: return pinCategory(state, action); + case ActionTypes.deleteCategory: return deleteCategory(state, action); default: return state; } } diff --git a/controllers/category.js b/controllers/category.js index 5ab26df..60e7305 100644 --- a/controllers/category.js +++ b/controllers/category.js @@ -79,6 +79,24 @@ exports.updateCategory = asyncWrapper(async (req, res, next) => { // @route DELETE /api/categories/:id // @access Public exports.deleteCategory = asyncWrapper(async (req, res, next) => { + const category = await Category.findOne({ + where: { id: req.params.id }, + include: [{ + model: Bookmark, + as: 'bookmarks' + }] + }); + + if (!category) { + return next(new ErrorResponse(`Category with id of ${req.params.id} was not found`, 404)) + } + + category.bookmarks.forEach(async (bookmark) => { + await Bookmark.destroy({ + where: { id: bookmark.id } + }) + }) + await Category.destroy({ where: { id: req.params.id } }) diff --git a/models/associateModels.js b/models/associateModels.js index 4ca1fab..2457092 100644 --- a/models/associateModels.js +++ b/models/associateModels.js @@ -3,11 +3,11 @@ const Bookmark = require('./Bookmark'); const associateModels = () => { // Category <> Bookmark - Bookmark.belongsTo(Category, { foreignKey: 'categoryId' }); Category.hasMany(Bookmark, { as: 'bookmarks', foreignKey: 'categoryId' }); + Bookmark.belongsTo(Category, { foreignKey: 'categoryId' }); } module.exports = associateModels; \ No newline at end of file