Merge branch 'dev' into 'staging'
Dev See merge request frontend/squzanswerer!123
This commit is contained in:
commit
0b0c7fbdce
@ -38,7 +38,13 @@ const DeviceType = device.type;
|
||||
let Device = md.mobile();
|
||||
if (Device === null) { Device = userAgent; }
|
||||
|
||||
export const publicationMakeRequest = ({ url, body }: any) => {
|
||||
type PublicationMakeRequestParams = {
|
||||
url: string;
|
||||
body: FormData;
|
||||
method: "POST";
|
||||
}
|
||||
|
||||
export const publicationMakeRequest = ({ url, body }: PublicationMakeRequestParams) => {
|
||||
return axios(url, {
|
||||
data: body,
|
||||
headers: {
|
||||
@ -56,7 +62,7 @@ export const publicationMakeRequest = ({ url, body }: any) => {
|
||||
export async function getData(quizId: string): Promise<{
|
||||
data: GetQuizDataResponse | null;
|
||||
isRecentlyCompleted: boolean;
|
||||
error?: any;
|
||||
error?: AxiosError;
|
||||
}> {
|
||||
try {
|
||||
const { data, headers } = await axios<GetQuizDataResponse>(
|
||||
@ -116,7 +122,14 @@ export async function getQuizData(quizId: string) {
|
||||
return res;
|
||||
}
|
||||
|
||||
export function sendAnswer({ questionId, body, qid, preview }: any) {
|
||||
type SendAnswerProps = {
|
||||
questionId: string;
|
||||
body: string | string[];
|
||||
qid: string;
|
||||
preview: boolean;
|
||||
}
|
||||
|
||||
export function sendAnswer({ questionId, body, qid, preview }: SendAnswerProps) {
|
||||
if (preview) return;
|
||||
const formData = new FormData();
|
||||
|
||||
@ -138,11 +151,26 @@ export function sendAnswer({ questionId, body, qid, preview }: any) {
|
||||
}
|
||||
|
||||
//body ={file, filename}
|
||||
export function sendFile({ questionId, body, qid, preview }: any) {
|
||||
if (preview) return;
|
||||
type SendFileParams = {
|
||||
questionId: string;
|
||||
body: {
|
||||
name: string;
|
||||
file: File;
|
||||
preview: boolean;
|
||||
};
|
||||
qid: string;
|
||||
}
|
||||
|
||||
type Answer = {
|
||||
question_id: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export function sendFile({ questionId, body, qid }: SendFileParams) {
|
||||
if (body.preview) return;
|
||||
const formData = new FormData();
|
||||
|
||||
const answers: any = [
|
||||
const answers: Answer[] = [
|
||||
{
|
||||
question_id: questionId,
|
||||
content: "file:" + body.name,
|
||||
@ -162,7 +190,20 @@ export function sendFile({ questionId, body, qid, preview }: any) {
|
||||
}
|
||||
|
||||
//форма контактов
|
||||
export function sendFC({ questionId, body, qid, preview }: any) {
|
||||
export type SendFCParams = {
|
||||
questionId: string;
|
||||
body: {
|
||||
name?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
address?: string;
|
||||
customs?: Record<string, string>;
|
||||
};
|
||||
qid: string;
|
||||
preview: boolean;
|
||||
}
|
||||
|
||||
export function sendFC({ questionId, body, qid, preview }: SendFCParams) {
|
||||
if (preview) return;
|
||||
const formData = new FormData();
|
||||
|
||||
|
@ -4,7 +4,7 @@ type InfoProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
sx?: SxProps;
|
||||
onClick?: any;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
color?: string
|
||||
};
|
||||
|
@ -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<number>(() => window.innerWidth);
|
||||
const rootContainerRef = useRef<HTMLDivElement>(null);
|
||||
@ -58,63 +60,71 @@ export default function QuizAnswerer({ quizSettings, quizId, preview = false, ch
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (isLoading) return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<LoadingSkeleton />
|
||||
</ThemeProvider>
|
||||
);
|
||||
if (error) return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApologyPage error={error} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
if (isLoading) return <LoadingSkeleton />;
|
||||
if (error) return <ApologyPage error={error} />;
|
||||
|
||||
quizSettings ??= data;
|
||||
if (!quizSettings) throw new Error("Quiz data is null");
|
||||
|
||||
if (quizSettings.questions.length === 0) return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApologyPage error={new Error("No questions found")} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
if(!quizId) return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApologyPage error={error} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
if (quizSettings.questions.length === 0) return <ApologyPage error={new Error("No questions found")} />;
|
||||
if (!quizId) return <ApologyPage error={new Error("No quiz id")} />;
|
||||
|
||||
const quizContainer = (
|
||||
<Box
|
||||
ref={rootContainerRef}
|
||||
className={className}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ApologyPage}
|
||||
onError={handleComponentError}
|
||||
>
|
||||
<ViewPublicationPage />
|
||||
</ErrorBoundary>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<QuizViewContext.Provider value={quizViewStore}>
|
||||
<RootContainerWidthContext.Provider value={rootContainerWidth}>
|
||||
<QuizDataContext.Provider value={{ ...quizSettings, quizId, preview, changeFaviconAndTitle }}>
|
||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="ru" localeText={localeText}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SnackbarProvider
|
||||
preventDuplicate={true}
|
||||
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
||||
>
|
||||
<CssBaseline />
|
||||
<Box
|
||||
ref={rootContainerRef}
|
||||
className={className}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ApologyPage}
|
||||
onError={handleComponentError}
|
||||
>
|
||||
<ViewPublicationPage />
|
||||
</ErrorBoundary>
|
||||
</Box>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</LocalizationProvider>
|
||||
{disableGlobalCss ? (
|
||||
<ScopedCssBaseline
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
>
|
||||
{quizContainer}
|
||||
</ScopedCssBaseline>
|
||||
) : (
|
||||
<CssBaseline>
|
||||
{quizContainer}
|
||||
</CssBaseline>
|
||||
)}
|
||||
</QuizDataContext.Provider>
|
||||
</RootContainerWidthContext.Provider>
|
||||
</QuizViewContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function QuizAnswerer(props: Props) {
|
||||
|
||||
return (
|
||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="ru" localeText={localeText}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SnackbarProvider
|
||||
preventDuplicate={true}
|
||||
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
||||
>
|
||||
<QuizAnswererInner {...props} />
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { FC, useRef, useState, useEffect } from "react";
|
||||
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
||||
import EmailIcon from "@icons/ContactFormIcon/EmailIcon";
|
||||
import NameIcon from "@icons/ContactFormIcon/NameIcon";
|
||||
import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon";
|
||||
import TextIcon from "@icons/ContactFormIcon/TextIcon";
|
||||
import {
|
||||
FC,
|
||||
useRef,
|
||||
useState,
|
||||
useEffect,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -14,11 +16,18 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useIMask } from "react-imask";
|
||||
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
|
||||
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
||||
import EmailIcon from "@icons/ContactFormIcon/EmailIcon";
|
||||
import NameIcon from "@icons/ContactFormIcon/NameIcon";
|
||||
import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon";
|
||||
import TextIcon from "@icons/ContactFormIcon/TextIcon";
|
||||
|
||||
import { DESIGN_LIST } from "@/utils/designList";
|
||||
import { sendFC } from "@api/quizRelase";
|
||||
import { sendFC, SendFCParams } from "@api/quizRelase";
|
||||
import { useQuizData } from "@contexts/QuizDataContext";
|
||||
import { NameplateLogo } from "@icons/NameplateLogo";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
@ -26,6 +35,32 @@ import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
||||
import {
|
||||
FormContactFieldData,
|
||||
FormContactFieldName,
|
||||
} from "@model/settingsData.ts";
|
||||
|
||||
type InputProps = {
|
||||
title: string;
|
||||
desc: string;
|
||||
Icon: FC<{ color: string; backgroundColor: string }>;
|
||||
onChange: TextFieldProps["onChange"];
|
||||
id: string;
|
||||
mask?: string;
|
||||
};
|
||||
|
||||
type InputsProps = {
|
||||
name: string;
|
||||
setName: Dispatch<SetStateAction<string>>;
|
||||
email: string;
|
||||
setEmail: Dispatch<SetStateAction<string>>;
|
||||
phone: string;
|
||||
setPhone: Dispatch<SetStateAction<string>>;
|
||||
text: string;
|
||||
setText: Dispatch<SetStateAction<string>>;
|
||||
adress: string;
|
||||
setAdress: Dispatch<SetStateAction<string>>;
|
||||
};
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
||||
const EMAIL_REGEXP =
|
||||
@ -86,7 +121,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
||||
|
||||
const inputHC = async () => {
|
||||
const FC = settings.cfg.formContact.fields || settings.cfg.formContact;
|
||||
const body = {} as any;
|
||||
const body: SendFCParams["body"] = {};
|
||||
if (name.length > 0) body.name = name;
|
||||
if (email.length > 0) body.email = email;
|
||||
if (phone.length > 0) body.phone = phone;
|
||||
@ -113,19 +148,21 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const FCcopy: any =
|
||||
const FCcopy: Record<FormContactFieldName, FormContactFieldData> =
|
||||
settings.cfg.formContact.fields || settings.cfg.formContact;
|
||||
|
||||
const filteredFC: any = {};
|
||||
const filteredFC: Partial<
|
||||
Record<FormContactFieldName, FormContactFieldData>
|
||||
> = {};
|
||||
for (const i in FCcopy) {
|
||||
const field = FCcopy[i];
|
||||
const field = FCcopy[i as keyof typeof FCcopy];
|
||||
if (field.used) {
|
||||
filteredFC[i] = field;
|
||||
filteredFC[i as FormContactFieldName] = field;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleShowResultsClick() {
|
||||
const FC: any = settings.cfg.formContact.fields;
|
||||
const FC = settings.cfg.formContact.fields;
|
||||
if (FC["email"].used !== EMAIL_REGEXP.test(email)) {
|
||||
return enqueueSnackbar("введена некорректная почта");
|
||||
}
|
||||
@ -145,9 +182,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
||||
try {
|
||||
await inputHC();
|
||||
fireOnce.current = false;
|
||||
const sessions: any = JSON.parse(
|
||||
localStorage.getItem("sessions") || "{}"
|
||||
);
|
||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||
sessions[quizId] = Date.now();
|
||||
localStorage.setItem("sessions", JSON.stringify(sessions));
|
||||
enqueueSnackbar("Данные успешно отправлены");
|
||||
@ -389,7 +424,7 @@ const Inputs = ({
|
||||
setText,
|
||||
adress,
|
||||
setAdress,
|
||||
}: any) => {
|
||||
}: InputsProps) => {
|
||||
const { settings } = useQuizData();
|
||||
|
||||
const FC = settings.cfg.formContact.fields;
|
||||
@ -421,6 +456,7 @@ const Inputs = ({
|
||||
title={FC["phone"].innerText || "Введите номер телефона"}
|
||||
desc={FC["phone"].text || "Номер телефона"}
|
||||
Icon={PhoneIcon}
|
||||
mask="+7 (000) 000-00-00"
|
||||
/>
|
||||
);
|
||||
const Text = (
|
||||
@ -463,22 +499,12 @@ const Inputs = ({
|
||||
}
|
||||
};
|
||||
|
||||
const CustomInput = ({
|
||||
title,
|
||||
desc,
|
||||
Icon,
|
||||
onChange,
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
Icon: FC<{ color: string; backgroundColor: string }>;
|
||||
onChange: TextFieldProps["onChange"];
|
||||
}) => {
|
||||
const CustomInput = ({ title, desc, Icon, onChange, mask }: InputProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useRootContainerSize() < 600;
|
||||
const { settings } = useQuizData();
|
||||
const { ref } = useIMask({ mask });
|
||||
|
||||
return (
|
||||
<Box m="10px 0">
|
||||
<Typography mb="7px" color={theme.palette.text.primary}>
|
||||
@ -486,6 +512,7 @@ const CustomInput = ({
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
inputRef={ref}
|
||||
onChange={onChange}
|
||||
sx={{
|
||||
width: isMobile ? "300px" : "390px",
|
||||
|
@ -21,7 +21,7 @@ import { useRootContainerSize } from "../../../contexts/RootContainerWidthContex
|
||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||
import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE, UPLOAD_FILE_DESCRIPTIONS_MAP } from "../tools/fileUpload";
|
||||
|
||||
type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
||||
export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
||||
|
||||
type FileProps = {
|
||||
currentQuestion: QuizQuestionFile;
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import {Answer, useQuizViewStore} from "@stores/quizView";
|
||||
|
||||
import { sendAnswer } from "@api/quizRelase";
|
||||
import { useQuizData } from "@contexts/QuizDataContext";
|
||||
@ -114,7 +114,7 @@ export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
|
||||
|
||||
interface Props {
|
||||
currentQuestion: QuizQuestionText;
|
||||
answer: any;
|
||||
answer?: Answer;
|
||||
inputHC: (a: string) => void;
|
||||
stepNumber?: number | null;
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ export type FormContactFieldName =
|
||||
| "text"
|
||||
| "address";
|
||||
|
||||
type FormContactFieldData = {
|
||||
export type FormContactFieldData = {
|
||||
text: string;
|
||||
innerText: string;
|
||||
key: string;
|
||||
|
@ -6,9 +6,11 @@ import { createContext, useContext } from "react";
|
||||
import { createStore, useStore } from "zustand";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
|
||||
export type Answer = string | string[] | Moment;
|
||||
|
||||
type QuestionAnswer = {
|
||||
questionId: string;
|
||||
answer: string | string[] | Moment;
|
||||
answer: Answer
|
||||
};
|
||||
|
||||
type OwnVariant = {
|
||||
@ -99,4 +101,4 @@ export const createQuizViewStore = () => createStore<QuizViewStore & QuizViewAct
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
);
|
||||
|
@ -2,13 +2,14 @@ import { FormControl, TextField as MuiTextField, SxProps, Theme, useTheme } from
|
||||
|
||||
import type { InputProps, TextFieldProps } from "@mui/material";
|
||||
import type { ChangeEvent, FC, FocusEvent, KeyboardEvent } from "react";
|
||||
import {Answer} from "@stores/quizView.ts";
|
||||
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
||||
|
||||
interface CustomTextFieldProps {
|
||||
placeholder: string;
|
||||
value?: string;
|
||||
value?: Answer;
|
||||
error?: string;
|
||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||
|
@ -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 };
|
||||
export { domain };
|
||||
|
@ -89,6 +89,7 @@
|
||||
"country-flag-emoji-polyfill": "^0.1.8",
|
||||
"current-device": "^0.10.2",
|
||||
"hex-rgb": "^5.0.0",
|
||||
"mobile-detect": "^1.4.5"
|
||||
"mobile-detect": "^1.4.5",
|
||||
"react-imask": "^7.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
import QuizAnswerer from "../lib/components/QuizAnswerer";
|
||||
|
||||
|
||||
interface Props {
|
||||
quizId: string;
|
||||
}
|
||||
|
||||
export default function WidgetApp({ quizId }: Props) {
|
||||
|
||||
return (
|
||||
<QuizAnswerer quizId={quizId} />
|
||||
);
|
||||
}
|
@ -1,24 +1,29 @@
|
||||
import { Root, createRoot } from "react-dom/client";
|
||||
import WidgetApp from "./WidgetApp";
|
||||
import QuizAnswerer from "@/components/QuizAnswerer";
|
||||
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 }: {
|
||||
create({ selector, quizId, changeFaviconAndTitle = true }: {
|
||||
selector: string;
|
||||
quizId: string;
|
||||
changeFaviconAndTitle: boolean;
|
||||
}) {
|
||||
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(<WidgetApp quizId={quizId} />);
|
||||
root.render(
|
||||
<QuizAnswerer
|
||||
quizId={quizId}
|
||||
changeFaviconAndTitle={changeFaviconAndTitle}
|
||||
disableGlobalCss
|
||||
/>
|
||||
);
|
||||
},
|
||||
unmount() {
|
||||
if (root) root.unmount();
|
||||
}
|
||||
};
|
||||
|
||||
export default widget;
|
||||
|
35
src/widgets/QuizDialog.tsx
Normal file
35
src/widgets/QuizDialog.tsx
Normal file
@ -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 (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
keepMounted
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: "transparent",
|
||||
width: "calc(min(100%, max(70%, 700px)))",
|
||||
height: "80%",
|
||||
maxWidth: "100%",
|
||||
m: "16px",
|
||||
}
|
||||
}}
|
||||
>
|
||||
<QuizAnswerer
|
||||
quizId={quizId}
|
||||
changeFaviconAndTitle={false}
|
||||
disableGlobalCss
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
29
src/widgets/banner/BannerWidget.tsx
Normal file
29
src/widgets/banner/BannerWidget.tsx
Normal file
@ -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<typeof QuizBanner>) {
|
||||
this.element.style.setProperty("display", "none");
|
||||
document.body.appendChild(this.element);
|
||||
|
||||
this.root = createRoot(this.element);
|
||||
|
||||
this.root.render(
|
||||
<QuizBanner
|
||||
quizId={quizId}
|
||||
position={position}
|
||||
onClose={() => this.destroy()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.root) this.root.unmount();
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
82
src/widgets/banner/QuizBanner.tsx
Normal file
82
src/widgets/banner/QuizBanner.tsx
Normal file
@ -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<boolean>(false);
|
||||
|
||||
return createPortal(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Box
|
||||
className="pena-quiz-widget-banner"
|
||||
sx={[
|
||||
{
|
||||
position: "fixed",
|
||||
height: "70px",
|
||||
width: `calc(min(calc(100% - ${PADDING * 2}px), max(500px, 70%)))`,
|
||||
},
|
||||
position === "topleft" && {
|
||||
top: PADDING,
|
||||
left: PADDING,
|
||||
},
|
||||
position === "topright" && {
|
||||
top: PADDING,
|
||||
right: PADDING,
|
||||
},
|
||||
position === "bottomleft" && {
|
||||
bottom: PADDING,
|
||||
left: PADDING,
|
||||
},
|
||||
position === "bottomright" && {
|
||||
bottom: PADDING,
|
||||
right: PADDING,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
onClick={() => setIsQuizDialogOpen(p => !p)}
|
||||
variant="contained"
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
Пройти квиз
|
||||
</Button>
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
p: 0,
|
||||
width: "34px",
|
||||
height: "34px",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "#333647",
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ color: "#FFFFFF" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<QuizDialog
|
||||
open={isQuizDialogOpen}
|
||||
quizId={quizId}
|
||||
onClose={() => setIsQuizDialogOpen(false)}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
document.body
|
||||
);
|
||||
}
|
31
src/widgets/button/ButtonWidget.tsx
Normal file
31
src/widgets/button/ButtonWidget.tsx
Normal file
@ -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<typeof OpenQuizButton>) {
|
||||
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(
|
||||
<OpenQuizButton
|
||||
selector={selector}
|
||||
fixedSide={fixedSide}
|
||||
quizId={quizId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.root) this.root.unmount();
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
55
src/widgets/button/OpenQuizButton.tsx
Normal file
55
src/widgets/button/OpenQuizButton.tsx
Normal file
@ -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<boolean>(false);
|
||||
|
||||
const portalContainer = !fixedSide && selector ? document.querySelector(selector)! : document.body;
|
||||
|
||||
return createPortal(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Button
|
||||
className="pena-quiz-widget-button"
|
||||
onClick={() => setIsQuizDialogOpen(p => !p)}
|
||||
variant="contained"
|
||||
sx={[
|
||||
{
|
||||
// generic styles
|
||||
},
|
||||
Boolean(fixedSide) && {
|
||||
position: "fixed",
|
||||
bottom: "50%",
|
||||
},
|
||||
fixedSide === "left" && {
|
||||
left: 0,
|
||||
transformOrigin: "left",
|
||||
transform: "rotate(-90deg) translateY(50%) translateX(-50%)",
|
||||
},
|
||||
fixedSide === "right" && {
|
||||
right: 0,
|
||||
transformOrigin: "right",
|
||||
transform: "rotate(-90deg) translateY(-50%) translateX(50%)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
Пройти квиз
|
||||
</Button>
|
||||
<QuizDialog
|
||||
open={isQuizDialogOpen}
|
||||
quizId={quizId}
|
||||
onClose={() => setIsQuizDialogOpen(false)}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
portalContainer
|
||||
);
|
||||
}
|
29
src/widgets/container/ContainerWidget.tsx
Normal file
29
src/widgets/container/ContainerWidget.tsx
Normal file
@ -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.querySelector(selector);
|
||||
if (!element) throw new Error("Element for widget doesn't exist");
|
||||
|
||||
this.root = createRoot(element);
|
||||
|
||||
this.root.render(
|
||||
<QuizAnswerer
|
||||
quizId={quizId}
|
||||
changeFaviconAndTitle={false}
|
||||
disableGlobalCss
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.root) this.root.unmount();
|
||||
}
|
||||
}
|
5
src/widgets/index.ts
Normal file
5
src/widgets/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./banner/BannerWidget";
|
||||
export * from "./button/ButtonWidget";
|
||||
export * from "./container/ContainerWidget";
|
||||
export * from "./popup/PopupWidget";
|
||||
export * from "./side/SideWidget";
|
30
src/widgets/popup/PopupWidget.tsx
Normal file
30
src/widgets/popup/PopupWidget.tsx
Normal file
@ -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(
|
||||
<QuizDialog
|
||||
quizId={quizId}
|
||||
onClose={() => this.destroy()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.root) this.root.unmount();
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
73
src/widgets/side/QuizSideButton.tsx
Normal file
73
src/widgets/side/QuizSideButton.tsx
Normal file
@ -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<boolean>(false);
|
||||
|
||||
return createPortal(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
{isQuizShown ? (
|
||||
<Grow in={true}>
|
||||
<Box
|
||||
sx={[
|
||||
{
|
||||
position: "fixed",
|
||||
height: `calc(min(calc(100% - ${PADDING * 2}px), 800px))`,
|
||||
width: `calc(min(calc(100% - ${PADDING * 2}px), 600px))`,
|
||||
},
|
||||
position === "left" && {
|
||||
bottom: PADDING,
|
||||
left: PADDING,
|
||||
},
|
||||
position === "right" && {
|
||||
bottom: PADDING,
|
||||
right: PADDING,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<QuizAnswerer
|
||||
quizId={quizId}
|
||||
changeFaviconAndTitle={false}
|
||||
disableGlobalCss
|
||||
/>
|
||||
</Box>
|
||||
</Grow>
|
||||
) : (
|
||||
<Button
|
||||
className="pena-quiz-widget-button"
|
||||
variant="contained"
|
||||
onClick={() => setIsQuizShown(true)}
|
||||
sx={[
|
||||
{
|
||||
position: "fixed",
|
||||
height: "70px",
|
||||
width: `calc(min(calc(100% - ${PADDING * 2}px), 600px))`,
|
||||
},
|
||||
position === "left" && {
|
||||
bottom: PADDING,
|
||||
left: PADDING,
|
||||
},
|
||||
position === "right" && {
|
||||
bottom: PADDING,
|
||||
right: PADDING,
|
||||
},
|
||||
]}
|
||||
>
|
||||
Пройти квиз
|
||||
</Button>
|
||||
)}
|
||||
</ThemeProvider>,
|
||||
document.body
|
||||
);
|
||||
}
|
28
src/widgets/side/SideWidget.tsx
Normal file
28
src/widgets/side/SideWidget.tsx
Normal file
@ -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<typeof QuizSideButton>) {
|
||||
this.element.style.setProperty("display", "none");
|
||||
document.body.appendChild(this.element);
|
||||
|
||||
this.root = createRoot(this.element);
|
||||
|
||||
this.root.render(
|
||||
<QuizSideButton
|
||||
quizId={quizId}
|
||||
position={position}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.root) this.root.unmount();
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
120
widget-test.html
Normal file
120
widget-test.html
Normal file
@ -0,0 +1,120 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Quiz</title>
|
||||
<style>
|
||||
#widget-container {
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
}
|
||||
p {
|
||||
font-size: x-large;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<!-- <div id="widget-container"></div> -->
|
||||
<div id="button-container"></div>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<p>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</p>
|
||||
<!-- <script type="module">
|
||||
import { ContainerWidget } from "./widget/widget.js";
|
||||
|
||||
new ContainerWidget({
|
||||
selector: "widget-container",
|
||||
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||
});
|
||||
</script> -->
|
||||
<!-- <script type="module">
|
||||
import { PopupWidget } from "./widget/widget.js";
|
||||
|
||||
new PopupWidget({
|
||||
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||
});
|
||||
</script> -->
|
||||
<!-- <script type="module">
|
||||
import { ButtonWidget } from "./widget/widget.js";
|
||||
|
||||
new ButtonWidget({
|
||||
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||
fixedSide: "right",
|
||||
});
|
||||
</script> -->
|
||||
<!-- <script type="module">
|
||||
import { BannerWidget } from "./widget/widget.js";
|
||||
|
||||
new BannerWidget({
|
||||
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||
position: "bottomright",
|
||||
});
|
||||
</script> -->
|
||||
<script type="module">
|
||||
import { SideWidget } from "./widget/widget.js";
|
||||
|
||||
new SideWidget({
|
||||
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||
position: "right",
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
28
yarn.lock
28
yarn.lock
@ -184,6 +184,14 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.22.5"
|
||||
|
||||
"@babel/runtime-corejs3@^7.24.4":
|
||||
version "7.24.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.4.tgz#b9ebe728087cfbb22bbaccc6f9a70d69834124a0"
|
||||
integrity sha512-VOQOexSilscN24VEY810G/PqtpFvx/z6UqDIjIWbDe2368HhDLkYN5TYwaEz/+eRCUkhJ2WaNLLmQAlxzfWj4w==
|
||||
dependencies:
|
||||
core-js-pure "^3.30.2"
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
|
||||
version "7.23.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||
@ -1563,6 +1571,11 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
core-js-pure@^3.30.2:
|
||||
version "3.37.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.37.0.tgz#ce99fb4a7cec023fdbbe5b5bd1f06bbcba83316e"
|
||||
integrity sha512-d3BrpyFr5eD4KcbRvQ3FTUx/KWmaDesr7+a3+1+P46IUnNoEt+oiLijPINZMEon7w9oGkIINWxrBAU9DEciwFQ==
|
||||
|
||||
core-util-is@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
@ -2315,6 +2328,13 @@ ignore@^5.2.0, ignore@^5.2.4:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
|
||||
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
|
||||
|
||||
imask@^7.6.0:
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/imask/-/imask-7.6.0.tgz#ed071748cfdf6b12ac153f69878e08c4333df984"
|
||||
integrity sha512-6EHsq1q7v5+M4Vas2MGrs2oRpxPRWPwPDiL0HmG1ikBI/0hOwvkxRhVRFQnWIlZcTG7R8iw0az5V+z868qnQ9A==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.24.4"
|
||||
|
||||
immer@^10.0.3:
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9"
|
||||
@ -2984,6 +3004,14 @@ react-error-boundary@^4.0.12:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
react-imask@^7.6.0:
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-imask/-/react-imask-7.6.0.tgz#5948fc39e1d7d036292d4fade43df4636d43e7b7"
|
||||
integrity sha512-SilPct67Xw4TN+dqn3SM4BVpy+FwNSeT0wblA/DXQ3El2KPBEWwrn4x3gQ39ZohFAphp7yG7w6gSKq5SeR/6Kg==
|
||||
dependencies:
|
||||
imask "^7.6.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
Loading…
Reference in New Issue
Block a user