mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 16:20:03 +00:00
Refactored NotificationManagement
Fixed default template titles
This commit is contained in:
@@ -1,29 +1,14 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import withConfigPage from "Frontend/components/administration/withConfigPage";
|
||||
import * as Yup from 'yup';
|
||||
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
|
||||
import Section from "Frontend/components/general/Section";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Chip,
|
||||
Link,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Textarea,
|
||||
Tooltip,
|
||||
useDisclosure
|
||||
} from "@nextui-org/react";
|
||||
import {Button, Card, Tooltip, useDisclosure} from "@nextui-org/react";
|
||||
import {MessageTemplateEndpoint, NotificationEndpoint} from "Frontend/generated/endpoints";
|
||||
import {toast} from "sonner";
|
||||
import {PaperPlaneRight, Pencil} from "@phosphor-icons/react";
|
||||
import MessageTemplateDto from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/MessageTemplateDto";
|
||||
import TemplateType from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/TemplateType";
|
||||
import {Form, Formik} from "formik";
|
||||
import Input from "Frontend/components/general/Input";
|
||||
import SendTestNotificationModal from "Frontend/components/administration/notifications/SendTestNotificationModal";
|
||||
import EditTemplateModal from "Frontend/components/administration/notifications/EditTemplateModel";
|
||||
|
||||
function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||
|
||||
@@ -31,8 +16,6 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||
const testNotificationModal = useDisclosure();
|
||||
const [availableTemplates, setAvailableTemplates] = useState<MessageTemplateDto[]>([]);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<MessageTemplateDto | null>(null);
|
||||
const [templateContent, setTemplateContent] = useState<string>("");
|
||||
const [defaultPlaceholders, setDefaultPlaceholders] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
MessageTemplateEndpoint.getAll().then((response: any) => {
|
||||
@@ -59,17 +42,6 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||
|
||||
async function openEditor(template: MessageTemplateDto) {
|
||||
setSelectedTemplate(template);
|
||||
|
||||
let templateContent = await MessageTemplateEndpoint.read(template.key, TemplateType.MJML);
|
||||
let defaultPlaceholders = await MessageTemplateEndpoint.getDefaultPlaceholders(TemplateType.MJML);
|
||||
setDefaultPlaceholders(defaultPlaceholders ? defaultPlaceholders as string[] : []);
|
||||
|
||||
if (templateContent === undefined) {
|
||||
toast.error("Can't read template content");
|
||||
return;
|
||||
}
|
||||
|
||||
setTemplateContent(templateContent);
|
||||
editorModal.onOpen();
|
||||
}
|
||||
|
||||
@@ -78,18 +50,6 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||
testNotificationModal.onOpen();
|
||||
}
|
||||
|
||||
async function saveTemplate(template: MessageTemplateDto) {
|
||||
await MessageTemplateEndpoint.save(template.key, TemplateType.MJML, templateContent);
|
||||
}
|
||||
|
||||
function generateValidationSchema(placeholders: string[]) {
|
||||
const shape: { [key: string]: Yup.StringSchema } = {};
|
||||
placeholders.forEach(placeholder => {
|
||||
shape[placeholder] = Yup.string().required(`Placeholder ${placeholder} is required`);
|
||||
});
|
||||
return Yup.object().shape(shape);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row">
|
||||
@@ -144,122 +104,19 @@ function NotificationManagementLayout({getConfig, getConfigs, formik}: any) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal isOpen={editorModal.isOpen} onOpenChange={editorModal.onOpenChange} size="5xl">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
className="flex flex-col gap-1">Edit {selectedTemplate?.name} Template</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="flex flex-row justify-between items-end">
|
||||
<table cellPadding="4rem">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Required placeholders:</td>
|
||||
<td>
|
||||
<div className="flex flex-row gap-2">
|
||||
{selectedTemplate?.availablePlaceholders?.map((placeholder) =>
|
||||
<Chip radius="sm"
|
||||
key={placeholder}
|
||||
color={templateContent.includes(`{${placeholder as string}}`) ? "success" : "danger"}
|
||||
>{placeholder}</Chip>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional placeholders:</td>
|
||||
<td>
|
||||
<div className="flex flex-row gap-2">
|
||||
{defaultPlaceholders.map((placeholder) =>
|
||||
<Chip radius="sm"
|
||||
key={placeholder}
|
||||
color={templateContent.includes(`{${placeholder as string}}`) ? "success" : "default"}
|
||||
>{placeholder}</Chip>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<small className="text-right">Powered by <Link href="https://documentation.mjml.io/"
|
||||
target="_blank">mjml.io</Link>
|
||||
</small>
|
||||
</div>
|
||||
<Textarea
|
||||
size="lg"
|
||||
autoFocus
|
||||
disableAutosize
|
||||
value={templateContent}
|
||||
onChange={(e) => {
|
||||
setTemplateContent(e.target.value)
|
||||
}}
|
||||
classNames={{
|
||||
input: "resize-y min-h-[500px]"
|
||||
}}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button color="primary" onPress={async () => {
|
||||
if (selectedTemplate) {
|
||||
await saveTemplate(selectedTemplate,);
|
||||
toast.success("Template saved")
|
||||
onClose();
|
||||
}
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<EditTemplateModal
|
||||
isOpen={editorModal.isOpen}
|
||||
onOpenChange={editorModal.onOpenChange}
|
||||
selectedTemplate={selectedTemplate}
|
||||
/>
|
||||
|
||||
<Modal isOpen={testNotificationModal.isOpen} onOpenChange={testNotificationModal.onOpenChange} size="3xl">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<Formik
|
||||
initialValues={{}}
|
||||
onSubmit={async (values) => {
|
||||
await NotificationEndpoint.sendTestNotification(selectedTemplate?.key, values);
|
||||
toast.success("Test notification to you has been sent");
|
||||
onClose();
|
||||
}}
|
||||
validationSchema={generateValidationSchema(selectedTemplate?.availablePlaceholders as string[])}
|
||||
>
|
||||
<Form>
|
||||
<ModalHeader className="flex flex-col gap-1">
|
||||
Send {selectedTemplate?.name} Test Message
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className="text-ls font-semibold mb-4">Fill the placeholders of the
|
||||
template</p>
|
||||
{selectedTemplate?.availablePlaceholders?.map((placeholder) =>
|
||||
<Input key={placeholder} label={placeholder} name={placeholder}/>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button color="primary" type="submit">
|
||||
Send
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
</Formik>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<SendTestNotificationModal
|
||||
isOpen={testNotificationModal.isOpen}
|
||||
onOpenChange={testNotificationModal.onOpenChange}
|
||||
selectedTemplate={selectedTemplate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object({});
|
||||
|
||||
export const NotificationManagement = withConfigPage(NotificationManagementLayout, "Notifications", "notifications", validationSchema);
|
||||
export const NotificationManagement = withConfigPage(NotificationManagementLayout, "Notifications", "notifications");
|
||||
@@ -0,0 +1,118 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
Link,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Textarea
|
||||
} from "@nextui-org/react";
|
||||
import {toast} from "sonner";
|
||||
import {MessageTemplateEndpoint} from "Frontend/generated/endpoints";
|
||||
import MessageTemplateDto from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/MessageTemplateDto";
|
||||
import TemplateType from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/TemplateType";
|
||||
|
||||
interface EditTemplateModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: () => void;
|
||||
selectedTemplate: MessageTemplateDto | null;
|
||||
}
|
||||
|
||||
export default function EditTemplateModal({isOpen, onOpenChange, selectedTemplate}: EditTemplateModalProps) {
|
||||
const [templateContent, setTemplateContent] = useState<string>("");
|
||||
const [defaultPlaceholders, setDefaultPlaceholders] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
MessageTemplateEndpoint.read(selectedTemplate?.key as string, TemplateType.MJML).then((response: any) => {
|
||||
setTemplateContent(response as string);
|
||||
});
|
||||
|
||||
MessageTemplateEndpoint.getDefaultPlaceholders(TemplateType.MJML).then((response: any) => {
|
||||
setDefaultPlaceholders(response as string[]);
|
||||
});
|
||||
}, [isOpen]);
|
||||
|
||||
async function saveTemplate(template: MessageTemplateDto) {
|
||||
await MessageTemplateEndpoint.save(template.key, TemplateType.MJML, templateContent);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="5xl">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader
|
||||
className="flex flex-col gap-1">Edit {selectedTemplate?.name} Template</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="flex flex-row justify-between items-end">
|
||||
<table cellPadding="4rem">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Required placeholders:</td>
|
||||
<td>
|
||||
<div className="flex flex-row gap-2">
|
||||
{selectedTemplate?.availablePlaceholders?.map((placeholder) =>
|
||||
<Chip radius="sm"
|
||||
key={placeholder}
|
||||
color={templateContent.includes(`{${placeholder as string}}`) ? "success" : "danger"}
|
||||
>{placeholder}</Chip>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional placeholders:</td>
|
||||
<td>
|
||||
<div className="flex flex-row gap-2">
|
||||
{defaultPlaceholders.map((placeholder) =>
|
||||
<Chip radius="sm"
|
||||
key={placeholder}
|
||||
color={templateContent.includes(`{${placeholder as string}}`) ? "success" : "default"}
|
||||
>{placeholder}</Chip>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<small className="text-right">Powered by <Link href="https://documentation.mjml.io/"
|
||||
target="_blank">mjml.io</Link></small>
|
||||
</div>
|
||||
<Textarea
|
||||
size="lg"
|
||||
autoFocus
|
||||
disableAutosize
|
||||
value={templateContent}
|
||||
onChange={(e) => {
|
||||
setTemplateContent(e.target.value)
|
||||
}}
|
||||
classNames={{
|
||||
input: "resize-y min-h-[500px]"
|
||||
}}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button color="primary" onPress={async () => {
|
||||
if (selectedTemplate) {
|
||||
await saveTemplate(selectedTemplate);
|
||||
toast.success("Template saved");
|
||||
onClose();
|
||||
}
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import React from "react";
|
||||
import {Form, Formik} from "formik";
|
||||
import {Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@nextui-org/react";
|
||||
import {toast} from "sonner";
|
||||
import Input from "Frontend/components/general/Input";
|
||||
import {NotificationEndpoint} from "Frontend/generated/endpoints";
|
||||
import * as Yup from "yup";
|
||||
import MessageTemplateDto from "Frontend/generated/de/grimsi/gameyfin/notifications/templates/MessageTemplateDto";
|
||||
|
||||
interface SendTestNotificationModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: () => void;
|
||||
selectedTemplate: MessageTemplateDto | null;
|
||||
}
|
||||
|
||||
export default function SendTestNotificationModal({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
selectedTemplate
|
||||
}: SendTestNotificationModalProps) {
|
||||
|
||||
function generateValidationSchema(placeholders: string[]) {
|
||||
const shape: { [key: string]: Yup.StringSchema } = {};
|
||||
placeholders.forEach(placeholder => {
|
||||
shape[placeholder] = Yup.string().required(`Placeholder ${placeholder} is required`);
|
||||
});
|
||||
return Yup.object().shape(shape);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="3xl">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<Formik
|
||||
initialValues={{}}
|
||||
onSubmit={async (values) => {
|
||||
await NotificationEndpoint.sendTestNotification(selectedTemplate?.key, values);
|
||||
toast.success("Test notification to you has been sent");
|
||||
onClose();
|
||||
}}
|
||||
validationSchema={generateValidationSchema(selectedTemplate?.availablePlaceholders as string[])}
|
||||
>
|
||||
<Form>
|
||||
<ModalHeader className="flex flex-col gap-1">
|
||||
Send {selectedTemplate?.name} Test Message
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className="text-ls font-semibold mb-4">Fill the placeholders of the template</p>
|
||||
{selectedTemplate?.availablePlaceholders?.map((placeholder) =>
|
||||
<Input key={placeholder} label={placeholder} name={placeholder}/>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button color="primary" type="submit">
|
||||
Send
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
</Formik>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user