diff --git a/reader/package.json b/reader/package.json
index fb82d0e..f1f7390 100644
--- a/reader/package.json
+++ b/reader/package.json
@@ -15,6 +15,7 @@
"marked": "^15.0.12",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-error-boundary": "^6.0.0",
"react-pdf": "^9.2.1",
"react-router": "^7.5.0",
"react-toastify": "^11.0.5",
@@ -34,5 +35,11 @@
"prettier": "^3.5.3",
"vite": "^6.2.0"
},
- "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b"
+ "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
+ "pnpm": {
+ "onlyBuiltDependencies": [
+ "canvas",
+ "esbuild"
+ ]
+ }
}
diff --git a/reader/pnpm-lock.yaml b/reader/pnpm-lock.yaml
index 30bf372..f381628 100644
--- a/reader/pnpm-lock.yaml
+++ b/reader/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.1.0(react@19.1.0)
+ react-error-boundary:
+ specifier: ^6.0.0
+ version: 6.0.0(react@19.1.0)
react-pdf:
specifier: ^9.2.1
version: 9.2.1(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -146,6 +149,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/runtime@7.27.6':
+ resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.27.0':
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
engines: {node: '>=6.9.0'}
@@ -1244,6 +1251,11 @@ packages:
peerDependencies:
react: ^19.1.0
+ react-error-boundary@6.0.0:
+ resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==}
+ peerDependencies:
+ react: '>=16.13.1'
+
react-pdf@9.2.1:
resolution: {integrity: sha512-AJt0lAIkItWEZRA5d/mO+Om4nPCuTiQ0saA+qItO967DTjmGjnhmF+Bi2tL286mOTfBlF5CyLzJ35KTMaDoH+A==}
peerDependencies:
@@ -1584,6 +1596,8 @@ snapshots:
'@babel/core': 7.26.10
'@babel/helper-plugin-utils': 7.26.5
+ '@babel/runtime@7.27.6': {}
+
'@babel/template@7.27.0':
dependencies:
'@babel/code-frame': 7.26.2
@@ -2560,6 +2574,11 @@ snapshots:
react: 19.1.0
scheduler: 0.26.0
+ react-error-boundary@6.0.0(react@19.1.0):
+ dependencies:
+ '@babel/runtime': 7.27.6
+ react: 19.1.0
+
react-pdf@9.2.1(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
clsx: 2.1.1
diff --git a/reader/src/App.jsx b/reader/src/App.jsx
index abd0978..f9e334d 100644
--- a/reader/src/App.jsx
+++ b/reader/src/App.jsx
@@ -1,4 +1,4 @@
-import { StrictMode, Fragment, useLayoutEffect, useRef, useState, useEffect, useMemo } from "react";
+import { Fragment, useLayoutEffect, useRef, useState, useEffect, useMemo } from "react";
import {
Navigate,
useSearchParams,
@@ -12,7 +12,7 @@ import { marked } from "marked";
import { resourcesInstance } from "./api.js";
import { useStore } from "./store.js";
import { useTopic, useTopicSyncParams, useTopicsAround } from "./hooks.js";
-import { ToastContainer, toast } from "react-toastify";
+import { ToastContainer } from "react-toastify";
import {
ArrowBackIcon,
ArrowForwardIcon,
@@ -22,13 +22,11 @@ import {
WidthIcon,
EllipsisIcon,
CloseIcon,
- JustifyTextIcon,
TextIncreaseIcon,
TextDecreaseIcon,
VRuleIcon,
} from "./icons/Icons";
import ResourcePage from "./ResourcePage.jsx";
-import { ViewPage, EditViewPage } from "./ViewPage.jsx";
const DIVIDER_AT = 16;
@@ -49,8 +47,6 @@ export function App() {
} />
} />
- } />
- } />
@@ -58,13 +54,6 @@ export function App() {
);
}
-function myToast(message) {
- return toast.info(message, {
- className: "border border-green-700",
- style: { backgroundColor: "oklch(98.7% 0.022 95.277)", color: "black" },
- });
-}
-
function LoadingWrapper({ children }) {
const isLoading = useStore((state) => state.isLoading);
diff --git a/reader/src/ViewPage.jsx b/reader/src/ViewPage.jsx
deleted file mode 100644
index 0867b2d..0000000
--- a/reader/src/ViewPage.jsx
+++ /dev/null
@@ -1,338 +0,0 @@
-import { useState, useEffect, useMemo, useRef } from "react";
-import io from "socket.io-client";
-import { useStore } from "./store.js";
-import { marked } from "marked";
-import { TextIncreaseIcon, TextDecreaseIcon, HeartIcon } from "./icons/Icons.jsx";
-
-const socket = io(import.meta.env.VITE_API_BASE_URL);
-
-export function ViewPage() {
- const iframeRef = useRef(null);
- const config = useStore((state) => state.config);
- const changeConfig = useStore((state) => state.changeConfig);
-
- const [isConnected, setIsConnected] = useState(false);
- const [data, setData] = useState(null);
- const [selectedOptionId, setSelectedOptionId] = useState(null);
- const [selectedIdx, setSelectedIdx] = useState(0);
-
- const options = useMemo(() => {
- if (!data || data.options.length === 0) {
- return [];
- }
- return data.options.map((option) => {
- let fileContent = option.text || "**No Data!**";
- fileContent = marked.parse(fileContent);
-
- fileContent = `
-
-
-
-
-
-
-
-
- ${fileContent}
-
-
- `;
- return { ...option, text: fileContent };
- });
- }, [data]);
-
- useEffect(() => {
- socket.on("connect", () => setIsConnected(true));
- socket.on("disconnect", () => setIsConnected(false));
- socket.on("dataChanged", (newData) => {
- setData(newData);
- setSelectedOptionId(null);
- setSelectedIdx(0);
- });
- socket.on("selectedOptionChanged", (newId) => {
- setSelectedOptionId(newId);
- });
- socket.on("lookingAtIdxChanged", (newIdx) => {
- setSelectedIdx(newIdx);
- });
-
- return () => {
- socket.off("connect");
- socket.off("dataChanged");
- socket.off("selectedOptionChanged");
- socket.off("lookingAtIdxChanged");
- socket.off("disconnect");
- };
- }, []);
-
- useEffect(() => {
- if (iframeRef.current && iframeRef.current.contentDocument) {
- iframeRef.current.contentDocument.body.style.zoom = `${config.contentZoomLevel}%`;
- }
- }, [config.contentZoomLevel]);
-
- if (!data || options.length === 0) {
- return (
-
-
- Status:{" "}
-
- {isConnected ? "Connected" : "Disconnected"}
-
-
-
- );
- }
-
- return (
-
-
- {data.title}
-
-
-
-
-
-
- {config.contentZoomLevel}%
-
-
-
-
-
-
-
-
-
-
-
- {options.map((option, optionIdx) => (
-
- ))}
-
-
- );
-}
-function LEditViewPage() {
- const [isConnected, setIsConnected] = useState(false);
-
- const [options, setOptions] = useState([]);
- const [selectedOptionId, setSelectedOptionId] = useState(null);
-
- const topics = useStore((state) => state.topics);
- const [selectedTopicIdx, setSelectedTopicIdx] = useState(0);
- const selectedTopic = topics[selectedTopicIdx];
-
- useEffect(() => {
- socket.on("connect", () => setIsConnected(true));
- socket.on("disconnect", () => setIsConnected(false));
- socket.on("selectedOptionChanged", (id) => setSelectedOptionId(id));
-
- return () => {
- socket.off("connect");
- socket.off("disconnect");
- socket.off("selectedOptionChanged");
- };
- }, []);
-
- function handleSend() {
- const finalV = {
- title: `${selectedTopic.seq}. ${selectedTopic.title}`,
- options,
- };
- socket.emit("setData", finalV);
- setSelectedOptionId(null);
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {options.map((option) => (
-
-
-
- setOptions((prev) =>
- prev.map((opt) =>
- opt.id === option.id ? { ...opt, title: event.target.value } : opt,
- ),
- )
- }
- />
-
- {selectedOptionId === option.id && YES!}
-
-
- ))}
-
-
- );
-}
-
-export function EditViewPage() {
- return (
-
-
-
- );
-}
-
-function LoadingWrapper({ children }) {
- const isLoading = useStore((state) => state.isLoading);
-
- if (isLoading) {
- return null;
- }
-
- return children;
-}
-
-function Layout() {
- const config = useStore((state) => state.config);
-
- return (
-
- {config.displayTitle && (
-
- {topic ? `${topic.seq}: ${topic.title}` : `Конспект за Държавен Изпит`}
-
- )}
-
-
- );
-}
diff --git a/reader/src/main.jsx b/reader/src/main.jsx
index fd99551..6ac5b5c 100644
--- a/reader/src/main.jsx
+++ b/reader/src/main.jsx
@@ -2,9 +2,17 @@ import "./index.css";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
+import { apiInstance } from "./api";
+import { ErrorBoundary } from "react-error-boundary";
+
+const logError = async (error, info) => {
+ await apiInstance.post("/log-error", { error: { error: error.toString(), info } });
+};
createRoot(document.getElementById("root")).render(
-
+ Something went wrong} onError={logError}>
+
+
,
);
diff --git a/reader/vite.config.js b/reader/vite.config.js
index 7b6e394..c826207 100644
--- a/reader/vite.config.js
+++ b/reader/vite.config.js
@@ -6,6 +6,6 @@ import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss(), react()],
server: {
- allowedHosts: ["personal.orb.local"],
+ allowedHosts: ["personal.utm.local", "personal.workstation.lan"],
},
});
diff --git a/resource-provider/server.js b/resource-provider/server.js
index ef710a9..3e6ff80 100644
--- a/resource-provider/server.js
+++ b/resource-provider/server.js
@@ -20,18 +20,6 @@ getStructure({ refresh: true }).catch(console.error);
const app = express();
const server = createServer(app);
-// Socket.IO setup with CORS
-const ORIGIN_URL =
- process.env.NODE_ENV === "production"
- ? "https://med.tomastm.com"
- : "http://localhost:5173";
-const io = new Server(server, {
- cors: {
- origin: ORIGIN_URL,
- methods: ["GET", "POST"],
- },
-});
-
const corsOptions = {
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
@@ -57,51 +45,28 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan("tiny"));
-// Global
-let data = null;
-let selectedOptionId = null;
-let lookingAtIdx = 0;
-
-// Socket connection handling
-io.on("connection", (socket) => {
- console.log("Client connected:", socket.id);
-
- socket.emit("dataChanged", data);
- socket.emit("selectedOptionChanged", selectedOptionId);
- socket.emit("lookingAtIdxChanged", lookingAtIdx);
-
- // Handle array updates from client
- socket.on("setData", (newData) => {
- data = newData;
- console.log("Data changed");
- io.emit("dataChanged", data);
- });
-
- socket.on("setSelectedOptionId", (newId) => {
- selectedOptionId = newId;
- console.log("Selected option changed to ", newId);
- io.emit("selectedOptionChanged", newId);
- });
-
- socket.on("setLookingAtIdx", (newIdx) => {
- lookingAtIdx = 0;
- console.log("Looking at id changed to ", newIdx);
- io.emit("lookingAtIdxChanged", newIdx);
- });
-
- socket.on("disconnect", () => {
- console.log("Client disconnected:", socket.id);
- });
-});
-
app.get("/_health", (req, res) => {
res.json({ healthy: true });
});
-app.get(
- "/resources-status",
+let errors = [];
+app.post(
+ "/log-error",
asyncHandler((req, res) => {
- res.json({ array: booleanArray });
+ const { error, clear } = req.body;
+ if (clear) {
+ errors = [];
+ }
+
+ errors.push(error);
+ res.json({ success: true });
+ }),
+);
+
+app.get(
+ "/log-error",
+ asyncHandler((req, res) => {
+ res.json({ errors });
}),
);
@@ -304,8 +269,14 @@ function errorRequestHandler(error, _req, res, next) {
function isOriginAllowed(origin) {
const url = new URL(origin);
-
- if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
+ if (
+ [
+ "localhost",
+ "127.0.0.1",
+ "personal.workstation.lan",
+ "personal.utm.local",
+ ].includes(url.hostname)
+ ) {
return true;
}