diff --git a/lib/components/QuizAnswerer.tsx b/lib/components/QuizAnswerer.tsx index 3424ad3..50c703c 100644 --- a/lib/components/QuizAnswerer.tsx +++ b/lib/components/QuizAnswerer.tsx @@ -1,22 +1,23 @@ import { getQuizData } from "@/api/quizRelase"; +import { QuizViewContext, createQuizViewStore } from "@/stores/quizView"; import LoadingSkeleton from "@/ui_kit/LoadingSkeleton"; import { QuizDataContext } from "@contexts/QuizDataContext"; import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext"; import { QuizSettings } from "@model/settingsData"; import { Box, CssBaseline, ThemeProvider } from "@mui/material"; +import ScopedCssBaseline from "@mui/material/ScopedCssBaseline"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; -import { ruRU } from '@mui/x-date-pickers/locales'; +import { ruRU } from "@mui/x-date-pickers/locales"; import { handleComponentError } from "@utils/handleComponentError"; import lightTheme from "@utils/themes/light"; import moment from "moment"; -import { SnackbarProvider } from 'notistack'; +import { SnackbarProvider } from "notistack"; import { startTransition, useEffect, useLayoutEffect, useRef, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import useSWR from "swr"; import { ApologyPage } from "./ViewPublicationPage/ApologyPage"; import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage"; -import { QuizViewContext, createQuizViewStore } from "@/stores/quizView"; moment.locale("ru"); @@ -28,9 +29,10 @@ type Props = { preview?: boolean; changeFaviconAndTitle?: boolean; className?: string; + disableGlobalCss?: boolean; }; -export default function QuizAnswerer({ quizSettings, quizId, preview = false, changeFaviconAndTitle = true, className }: Props) { +function QuizAnswererInner({ quizSettings, quizId, preview = false, changeFaviconAndTitle = true, className, disableGlobalCss = false }: Props) { const [quizViewStore] = useState(createQuizViewStore); const [rootContainerWidth, setRootContainerWidth] = useState(() => window.innerWidth); const rootContainerRef = useRef(null); @@ -58,63 +60,71 @@ export default function QuizAnswerer({ quizSettings, quizId, preview = false, ch }; }, []); - if (isLoading) return ( - - - - ); - if (error) return ( - - - - ); + if (isLoading) return ; + if (error) return ; quizSettings ??= data; if (!quizSettings) throw new Error("Quiz data is null"); - if (quizSettings.questions.length === 0) return ( - - - - ); - if(!quizId) return ( - - - - ); + if (quizSettings.questions.length === 0) return ; + if (!quizId) return ; + const quizContainer = ( + + + + + + ); return ( - - - - - - - - - - - - + {disableGlobalCss ? ( + + {quizContainer} + + ) : ( + + {quizContainer} + + )} ); -} +} + +export default function QuizAnswerer(props: Props) { + + return ( + + + + + + + + ); +} diff --git a/lib/components/ViewPublicationPage/ApologyPage.tsx b/lib/components/ViewPublicationPage/ApologyPage.tsx index f708bcb..1a983f2 100644 --- a/lib/components/ViewPublicationPage/ApologyPage.tsx +++ b/lib/components/ViewPublicationPage/ApologyPage.tsx @@ -9,7 +9,7 @@ export const ApologyPage = ({ error }: Props) => { if (error.response?.data === "quiz is inactive") message = "Квиз не активирован"; if (error.message === "No questions found") message = "Нет созданных вопросов"; if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос"; - if (error.message === "No questions found") message = "Вопросы отсутствуют"; + if (error.message === "No quiz id") message = "Отсутствует id квиза"; if (error.response?.data === "Invalid request data") message = "Такого квиза не существует"; return ( diff --git a/lib/utils/defineDomain.ts b/lib/utils/defineDomain.ts index 2737da5..14095f1 100644 --- a/lib/utils/defineDomain.ts +++ b/lib/utils/defineDomain.ts @@ -6,10 +6,11 @@ let domain = "https://hbpn.link"; const currentDomain = location.hostname; if ( - currentDomain === "s.hbpn.link" || -//Исключение - туризм. Он на стейджинговом квизе и на чужом для публикации домене - currentDomain === "tourism.pena.digital" || - currentDomain.includes("localhost") + currentDomain === "s.hbpn.link" + //Исключение - туризм. Он на стейджинговом квизе и на чужом для публикации домене + || currentDomain === "tourism.pena.digital" + || currentDomain.includes("localhost") + || currentDomain.includes("127.0.0.1") ) domain = "https://s.hbpn.link"; -export { domain }; \ No newline at end of file +export { domain }; diff --git a/src/widget.tsx b/src/widget.tsx index 2364cec..548b864 100644 --- a/src/widget.tsx +++ b/src/widget.tsx @@ -1,9 +1,10 @@ import QuizAnswerer from "@/components/QuizAnswerer"; -import { Root, createRoot } from "react-dom/client"; +import { createRoot } from "react-dom/client"; +// eslint-disable-next-line react-refresh/only-export-components +export * from "./widgets"; -let root: Root | undefined = undefined; - +// old widget const widget = { create({ selector, quizId, changeFaviconAndTitle = true }: { selector: string; @@ -13,13 +14,16 @@ const widget = { const element = document.getElementById(selector); if (!element) throw new Error("Element for widget doesn't exist"); - root = createRoot(element); + const root = createRoot(element); - root.render(); + root.render( + + ); }, - unmount() { - if (root) root.unmount(); - } }; export default widget; diff --git a/src/widgets/QuizDialog.tsx b/src/widgets/QuizDialog.tsx new file mode 100644 index 0000000..44cf2eb --- /dev/null +++ b/src/widgets/QuizDialog.tsx @@ -0,0 +1,35 @@ +import QuizAnswerer from "@/components/QuizAnswerer"; +import { Dialog } from "@mui/material"; + + +interface Props { + open?: boolean; + quizId: string; + onClose?: () => void; +} + +export default function QuizDialog({ open = true, quizId, onClose }: Props) { + + return ( + + + + ); +} diff --git a/src/widgets/banner/BannerWidget.tsx b/src/widgets/banner/BannerWidget.tsx new file mode 100644 index 0000000..3153d6f --- /dev/null +++ b/src/widgets/banner/BannerWidget.tsx @@ -0,0 +1,29 @@ +import { Root, createRoot } from "react-dom/client"; +import QuizBanner from "./QuizBanner"; +import { ComponentPropsWithoutRef } from "react"; + + +export class BannerWidget { + root: Root | undefined; + element = document.createElement("div"); + + constructor({ quizId, position }: ComponentPropsWithoutRef) { + this.element.style.setProperty("display", "none"); + document.body.appendChild(this.element); + + this.root = createRoot(this.element); + + this.root.render( + this.destroy()} + /> + ); + } + + destroy() { + if (this.root) this.root.unmount(); + this.element.remove(); + } +} diff --git a/src/widgets/banner/QuizBanner.tsx b/src/widgets/banner/QuizBanner.tsx new file mode 100644 index 0000000..da72cfe --- /dev/null +++ b/src/widgets/banner/QuizBanner.tsx @@ -0,0 +1,82 @@ +import lightTheme from "@/utils/themes/light"; +import CloseIcon from '@mui/icons-material/Close'; +import { Box, Button, IconButton, ThemeProvider } from "@mui/material"; +import { useState } from "react"; +import { createPortal } from "react-dom"; +import QuizDialog from "../QuizDialog"; + + +const PADDING = 10; + +interface Props { + position: "topleft" | "topright" | "bottomleft" | "bottomright"; + quizId: string; + onClose: () => void; +} + +export default function QuizBanner({ quizId, position, onClose }: Props) { + const [isQuizDialogOpen, setIsQuizDialogOpen] = useState(false); + + return createPortal( + + + + + + + + setIsQuizDialogOpen(false)} + /> + , + document.body + ); +} diff --git a/src/widgets/button/ButtonWidget.tsx b/src/widgets/button/ButtonWidget.tsx new file mode 100644 index 0000000..2c489ac --- /dev/null +++ b/src/widgets/button/ButtonWidget.tsx @@ -0,0 +1,31 @@ +import { Root, createRoot } from "react-dom/client"; +import OpenQuizButton from "./OpenQuizButton"; +import { ComponentPropsWithoutRef } from "react"; + + +export class ButtonWidget { + root: Root | undefined; + element = document.createElement("div"); + + constructor({ quizId, selector, fixedSide }: ComponentPropsWithoutRef) { + if (!fixedSide && !selector) throw new Error("ButtonWidget: Either selector or fixedSide params must be provided"); + + this.element.style.setProperty("display", "none"); + document.body.appendChild(this.element); + + this.root = createRoot(this.element); + + this.root.render( + + ); + } + + destroy() { + if (this.root) this.root.unmount(); + this.element.remove(); + } +} diff --git a/src/widgets/button/OpenQuizButton.tsx b/src/widgets/button/OpenQuizButton.tsx new file mode 100644 index 0000000..1567a0c --- /dev/null +++ b/src/widgets/button/OpenQuizButton.tsx @@ -0,0 +1,55 @@ +import lightTheme from "@/utils/themes/light"; +import { Button, ThemeProvider } from "@mui/material"; +import { useState } from "react"; +import { createPortal } from "react-dom"; +import QuizDialog from "../QuizDialog"; + + +interface Props { + selector?: string; + fixedSide?: "left" | "right"; + quizId: string; +} + +export default function OpenQuizButton({ selector, quizId, fixedSide }: Props) { + const [isQuizDialogOpen, setIsQuizDialogOpen] = useState(false); + + const portalContainer = !fixedSide && selector ? document.getElementById(selector)! : document.body; + + return createPortal( + + + setIsQuizDialogOpen(false)} + /> + , + portalContainer + ); +} diff --git a/src/widgets/container/ContainerWidget.tsx b/src/widgets/container/ContainerWidget.tsx new file mode 100644 index 0000000..63b4d62 --- /dev/null +++ b/src/widgets/container/ContainerWidget.tsx @@ -0,0 +1,29 @@ +import QuizAnswerer from "@/components/QuizAnswerer"; +import { Root, createRoot } from "react-dom/client"; + + +export class ContainerWidget { + root: Root | undefined; + + constructor({ selector, quizId }: { + quizId: string; + selector: string; + }) { + const element = document.getElementById(selector); + if (!element) throw new Error("Element for widget doesn't exist"); + + this.root = createRoot(element); + + this.root.render( + + ); + } + + destroy() { + if (this.root) this.root.unmount(); + } +} diff --git a/src/widgets/index.ts b/src/widgets/index.ts new file mode 100644 index 0000000..2e47dfd --- /dev/null +++ b/src/widgets/index.ts @@ -0,0 +1,5 @@ +export * from "./banner/BannerWidget"; +export * from "./button/ButtonWidget"; +export * from "./container/ContainerWidget"; +export * from "./popup/PopupWidget"; +export * from "./side/SideWidget"; diff --git a/src/widgets/popup/PopupWidget.tsx b/src/widgets/popup/PopupWidget.tsx new file mode 100644 index 0000000..b7a6c97 --- /dev/null +++ b/src/widgets/popup/PopupWidget.tsx @@ -0,0 +1,30 @@ +import { Root, createRoot } from "react-dom/client"; +import QuizDialog from "../QuizDialog"; + + +export class PopupWidget { + root: Root | undefined; + element: HTMLDivElement; + + constructor({ quizId }: { + quizId: string; + }) { + this.element = document.createElement("div"); + this.element.style.setProperty("display", "none"); + document.body.appendChild(this.element); + + this.root = createRoot(this.element); + + this.root.render( + this.destroy()} + /> + ); + } + + destroy() { + if (this.root) this.root.unmount(); + this.element.remove(); + } +} diff --git a/src/widgets/side/QuizSideButton.tsx b/src/widgets/side/QuizSideButton.tsx new file mode 100644 index 0000000..ad3ea43 --- /dev/null +++ b/src/widgets/side/QuizSideButton.tsx @@ -0,0 +1,73 @@ +import { QuizAnswerer } from "@/index"; +import lightTheme from "@/utils/themes/light"; +import { Box, Button, Grow, ThemeProvider } from "@mui/material"; +import { useState } from "react"; +import { createPortal } from "react-dom"; + + +const PADDING = 10; + +interface Props { + quizId: string; + position: "left" | "right"; +} + +export default function QuizSideButton({ quizId, position }: Props) { + const [isQuizShown, setIsQuizShown] = useState(false); + + return createPortal( + + {isQuizShown ? ( + + + + + + ) : ( + + )} + , + document.body + ); +} diff --git a/src/widgets/side/SideWidget.tsx b/src/widgets/side/SideWidget.tsx new file mode 100644 index 0000000..f42f47b --- /dev/null +++ b/src/widgets/side/SideWidget.tsx @@ -0,0 +1,28 @@ +import { Root, createRoot } from "react-dom/client"; +import QuizSideButton from "./QuizSideButton"; +import { ComponentPropsWithoutRef } from "react"; + + +export class SideWidget { + root: Root | undefined; + element = document.createElement("div"); + + constructor({ quizId, position }: ComponentPropsWithoutRef) { + this.element.style.setProperty("display", "none"); + document.body.appendChild(this.element); + + this.root = createRoot(this.element); + + this.root.render( + + ); + } + + destroy() { + if (this.root) this.root.unmount(); + this.element.remove(); + } +} diff --git a/widget-test.html b/widget-test.html new file mode 100644 index 0000000..77f6d49 --- /dev/null +++ b/widget-test.html @@ -0,0 +1,120 @@ + + + + + + + + Quiz + + + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu

+ + + + + + + +