diff --git a/client/src/components/Actions/TableActions.tsx b/client/src/components/Actions/TableActions.tsx index 75613ee..6d9460c 100644 --- a/client/src/components/Actions/TableActions.tsx +++ b/client/src/components/Actions/TableActions.tsx @@ -4,7 +4,7 @@ import classes from './TableActions.module.css'; interface Entity { id: number; name: string; - isPinned: boolean; + isPinned?: boolean; isPublic: boolean; } @@ -12,7 +12,7 @@ interface Props { entity: Entity; deleteHandler: (id: number, name: string) => void; updateHandler: (id: number) => void; - pinHanlder: (id: number) => void; + pinHanlder?: (id: number) => void; changeVisibilty: (id: number) => void; showPin?: boolean; } @@ -27,6 +27,8 @@ export const TableActions = (props: Props): JSX.Element => { showPin = true, } = props; + const _pinHandler = pinHanlder || function () {}; + return ( {/* DELETE */} @@ -51,7 +53,7 @@ export const TableActions = (props: Props): JSX.Element => { {showPin && (
pinHanlder(entity.id)} + onClick={() => _pinHandler(entity.id)} tabIndex={0} > {entity.isPinned ? ( diff --git a/client/src/components/Apps/AppForm/AppForm.tsx b/client/src/components/Apps/AppForm/AppForm.tsx index 10bc694..8679f82 100644 --- a/client/src/components/Apps/AppForm/AppForm.tsx +++ b/client/src/components/Apps/AppForm/AppForm.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, ChangeEvent, SyntheticEvent } from 'react'; -import { useDispatch } from 'react-redux'; -import { App, NewApp } from '../../../interfaces'; +import { useDispatch, useSelector } from 'react-redux'; +import { NewApp } from '../../../interfaces'; import classes from './AppForm.module.css'; @@ -8,29 +8,34 @@ import { ModalForm, InputGroup, Button } from '../../UI'; import { inputHandler, newAppTemplate } from '../../../utility'; import { bindActionCreators } from 'redux'; import { actionCreators } from '../../../store'; +import { State } from '../../../store/reducers'; interface Props { modalHandler: () => void; - app?: App; } -export const AppForm = ({ app, modalHandler }: Props): JSX.Element => { +export const AppForm = ({ modalHandler }: Props): JSX.Element => { + const { appInUpdate } = useSelector((state: State) => state.apps); + const dispatch = useDispatch(); - const { addApp, updateApp } = bindActionCreators(actionCreators, dispatch); + const { addApp, updateApp, setEditApp } = bindActionCreators( + actionCreators, + dispatch + ); const [useCustomIcon, toggleUseCustomIcon] = useState(false); const [customIcon, setCustomIcon] = useState(null); const [formData, setFormData] = useState(newAppTemplate); useEffect(() => { - if (app) { + if (appInUpdate) { setFormData({ - ...app, + ...appInUpdate, }); } else { setFormData(newAppTemplate); } - }, [app]); + }, [appInUpdate]); const inputChangeHandler = ( e: ChangeEvent, @@ -66,7 +71,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => { return data; }; - if (!app) { + if (!appInUpdate) { if (customIcon) { const data = createFormData(); addApp(data); @@ -76,14 +81,15 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => { } else { if (customIcon) { const data = createFormData(); - updateApp(app.id, data); + updateApp(appInUpdate.id, data); } else { - updateApp(app.id, formData); + updateApp(appInUpdate.id, formData); modalHandler(); } } setFormData(newAppTemplate); + setEditApp(null); }; return ( @@ -182,7 +188,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => { - {!app ? ( + {!appInUpdate ? ( ) : ( diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx index 7231e18..88db874 100644 --- a/client/src/components/Apps/Apps.tsx +++ b/client/src/components/Apps/Apps.tsx @@ -19,7 +19,6 @@ import { AppForm } from './AppForm/AppForm'; import { AppTable } from './AppTable/AppTable'; // Utils -import { appTemplate } from '../../utility'; import { State } from '../../store/reducers'; import { bindActionCreators } from 'redux'; import { actionCreators } from '../../store'; @@ -37,7 +36,7 @@ export const Apps = (props: Props): JSX.Element => { // Get Redux action creators const dispatch = useDispatch(); - const { getApps } = bindActionCreators(actionCreators, dispatch); + const { getApps, setEditApp } = bindActionCreators(actionCreators, dispatch); // Load apps if array is empty useEffect(() => { @@ -49,8 +48,6 @@ export const Apps = (props: Props): JSX.Element => { // Form const [modalIsOpen, setModalIsOpen] = useState(false); const [showTable, setShowTable] = useState(false); - const [isInUpdate, setIsInUpdate] = useState(false); - const [appInUpdate, setAppInUpdate] = useState(appTemplate); // Observe if user is authenticated -> set default view if not useEffect(() => { @@ -63,28 +60,21 @@ export const Apps = (props: Props): JSX.Element => { // Form actions const toggleModal = (): void => { setModalIsOpen(!modalIsOpen); - setIsInUpdate(false); }; const toggleEdit = (): void => { setShowTable(!showTable); - setIsInUpdate(false); }; const openFormForUpdating = (app: App): void => { - setAppInUpdate(app); - setIsInUpdate(true); + setEditApp(app); setModalIsOpen(true); }; return ( - {!isInUpdate ? ( - - ) : ( - - )} + { {isAuthenticated && (
- + { + setEditApp(null); + toggleModal(); + }} + />
)} diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css index b840a42..c61217d 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css @@ -10,6 +10,10 @@ text-transform: uppercase; } +.BookmarkCard h3:hover { + cursor: pointer; +} + .Bookmarks { display: flex; flex-direction: column; diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx index 146bf67..fe6c97d 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx @@ -1,14 +1,17 @@ import { Fragment } from 'react'; -import { useSelector } from 'react-redux'; +// Redux +import { useDispatch, useSelector } from 'react-redux'; import { State } from '../../../store/reducers'; +import { bindActionCreators } from 'redux'; +import { actionCreators } from '../../../store'; +// Typescript import { Bookmark, Category } from '../../../interfaces'; +// Other import classes from './BookmarkCard.module.css'; - import { Icon } from '../../UI'; - import { iconParser, isImage, isSvg, isUrl, urlParser } from '../../../utility'; interface Props { @@ -18,9 +21,19 @@ interface Props { export const BookmarkCard = (props: Props): JSX.Element => { const { config } = useSelector((state: State) => state.config); + const dispatch = useDispatch(); + const { setEditCategory } = bindActionCreators(actionCreators, dispatch); + return (
-

{props.category.name}

+

{ + setEditCategory(props.category); + }} + > + {props.category.name} +

+
{props.category.bookmarks.map((bookmark: Bookmark) => { const redirectUrl = urlParser(bookmark.url)[1]; diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx index ba5570e..f461d48 100644 --- a/client/src/components/Bookmarks/Bookmarks.tsx +++ b/client/src/components/Bookmarks/Bookmarks.tsx @@ -19,9 +19,6 @@ import { Container, Headline, ActionButton, Spinner, Modal } from '../UI'; // Components import { BookmarkGrid } from './BookmarkGrid/BookmarkGrid'; import { Form } from './Form/Form'; - -// Utils -import { bookmarkTemplate, categoryTemplate } from '../../utility'; import { Table } from './Table/Table'; interface Props { @@ -36,13 +33,14 @@ export enum ContentType { export const Bookmarks = (props: Props): JSX.Element => { // Get Redux state const { - bookmarks: { loading, categories }, + bookmarks: { loading, categories, categoryInEdit }, auth: { isAuthenticated }, } = useSelector((state: State) => state); // Get Redux action creators const dispatch = useDispatch(); - const { getCategories } = bindActionCreators(actionCreators, dispatch); + const { getCategories, setEditCategory, setEditBookmark } = + bindActionCreators(actionCreators, dispatch); // Load categories if array is empty useEffect(() => { @@ -55,10 +53,6 @@ export const Bookmarks = (props: Props): JSX.Element => { const [modalIsOpen, setModalIsOpen] = useState(false); const [formContentType, setFormContentType] = useState(ContentType.category); const [isInUpdate, setIsInUpdate] = useState(false); - const [categoryInUpdate, setCategoryInUpdate] = - useState(categoryTemplate); - const [bookmarkInUpdate, setBookmarkInUpdate] = - useState(bookmarkTemplate); // Table const [showTable, setShowTable] = useState(false); @@ -74,6 +68,13 @@ export const Bookmarks = (props: Props): JSX.Element => { } }, [isAuthenticated]); + useEffect(() => { + if (categoryInEdit && !showTable) { + setTableContentType(ContentType.bookmark); + setShowTable(true); + } + }, [categoryInEdit]); + // Form actions const toggleModal = (): void => { setModalIsOpen(!modalIsOpen); @@ -94,10 +95,10 @@ export const Bookmarks = (props: Props): JSX.Element => { if (instanceOfCategory(data)) { setFormContentType(ContentType.category); - setCategoryInUpdate(data); + setEditCategory(data); } else { setFormContentType(ContentType.bookmark); - setBookmarkInUpdate(data); + setEditBookmark(data); } toggleModal(); @@ -121,8 +122,6 @@ export const Bookmarks = (props: Props): JSX.Element => { modalHandler={toggleModal} contentType={formContentType} inUpdate={isInUpdate} - category={categoryInUpdate} - bookmark={bookmarkInUpdate} /> @@ -145,11 +144,11 @@ export const Bookmarks = (props: Props): JSX.Element => { icon="mdiPencil" handler={() => showTableForEditing(ContentType.category)} /> - {/* showTableForEditing(ContentType.bookmark)} - /> */} + />
)} diff --git a/client/src/components/Bookmarks/Form/Form.tsx b/client/src/components/Bookmarks/Form/Form.tsx index 41ed1bb..960e8cf 100644 --- a/client/src/components/Bookmarks/Form/Form.tsx +++ b/client/src/components/Bookmarks/Form/Form.tsx @@ -1,22 +1,26 @@ // Typescript -import { Bookmark, Category } from '../../../interfaces'; import { ContentType } from '../Bookmarks'; // Utils import { CategoryForm } from './CategoryForm'; import { BookmarksForm } from './BookmarksForm'; import { Fragment } from 'react'; +import { useSelector } from 'react-redux'; +import { State } from '../../../store/reducers'; +import { bookmarkTemplate, categoryTemplate } from '../../../utility'; interface Props { modalHandler: () => void; contentType: ContentType; inUpdate?: boolean; - category?: Category; - bookmark?: Bookmark; } export const Form = (props: Props): JSX.Element => { - const { modalHandler, contentType, inUpdate, category, bookmark } = props; + const { categoryInEdit, bookmarkInEdit } = useSelector( + (state: State) => state.bookmarks + ); + + const { modalHandler, contentType, inUpdate } = props; return ( @@ -33,9 +37,15 @@ export const Form = (props: Props): JSX.Element => { // form: update {contentType === ContentType.category ? ( - + ) : ( - + )} )} diff --git a/client/src/store/action-creators/app.ts b/client/src/store/action-creators/app.ts index ddd88fa..7e285b6 100644 --- a/client/src/store/action-creators/app.ts +++ b/client/src/store/action-creators/app.ts @@ -7,6 +7,7 @@ import { GetAppsAction, PinAppAction, ReorderAppsAction, + SetEditAppAction, SortAppsAction, UpdateAppAction, } from '../actions/app'; @@ -196,3 +197,11 @@ export const sortApps = () => async (dispatch: Dispatch) => { console.log(err); } }; + +export const setEditApp = + (app: App | null) => (dispatch: Dispatch) => { + dispatch({ + type: ActionType.setEditApp, + payload: app, + }); + }; diff --git a/client/src/store/action-creators/bookmark.ts b/client/src/store/action-creators/bookmark.ts index 5010bd3..4b1b0d2 100644 --- a/client/src/store/action-creators/bookmark.ts +++ b/client/src/store/action-creators/bookmark.ts @@ -18,6 +18,8 @@ import { GetCategoriesAction, PinCategoryAction, ReorderCategoriesAction, + SetEditBookmarkAction, + SetEditCategoryAction, SortCategoriesAction, UpdateBookmarkAction, UpdateCategoryAction, @@ -319,3 +321,21 @@ export const reorderCategories = console.log(err); } }; + +export const setEditCategory = + (category: Category | null) => + (dispatch: Dispatch) => { + dispatch({ + type: ActionType.setEditCategory, + payload: category, + }); + }; + +export const setEditBookmark = + (bookmark: Bookmark | null) => + (dispatch: Dispatch) => { + dispatch({ + type: ActionType.setEditBookmark, + payload: bookmark, + }); + }; diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts index 58ca529..81e1a84 100644 --- a/client/src/store/action-types/index.ts +++ b/client/src/store/action-types/index.ts @@ -23,6 +23,7 @@ export enum ActionType { updateApp = 'UPDATE_APP', reorderApps = 'REORDER_APPS', sortApps = 'SORT_APPS', + setEditApp = 'SET_EDIT_APP', // CATEGORES getCategories = 'GET_CATEGORIES', getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS', @@ -33,10 +34,12 @@ export enum ActionType { updateCategory = 'UPDATE_CATEGORY', sortCategories = 'SORT_CATEGORIES', reorderCategories = 'REORDER_CATEGORIES', + setEditCategory = 'SET_EDIT_CATEGORY', // BOOKMARKS addBookmark = 'ADD_BOOKMARK', deleteBookmark = 'DELETE_BOOKMARK', updateBookmark = 'UPDATE_BOOKMARK', + setEditBookmark = 'SET_EDIT_BOOKMARK', // AUTH login = 'LOGIN', logout = 'LOGOUT', diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts index 37f5419..689014a 100644 --- a/client/src/store/actions/app.ts +++ b/client/src/store/actions/app.ts @@ -36,3 +36,8 @@ export interface SortAppsAction { type: ActionType.sortApps; payload: string; } + +export interface SetEditAppAction { + type: ActionType.setEditApp; + payload: App | null; +} diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts index e4cfcfd..60e7c9f 100644 --- a/client/src/store/actions/bookmark.ts +++ b/client/src/store/actions/bookmark.ts @@ -56,3 +56,13 @@ export interface ReorderCategoriesAction { type: ActionType.reorderCategories; payload: Category[]; } + +export interface SetEditCategoryAction { + type: ActionType.setEditCategory; + payload: Category | null; +} + +export interface SetEditBookmarkAction { + type: ActionType.setEditBookmark; + payload: Bookmark | null; +} diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts index 02862b6..3193873 100644 --- a/client/src/store/actions/index.ts +++ b/client/src/store/actions/index.ts @@ -24,6 +24,7 @@ import { UpdateAppAction, ReorderAppsAction, SortAppsAction, + SetEditAppAction, } from './app'; import { @@ -37,6 +38,8 @@ import { AddBookmarkAction, DeleteBookmarkAction, UpdateBookmarkAction, + SetEditCategoryAction, + SetEditBookmarkAction, } from './bookmark'; import { @@ -67,6 +70,7 @@ export type Action = | UpdateAppAction | ReorderAppsAction | SortAppsAction + | SetEditAppAction // Categories | GetCategoriesAction | AddCategoryAction @@ -75,10 +79,12 @@ export type Action = | UpdateCategoryAction | SortCategoriesAction | ReorderCategoriesAction + | SetEditCategoryAction // Bookmarks | AddBookmarkAction | DeleteBookmarkAction | UpdateBookmarkAction + | SetEditBookmarkAction // Auth | LoginAction | LogoutAction diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts index e6da902..7f10793 100644 --- a/client/src/store/reducers/app.ts +++ b/client/src/store/reducers/app.ts @@ -7,12 +7,14 @@ interface AppsState { loading: boolean; apps: App[]; errors: string | undefined; + appInUpdate: App | null; } const initialState: AppsState = { loading: true, apps: [], errors: undefined, + appInUpdate: null, }; export const appsReducer = ( @@ -86,6 +88,12 @@ export const appsReducer = ( apps: sortData(state.apps, action.payload), }; + case ActionType.setEditApp: + return { + ...state, + appInUpdate: action.payload, + }; + default: return state; } diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts index b8e7e61..d1fc380 100644 --- a/client/src/store/reducers/bookmark.ts +++ b/client/src/store/reducers/bookmark.ts @@ -1,4 +1,4 @@ -import { Category } from '../../interfaces'; +import { Bookmark, Category } from '../../interfaces'; import { sortData } from '../../utility'; import { ActionType } from '../action-types'; import { Action } from '../actions'; @@ -7,12 +7,16 @@ interface BookmarksState { loading: boolean; errors: string | undefined; categories: Category[]; + categoryInEdit: Category | null; + bookmarkInEdit: Bookmark | null; } const initialState: BookmarksState = { loading: true, errors: undefined, categories: [], + categoryInEdit: null, + bookmarkInEdit: null, }; export const bookmarksReducer = ( @@ -45,19 +49,19 @@ export const bookmarksReducer = ( (category) => category.id === action.payload.categoryId ); + const categoryWithNewBookmark = { + ...state.categories[categoryIdx], + bookmarks: [...state.categories[categoryIdx].bookmarks, action.payload], + }; + return { ...state, categories: [ ...state.categories.slice(0, categoryIdx), - { - ...state.categories[categoryIdx], - bookmarks: [ - ...state.categories[categoryIdx].bookmarks, - action.payload, - ], - }, + categoryWithNewBookmark, ...state.categories.slice(categoryIdx + 1), ], + categoryInEdit: categoryWithNewBookmark, }; case ActionType.pinCategory: @@ -112,47 +116,54 @@ export const bookmarksReducer = ( (category) => category.id === action.payload.categoryId ); + const targetCategory = { + ...state.categories[categoryInUpdateIdx], + bookmarks: state.categories[categoryInUpdateIdx].bookmarks.filter( + (bookmark) => bookmark.id !== action.payload.bookmarkId + ), + }; + return { ...state, categories: [ ...state.categories.slice(0, categoryInUpdateIdx), - { - ...state.categories[categoryInUpdateIdx], - bookmarks: state.categories[categoryInUpdateIdx].bookmarks.filter( - (bookmark) => bookmark.id !== action.payload.bookmarkId - ), - }, + targetCategory, ...state.categories.slice(categoryInUpdateIdx + 1), ], + categoryInEdit: targetCategory, }; case ActionType.updateBookmark: const parentCategoryIdx = state.categories.findIndex( (category) => category.id === action.payload.categoryId ); + const updatedBookmarkIdx = state.categories[ parentCategoryIdx ].bookmarks.findIndex((bookmark) => bookmark.id === action.payload.id); + const categoryWithUpdatedBookmark = { + ...state.categories[parentCategoryIdx], + bookmarks: [ + ...state.categories[parentCategoryIdx].bookmarks.slice( + 0, + updatedBookmarkIdx + ), + action.payload, + ...state.categories[parentCategoryIdx].bookmarks.slice( + updatedBookmarkIdx + 1 + ), + ], + }; + return { ...state, categories: [ ...state.categories.slice(0, parentCategoryIdx), - { - ...state.categories[parentCategoryIdx], - bookmarks: [ - ...state.categories[parentCategoryIdx].bookmarks.slice( - 0, - updatedBookmarkIdx - ), - action.payload, - ...state.categories[parentCategoryIdx].bookmarks.slice( - updatedBookmarkIdx + 1 - ), - ], - }, + categoryWithUpdatedBookmark, ...state.categories.slice(parentCategoryIdx + 1), ], + categoryInEdit: categoryWithUpdatedBookmark, }; case ActionType.sortCategories: @@ -166,6 +177,19 @@ export const bookmarksReducer = ( ...state, categories: action.payload, }; + + case ActionType.setEditCategory: + return { + ...state, + categoryInEdit: action.payload, + }; + + case ActionType.setEditBookmark: + return { + ...state, + bookmarkInEdit: action.payload, + }; + default: return state; }