) => {
+ if (e.target.files) {
+ const file = e.target.files[0];
+ console.log(file);
+ uploadFile(file).then((ignore) => {
+ setConfig((oldConfig) => {
+ const alerts = config.get(id)?.alerts;
+ let updatedAlerts = alerts?.at(selected);
+ updatedAlerts.audio = file.name;
+ alerts[selected] = updatedAlerts;
+ const newConfig = new Map(oldConfig).set(id, {
+ alerts: alerts,
+ });
+ return newConfig;
+ });
+ onChange.call({});
+ });
+ }
+ };
+
+ function update(key: string, value: any) {
+ setConfig((oldConfig) => {
+ let updatedProperties = oldConfig
+ .get(id)
+ ?.alerts?.at(selected)
+ ?.properties.map((it) => {
+ if (it.name === key) {
+ it.value = value;
+ }
+ return it;
+ });
+ let updatedAlerts = oldConfig
+ .get(id)
+ ?.alerts?.map((it, number: number) => {
+ if (number === selected && updatedProperties) {
+ it.properties = updatedProperties;
+ }
+ return it;
+ });
+ return new Map(oldConfig).set(id, { alerts: updatedAlerts });
+ });
+ onChange.call({});
+ }
+
+ function updateTrigger(value: string) {
+ const amount = parseInt(value);
+ setConfig((oldConfig) => {
+ const alerts = oldConfig.get(id)?.alerts;
+ if (alerts) {
+ const updatedAlert = alerts.at(selected);
+ updatedAlert.trigger.amount = amount;
+ alerts[selected] = updatedAlert;
+ }
+ return new Map(oldConfig);
+ });
+ onChange.call({});
+ }
+
+ const previews = () => (
+
+ {config.get(id)?.alerts?.map((alert, number: number) => (
+
+ {alert.image && (
+
{
+ setTab("trigger");
+ setSelected(selected === number ? -2 : number);
+ }}
+ />
+ )}
+ {!alert.image && (
+
{
+ setTab("trigger");
+ setSelected(selected === number ? -2 : number);
+ }}
+ className={`payment-alert-image-preview ${
+ selected === number ? "selected" : ""
+ }`}
+ >
+ )}
+ {selected === number && (
+
deleteAlert()} className="alert-delete-button">
+ delete
+
+ )}
+
+ ))}
+
+
addDefaultAlert()}
+ >
+ add_box
+
+
+
+ );
+
+ const tabs = () => (
+
+
setTab("trigger")}
+ >
+ условие
+
+
setTab("image")}
+ >
+ изображение
+
+
setTab("sound")}
+ >
+ аудио
+
+
setTab("voice")}
+ >
+ голос
+
+
setTab("header")}
+ >
+ заголовок
+
+
setTab("message")}
+ >
+ сообщение
+
+
+ );
+
+ const tabContent = () => (
+ <>
+ {config
+ .get(id)
+ ?.alerts?.at(selected)
+ ?.properties.filter((it) => it.tab === tab)
+ .map((prop) => (
+
+
{prop.displayName}
+ {(!prop.type || prop.type == "string") && (
+
update(prop.name, e.target.value)}
+ />
+ )}
+ {prop.type === "fontselect" && (
+
+ )}
+ {prop.type === "color" && (
+
update(prop.name, value)}
+ />
+ )}
+ {prop.type === "text" && (
+ <>
+
+ ))}
+ {"trigger" === tab && (
+ <>
+
+
Сумма
+
updateTrigger(e.target.value)}
+ />
+
+ >
+ )}
+ {"image" === tab && (
+
+ )}
+ {"sound" === tab && (
+ <>
+
+
+ playAudio(config.get(id)?.alerts?.at(selected).audio)
+ }
+ className="material-symbols-sharp"
+ >
+ play_circle
+
+
+
+ >
+ )}
+ >
+ );
+
+ return (
+ <>
+ {previews()}
+ {selected > -1 && (
+
+ {tabs()}
+ {tabContent()}
+
+ )}
+ >
+ );
+}
diff --git a/src/components/DonatersTopList/DonatersTopList.css b/src/components/DonatersTopList/DonatersTopList.css
new file mode 100644
index 0000000..f0d495b
--- /dev/null
+++ b/src/components/DonatersTopList/DonatersTopList.css
@@ -0,0 +1,24 @@
+.donaters-list {
+ color: white;
+ font-size: 28px;
+ position: relative;
+ animation: linear dotleft 60s infinite;
+ white-space: nowrap;
+}
+
+.donaters-top {
+ text-align: center;
+}
+
+.donaters-list .donater {
+ margin-right: 30px;
+}
+
+@keyframes dotleft {
+ 0% {
+ left: 100%;
+ }
+ 100% {
+ left: -100%;
+ }
+}
diff --git a/src/components/DonatersTopList/DonatersTopList.tsx b/src/components/DonatersTopList/DonatersTopList.tsx
new file mode 100644
index 0000000..c23e18e
--- /dev/null
+++ b/src/components/DonatersTopList/DonatersTopList.tsx
@@ -0,0 +1,91 @@
+import React, { useEffect } from "react";
+import { useState } from "react";
+import axios from "axios";
+import "./DonatersTopList.css";
+import { useLoaderData, useNavigate } from "react-router";
+import { findSetting } from "../utils";
+import { setupCommandListener, subscribe } from "../../socket";
+import FontImport from "../FontImport/FontImport";
+
+export default function DonatersTopList({}: {}) {
+ const [donaters, setDonaters] = useState(new Map());
+ const { recipientId, settings, conf, widgetId } = useLoaderData();
+ const navigate = useNavigate();
+
+ const period = findSetting(settings, "period", "month");
+ const topsize = findSetting(settings, "topsize", 3);
+
+ function updateDonaters() {
+ axios
+ .get(
+ `${process.env.REACT_APP_API_ENDPOINT}/recipient/${recipientId}/donaters?period=${period}`,
+ )
+ .then((response) => response.data)
+ .then((data) => {
+ const map = new Map();
+ Object.keys(data).forEach((key) => map.set(key, data[key]));
+ const sortedMap = new Map(
+ [...map.entries()].sort((a, b) => b[1].amount - a[1].amount),
+ );
+ setDonaters(sortedMap);
+ });
+ }
+
+ useEffect(() => {
+ subscribe(widgetId, conf.topic.alerts, (message) => {
+ updateDonaters();
+ message.ack();
+ });
+ setupCommandListener(widgetId, () => navigate(0));
+ updateDonaters();
+ }, [recipientId]);
+
+ const type = findSetting(settings, "type", "All");
+ const fontSize = findSetting(settings, "fontSize", "24px");
+ const font = findSetting(settings, "font", "Roboto");
+ const color = findSetting(settings, "color", "white");
+ const textStyle = {
+ fontSize: fontSize ? fontSize + "px" : "unset",
+ lineHeight: fontSize ? fontSize + "px" : "unset",
+ fontFamily: font ? font : "unset",
+ color: color,
+ };
+
+ return (
+ <>
+
+ {"All" === type && (
+
+ {donaters &&
+ donaters.size > 0 &&
+ Array.from(donaters.keys()).map((donater) => (
+
+ {donater} - {donaters.get(donater).amount}{" "}
+ {donaters.get(donater).currency}{" "}
+
+ ))}
+
+ )}
+ {"Top" === type && (
+ <>
+
+ Топ {topsize} {period === "day" && дня}
+ {period === "month" && месяца}
+
+
+ {donaters &&
+ donaters.size > 0 &&
+ Array.from(donaters.keys())
+ .slice(0, topsize)
+ .map((donater) => (
+
+ {donater} - {donaters.get(donater).amount}{" "}
+ {donaters.get(donater).currency}{" "}
+
+ ))}
+
+ >
+ )}
+ >
+ );
+}
diff --git a/src/components/DonationTimer/DonationTimer.css b/src/components/DonationTimer/DonationTimer.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/DonationTimer/DonationTimer.tsx b/src/components/DonationTimer/DonationTimer.tsx
new file mode 100644
index 0000000..0c8a48a
--- /dev/null
+++ b/src/components/DonationTimer/DonationTimer.tsx
@@ -0,0 +1,87 @@
+import React from "react";
+import { useRef, useEffect, useState } from "react";
+import axios from "axios";
+import "./DonationTimer.css";
+import { useLoaderData, useNavigate } from "react-router";
+import { findSetting } from "../utils";
+import { setupCommandListener, subscribe } from "../../socket";
+import FontImport from "../FontImport/FontImport";
+import { log } from "../../logging";
+
+export default function DonationTimer({}: {}) {
+ const { recipientId, settings, conf, widgetId } = useLoaderData();
+ const navigate = useNavigate();
+ const [lastDonationTime, setLastDonationTime] = useState(null);
+ const [time, setTime] = useState("");
+
+ useEffect(() => {
+ updateDonationTime();
+
+ subscribe(widgetId, conf.topic.alerts, (message) => {
+ updateDonationTime();
+ message.ack();
+ });
+ setupCommandListener(widgetId, () => navigate(0));
+ }, [recipientId]);
+
+ useEffect(() => {
+ const intervalId = setInterval(() => {
+ if (!lastDonationTime) {
+ return;
+ }
+ const now = Date.now();
+ console.log(now);
+ const paymentDate = new Date(lastDonationTime);
+ console.log(paymentDate);
+ const difference = now - paymentDate.getTime();
+ console.log(difference);
+ const days = Math.floor(difference / (24 * 36e5));
+ const hours = Math.floor((difference % (24 * 36e5)) / 36e5);
+ const minutes = Math.floor((difference % 36e5) / 60000);
+ const seconds = Math.floor((difference % 60000) / 1000);
+ setTime(
+ `${days > 0 ? days + "D " : ""}${hours < 10 ? "0" + hours : hours}:${
+ minutes < 10 ? "0" + minutes : minutes
+ }:${seconds < 10 ? "0" + seconds : seconds}`,
+ );
+ }, 1000);
+ return () => clearInterval(intervalId);
+ }, [lastDonationTime]);
+
+ function updateDonationTime() {
+ if (findSetting(settings, "resetOnLoad", false)) {
+ setLastDonationTime(Date.now());
+ return;
+ }
+ axios
+ .get(
+ `${process.env.REACT_APP_API_ENDPOINT}/payment?recipientId=${recipientId}`,
+ )
+ .then((response) => response.data)
+ .then((data) => {
+ if (data.length > 0) {
+ log.debug(data[0].authorizationTimestamp);
+ setLastDonationTime(data[0].authorizationTimestamp);
+ }
+ });
+ }
+
+ const fontSize = findSetting(settings, "fontSize", "24px");
+ const font = findSetting(settings, "font", "Roboto");
+ const text = findSetting(settings, "text", "Без донатов уже