diff --git a/client/src/components/Apps/AppTable/AppTable.module.css b/client/src/components/Apps/AppTable/AppTable.module.css
index 2ccabb7..fc79b68 100644
--- a/client/src/components/Apps/AppTable/AppTable.module.css
+++ b/client/src/components/Apps/AppTable/AppTable.module.css
@@ -1,58 +1,8 @@
-.TableContainer {
- width: 100%;
-}
-
-.Table {
- border-collapse: collapse;
- width: 100%;
- text-align: left;
- font-size: 16px;
- color: var(--color-primary);
-}
-
-.Table th,
-.Table td {
- /* border: 1px solid orange; */
- padding: 10px;
-}
-
-/* Head */
-
-.Table th {
- --header-radius: 4px;
- background-color: var(--color-primary);
- color: var(--color-background);
-}
-
-.Table th:first-child {
- border-top-left-radius: var(--header-radius);
- border-bottom-left-radius: var(--header-radius);
-}
-
-.Table th:last-child {
- border-top-right-radius: var(--header-radius);
- border-bottom-right-radius: var(--header-radius);
-}
-
-/* Body */
-
-.Table td {
- /* opacity: 0.5; */
- transition: all 0.2s;
-}
-
-/* .Table td:hover {
- opacity: 1;
-} */
-
-/* Actions */
-
.TableActions {
display: flex;
align-items: center;
}
-
.TableAction {
width: 22px;
}
diff --git a/client/src/components/Apps/AppTable/AppTable.tsx b/client/src/components/Apps/AppTable/AppTable.tsx
index ec0a11c..728e079 100644
--- a/client/src/components/Apps/AppTable/AppTable.tsx
+++ b/client/src/components/Apps/AppTable/AppTable.tsx
@@ -5,6 +5,7 @@ import { pinApp, deleteApp } from '../../../store/actions';
import classes from './AppTable.module.css';
import Icon from '../../UI/Icons/Icon/Icon';
+import Table from '../../UI/Table/Table';
interface ComponentProps {
apps: App[];
@@ -29,55 +30,48 @@ const AppTable = (props: ComponentProps): JSX.Element => {
}
return (
-
-
-
-
- Name |
- Url |
- Icon |
- Actions |
+
+ {props.apps.map((app: App): JSX.Element => {
+ return (
+
+ {app.name} |
+ {app.url} |
+ {app.icon} |
+
+ deleteAppHandler(app)}
+ onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
+ tabIndex={0}>
+
+
+ props.updateAppHandler(app)}
+ onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
+ tabIndex={0}>
+
+
+ props.pinApp(app)}
+ onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
+ tabIndex={0}>
+ {app.isPinned
+ ?
+ :
+ }
+
+ |
-
-
- {props.apps.map((app: App): JSX.Element => {
- return (
-
- {app.name} |
- {app.url} |
- {app.icon} |
-
- deleteAppHandler(app)}
- onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
- tabIndex={0}>
-
-
- props.updateAppHandler(app)}
- onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
- tabIndex={0}>
-
-
- props.pinApp(app)}
- onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
- tabIndex={0}>
- {app.isPinned
- ?
- :
- }
-
- |
-
- )
- })}
-
-
-
+ )
+ })}
+
)
}
diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
index 81c5cc1..e3a672f 100644
--- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
+++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
@@ -21,7 +21,8 @@
transition: all 0.25s;
}
-.BookmarkCard a:hover {
+.BookmarkCard a:hover,
+.BookmarkCard a:focus {
text-decoration: underline;
padding-left: 10px;
}
\ No newline at end of file
diff --git a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx
index 728baaa..0d6223e 100644
--- a/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx
+++ b/client/src/components/Bookmarks/BookmarkForm/BookmarkForm.tsx
@@ -4,12 +4,12 @@ import { connect } from 'react-redux';
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 { ContentType } from '../Bookmarks';
import { getCategories, addCategory, addBookmark } from '../../../store/actions';
interface ComponentProps {
modalHandler: () => void;
- contentType: FormContentType;
+ contentType: ContentType;
categories: Category[];
addCategory: (formData: NewCategory) => void;
addBookmark: (formData: NewBookmark) => void;
@@ -29,10 +29,10 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
const formSubmitHandler = (e: SyntheticEvent
): void => {
e.preventDefault();
- if (props.contentType === FormContentType.category) {
+ if (props.contentType === ContentType.category) {
props.addCategory(categoryName);
setCategoryName({ name: '' });
- } else if (props.contentType === FormContentType.bookmark) {
+ } else if (props.contentType === ContentType.bookmark) {
if (formData.categoryId === -1) {
alert('select category');
return;
@@ -66,7 +66,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
modalHandler={props.modalHandler}
formHandler={formSubmitHandler}
>
- {props.contentType === FormContentType.category
+ {props.contentType === ContentType.category
? (
diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.module.css b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.module.css
new file mode 100644
index 0000000..fc79b68
--- /dev/null
+++ b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.module.css
@@ -0,0 +1,12 @@
+.TableActions {
+ display: flex;
+ align-items: center;
+}
+
+.TableAction {
+ width: 22px;
+}
+
+.TableAction:hover {
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx
new file mode 100644
index 0000000..388b4d8
--- /dev/null
+++ b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx
@@ -0,0 +1,103 @@
+import { ContentType } from '../Bookmarks';
+import classes from './BookmarkTable.module.css';
+
+import Table from '../../UI/Table/Table';
+import { Bookmark, Category } from '../../../interfaces';
+import Icon from '../../UI/Icons/Icon/Icon';
+
+interface ComponentProps {
+ contentType: ContentType;
+ categories: Category[];
+}
+
+const BookmarkTable = (props: ComponentProps): JSX.Element => {
+ if (props.contentType === ContentType.category) {
+ return (
+
+ {props.categories.map((category: Category) => {
+ return (
+
+ {category.name} |
+
+ deleteAppHandler(app)}
+ // onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
+ tabIndex={0}>
+
+
+ props.updateAppHandler(app)}
+ // onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
+ tabIndex={0}>
+
+
+ props.pinApp(app)}
+ // onKeyDown={(e) => keyboardActionHandler(e, app, props.pinApp)}
+ tabIndex={0}>
+ {category.isPinned
+ ?
+ :
+ }
+
+ |
+
+ )
+ })}
+
+ )
+ } else {
+ const bookmarks: {bookmark: Bookmark, categoryName: string}[] = [];
+ props.categories.forEach((category: Category) => {
+ category.bookmarks.forEach((bookmark: Bookmark) => {
+ bookmarks.push({
+ bookmark,
+ categoryName: category.name
+ });
+ })
+ })
+
+ return (
+
+ {bookmarks.map((bookmark: {bookmark: Bookmark, categoryName: string}) => {
+ return (
+
+ {bookmark.bookmark.name} |
+ {bookmark.bookmark.url} |
+ {bookmark.categoryName} |
+
+ deleteAppHandler(app)}
+ // onKeyDown={(e) => keyboardActionHandler(e, app, deleteAppHandler)}
+ tabIndex={0}>
+
+
+ props.updateAppHandler(app)}
+ // onKeyDown={(e) => keyboardActionHandler(e, app, props.updateAppHandler)}
+ tabIndex={0}>
+
+
+ |
+
+ )
+ })}
+
+ )
+ }
+}
+
+export default BookmarkTable;
\ No newline at end of file
diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx
index 3c4aadd..ba14b5e 100644
--- a/client/src/components/Bookmarks/Bookmarks.tsx
+++ b/client/src/components/Bookmarks/Bookmarks.tsx
@@ -14,6 +14,7 @@ import { Category, GlobalState } from '../../interfaces';
import Spinner from '../UI/Spinner/Spinner';
import Modal from '../UI/Modal/Modal';
import BookmarkForm from './BookmarkForm/BookmarkForm';
+import BookmarkTable from './BookmarkTable/BookmarkTable';
interface ComponentProps {
loading: boolean;
@@ -21,14 +22,16 @@ interface ComponentProps {
getCategories: () => void;
}
-export enum FormContentType {
+export enum ContentType {
category,
bookmark
}
const Bookmarks = (props: ComponentProps): JSX.Element => {
const [modalIsOpen, setModalIsOpen] = useState(false);
- const [formContentType, setFormContentType] = useState(FormContentType.category);
+ const [formContentType, setFormContentType] = useState(ContentType.category);
+ const [isInEdit, setIsInEdit] = useState(false);
+ const [tableContentType, setTableContentType] = useState(ContentType.category);
useEffect(() => {
if (props.categories.length === 0) {
@@ -40,24 +43,29 @@ const Bookmarks = (props: ComponentProps): JSX.Element => {
setModalIsOpen(!modalIsOpen);
}
- const addActionHandler = (contentType: FormContentType) => {
+ const addActionHandler = (contentType: ContentType) => {
setFormContentType(contentType);
toggleModal();
}
+ const toggleEdit = (): void => {
+ setIsInEdit(!isInEdit);
+ }
+
+ const editActionHandler = (contentType: ContentType) => {
+ // We're in the edit mode and the same button was clicked - go back to list
+ if (isInEdit && contentType === tableContentType) {
+ setIsInEdit(false);
+ } else {
+ setIsInEdit(true);
+ setTableContentType(contentType);
+ }
+ }
+
return (
- {formContentType === FormContentType.category
- ?
- :
- }
+
{
addActionHandler(FormContentType.category)}
+ handler={() => addActionHandler(ContentType.category)}
/>
addActionHandler(FormContentType.bookmark)}
+ handler={() => addActionHandler(ContentType.bookmark)}
/>
editActionHandler(ContentType.category)}
/>
editActionHandler(ContentType.bookmark)}
/>
{props.loading
?
- :
+ : (!isInEdit
+ ?
+ : )
}
)
diff --git a/client/src/components/UI/Layout/Layout.tsx b/client/src/components/UI/Layout/Layout.tsx
index 03a20fc..b7fe50f 100644
--- a/client/src/components/UI/Layout/Layout.tsx
+++ b/client/src/components/UI/Layout/Layout.tsx
@@ -1,6 +1,10 @@
import classes from './Layout.module.css';
-export const Container = (props: any): JSX.Element => {
+interface ComponentProps {
+ children: JSX.Element | JSX.Element[];
+}
+
+export const Container = (props: ComponentProps): JSX.Element => {
return (
{props.children}
diff --git a/client/src/components/UI/Table/Table.module.css b/client/src/components/UI/Table/Table.module.css
new file mode 100644
index 0000000..33b712b
--- /dev/null
+++ b/client/src/components/UI/Table/Table.module.css
@@ -0,0 +1,41 @@
+.TableContainer {
+ width: 100%;
+}
+
+.Table {
+ border-collapse: collapse;
+ width: 100%;
+ text-align: left;
+ font-size: 16px;
+ color: var(--color-primary);
+}
+
+.Table th,
+.Table td {
+ padding: 10px;
+}
+
+/* Head */
+
+.Table th {
+ --header-radius: 4px;
+ background-color: var(--color-primary);
+ color: var(--color-background);
+}
+
+.Table th:first-child {
+ border-top-left-radius: var(--header-radius);
+ border-bottom-left-radius: var(--header-radius);
+}
+
+.Table th:last-child {
+ border-top-right-radius: var(--header-radius);
+ border-bottom-right-radius: var(--header-radius);
+}
+
+/* Body */
+
+.Table td {
+ /* opacity: 0.5; */
+ transition: all 0.2s;
+}
\ No newline at end of file
diff --git a/client/src/components/UI/Table/Table.tsx b/client/src/components/UI/Table/Table.tsx
new file mode 100644
index 0000000..882ebfb
--- /dev/null
+++ b/client/src/components/UI/Table/Table.tsx
@@ -0,0 +1,25 @@
+import classes from './Table.module.css';
+
+interface ComponentProps {
+ children: JSX.Element | JSX.Element[];
+ headers: string[];
+}
+
+const Table = (props: ComponentProps): JSX.Element => {
+ return (
+
+
+
+
+ {props.headers.map((header: string, index: number): JSX.Element => ({header} | ))}
+
+
+
+ {props.children}
+
+
+
+ )
+}
+
+export default Table;
\ No newline at end of file
diff --git a/client/src/index.css b/client/src/index.css
index a9c6013..4f64f2a 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -2,7 +2,6 @@
margin: 0;
padding: 0;
box-sizing: border-box;
- /* transition: all 0.3s; */
user-select: none;
}
@@ -14,13 +13,6 @@ body {
background-color: var(--color-background);
transition: background-color 0.3s;
- /* font weights
- light 300
- regular 400
- semi-bold 600
- bold 700
- extra-bold 800
- */
font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif;
font-size: 14px;
}
@@ -28,11 +20,4 @@ body {
a {
color: var(--color-primary);
text-decoration: none;
- /* opacity: 0.75; */
-}
-
-/* 320px — 480px: Mobile devices.
-481px — 768px: iPads, Tablets.
-769px — 1024px: Small screens, laptops.
-1025px — 1200px: Desktops, large screens.
-1201px and more — Extra large screens, TV. */
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts
index b1bd6f1..7c121dc 100644
--- a/client/src/store/actions/bookmark.ts
+++ b/client/src/store/actions/bookmark.ts
@@ -2,6 +2,7 @@ import axios from 'axios';
import { Dispatch } from 'redux';
import { ActionTypes } from './actionTypes';
import { Category, ApiResponse, NewCategory, Bookmark, NewBookmark } from '../../interfaces';
+import { CreateNotificationAction } from './notification';
export interface GetCategoriesAction
{
type: ActionTypes.getCategories | ActionTypes.getCategoriesSuccess | ActionTypes.getCategoriesError;
@@ -35,6 +36,14 @@ export const addCategory = (formData: NewCategory) => async (dispatch: Dispatch)
try {
const res = await axios.post>('/api/categories', formData);
+ dispatch({
+ type: ActionTypes.createNotification,
+ payload: {
+ title: 'Success',
+ message: `Category ${formData.name} created`
+ }
+ })
+
dispatch({
type: ActionTypes.addCategory,
payload: res.data.data
@@ -53,6 +62,14 @@ export const addBookmark = (formData: NewBookmark) => async (dispatch: Dispatch)
try {
const res = await axios.post>('/api/bookmarks', formData);
+ dispatch({
+ type: ActionTypes.createNotification,
+ payload: {
+ title: 'Success',
+ message: `Bookmark ${formData.name} created`
+ }
+ })
+
dispatch({
type: ActionTypes.addBookmark,
payload: res.data.data