diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/src/components/ModalEditor.jsx b/src/components/ModalEditor.jsx
new file mode 100644
index 0000000..4c6b6a1
--- /dev/null
+++ b/src/components/ModalEditor.jsx
@@ -0,0 +1,25 @@
+import { Modal, Text } from '@mantine/core';
+
+const ModalEditor = ({ title, opened, handlerClose }) => {
+
+ return (
+
+
+
+
+
+
+ {title}
+
+
+
+
+
+ {content}
+
+
+
+ );
+};
+
+export default ModalEditor;
diff --git a/src/components/media-player.jsx b/src/components/media-player.jsx
index 7300773..1e273cc 100644
--- a/src/components/media-player.jsx
+++ b/src/components/media-player.jsx
@@ -1,8 +1,8 @@
import { Image } from '@mantine/core';
+import { isImage } from '../tools/fileUtil';
const MediaPlayer = ({ file, fileId, shouldContain }) => {
- const isImage = () => file.type.split('/')[0] === 'image';
- return isImage() ? (
+ return isImage(file.type) ? (
{
const handleSubmit = () => {
setIsLoading(true);
const formData = new FormData();
- const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB chunks
files.forEach((file) => formData.append(`${file.name}`, file, file.name));
diff --git a/src/pages/playlist/content.jsx b/src/pages/playlist/content.jsx
index d7934cd..6c1145b 100644
--- a/src/pages/playlist/content.jsx
+++ b/src/pages/playlist/content.jsx
@@ -12,6 +12,9 @@ import { Perm, checkPerm } from '../../tools/grant-access';
import { useAuth } from '../../tools/auth-provider';
import { parseTime } from '../../tools/timeUtil';
import MediaPlayer from '../../components/media-player';
+import { isVideo } from '../../tools/fileUtil';
+
+const DEFAULT_FILE_TIME = 2;
const Content = ({ form, playlistId, playlist }) => {
const [fileSelector, setFileSelector] = useState(false);
@@ -36,7 +39,7 @@ const Content = ({ form, playlistId, playlist }) => {
max_position++;
file.position = max_position;
- file.seconds = 10;
+ file.seconds = DEFAULT_FILE_TIME;
const index = form.values.files.length;
form.insertListItem('files', file);
API.playlists
@@ -154,10 +157,18 @@ const Content = ({ form, playlistId, playlist }) => {
handleChangeSeconds(secs, index),
+ })}
hideControls
- description="Seconds to display"
- value={form.getInputProps(`files.${index}.seconds`).value}
- onChange={(secs) => handleChangeSeconds(secs, index)}
+ label="Seconds to display"
error={
form.getInputProps(`files.${index}.seconds`).errors && 'This field is required'
}
diff --git a/src/pages/roles/create.jsx b/src/pages/roles/create.jsx
new file mode 100644
index 0000000..dfdff03
--- /dev/null
+++ b/src/pages/roles/create.jsx
@@ -0,0 +1,38 @@
+import { Modal, Text } from '@mantine/core';
+import API from '../../services/api';
+import RoleViewEditor from './role-view-editor';
+
+const ModalCreateRole = ({ opened, handler, addRole, item }) => {
+ const validate = (role) => {
+ if (role) {
+ addRole(role);
+ }
+ handler();
+ };
+
+ return (
+
+
+
+
+
+
+ Create Role
+
+
+
+
+
+ validate(role)}
+ />
+
+
+
+ );
+};
+
+export default ModalCreateRole;
diff --git a/src/pages/roles/role-selector.jsx b/src/pages/roles/role-selector.jsx
new file mode 100644
index 0000000..564e0ff
--- /dev/null
+++ b/src/pages/roles/role-selector.jsx
@@ -0,0 +1,82 @@
+import { MultiSelect } from '@mantine/core';
+import { useEffect, useState } from 'react';
+import setNotification from '../errors/error-notification';
+import API from '../../services/api';
+import { Perm, checkPerm } from '../../tools/grant-access';
+import { useAuth } from '../../tools/auth-provider';
+import ModalCreateRole from './create';
+
+const RoleSelector = ({ defaultRoles, label, value, setValue }) => {
+ const [data, setData] = useState([]);
+ const [search, setSearch] = useState();
+ const [showCreateRole, setShowCreateRole] = useState(false);
+ const toggleCreateRole = () => setShowCreateRole(!showCreateRole);
+ const [query, setQuery] = useState('');
+
+ const { user } = useAuth();
+ const canCreateRole = checkPerm(Perm.CREATE_ROLE, user);
+
+ const addRoles = (roles) => {
+ if (!roles) return;
+ for (let i = 0; i < roles.length; i++) {
+ const role = roles[i];
+ if (!data.find((r) => r.id === role.id)) {
+ role.label = role.name;
+ role.value = role.id.toString();
+ setData((prev) => [...prev, role]);
+ }
+ }
+ };
+
+ useEffect(() => {
+ API.roles
+ .search(search)
+ .then((res) => {
+ if (res.status === 200) {
+ addRoles(res.data);
+ } else {
+ setNotification(true, res);
+ }
+ })
+ .catch((err) => {
+ setNotification(true, err);
+ });
+ // eslint-disable-next-line
+ }, [search]);
+
+ useEffect(() => {
+ addRoles(defaultRoles);
+ // eslint-disable-next-line
+ }, [defaultRoles]);
+
+ return (
+ <>
+ `+ Create ${query}`}
+ onCreate={(query) => {
+ setQuery(query);
+ setShowCreateRole(true);
+ }}
+ />
+ {canCreateRole && (
+ addRoles([role])}
+ handler={toggleCreateRole}
+ />
+ )}
+ >
+ );
+};
+
+export default RoleSelector;
diff --git a/src/pages/roles/role-view-editor.jsx b/src/pages/roles/role-view-editor.jsx
new file mode 100644
index 0000000..1928d38
--- /dev/null
+++ b/src/pages/roles/role-view-editor.jsx
@@ -0,0 +1,64 @@
+import { Button, TextInput, Group, Stack } from '@mantine/core';
+import { useForm, isNotEmpty } from '@mantine/form';
+import { useEffect, useState } from 'react';
+import setNotification from '../errors/error-notification';
+
+const RoleViewEditor = ({ item, handler, buttonText, APICall }) => {
+ const handleClose = (role) => {
+ form.reset();
+ handler(role);
+ };
+
+ const [isLoading, setIsLoading] = useState(false);
+
+ const form = useForm({
+ initialValues: {
+ name: item?.name ?? '',
+ },
+ validate: {
+ name: isNotEmpty('Name is required'),
+ },
+ });
+
+ useEffect(() => {
+ form.setFieldValue('name', item?.name);
+ return () => {};
+ }, [item]);
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ if (form.validate().hasErrors) return;
+ try {
+ setIsLoading(true);
+ if (item?.id) {
+ const res = await APICall(item?.id, { name: form.values.name });
+ handleClose(res.data);
+ } else {
+ const res = await APICall({ name: form.values.name });
+ handleClose(res.data);
+ }
+ setIsLoading(false);
+ } catch (err) {
+ setIsLoading(false);
+ setNotification(true, err);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default RoleViewEditor;
diff --git a/src/tools/fileUtil.js b/src/tools/fileUtil.js
new file mode 100644
index 0000000..e26e92c
--- /dev/null
+++ b/src/tools/fileUtil.js
@@ -0,0 +1,10 @@
+export const isImage = (type) => {
+ if (!type) return false;
+ return type.split('/')[0] === 'image';
+};
+
+export const isVideo = (type) => {
+ if (!type) return false;
+ return type.split('/')[0] === 'video';
+};
+