AppForm.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import { useAtom } from 'jotai';
  2. import { ChangeEvent, SyntheticEvent, useEffect, useState } from 'react';
  3. import { NewApp } from '../../../interfaces';
  4. import { appInUpdateAtom, useAddApp, useUpdateApp } from '../../../state/app';
  5. import { useCreateNotification } from '../../../state/notification';
  6. import { inputHandler, newAppTemplate } from '../../../utility';
  7. import { Button, InputGroup, ModalForm } from '../../UI';
  8. import classes from './AppForm.module.css';
  9. interface Props {
  10. modalHandler: () => void;
  11. }
  12. export const AppForm = ({ modalHandler }: Props): JSX.Element => {
  13. const createNotification = useCreateNotification();
  14. const [appInUpdate, setEditApp] = useAtom(appInUpdateAtom);
  15. const addApp = useAddApp();
  16. const updateApp = useUpdateApp();
  17. const [useCustomIcon, toggleUseCustomIcon] = useState<boolean>(false);
  18. const [customIcon, setCustomIcon] = useState<File | null>(null);
  19. const [formData, setFormData] = useState<NewApp>(newAppTemplate);
  20. useEffect(() => {
  21. if (appInUpdate) {
  22. setFormData({
  23. ...appInUpdate,
  24. });
  25. } else {
  26. setFormData(newAppTemplate);
  27. }
  28. }, [appInUpdate]);
  29. const inputChangeHandler = (
  30. e: ChangeEvent<HTMLInputElement | HTMLSelectElement>,
  31. options?: { isNumber?: boolean; isBool?: boolean }
  32. ) => {
  33. inputHandler<NewApp>({
  34. e,
  35. options,
  36. setStateHandler: setFormData,
  37. state: formData,
  38. });
  39. };
  40. const fileChangeHandler = (e: ChangeEvent<HTMLInputElement>): void => {
  41. if (e.target.files) {
  42. setCustomIcon(e.target.files[0]);
  43. }
  44. };
  45. const formSubmitHandler = (e: SyntheticEvent<HTMLFormElement>): void => {
  46. e.preventDefault();
  47. for (let field of ['name', 'url', 'icon'] as const) {
  48. if (/^ +$/.test(formData[field])) {
  49. createNotification({
  50. title: 'Error',
  51. message: `Field cannot be empty: ${field}`,
  52. });
  53. return;
  54. }
  55. }
  56. const createFormData = (): FormData => {
  57. const data = new FormData();
  58. if (customIcon) {
  59. data.append('icon', customIcon);
  60. }
  61. data.append('name', formData.name);
  62. data.append('description', formData.description);
  63. data.append('url', formData.url);
  64. data.append('isPublic', `${formData.isPublic ? 1 : 0}`);
  65. return data;
  66. };
  67. if (!appInUpdate) {
  68. if (customIcon) {
  69. const data = createFormData();
  70. addApp(data);
  71. } else {
  72. addApp(formData);
  73. }
  74. } else {
  75. if (customIcon) {
  76. const data = createFormData();
  77. updateApp(appInUpdate.id, data);
  78. } else {
  79. updateApp(appInUpdate.id, formData);
  80. modalHandler();
  81. }
  82. }
  83. setFormData(newAppTemplate);
  84. setEditApp(null);
  85. };
  86. return (
  87. <ModalForm modalHandler={modalHandler} formHandler={formSubmitHandler}>
  88. {/* NAME */}
  89. <InputGroup>
  90. <label htmlFor="name">App name</label>
  91. <input
  92. type="text"
  93. name="name"
  94. id="name"
  95. placeholder="Bookstack"
  96. required
  97. value={formData.name}
  98. onChange={(e) => inputChangeHandler(e)}
  99. />
  100. </InputGroup>
  101. {/* URL */}
  102. <InputGroup>
  103. <label htmlFor="url">App URL</label>
  104. <input
  105. type="text"
  106. name="url"
  107. id="url"
  108. placeholder="bookstack.example.com"
  109. required
  110. value={formData.url}
  111. onChange={(e) => inputChangeHandler(e)}
  112. />
  113. </InputGroup>
  114. {/* DESCRIPTION */}
  115. <InputGroup>
  116. <label htmlFor="description">App description</label>
  117. <input
  118. type="text"
  119. name="description"
  120. id="description"
  121. placeholder="My self-hosted app"
  122. value={formData.description}
  123. onChange={(e) => inputChangeHandler(e)}
  124. />
  125. <span>
  126. Optional - If description is not set, app URL will be displayed
  127. </span>
  128. </InputGroup>
  129. {/* ICON */}
  130. {!useCustomIcon ? (
  131. // use mdi icon
  132. <InputGroup>
  133. <label htmlFor="icon">App icon</label>
  134. <input
  135. type="text"
  136. name="icon"
  137. id="icon"
  138. placeholder="book-open-outline"
  139. required
  140. value={formData.icon}
  141. onChange={(e) => inputChangeHandler(e)}
  142. />
  143. <span>
  144. Use icon name from{' '}
  145. <a href="https://materialdesignicons.com/" target="blank">
  146. MDI
  147. </a>
  148. , icon name from{' '}
  149. <a
  150. href="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/"
  151. target="_blank"
  152. rel="noreferrer"
  153. >
  154. dashboard-icons
  155. </a>
  156. , or pass a valid URL.
  157. </span>
  158. <span
  159. onClick={() => toggleUseCustomIcon(!useCustomIcon)}
  160. className={classes.Switch}
  161. >
  162. Switch to custom icon upload
  163. </span>
  164. </InputGroup>
  165. ) : (
  166. // upload custom icon
  167. <InputGroup>
  168. <label htmlFor="icon">App Icon</label>
  169. <input
  170. type="file"
  171. name="icon"
  172. id="icon"
  173. required
  174. onChange={(e) => fileChangeHandler(e)}
  175. accept=".jpg,.jpeg,.png,.svg,.ico"
  176. />
  177. <span
  178. onClick={() => {
  179. setCustomIcon(null);
  180. toggleUseCustomIcon(!useCustomIcon);
  181. }}
  182. className={classes.Switch}
  183. >
  184. Switch to icon name
  185. </span>
  186. </InputGroup>
  187. )}
  188. {/* VISIBILITY */}
  189. <InputGroup>
  190. <label htmlFor="isPublic">App visibility</label>
  191. <select
  192. id="isPublic"
  193. name="isPublic"
  194. value={formData.isPublic ? 1 : 0}
  195. onChange={(e) => inputChangeHandler(e, { isBool: true })}
  196. >
  197. <option value={1}>Visible (anyone can access it)</option>
  198. <option value={0}>Hidden (authentication required)</option>
  199. </select>
  200. </InputGroup>
  201. {!appInUpdate ? (
  202. <Button>Add new application</Button>
  203. ) : (
  204. <Button>Update application</Button>
  205. )}
  206. </ModalForm>
  207. );
  208. };