1
0
Fork 0
mirror of https://github.com/pawelmalak/flame.git synced 2025-07-24 13:39:35 +02:00
flame/client/src/components/Bookmarks/Form/BookmarksForm.tsx

275 lines
7.1 KiB
TypeScript

import { useState, ChangeEvent, useEffect, FormEvent } from 'react';
// Redux
import { useDispatch, useSelector } from 'react-redux';
import { State } from '../../../store/reducers';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../../store';
// Typescript
import { Bookmark, Category, NewBookmark } from '../../../interfaces';
// UI
import { ModalForm, InputGroup, Button } from '../../UI';
// CSS
import classes from './Form.module.css';
// Utils
import { inputHandler, newBookmarkTemplate } from '../../../utility';
interface Props {
modalHandler: () => void;
bookmark?: Bookmark;
}
export const BookmarksForm = ({
bookmark,
modalHandler,
}: Props): JSX.Element => {
const { categories } = useSelector((state: State) => state.bookmarks);
const dispatch = useDispatch();
const { addBookmark, updateBookmark, createNotification } =
bindActionCreators(actionCreators, dispatch);
const [useCustomIcon, toggleUseCustomIcon] = useState<boolean>(false);
const [customIcon, setCustomIcon] = useState<File | null>(null);
const [formData, setFormData] = useState<NewBookmark>(newBookmarkTemplate);
// Load bookmark data if provided for editing
useEffect(() => {
if (bookmark) {
setFormData({ ...bookmark });
} else {
setFormData(newBookmarkTemplate);
}
}, [bookmark]);
const inputChangeHandler = (
e: ChangeEvent<HTMLInputElement | HTMLSelectElement>,
options?: { isNumber?: boolean; isBool?: boolean }
) => {
inputHandler<NewBookmark>({
e,
options,
setStateHandler: setFormData,
state: formData,
});
};
const fileChangeHandler = (e: ChangeEvent<HTMLInputElement>): void => {
if (e.target.files) {
setCustomIcon(e.target.files[0]);
}
};
// Bookmarks form handler
const formSubmitHandler = (e: FormEvent): void => {
e.preventDefault();
for (let field of ['name', 'url', 'icon'] as const) {
if (/^ +$/.test(formData[field])) {
createNotification({
title: 'Error',
message: `Field cannot be empty: ${field}`,
});
return;
}
}
const createFormData = (): FormData => {
const data = new FormData();
if (customIcon) {
data.append('icon', customIcon);
}
data.append('name', formData.name);
data.append('url', formData.url);
data.append('categoryId', `${formData.categoryId}`);
data.append('isPublic', `${formData.isPublic ? 1 : 0}`);
return data;
};
const checkCategory = (): boolean => {
if (formData.categoryId < 0) {
createNotification({
title: 'Error',
message: 'Please select category',
});
return false;
}
return true;
};
if (!bookmark) {
// add new bookmark
if (!checkCategory()) return;
if (formData.categoryId < 0) {
createNotification({
title: 'Error',
message: 'Please select category',
});
return;
}
if (customIcon) {
const data = createFormData();
addBookmark(data);
} else {
addBookmark(formData);
}
setFormData({
...newBookmarkTemplate,
categoryId: formData.categoryId,
isPublic: formData.isPublic,
});
} else {
// update
if (!checkCategory()) return;
if (customIcon) {
const data = createFormData();
updateBookmark(bookmark.id, data, {
prev: bookmark.categoryId,
curr: formData.categoryId,
});
} else {
updateBookmark(bookmark.id, formData, {
prev: bookmark.categoryId,
curr: formData.categoryId,
});
}
modalHandler();
}
setFormData({ ...newBookmarkTemplate, categoryId: formData.categoryId });
setCustomIcon(null);
};
return (
<ModalForm modalHandler={modalHandler} formHandler={formSubmitHandler}>
{/* NAME */}
<InputGroup>
<label htmlFor="name">Bookmark Name</label>
<input
type="text"
name="name"
id="name"
placeholder="Reddit"
required
value={formData.name}
onChange={(e) => inputChangeHandler(e)}
/>
</InputGroup>
{/* URL */}
<InputGroup>
<label htmlFor="url">Bookmark URL</label>
<input
type="text"
name="url"
id="url"
placeholder="reddit.com"
required
value={formData.url}
onChange={(e) => inputChangeHandler(e)}
/>
</InputGroup>
{/* CATEGORY */}
<InputGroup>
<label htmlFor="categoryId">Bookmark Category</label>
<select
name="categoryId"
id="categoryId"
required
onChange={(e) => inputChangeHandler(e, { isNumber: true })}
value={formData.categoryId}
>
<option value={-1}>Select category</option>
{categories.map((category: Category): JSX.Element => {
return (
<option key={category.id} value={category.id}>
{category.name}
</option>
);
})}
</select>
</InputGroup>
{/* ICON */}
{!useCustomIcon ? (
// mdi
<InputGroup>
<label htmlFor="icon">Bookmark Icon (optional)</label>
<input
type="text"
name="icon"
id="icon"
placeholder="book-open-outline"
value={formData.icon}
onChange={(e) => inputChangeHandler(e)}
/>
<span>
Use icon name from MDI or pass a valid URL.
<a href="https://materialdesignicons.com/" target="blank">
{' '}
Click here for reference
</a>
</span>
<span
onClick={() => toggleUseCustomIcon(!useCustomIcon)}
className={classes.Switch}
>
Switch to custom icon upload
</span>
</InputGroup>
) : (
// custom
<InputGroup>
<label htmlFor="icon">Bookmark Icon (optional)</label>
<input
type="file"
name="icon"
id="icon"
onChange={(e) => fileChangeHandler(e)}
accept=".jpg,.jpeg,.png,.svg,.ico"
/>
<span
onClick={() => {
setCustomIcon(null);
toggleUseCustomIcon(!useCustomIcon);
}}
className={classes.Switch}
>
Switch to MDI
</span>
</InputGroup>
)}
{/* VISIBILTY */}
<InputGroup>
<label htmlFor="isPublic">Bookmark visibility</label>
<select
id="isPublic"
name="isPublic"
value={formData.isPublic ? 1 : 0}
onChange={(e) => inputChangeHandler(e, { isBool: true })}
>
<option value={1}>Visible (anyone can access it)</option>
<option value={0}>Hidden (authentication required)</option>
</select>
</InputGroup>
<Button>{bookmark ? 'Update bookmark' : 'Add new bookmark'}</Button>
</ModalForm>
);
};