mirror of
https://github.com/pawelmalak/flame.git
synced 2025-07-23 13:29:35 +02:00
Pin/Delete category
This commit is contained in:
parent
bd5354a2e3
commit
0f2125e720
8 changed files with 156 additions and 7 deletions
|
@ -1,5 +1,8 @@
|
||||||
import { ContentType } from '../Bookmarks';
|
import { ContentType } from '../Bookmarks';
|
||||||
import classes from './BookmarkTable.module.css';
|
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 Table from '../../UI/Table/Table';
|
||||||
import { Bookmark, Category } from '../../../interfaces';
|
import { Bookmark, Category } from '../../../interfaces';
|
||||||
|
@ -8,9 +11,25 @@ import Icon from '../../UI/Icons/Icon/Icon';
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
contentType: ContentType;
|
contentType: ContentType;
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
|
pinCategory: (category: Category) => void;
|
||||||
|
deleteCategory: (id: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
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) {
|
if (props.contentType === ContentType.category) {
|
||||||
return (
|
return (
|
||||||
<Table headers={[
|
<Table headers={[
|
||||||
|
@ -24,8 +43,8 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
||||||
<td className={classes.TableActions}>
|
<td className={classes.TableActions}>
|
||||||
<div
|
<div
|
||||||
className={classes.TableAction}
|
className={classes.TableAction}
|
||||||
// onClick={() => deleteAppHandler(app)}
|
onClick={() => deleteCategoryHandler(category)}
|
||||||
// onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
|
onKeyDown={(e) => keyboardActionHandler(e, category, deleteCategoryHandler)}
|
||||||
tabIndex={0}>
|
tabIndex={0}>
|
||||||
<Icon icon='mdiDelete' />
|
<Icon icon='mdiDelete' />
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,8 +57,8 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classes.TableAction}
|
className={classes.TableAction}
|
||||||
// onClick={() => props.pinApp(app)}
|
onClick={() => props.pinCategory(category)}
|
||||||
// onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
|
onKeyDown={(e) => keyboardActionHandler(e, category, props.pinCategory)}
|
||||||
tabIndex={0}>
|
tabIndex={0}>
|
||||||
{category.isPinned
|
{category.isPinned
|
||||||
? <Icon icon='mdiPinOff' color='var(--color-accent)' />
|
? <Icon icon='mdiPinOff' color='var(--color-accent)' />
|
||||||
|
@ -100,4 +119,4 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BookmarkTable;
|
export default connect(null, { pinCategory, deleteCategory })(BookmarkTable);
|
|
@ -2,3 +2,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.BookmarksMessage {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.BookmarksMessage a {
|
||||||
|
color: var(--color-accent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
|
@ -99,7 +99,9 @@ const Bookmarks = (props: ComponentProps): JSX.Element => {
|
||||||
{props.loading
|
{props.loading
|
||||||
? <Spinner />
|
? <Spinner />
|
||||||
: (!isInEdit
|
: (!isInEdit
|
||||||
? <BookmarkGrid categories={props.categories} />
|
? props.categories.length > 0
|
||||||
|
? <BookmarkGrid categories={props.categories} />
|
||||||
|
: <p className={classes.BookmarksMessage}>You don't have any bookmarks. You can a new one from <Link to='/bookmarks'>/bookmarks</Link> menu</p>
|
||||||
: <BookmarkTable contentType={tableContentType} categories={props.categories} />)
|
: <BookmarkTable contentType={tableContentType} categories={props.categories} />)
|
||||||
}
|
}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
GetCategoriesAction,
|
GetCategoriesAction,
|
||||||
AddCategoryAction,
|
AddCategoryAction,
|
||||||
AddBookmarkAction,
|
AddBookmarkAction,
|
||||||
|
PinCategoryAction,
|
||||||
|
DeleteCategoryAction,
|
||||||
// Notifications
|
// Notifications
|
||||||
CreateNotificationAction,
|
CreateNotificationAction,
|
||||||
ClearNotificationAction
|
ClearNotificationAction
|
||||||
|
@ -34,6 +36,8 @@ export enum ActionTypes {
|
||||||
getCategoriesError = 'GET_CATEGORIES_ERROR',
|
getCategoriesError = 'GET_CATEGORIES_ERROR',
|
||||||
addCategory = 'ADD_CATEGORY',
|
addCategory = 'ADD_CATEGORY',
|
||||||
addBookmark = 'ADD_BOOKMARK',
|
addBookmark = 'ADD_BOOKMARK',
|
||||||
|
pinCategory = 'PIN_CATEGORY',
|
||||||
|
deleteCategory = 'DELETE_CATEGORY',
|
||||||
// Notifications
|
// Notifications
|
||||||
createNotification = 'CREATE_NOTIFICATION',
|
createNotification = 'CREATE_NOTIFICATION',
|
||||||
clearNotification = 'CLEAR_NOTIFICATION'
|
clearNotification = 'CLEAR_NOTIFICATION'
|
||||||
|
@ -52,6 +56,8 @@ export type Action =
|
||||||
GetCategoriesAction<any> |
|
GetCategoriesAction<any> |
|
||||||
AddCategoryAction |
|
AddCategoryAction |
|
||||||
AddBookmarkAction |
|
AddBookmarkAction |
|
||||||
|
PinCategoryAction |
|
||||||
|
DeleteCategoryAction |
|
||||||
// Notifications
|
// Notifications
|
||||||
CreateNotificationAction |
|
CreateNotificationAction |
|
||||||
ClearNotificationAction;
|
ClearNotificationAction;
|
|
@ -4,6 +4,9 @@ import { ActionTypes } from './actionTypes';
|
||||||
import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark } from '../../interfaces';
|
import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark } from '../../interfaces';
|
||||||
import { CreateNotificationAction } from './notification';
|
import { CreateNotificationAction } from './notification';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET CATEGORIES
|
||||||
|
*/
|
||||||
export interface GetCategoriesAction<T> {
|
export interface GetCategoriesAction<T> {
|
||||||
type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError;
|
type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError;
|
||||||
payload: T;
|
payload: T;
|
||||||
|
@ -27,6 +30,9 @@ export const getCategories = () => async (dispatch: Dispatch) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADD CATEGORY
|
||||||
|
*/
|
||||||
export interface AddCategoryAction {
|
export interface AddCategoryAction {
|
||||||
type: ActionTypes.addCategory,
|
type: ActionTypes.addCategory,
|
||||||
payload: Category
|
payload: Category
|
||||||
|
@ -53,6 +59,9 @@ export const addCategory = (formData: NewCategory) => async (dispatch: Dispatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADD BOOKMARK
|
||||||
|
*/
|
||||||
export interface AddBookmarkAction {
|
export interface AddBookmarkAction {
|
||||||
type: ActionTypes.addBookmark,
|
type: ActionTypes.addBookmark,
|
||||||
payload: Bookmark
|
payload: Bookmark
|
||||||
|
@ -78,3 +87,64 @@ export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch)
|
||||||
console.log(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<ApiResponse<Category>>(`/api/categories/${id}`, { isPinned: !isPinned });
|
||||||
|
|
||||||
|
const status = isPinned ? 'unpinned from Homescreen' : 'pinned to Homescreen';
|
||||||
|
|
||||||
|
dispatch<CreateNotificationAction>({
|
||||||
|
type: ActionTypes.createNotification,
|
||||||
|
payload: {
|
||||||
|
title: 'Success',
|
||||||
|
message: `Category ${name} ${status}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch<PinCategoryAction>({
|
||||||
|
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<ApiResponse<{}>>(`/api/categories/${id}`);
|
||||||
|
|
||||||
|
dispatch<CreateNotificationAction>({
|
||||||
|
type: ActionTypes.createNotification,
|
||||||
|
payload: {
|
||||||
|
title: 'Success',
|
||||||
|
message: `Category deleted`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch<DeleteCategoryAction>({
|
||||||
|
type: ActionTypes.deleteCategory,
|
||||||
|
payload: id
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) => {
|
const bookmarkReducer = (state = initialState, action: Action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.getCategories: return getCategories(state, action);
|
case ActionTypes.getCategories: return getCategories(state, action);
|
||||||
case ActionTypes.getCategoriesSuccess: return getCategoriesSuccess(state, action);
|
case ActionTypes.getCategoriesSuccess: return getCategoriesSuccess(state, action);
|
||||||
case ActionTypes.addCategory: return addCategory(state, action);
|
case ActionTypes.addCategory: return addCategory(state, action);
|
||||||
case ActionTypes.addBookmark: return addBookmark(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;
|
default: return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,24 @@ exports.updateCategory = asyncWrapper(async (req, res, next) => {
|
||||||
// @route DELETE /api/categories/:id
|
// @route DELETE /api/categories/:id
|
||||||
// @access Public
|
// @access Public
|
||||||
exports.deleteCategory = asyncWrapper(async (req, res, next) => {
|
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({
|
await Category.destroy({
|
||||||
where: { id: req.params.id }
|
where: { id: req.params.id }
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,11 +3,11 @@ const Bookmark = require('./Bookmark');
|
||||||
|
|
||||||
const associateModels = () => {
|
const associateModels = () => {
|
||||||
// Category <> Bookmark
|
// Category <> Bookmark
|
||||||
Bookmark.belongsTo(Category, { foreignKey: 'categoryId' });
|
|
||||||
Category.hasMany(Bookmark, {
|
Category.hasMany(Bookmark, {
|
||||||
as: 'bookmarks',
|
as: 'bookmarks',
|
||||||
foreignKey: 'categoryId'
|
foreignKey: 'categoryId'
|
||||||
});
|
});
|
||||||
|
Bookmark.belongsTo(Category, { foreignKey: 'categoryId' });
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = associateModels;
|
module.exports = associateModels;
|
Loading…
Add table
Add a link
Reference in a new issue