Merge branch 'requests-refactor' into 'dev'
style: requests See merge request frontend/squiz!321
This commit is contained in:
commit
73e8a03a22
@ -24,7 +24,7 @@ import {
|
|||||||
UserAccount,
|
UserAccount,
|
||||||
useUserFetcher,
|
useUserFetcher,
|
||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import type { OriginalUserAccount } from "@root/user";
|
import type { OriginalUserAccount } from "@root/user";
|
||||||
import {
|
import {
|
||||||
clearUserData,
|
clearUserData,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
LoginRequest,
|
LoginRequest,
|
||||||
@ -8,19 +8,19 @@ import type {
|
|||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "../utils/parse-error";
|
import { parseAxiosError } from "../utils/parse-error";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/auth";
|
const API_URL = process.env.REACT_APP_DOMAIN + "/auth";
|
||||||
|
|
||||||
export async function register(
|
export const register = async (
|
||||||
login: string,
|
login: string,
|
||||||
password: string,
|
password: string,
|
||||||
phoneNumber: string,
|
phoneNumber: string,
|
||||||
): Promise<[RegisterResponse | null, string?]> {
|
): Promise<[RegisterResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const registerResponse = await makeRequest<
|
const registerResponse = await makeRequest<
|
||||||
RegisterRequest,
|
RegisterRequest,
|
||||||
RegisterResponse
|
RegisterResponse
|
||||||
>({
|
>({
|
||||||
url: apiUrl + "/register",
|
url: `${API_URL}/register`,
|
||||||
body: { login, password, phoneNumber },
|
body: { login, password, phoneNumber },
|
||||||
useToken: false,
|
useToken: false,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -32,15 +32,15 @@ export async function register(
|
|||||||
|
|
||||||
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
|
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function login(
|
export const login = async (
|
||||||
login: string,
|
login: string,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<[LoginResponse | null, string?]> {
|
): Promise<[LoginResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const loginResponse = await makeRequest<LoginRequest, LoginResponse>({
|
const loginResponse = await makeRequest<LoginRequest, LoginResponse>({
|
||||||
url: apiUrl + "/login",
|
url: `${API_URL}/login`,
|
||||||
body: { login, password },
|
body: { login, password },
|
||||||
useToken: false,
|
useToken: false,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -52,13 +52,13 @@ export async function login(
|
|||||||
|
|
||||||
return [null, `Не удалось войти. ${error}`];
|
return [null, `Не удалось войти. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function logout(): Promise<[unknown, string?]> {
|
export const logout = async (): Promise<[unknown, string?]> => {
|
||||||
try {
|
try {
|
||||||
const logoutResponse = await makeRequest<never, void>({
|
const logoutResponse = await makeRequest<never, void>({
|
||||||
url: apiUrl + "/logout",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/logout`,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
@ -67,13 +67,13 @@ export async function logout(): Promise<[unknown, string?]> {
|
|||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null];
|
return [null, error];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function recover(
|
export const recover = async (
|
||||||
email: string,
|
email: string,
|
||||||
): Promise<[unknown | null, string?]> {
|
): Promise<[unknown | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("email", email);
|
formData.append("email", email);
|
||||||
@ -81,16 +81,18 @@ export async function recover(
|
|||||||
"RedirectionURL",
|
"RedirectionURL",
|
||||||
process.env.REACT_APP_DOMAIN + "/changepwd",
|
process.env.REACT_APP_DOMAIN + "/changepwd",
|
||||||
);
|
);
|
||||||
|
|
||||||
const recoverResponse = await makeRequest<unknown, unknown>({
|
const recoverResponse = await makeRequest<unknown, unknown>({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
|
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
|
||||||
body: formData,
|
body: formData,
|
||||||
useToken: false,
|
useToken: false,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [recoverResponse];
|
return [recoverResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null, `Не удалось восстановить пароль. ${error}`];
|
return [null, `Не удалось восстановить пароль. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { UserAccount } from "@frontend/kitui";
|
import { UserAccount } from "@frontend/kitui";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
import { parseAxiosError } from "@utils/parse-error";
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
const API_URL = process.env.REACT_APP_DOMAIN + "/customer";
|
||||||
|
|
||||||
export async function payCart(): Promise<[UserAccount | null, string?]> {
|
export const payCart = async (): Promise<[UserAccount | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const payCartResponse = await makeRequest<never, UserAccount>({
|
const payCartResponse = await makeRequest<never, UserAccount>({
|
||||||
url: apiUrl + "/cart/pay",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/cart/pay`,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -19,4 +19,4 @@ export async function payCart(): Promise<[UserAccount | null, string?]> {
|
|||||||
|
|
||||||
return [null, `Не удалось оплатить товар из корзины. ${error}`];
|
return [null, `Не удалось оплатить товар из корзины. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,16 +1,31 @@
|
|||||||
import axios from "axios";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
const domen = process.env.REACT_APP_DOMAIN;
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
export function sendContactFormRequest(body: {
|
const API_URL = process.env.REACT_APP_DOMAIN + "/feedback";
|
||||||
|
|
||||||
|
type SendContactFormBody = {
|
||||||
contact: string;
|
contact: string;
|
||||||
whoami: string;
|
whoami: string;
|
||||||
}) {
|
};
|
||||||
return axios(`${domen}/feedback/callme`, {
|
|
||||||
method: "POST",
|
export const sendContactFormRequest = async (
|
||||||
headers: {
|
body: SendContactFormBody,
|
||||||
"Content-Type": "application/json",
|
): Promise<[unknown | null, string?, number?]> => {
|
||||||
},
|
try {
|
||||||
data: body,
|
const sendContactFormResponse = await makeRequest<
|
||||||
});
|
SendContactFormBody,
|
||||||
}
|
unknown
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/callme`,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [sendContactFormResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error, status] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось отправить контакты. ${error}`, status];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -5,12 +5,15 @@ import type { Discount } from "@model/discounts";
|
|||||||
|
|
||||||
const API_URL = process.env.REACT_APP_DOMAIN + "/price/discount";
|
const API_URL = process.env.REACT_APP_DOMAIN + "/price/discount";
|
||||||
|
|
||||||
export async function getDiscounts(
|
export const getDiscounts = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
): Promise<[Discount[] | null, string?]> {
|
): Promise<[Discount[] | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const { Discounts } = await makeRequest<unknown, { Discounts: Discount[] }>(
|
const { Discounts } = await makeRequest<unknown, { Discounts: Discount[] }>(
|
||||||
{ method: "GET", url: `${API_URL}/user/${userId}` },
|
{
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/user/${userId}`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return [Discounts];
|
return [Discounts];
|
||||||
@ -19,4 +22,4 @@ export async function getDiscounts(
|
|||||||
|
|
||||||
return [null, `Не удалось получить скидки. ${error}`];
|
return [null, `Не удалось получить скидки. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -6,6 +6,8 @@ import { clearUserData } from "@root/user";
|
|||||||
import { clearQuizData } from "@root/quizes/store";
|
import { clearQuizData } from "@root/quizes/store";
|
||||||
import { redirect } from "react-router-dom";
|
import { redirect } from "react-router-dom";
|
||||||
|
|
||||||
|
import type { AxiosResponse } from "axios";
|
||||||
|
|
||||||
interface MakeRequest {
|
interface MakeRequest {
|
||||||
method?: Method | undefined;
|
method?: Method | undefined;
|
||||||
url: string;
|
url: string;
|
||||||
@ -17,18 +19,22 @@ interface MakeRequest {
|
|||||||
withCredentials?: boolean | undefined;
|
withCredentials?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeRequest<TRequest = unknown, TResponse = unknown>(
|
type ExtendedAxiosResponse = AxiosResponse & { message: string };
|
||||||
data: MakeRequest,
|
|
||||||
): Promise<TResponse> {
|
export const makeRequest = async <TRequest = unknown, TResponse = unknown>(
|
||||||
try {
|
data: MakeRequest,
|
||||||
const response = await KIT.makeRequest<unknown>(data);
|
): Promise<TResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await KIT.makeRequest<unknown, TResponse>(data);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (nativeError) {
|
||||||
|
const error = nativeError as AxiosError;
|
||||||
|
|
||||||
return response as TResponse;
|
|
||||||
} catch (e) {
|
|
||||||
const error = e as AxiosError;
|
|
||||||
if (
|
if (
|
||||||
error.response?.status === 400 &&
|
error.response?.status === 400 &&
|
||||||
error.response?.data?.message === "refreshToken is empty"
|
(error.response?.data as ExtendedAxiosResponse)?.message ===
|
||||||
|
"refreshToken is empty"
|
||||||
) {
|
) {
|
||||||
cleanAuthTicketData();
|
cleanAuthTicketData();
|
||||||
clearAuthToken();
|
clearAuthToken();
|
||||||
@ -36,7 +42,7 @@ async function makeRequest<TRequest = unknown, TResponse = unknown>(
|
|||||||
clearQuizData();
|
clearQuizData();
|
||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
||||||
throw e;
|
|
||||||
|
throw nativeError;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
export default makeRequest;
|
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
import { parseAxiosError } from "@utils/parse-error";
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
|
type ActivatePromocodeRequest = { codeword: string } | { fastLink: string };
|
||||||
|
type ActivatePromocodeResponse = { greetings: string };
|
||||||
|
|
||||||
export async function activatePromocode(promocode: string) {
|
const API_URL = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
|
||||||
|
|
||||||
|
export const activatePromocode = async (promocode: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await makeRequest<
|
const response = await makeRequest<
|
||||||
{ codeword: string } | { fastLink: string },
|
ActivatePromocodeRequest,
|
||||||
{ greetings: string }
|
ActivatePromocodeResponse
|
||||||
>({
|
>({
|
||||||
url: apiUrl + "/activate",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
contentType: true,
|
url: API_URL + "/activate",
|
||||||
body: { codeword: promocode },
|
body: { codeword: promocode },
|
||||||
|
contentType: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.greetings;
|
return response.greetings;
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { CreateQuestionRequest } from "model/question/create";
|
import { CreateQuestionRequest } from "model/question/create";
|
||||||
import { RawQuestion } from "model/question/question";
|
import { RawQuestion } from "model/question/question";
|
||||||
import {
|
import {
|
||||||
@ -18,67 +18,136 @@ import {
|
|||||||
CopyQuestionResponse,
|
CopyQuestionResponse,
|
||||||
} from "@model/question/copy";
|
} from "@model/question/copy";
|
||||||
import { replaceSpacesToEmptyLines } from "../utils/replaceSpacesToEmptyLines";
|
import { replaceSpacesToEmptyLines } from "../utils/replaceSpacesToEmptyLines";
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/squiz";
|
const API_URL = process.env.REACT_APP_DOMAIN + "/squiz";
|
||||||
|
|
||||||
function createQuestion(body: CreateQuestionRequest) {
|
export const createQuestion = async (
|
||||||
return makeRequest<CreateQuestionRequest, RawQuestion>({
|
body: CreateQuestionRequest,
|
||||||
url: `${baseUrl}/question/create`,
|
): Promise<[RawQuestion | null, string?]> => {
|
||||||
body,
|
try {
|
||||||
method: "POST",
|
const createdQuestion = await makeRequest<
|
||||||
});
|
CreateQuestionRequest,
|
||||||
}
|
RawQuestion
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/question/create`,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
async function getQuestionList(body?: Partial<GetQuestionListRequest>) {
|
return [createdQuestion];
|
||||||
if (!body?.quiz_id) return null;
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
const response = await makeRequest<
|
return [null, error];
|
||||||
GetQuestionListRequest,
|
}
|
||||||
GetQuestionListResponse
|
};
|
||||||
>({
|
|
||||||
url: `${baseUrl}/question/getList`,
|
|
||||||
body: { ...defaultGetQuestionListBody, ...body },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
const clearArrayFromEmptySpaceBlaBlaValue = response.items?.map(
|
|
||||||
(question) => {
|
|
||||||
let data = question;
|
|
||||||
for (let key in question) {
|
|
||||||
const k = key as keyof RawQuestion;
|
|
||||||
//@ts-ignore
|
|
||||||
if (question[key] === " ") data[key] = "";
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return replaceSpacesToEmptyLines(clearArrayFromEmptySpaceBlaBlaValue);
|
const getQuestionList = async (
|
||||||
}
|
body?: Partial<GetQuestionListRequest>,
|
||||||
|
): Promise<[RawQuestion[] | null, string?]> => {
|
||||||
|
try {
|
||||||
|
if (!body?.quiz_id) return [null, "Квиз не найден"];
|
||||||
|
|
||||||
function editQuestion(body: EditQuestionRequest, signal?: AbortSignal) {
|
const response = await makeRequest<
|
||||||
return makeRequest<EditQuestionRequest, EditQuestionResponse>({
|
GetQuestionListRequest,
|
||||||
url: `${baseUrl}/question/edit`,
|
GetQuestionListResponse
|
||||||
body,
|
>({
|
||||||
method: "PATCH",
|
method: "POST",
|
||||||
signal,
|
url: `${API_URL}/question/getList`,
|
||||||
});
|
body: { ...defaultGetQuestionListBody, ...body },
|
||||||
}
|
});
|
||||||
|
|
||||||
function copyQuestion(questionId: number, quizId: number) {
|
const clearArrayFromEmptySpaceBlaBlaValue = response.items?.map(
|
||||||
return makeRequest<CopyQuestionRequest, CopyQuestionResponse>({
|
(question) => {
|
||||||
url: `${baseUrl}/question/copy`,
|
let data = question;
|
||||||
body: { id: questionId, quiz_id: quizId },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteQuestion(id: number) {
|
for (let key in question) {
|
||||||
return makeRequest<DeleteQuestionRequest, DeleteQuestionResponse>({
|
if (question[key as keyof RawQuestion] === " ") {
|
||||||
url: `${baseUrl}/question/delete`,
|
//@ts-ignore
|
||||||
body: { id },
|
data[key] = "";
|
||||||
method: "DELETE",
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
replaceSpacesToEmptyLines(clearArrayFromEmptySpaceBlaBlaValue) ?? null,
|
||||||
|
];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editQuestion = async (
|
||||||
|
body: EditQuestionRequest,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<[EditQuestionResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const editedQuestion = await makeRequest<
|
||||||
|
EditQuestionRequest,
|
||||||
|
EditQuestionResponse
|
||||||
|
>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/question/edit`,
|
||||||
|
body,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [editedQuestion];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyQuestion = async (
|
||||||
|
questionId: number,
|
||||||
|
quizId: number,
|
||||||
|
): Promise<[CopyQuestionResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const copiedQuestion = await makeRequest<
|
||||||
|
CopyQuestionRequest,
|
||||||
|
CopyQuestionResponse
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/question/copy`,
|
||||||
|
body: { id: questionId, quiz_id: quizId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [copiedQuestion];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteQuestion = async (
|
||||||
|
id: number,
|
||||||
|
): Promise<[DeleteQuestionResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const deletedQuestion = await makeRequest<
|
||||||
|
DeleteQuestionRequest,
|
||||||
|
DeleteQuestionResponse
|
||||||
|
>({
|
||||||
|
url: `${API_URL}/question/delete`,
|
||||||
|
body: { id },
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [deletedQuestion];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const questionApi = {
|
export const questionApi = {
|
||||||
create: createQuestion,
|
create: createQuestion,
|
||||||
|
198
src/api/quiz.ts
198
src/api/quiz.ts
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { defaultQuizConfig } from "@model/quizSettings";
|
import { defaultQuizConfig } from "@model/quizSettings";
|
||||||
import { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
|
import { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
|
||||||
import { CreateQuizRequest } from "model/quiz/create";
|
import { CreateQuizRequest } from "model/quiz/create";
|
||||||
@ -7,73 +7,157 @@ import { EditQuizRequest, EditQuizResponse } from "model/quiz/edit";
|
|||||||
import { GetQuizRequest, GetQuizResponse } from "model/quiz/get";
|
import { GetQuizRequest, GetQuizResponse } from "model/quiz/get";
|
||||||
import { GetQuizListRequest, GetQuizListResponse } from "model/quiz/getList";
|
import { GetQuizListRequest, GetQuizListResponse } from "model/quiz/getList";
|
||||||
import { RawQuiz } from "model/quiz/quiz";
|
import { RawQuiz } from "model/quiz/quiz";
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/squiz";
|
type AddedQuizImagesResponse = {
|
||||||
const imagesUrl = process.env.REACT_APP_DOMAIN + "/squizstorer";
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
|
||||||
function createQuiz(body?: Partial<CreateQuizRequest>) {
|
const API_URL = process.env.REACT_APP_DOMAIN + "/squiz";
|
||||||
return makeRequest<CreateQuizRequest, RawQuiz>({
|
const IMAGES_URL = process.env.REACT_APP_DOMAIN + "/squizstorer";
|
||||||
url: `${baseUrl}/quiz/create`,
|
|
||||||
body: { ...defaultCreateQuizBody, ...body },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getQuizList(body?: Partial<GetQuizListRequest>) {
|
export const createQuiz = async (
|
||||||
const response = await makeRequest<GetQuizListRequest, GetQuizListResponse>({
|
body?: Partial<CreateQuizRequest>,
|
||||||
url: `${baseUrl}/quiz/getList`,
|
): Promise<[RawQuiz | null, string?]> => {
|
||||||
body: { ...defaultGetQuizListBody, ...body },
|
try {
|
||||||
method: "POST",
|
const createdQuiz = await makeRequest<CreateQuizRequest, RawQuiz>({
|
||||||
});
|
method: "POST",
|
||||||
|
url: `${API_URL}/quiz/create`,
|
||||||
|
body: { ...defaultCreateQuizBody, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
return response.items;
|
return [createdQuiz];
|
||||||
}
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
function getQuiz(body?: Partial<GetQuizRequest>) {
|
return [null, error];
|
||||||
return makeRequest<GetQuizRequest, GetQuizResponse>({
|
}
|
||||||
url: `${baseUrl}/quiz/get`,
|
};
|
||||||
body: { ...defaultGetQuizBody, ...body },
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function editQuiz(body: EditQuizRequest, signal?: AbortSignal) {
|
export const getQuizList = async (
|
||||||
return makeRequest<EditQuizRequest, EditQuizResponse>({
|
body?: Partial<CreateQuizRequest>,
|
||||||
url: `${baseUrl}/quiz/edit`,
|
): Promise<[RawQuiz[] | null, string?]> => {
|
||||||
body,
|
try {
|
||||||
method: "PATCH",
|
const { items } = await makeRequest<
|
||||||
signal,
|
GetQuizListRequest,
|
||||||
});
|
GetQuizListResponse
|
||||||
}
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/quiz/getList`,
|
||||||
|
body: { ...defaultGetQuizListBody, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
function copyQuiz(id: number) {
|
return [items];
|
||||||
return makeRequest<CopyQuizRequest, CopyQuizResponse>({
|
} catch (nativeError) {
|
||||||
url: `${baseUrl}/quiz/copy`,
|
const [error] = parseAxiosError(nativeError);
|
||||||
body: { id },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteQuiz(id: number) {
|
return [null, error];
|
||||||
return makeRequest<DeleteQuizRequest, DeleteQuizResponse>({
|
}
|
||||||
url: `${baseUrl}/quiz/delete`,
|
};
|
||||||
body: { id },
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addQuizImages(quizId: number, image: Blob) {
|
export const getQuiz = async (
|
||||||
const formData = new FormData();
|
body?: Partial<GetQuizRequest>,
|
||||||
|
): Promise<[GetQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const quiz = await makeRequest<GetQuizRequest, GetQuizResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/quiz/get`,
|
||||||
|
body: { ...defaultGetQuizBody, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
formData.append("quiz", quizId.toString());
|
return [quiz];
|
||||||
formData.append("image", image);
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return makeRequest<FormData, { [key: string]: string }>({
|
return [null, error];
|
||||||
url: `${imagesUrl}/quiz/putImages`,
|
}
|
||||||
body: formData,
|
};
|
||||||
method: "PUT",
|
|
||||||
});
|
export const editQuiz = async (
|
||||||
}
|
body: EditQuizRequest,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<[EditQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const editedQuiz = await makeRequest<EditQuizRequest, EditQuizResponse>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/quiz/edit`,
|
||||||
|
body,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [editedQuiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyQuiz = async (
|
||||||
|
id: number,
|
||||||
|
): Promise<[EditQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const copiedQuiz = await makeRequest<CopyQuizRequest, CopyQuizResponse>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/quiz/copy`,
|
||||||
|
body: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [copiedQuiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteQuiz = async (
|
||||||
|
id: number,
|
||||||
|
): Promise<[DeleteQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const deletedQuiz = await makeRequest<
|
||||||
|
DeleteQuizRequest,
|
||||||
|
DeleteQuizResponse
|
||||||
|
>({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `${API_URL}/quiz/delete`,
|
||||||
|
body: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [deletedQuiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addQuizImages = async (
|
||||||
|
quizId: number,
|
||||||
|
image: Blob,
|
||||||
|
): Promise<[AddedQuizImagesResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append("quiz", quizId.toString());
|
||||||
|
formData.append("image", image);
|
||||||
|
|
||||||
|
const addedQuizImages = await makeRequest<
|
||||||
|
FormData,
|
||||||
|
AddedQuizImagesResponse
|
||||||
|
>({
|
||||||
|
url: `${IMAGES_URL}/quiz/putImages`,
|
||||||
|
body: formData,
|
||||||
|
method: "PUT",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [addedQuizImages];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, error];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const quizApi = {
|
export const quizApi = {
|
||||||
create: createQuiz,
|
create: createQuiz,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { RawResult } from "@model/result/result";
|
import { RawResult } from "@model/result/result";
|
||||||
|
|
||||||
interface IResultListBody {
|
interface IResultListBody {
|
||||||
@ -29,47 +29,47 @@ export interface IAnswerResult {
|
|||||||
question_id: number;
|
question_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getResultList(quizId: number, page: number, body: any) {
|
const API_URL = process.env.REACT_APP_DOMAIN + `/squiz`;
|
||||||
|
|
||||||
|
const getResultList = async (quizId: number, page: number, body: any) => {
|
||||||
return makeRequest<IResultListBody, RawResult>({
|
return makeRequest<IResultListBody, RawResult>({
|
||||||
url: process.env.REACT_APP_DOMAIN + `/squiz/results/getResults/${quizId}`,
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/results/getResults/${quizId}`,
|
||||||
body: { page: page, limit: 10, ...body },
|
body: { page: page, limit: 10, ...body },
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function deleteResult(resultId: number) {
|
const deleteResult = async (resultId: number) => {
|
||||||
return makeRequest<unknown, unknown>({
|
return makeRequest<unknown, unknown>({
|
||||||
url: process.env.REACT_APP_DOMAIN + `/squiz/results/delete/${resultId}`,
|
|
||||||
body: {},
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: `${API_URL}/results/delete/${resultId}`,
|
||||||
|
body: {},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function obsolescenceResult(idResultArray: number[]) {
|
const obsolescenceResult = (idResultArray: number[]) => {
|
||||||
return makeRequest<unknown, unknown>({
|
return makeRequest<unknown, unknown>({
|
||||||
url: process.env.REACT_APP_DOMAIN + `/squiz/result/seen`,
|
|
||||||
body: {
|
|
||||||
answers: idResultArray,
|
|
||||||
},
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/result/seen`,
|
||||||
|
body: { answers: idResultArray },
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function getAnswerResultList(resultId: number) {
|
const getAnswerResultList = (resultId: number) => {
|
||||||
return makeRequest<unknown, IAnswerResult[]>({
|
return makeRequest<unknown, IAnswerResult[]>({
|
||||||
url: process.env.REACT_APP_DOMAIN + `/squiz/result/${resultId}`,
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: `${API_URL}/result/${resultId}`,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function AnswerResultListEx(quizId: number, body: any) {
|
const AnswerResultListEx = (quizId: number, body: any) => {
|
||||||
return makeRequest<unknown, unknown>({
|
return makeRequest<unknown, unknown>({
|
||||||
responseType: "blob",
|
|
||||||
url: process.env.REACT_APP_DOMAIN + `/squiz/results/${quizId}/export`,
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/results/${quizId}/export`,
|
||||||
body: body,
|
body: body,
|
||||||
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const resultApi = {
|
export const resultApi = {
|
||||||
getList: getResultList,
|
getList: getResultList,
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
import { parseAxiosError } from "@utils/parse-error";
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/squiz/statistic";
|
|
||||||
|
|
||||||
export type DevicesResponse = {
|
export type DevicesResponse = {
|
||||||
Device: Record<string, number>;
|
Device: Record<string, number>;
|
||||||
OS: Record<string, number>;
|
OS: Record<string, number>;
|
||||||
@ -29,6 +27,8 @@ type TRequest = {
|
|||||||
from: number;
|
from: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_URL = process.env.REACT_APP_DOMAIN + "/squiz/statistic";
|
||||||
|
|
||||||
export const getDevices = async (
|
export const getDevices = async (
|
||||||
quizId: string,
|
quizId: string,
|
||||||
to: number,
|
to: number,
|
||||||
@ -37,7 +37,7 @@ export const getDevices = async (
|
|||||||
try {
|
try {
|
||||||
const devicesResponse = await makeRequest<TRequest, DevicesResponse>({
|
const devicesResponse = await makeRequest<TRequest, DevicesResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/devices`,
|
url: `${API_URL}/${quizId}/devices`,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
body: { to, from },
|
body: { to, from },
|
||||||
});
|
});
|
||||||
@ -58,7 +58,7 @@ export const getGeneral = async (
|
|||||||
try {
|
try {
|
||||||
const generalResponse = await makeRequest<TRequest, GeneralResponse>({
|
const generalResponse = await makeRequest<TRequest, GeneralResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/general`,
|
url: `${API_URL}/${quizId}/general`,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
body: { to, from },
|
body: { to, from },
|
||||||
});
|
});
|
||||||
@ -79,7 +79,7 @@ export const getQuestions = async (
|
|||||||
try {
|
try {
|
||||||
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
|
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/questions`,
|
url: `${API_URL}/${quizId}/questions`,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
body: { to, from },
|
body: { to, from },
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { parseAxiosError } from "../utils/parse-error";
|
import { parseAxiosError } from "../utils/parse-error";
|
||||||
|
|
||||||
import { SendTicketMessageRequest } from "@frontend/kitui";
|
import { SendTicketMessageRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/heruvym";
|
const API_URL = process.env.REACT_APP_DOMAIN + "/heruvym";
|
||||||
|
|
||||||
export async function sendTicketMessage(
|
export const sendTicketMessage = async (
|
||||||
ticketId: string,
|
ticketId: string,
|
||||||
message: string,
|
message: string,
|
||||||
): Promise<[null, string?]> {
|
): Promise<[null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const sendTicketMessageResponse = await makeRequest<
|
const sendTicketMessageResponse = await makeRequest<
|
||||||
SendTicketMessageRequest,
|
SendTicketMessageRequest,
|
||||||
null
|
null
|
||||||
>({
|
>({
|
||||||
url: `${apiUrl}/send`,
|
url: `${API_URL}/send`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
||||||
@ -26,12 +26,12 @@ export async function sendTicketMessage(
|
|||||||
|
|
||||||
return [null, `Не удалось отправить сообщение. ${error}`];
|
return [null, `Не удалось отправить сообщение. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function shownMessage(id: string): Promise<[null, string?]> {
|
export const shownMessage = async (id: string): Promise<[null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
||||||
url: apiUrl + "/shown",
|
url: `${API_URL}/shown`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
body: { id },
|
body: { id },
|
||||||
@ -43,4 +43,4 @@ export async function shownMessage(id: string): Promise<[null, string?]> {
|
|||||||
|
|
||||||
return [null, `Не удалось прочесть сообщение. ${error}`];
|
return [null, `Не удалось прочесть сообщение. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -4,10 +4,15 @@ interface Props {
|
|||||||
color?: string;
|
color?: string;
|
||||||
bgcolor?: string;
|
bgcolor?: string;
|
||||||
marL?: string;
|
marL?: string;
|
||||||
width?: string
|
width?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CopyIcon({ color, bgcolor, marL, width = "36px" }: Props) {
|
export default function CopyIcon({
|
||||||
|
color,
|
||||||
|
bgcolor,
|
||||||
|
marL,
|
||||||
|
width = "36px",
|
||||||
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -68,13 +68,16 @@ export default function Analytics() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getData = async (): Promise<void> => {
|
const getData = async (): Promise<void> => {
|
||||||
try {
|
if (editQuizId !== null) {
|
||||||
if (editQuizId !== null) {
|
const [gottenQuizes, gottenQuizesError] = await quizApi.getList();
|
||||||
const gottenQuizes = await quizApi.getList();
|
|
||||||
setQuizes(gottenQuizes);
|
if (gottenQuizesError) {
|
||||||
|
console.error("Не удалось получить квизы", gottenQuizesError);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Не удалось получить квизы", error);
|
setQuizes(gottenQuizes);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,7 +182,9 @@ export default function Analytics() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
value={from}
|
value={from}
|
||||||
onChange={(date) => {setFrom(date ? date.startOf("day") : moment())}}
|
onChange={(date) => {
|
||||||
|
setFrom(date ? date.startOf("day") : moment());
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
@ -222,7 +227,9 @@ export default function Analytics() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
value={to}
|
value={to}
|
||||||
onChange={(date) => {setTo(date ? date.endOf("day") : moment())}}
|
onChange={(date) => {
|
||||||
|
setTo(date ? date.endOf("day") : moment());
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -122,7 +122,7 @@ export const DesignFilling = ({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
height: "calc(100vh - 300px)",
|
height: "calc(100vh - 300px)",
|
||||||
mb: "76px"
|
mb: "76px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
@ -158,11 +158,14 @@ export default function QuizInstallationCard() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
edge="end"
|
edge="end"
|
||||||
sx={{ marginTop: "22px" }}
|
sx={{ marginTop: "22px" }}
|
||||||
onClick={() => navigator.clipboard.writeText( // TODO
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
// TODO
|
||||||
document.getElementById(
|
document.getElementById(
|
||||||
"outlined-multiline-static"
|
"outlined-multiline-static",
|
||||||
).value
|
).value,
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<CopyIcon
|
<CopyIcon
|
||||||
color={"#ffffff"}
|
color={"#ffffff"}
|
||||||
|
@ -136,8 +136,7 @@ export const VKPixelInstruction = () => {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<Typography>
|
<Typography>
|
||||||
• Посетитель отправил заявку с заполненным полем Х:
|
• Посетитель отправил заявку с заполненным полем Х:
|
||||||
<b>penaquiz-formfield-X</b>, где X — одно из полей.
|
<b>penaquiz-formfield-X</b>, где X — одно из полей. Например,
|
||||||
Например,
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<List>
|
<List>
|
||||||
@ -163,7 +162,8 @@ export const VKPixelInstruction = () => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<Typography>
|
<Typography>
|
||||||
<b>penaquiz</b>-formfield-text (это будет кастомное поле, которое вы настроили сами)
|
<b>penaquiz</b>-formfield-text (это будет кастомное поле,
|
||||||
|
которое вы настроили сами)
|
||||||
</Typography>
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
@ -132,8 +132,7 @@ export const YandexInstruction = () => {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<Typography>
|
<Typography>
|
||||||
• Посетитель отправил заявку с заполненным полем Х:
|
• Посетитель отправил заявку с заполненным полем Х:
|
||||||
<b>penaquiz-formfield-X</b>, где X — одно из полей.
|
<b>penaquiz-formfield-X</b>, где X — одно из полей. Например,
|
||||||
Например,
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<List>
|
<List>
|
||||||
@ -159,7 +158,8 @@ export const YandexInstruction = () => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<Typography>
|
<Typography>
|
||||||
<b>penaquiz</b>-formfield-text (это будет кастомное поле, которое вы настроили сами)
|
<b>penaquiz</b>-formfield-text (это будет кастомное поле,
|
||||||
|
которое вы настроили сами)
|
||||||
</Typography>
|
</Typography>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
@ -168,10 +168,7 @@ export default function HowItWorks() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon21 />
|
<Icon21 />
|
||||||
<Typography fontSize="18px">
|
<Typography fontSize="18px"> на сайте</Typography>
|
||||||
{" "}
|
|
||||||
на сайте
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -99,283 +99,290 @@ const QuestionPageCardTitle = memo<Props>(function ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: isMobile ? "10px" : "20px 10px 20px 20px",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: isMobile && isExpanded ? "wrap" : "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
variant="standard"
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
maxWidth: isTablet ? "549px" : "640px",
|
||||||
|
width: "100%",
|
||||||
|
marginRight: isMobile ? "0px" : "16.1px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexBasis: isMobile && isExpanded ? "calc(100% - 30px)" : null,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
id="questionTitle"
|
||||||
|
value={title}
|
||||||
|
placeholder={"Заголовок вопроса"}
|
||||||
|
onChange={({ target }) => setTitle(target.value || " ")}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
inputProps={{
|
||||||
|
maxLength: maxLengthTextField,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<Box>
|
||||||
|
<InputAdornment
|
||||||
|
ref={anchorRef}
|
||||||
|
position="start"
|
||||||
|
sx={{ cursor: "pointer" }}
|
||||||
|
onClick={() => setOpen((isOpened) => !isOpened)}
|
||||||
|
>
|
||||||
|
{IconAndrom(isExpanded, questionType)}
|
||||||
|
</InputAdornment>
|
||||||
|
<ChooseAnswerModal
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
anchorRef={anchorRef}
|
||||||
|
questionId={questionId}
|
||||||
|
questionContentId={questionContentId}
|
||||||
|
questionType={questionType}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
endAdornment: isTextFieldtActive &&
|
||||||
|
title.length >= maxLengthTextField - 7 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
marginTop: "5px",
|
||||||
|
marginLeft: "auto",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "-28px",
|
||||||
|
right: "0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography fontSize="14px">{title.length}</Typography>
|
||||||
|
<span>/</span>
|
||||||
|
<Typography fontSize="14px">
|
||||||
|
{maxLengthTextField}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
color: "#000000",
|
||||||
|
backgroundColor: isExpanded
|
||||||
|
? theme.palette.background.default
|
||||||
|
: "transparent",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderWidth: "1px !important",
|
||||||
|
border: !isExpanded ? "none" : null,
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-input::placeholder": {
|
||||||
|
color: "#4D4D4D",
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<IconButton
|
||||||
|
disableRipple
|
||||||
|
sx={{
|
||||||
|
order: isMobile && isExpanded ? "0" : "1",
|
||||||
|
padding: isMobile ? "0" : "0 5px",
|
||||||
|
right: isMobile ? "0" : null,
|
||||||
|
bottom: isMobile ? "0" : null,
|
||||||
|
marginLeft: !isMobile && isExpanded ? "10px" : null,
|
||||||
|
}}
|
||||||
|
{...draggableProps}
|
||||||
|
onMouseDown={collapseAllQuestions}
|
||||||
|
onTouchStart={collapseAllQuestions}
|
||||||
|
>
|
||||||
|
<PointsIcon style={{ color: "#4D4D4D", fontSize: "30px" }} />
|
||||||
|
</IconButton>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{ padding: "0", margin: "5px" }}
|
||||||
|
disableRipple
|
||||||
|
data-cy="expand-question"
|
||||||
|
onClick={() => toggleExpandQuestion(questionId)}
|
||||||
|
>
|
||||||
|
{isExpanded ? (
|
||||||
|
<ArrowDownIcon
|
||||||
|
style={{
|
||||||
|
width: "18px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ExpandLessIcon
|
||||||
|
sx={{
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fill: theme.palette.brightPurple.main,
|
||||||
|
background: "#FFF",
|
||||||
|
borderRadius: "6px",
|
||||||
|
height: "30px",
|
||||||
|
width: "30px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
{isExpanded ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
height: "30px",
|
||||||
|
borderRight: "solid 1px #4D4D4D",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{ padding: "0" }}
|
||||||
|
onClick={() => copyQuestion(questionId, quizId)}
|
||||||
|
>
|
||||||
|
<CopyIcon style={{ color: theme.palette.brightPurple.main }} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "6px",
|
||||||
|
padding: "0",
|
||||||
|
margin: "0 5px 0 10px",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (questionType === null) {
|
||||||
|
deleteQuestion(questionId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (questionHasParent) {
|
||||||
|
setOpenDelete(true);
|
||||||
|
} else {
|
||||||
|
deleteQuestionWithTimeout(questionId, () =>
|
||||||
|
DeleteFunction(questionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-cy="delete-question"
|
||||||
|
>
|
||||||
|
<DeleteIcon
|
||||||
|
style={{ color: theme.palette.brightPurple.main }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
padding: "30px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
background: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
||||||
|
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
||||||
|
потеряют данные ветвления. Вы уверены, что хотите удалить
|
||||||
|
вопрос?
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: "30px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ minWidth: "150px" }}
|
||||||
|
onClick={() => setOpenDelete(false)}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ minWidth: "150px" }}
|
||||||
|
onClick={() => {
|
||||||
|
deleteQuestionWithTimeout(questionId, () =>
|
||||||
|
DeleteFunction(questionId),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Подтвердить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{page !== null && (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: isMobile ? "10px" : "20px 10px 20px 20px",
|
justifyContent: "center",
|
||||||
flexDirection: "row",
|
height: "30px",
|
||||||
flexWrap: isMobile && isExpanded ? "wrap" : "nowrap",
|
width: "30px",
|
||||||
}}
|
marginLeft: "3px",
|
||||||
>
|
borderRadius: "50%",
|
||||||
<FormControl
|
fontSize: "16px",
|
||||||
variant="standard"
|
color: isExpanded ? theme.palette.brightPurple.main : "#FFF",
|
||||||
sx={{
|
background: isExpanded
|
||||||
p: 0,
|
? "#EEE4FC"
|
||||||
maxWidth: isTablet ? "549px" : "640px",
|
: theme.palette.brightPurple.main,
|
||||||
width: "100%",
|
}}
|
||||||
marginRight: isMobile ? "0px" : "16.1px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
flexBasis: isMobile && isExpanded ? "calc(100% - 30px)" : null,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<TextField
|
{page + 1}
|
||||||
id="questionTitle"
|
|
||||||
value={title}
|
|
||||||
placeholder={"Заголовок вопроса"}
|
|
||||||
onChange={({ target }) => setTitle(target.value || " ")}
|
|
||||||
onFocus={handleInputFocus}
|
|
||||||
onBlur={handleInputBlur}
|
|
||||||
inputProps={{
|
|
||||||
maxLength: maxLengthTextField,
|
|
||||||
}}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<Box>
|
|
||||||
<InputAdornment
|
|
||||||
ref={anchorRef}
|
|
||||||
position="start"
|
|
||||||
sx={{ cursor: "pointer" }}
|
|
||||||
onClick={() => setOpen((isOpened) => !isOpened)}
|
|
||||||
>
|
|
||||||
{IconAndrom(isExpanded, questionType)}
|
|
||||||
</InputAdornment>
|
|
||||||
<ChooseAnswerModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
anchorRef={anchorRef}
|
|
||||||
questionId={questionId}
|
|
||||||
questionContentId={questionContentId}
|
|
||||||
questionType={questionType}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
endAdornment: isTextFieldtActive &&
|
|
||||||
title.length >= maxLengthTextField - 7 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
marginTop: "5px",
|
|
||||||
marginLeft: "auto",
|
|
||||||
position: "absolute",
|
|
||||||
bottom: "-28px",
|
|
||||||
right: "0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography fontSize="14px">{title.length}</Typography>
|
|
||||||
<span>/</span>
|
|
||||||
<Typography fontSize="14px">{maxLengthTextField}</Typography>
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
margin: isMobile ? "10px 0" : 0,
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
color: "#000000",
|
|
||||||
backgroundColor: isExpanded
|
|
||||||
? theme.palette.background.default
|
|
||||||
: "transparent",
|
|
||||||
height: "48px",
|
|
||||||
borderRadius: "10px",
|
|
||||||
".MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderWidth: "1px !important",
|
|
||||||
border: !isExpanded ? "none" : null,
|
|
||||||
},
|
|
||||||
"& .MuiInputBase-input::placeholder": {
|
|
||||||
color: "#4D4D4D",
|
|
||||||
opacity: 0.8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<IconButton
|
|
||||||
disableRipple
|
|
||||||
sx={{
|
|
||||||
order: isMobile && isExpanded ? "0" : "1",
|
|
||||||
padding: isMobile ? "0" : "0 5px",
|
|
||||||
right: isMobile ? "0" : null,
|
|
||||||
bottom: isMobile ? "0" : null,
|
|
||||||
marginLeft: !isMobile && isExpanded ? "10px" : null,
|
|
||||||
}}
|
|
||||||
{...draggableProps}
|
|
||||||
onMouseDown={collapseAllQuestions}
|
|
||||||
onTouchStart={collapseAllQuestions}
|
|
||||||
>
|
|
||||||
<PointsIcon style={{ color: "#4D4D4D", fontSize: "30px" }} />
|
|
||||||
</IconButton>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
width: isMobile ? "100%" : "auto",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
sx={{ padding: "0", margin: "5px" }}
|
|
||||||
disableRipple
|
|
||||||
data-cy="expand-question"
|
|
||||||
onClick={() => toggleExpandQuestion(questionId)}
|
|
||||||
>
|
|
||||||
{isExpanded ? (
|
|
||||||
<ArrowDownIcon
|
|
||||||
style={{
|
|
||||||
width: "18px",
|
|
||||||
color: "#4D4D4D",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ExpandLessIcon
|
|
||||||
sx={{
|
|
||||||
boxSizing: "border-box",
|
|
||||||
fill: theme.palette.brightPurple.main,
|
|
||||||
background: "#FFF",
|
|
||||||
borderRadius: "6px",
|
|
||||||
height: "30px",
|
|
||||||
width: "30px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</IconButton>
|
|
||||||
{isExpanded ? (
|
|
||||||
<></>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
height: "30px",
|
|
||||||
borderRight: "solid 1px #4D4D4D",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
sx={{ padding: "0" }}
|
|
||||||
onClick={() => copyQuestion(questionId, quizId)}
|
|
||||||
>
|
|
||||||
<CopyIcon style={{ color: theme.palette.brightPurple.main }} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
borderRadius: "6px",
|
|
||||||
padding: "0",
|
|
||||||
margin: "0 5px 0 10px",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (questionType === null) {
|
|
||||||
deleteQuestion(questionId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (questionHasParent) {
|
|
||||||
setOpenDelete(true);
|
|
||||||
} else {
|
|
||||||
deleteQuestionWithTimeout(questionId, () =>
|
|
||||||
DeleteFunction(questionId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
data-cy="delete-question"
|
|
||||||
>
|
|
||||||
<DeleteIcon style={{ color: theme.palette.brightPurple.main }} />
|
|
||||||
</IconButton>
|
|
||||||
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
padding: "30px",
|
|
||||||
borderRadius: "10px",
|
|
||||||
background: "#FFFFFF",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
|
||||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
|
||||||
потеряют данные ветвления. Вы уверены, что хотите удалить
|
|
||||||
вопрос?
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
marginTop: "30px",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
gap: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{ minWidth: "150px" }}
|
|
||||||
onClick={() => setOpenDelete(false)}
|
|
||||||
>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{ minWidth: "150px" }}
|
|
||||||
onClick={() => {
|
|
||||||
deleteQuestionWithTimeout(questionId, () =>
|
|
||||||
DeleteFunction(questionId),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Подтвердить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{page !== null && (
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
height: "30px",
|
|
||||||
width: "30px",
|
|
||||||
marginLeft: "3px",
|
|
||||||
borderRadius: "50%",
|
|
||||||
fontSize: "16px",
|
|
||||||
color: isExpanded ? theme.palette.brightPurple.main : "#FFF",
|
|
||||||
background: isExpanded
|
|
||||||
? "#EEE4FC"
|
|
||||||
: theme.palette.brightPurple.main,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{page + 1}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{questionType !== null &&
|
</Box>
|
||||||
<Box sx={{
|
{questionType !== null && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexWrap: isMobile ? "wrap" : undefined,
|
flexWrap: isMobile ? "wrap" : undefined,
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
padding: "0 20px 20px 20px"
|
padding: "0 20px 20px 20px",
|
||||||
}}>
|
}}
|
||||||
<Typography>ID Вопроса</Typography>
|
>
|
||||||
<Typography
|
<Typography>ID Вопроса</Typography>
|
||||||
id={"id-copy"}
|
<Typography id={"id-copy"}>{questionBackendId}</Typography>
|
||||||
>{questionBackendId}</Typography>
|
<IconButton
|
||||||
<IconButton
|
edge="end"
|
||||||
edge="end"
|
onClick={() =>
|
||||||
onClick={() => navigator.clipboard.writeText(document.querySelector("#id-copy").innerText)
|
navigator.clipboard.writeText(
|
||||||
}
|
document.querySelector("#id-copy").innerText,
|
||||||
>
|
)
|
||||||
<CopyIconPurple
|
}
|
||||||
color={"#ffffff"}
|
>
|
||||||
width={"30px"}
|
<CopyIconPurple
|
||||||
bgcolor={theme.palette.brightPurple.main}
|
color={"#ffffff"}
|
||||||
/>
|
width={"30px"}
|
||||||
</IconButton>
|
bgcolor={theme.palette.brightPurple.main}
|
||||||
</Box>}
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -13,26 +13,39 @@ export const useGetData = (filterNew: string, filterDate: string): void => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getData = async (): Promise<void> => {
|
const getData = async (): Promise<void> => {
|
||||||
try {
|
if (editQuizId !== null) {
|
||||||
if (editQuizId !== null) {
|
const [quizes, quizesError] = await quizApi.getList();
|
||||||
const quizes = await quizApi.getList();
|
|
||||||
setQuizes(quizes);
|
|
||||||
|
|
||||||
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
if (quizesError) {
|
||||||
setQuestions(questions);
|
console.error(
|
||||||
|
"An error occurred while receiving data: ",
|
||||||
const result = await resultApi.getList(
|
quizesError,
|
||||||
editQuizId,
|
|
||||||
0,
|
|
||||||
parseFilters(filterNew, filterDate),
|
|
||||||
);
|
);
|
||||||
if (result.total_count === 0) {
|
|
||||||
console.error("No results found");
|
return;
|
||||||
}
|
|
||||||
setResults(result);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("An error occurred while receiving data: ", error);
|
setQuizes(quizes);
|
||||||
|
|
||||||
|
const [questions, questionsError] = await questionApi.getList({
|
||||||
|
quiz_id: editQuizId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (questionsError) {
|
||||||
|
return console.error(questionsError);
|
||||||
|
}
|
||||||
|
|
||||||
|
setQuestions(questions);
|
||||||
|
|
||||||
|
const result = await resultApi.getList(
|
||||||
|
editQuizId,
|
||||||
|
0,
|
||||||
|
parseFilters(filterNew, filterDate),
|
||||||
|
);
|
||||||
|
if (result.total_count === 0) {
|
||||||
|
console.error("No results found");
|
||||||
|
}
|
||||||
|
setResults(result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,16 +9,17 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
IconButton,
|
IconButton,
|
||||||
Paper,
|
Paper,
|
||||||
Button,
|
Button,
|
||||||
Typography,
|
Typography,
|
||||||
TextField,
|
TextField,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
FormControl,
|
FormControl,
|
||||||
Popover, InputAdornment,
|
Popover,
|
||||||
|
InputAdornment,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||||
|
|
||||||
@ -260,28 +261,31 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ p: "0 20px", pt: "10px" }}>
|
<Box sx={{ p: "0 20px", pt: "10px" }}>
|
||||||
<Box sx={{
|
<Box
|
||||||
display: "flex",
|
sx={{
|
||||||
alignItems: "center",
|
display: "flex",
|
||||||
gap: "10px",
|
alignItems: "center",
|
||||||
mb: "19px",
|
gap: "10px",
|
||||||
}}>
|
mb: "19px",
|
||||||
<Typography>ID результата</Typography>
|
}}
|
||||||
<Typography
|
>
|
||||||
id={"id-copy"}
|
<Typography>ID результата</Typography>
|
||||||
>{resultData.backendId}</Typography>
|
<Typography id={"id-copy"}>{resultData.backendId}</Typography>
|
||||||
<IconButton
|
<IconButton
|
||||||
edge="end"
|
edge="end"
|
||||||
onClick={() => navigator.clipboard.writeText(document.querySelector("#id-copy").innerText)
|
onClick={() =>
|
||||||
}
|
navigator.clipboard.writeText(
|
||||||
>
|
document.querySelector("#id-copy").innerText,
|
||||||
<CopyIcon
|
)
|
||||||
color={"#ffffff"}
|
}
|
||||||
width={"30px"}
|
>
|
||||||
bgcolor={theme.palette.brightPurple.main}
|
<CopyIcon
|
||||||
/>
|
color={"#ffffff"}
|
||||||
</IconButton>
|
width={"30px"}
|
||||||
</Box>
|
bgcolor={theme.palette.brightPurple.main}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
@ -2,7 +2,7 @@ import { logout } from "@api/auth";
|
|||||||
import { activatePromocode } from "@api/promocode";
|
import { activatePromocode } from "@api/promocode";
|
||||||
import type { Tariff } from "@frontend/kitui";
|
import type { Tariff } from "@frontend/kitui";
|
||||||
import { useToken } from "@frontend/kitui";
|
import { useToken } from "@frontend/kitui";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||||
import type { GetTariffsResponse } from "@model/tariff";
|
import type { GetTariffsResponse } from "@model/tariff";
|
||||||
import {
|
import {
|
||||||
@ -200,7 +200,10 @@ function TariffPage() {
|
|||||||
setDiscounts(discounts);
|
setDiscounts(discounts);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {if (error.message !== "" && typeof(error.message)==="string") enqueueSnackbar(error.message)})
|
.catch((error) => {
|
||||||
|
if (error.message !== "" && typeof error.message === "string")
|
||||||
|
enqueueSnackbar(error.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -63,7 +63,9 @@ export const createTariffElements = (
|
|||||||
lineHeight: "21px",
|
lineHeight: "21px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currencyFormatter.format(Math.trunc(priceBeforeDiscounts) / 100)}
|
{currencyFormatter.format(
|
||||||
|
Math.trunc(priceBeforeDiscounts) / 100,
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Typography
|
<Typography
|
||||||
@ -74,7 +76,9 @@ export const createTariffElements = (
|
|||||||
color: "#4D4D4D",
|
color: "#4D4D4D",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currencyFormatter.format(Math.trunc(priceAfterDiscounts) / 100)}
|
{currencyFormatter.format(
|
||||||
|
Math.trunc(priceAfterDiscounts) / 100,
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -57,9 +57,9 @@ export default function ViewPublicationPage() {
|
|||||||
if (questionsIsLoading) return null;
|
if (questionsIsLoading) return null;
|
||||||
|
|
||||||
if (!quiz) throw new Error("Quiz not found");
|
if (!quiz) throw new Error("Quiz not found");
|
||||||
if (!rawQuestions) throw new Error("Questions not found");
|
if (!rawQuestions?.[0]) throw new Error("Questions not found");
|
||||||
|
|
||||||
const questions = rawQuestions.map(rawQuestionToQuestion);
|
const questions = rawQuestions[0].map(rawQuestionToQuestion);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -68,7 +68,7 @@ export default function ViewPublicationPage() {
|
|||||||
>
|
>
|
||||||
<QuizAnswerer
|
<QuizAnswerer
|
||||||
quizSettings={{
|
quizSettings={{
|
||||||
cnt: rawQuestions?.length,
|
cnt: rawQuestions[0]?.length,
|
||||||
questions,
|
questions,
|
||||||
recentlyCompleted: false,
|
recentlyCompleted: false,
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -18,7 +18,7 @@ import { object, string } from "yup";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useUserStore } from "@root/user";
|
import { useUserStore } from "@root/user";
|
||||||
|
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { setAuthToken } from "@frontend/kitui";
|
import { setAuthToken } from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "@utils/parse-error";
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
interface Values {
|
interface Values {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { inCart } from "../../pages/Tariffs/Tariffs";
|
import { inCart } from "../../pages/Tariffs/Tariffs";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
||||||
import CopyIcon from "@icons/CopyIcon";
|
import CopyIcon from "@icons/CopyIcon";
|
||||||
|
@ -38,11 +38,11 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
|
|||||||
const { isTestServer } = useDomainDefine();
|
const { isTestServer } = useDomainDefine();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
const quizes = await quizApi.getList();
|
const [quizes] = await quizApi.getList();
|
||||||
setQuizes(quizes);
|
setQuizes(quizes);
|
||||||
|
|
||||||
if (editQuizId) {
|
if (editQuizId) {
|
||||||
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
const [questions] = await questionApi.getList({ quiz_id: editQuizId });
|
||||||
|
|
||||||
setQuestions(questions);
|
setQuestions(questions);
|
||||||
//Всегда должен существовать хоть 1 резулт - "line"
|
//Всегда должен существовать хоть 1 резулт - "line"
|
||||||
|
@ -58,12 +58,14 @@ export const sendContactForm = async (): Promise<string | null> => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
useContactFormStore.setState({ isSubmitDisabled: true });
|
useContactFormStore.setState({ isSubmitDisabled: true });
|
||||||
const response = await sendContactFormRequest({
|
const [_, error, status] = await sendContactFormRequest({
|
||||||
contact,
|
contact,
|
||||||
whoami: mail,
|
whoami: mail,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status !== 200) throw new Error(response.statusText);
|
if (status !== 200) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
useContactFormStore.setState({ ...initialState });
|
useContactFormStore.setState({ ...initialState });
|
||||||
|
|
||||||
|
@ -296,35 +296,36 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
|
|||||||
if (!q) return;
|
if (!q) return;
|
||||||
if (q.type === null)
|
if (q.type === null)
|
||||||
throw new Error("Cannot send update request for untyped question");
|
throw new Error("Cannot send update request for untyped question");
|
||||||
try {
|
const [response, editError] = await questionApi.edit(
|
||||||
const response = await questionApi.edit(
|
questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)),
|
||||||
questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)),
|
);
|
||||||
);
|
|
||||||
rollbackQuestions = useQuestionsStore.getState();
|
|
||||||
|
|
||||||
//Если мы делаем листочек веточкой - удаляем созданный к нему результ
|
|
||||||
const questionResult = useQuestionsStore
|
|
||||||
.getState()
|
|
||||||
.questions.find(
|
|
||||||
(questionResult) =>
|
|
||||||
questionResult.type === "result" &&
|
|
||||||
questionResult.content.rule.parentId === q.content.id,
|
|
||||||
);
|
|
||||||
if (questionResult && q.content.rule.default.length !== 0)
|
|
||||||
deleteQuestion(questionResult.quizId);
|
|
||||||
|
|
||||||
if (q.backendId !== response.updated) {
|
|
||||||
console.warn(
|
|
||||||
`Question backend id has changed from ${q.backendId} to ${response.updated}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (isAxiosCanceledError(error)) return;
|
|
||||||
|
|
||||||
|
if (editError) {
|
||||||
useQuestionsStore.setState(rollbackQuestions);
|
useQuestionsStore.setState(rollbackQuestions);
|
||||||
|
|
||||||
devlog("Error editing question", { error, questionId });
|
devlog("Error editing question", { editError, questionId });
|
||||||
enqueueSnackbar("Не удалось сохранить вопрос");
|
enqueueSnackbar(editError);
|
||||||
|
}
|
||||||
|
|
||||||
|
rollbackQuestions = useQuestionsStore.getState();
|
||||||
|
|
||||||
|
//Если мы делаем листочек веточкой - удаляем созданный к нему результ
|
||||||
|
const questionResult = useQuestionsStore
|
||||||
|
.getState()
|
||||||
|
.questions.find(
|
||||||
|
(questionResult) =>
|
||||||
|
questionResult.type === "result" &&
|
||||||
|
questionResult.content.rule.parentId === q.content.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (questionResult && q.content.rule.default.length !== 0) {
|
||||||
|
deleteQuestion(String(questionResult.quizId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.backendId !== response?.updated) {
|
||||||
|
console.warn(
|
||||||
|
`Question backend id has changed from ${q.backendId} to ${response?.updated}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -430,28 +431,32 @@ export const uploadQuestionImage = async (
|
|||||||
.questions.find((q) => q.id === questionId);
|
.questions.find((q) => q.id === questionId);
|
||||||
if (!question || !quizQid) return;
|
if (!question || !quizQid) return;
|
||||||
|
|
||||||
try {
|
const [images, addImagesError] = await quizApi.addImages(
|
||||||
const response = await quizApi.addImages(question.quizId, blob);
|
question.quizId,
|
||||||
|
blob,
|
||||||
const values = Object.values(response);
|
);
|
||||||
if (values.length !== 1) {
|
|
||||||
console.warn("Error uploading image");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageId = values[0];
|
|
||||||
const imageUrl = `https://storage.yandexcloud.net/squizimages/${quizQid}/${imageId}`;
|
|
||||||
|
|
||||||
updateQuestion(questionId, (question) => {
|
|
||||||
updateFn(question, imageUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
return imageUrl;
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error uploading question image", error);
|
|
||||||
|
|
||||||
|
if (addImagesError || !images) {
|
||||||
|
devlog("Error uploading question image", addImagesError);
|
||||||
enqueueSnackbar("Не удалось загрузить изображение");
|
enqueueSnackbar("Не удалось загрузить изображение");
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const values = Object.values(images);
|
||||||
|
if (values.length !== 1) {
|
||||||
|
console.warn("Error uploading image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageId = values[0];
|
||||||
|
const imageUrl = `https://storage.yandexcloud.net/squizimages/${quizQid}/${imageId}`;
|
||||||
|
|
||||||
|
updateQuestion(questionId, (question) => {
|
||||||
|
updateFn(question, imageUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
return imageUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionInnerName = (questionId: string, name: string) => {
|
export const setQuestionInnerName = (questionId: string, name: string) => {
|
||||||
@ -489,40 +494,42 @@ export const createTypedQuestion = async (
|
|||||||
(q) => q.type === "result" || q.type === null,
|
(q) => q.type === "result" || q.type === null,
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
try {
|
const [createdQuestion, createdQuestionError] = await questionApi.create({
|
||||||
const createdQuestion = await questionApi.create({
|
quiz_id: question.quizId,
|
||||||
quiz_id: question.quizId,
|
type,
|
||||||
type,
|
title: question.title,
|
||||||
title: question.title,
|
description: question.description,
|
||||||
description: question.description,
|
page: questions.length - untypedOrResultQuestionsLength,
|
||||||
page: questions.length - untypedOrResultQuestionsLength,
|
required: false,
|
||||||
required: false,
|
content: JSON.stringify(defaultQuestionByType[type].content),
|
||||||
content: JSON.stringify(defaultQuestionByType[type].content),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setProducedState(
|
if (createdQuestionError || !createdQuestion) {
|
||||||
(state) => {
|
devlog("Error creating question", createdQuestionError);
|
||||||
const questionIndex = state.questions.findIndex(
|
enqueueSnackbar(createdQuestionError);
|
||||||
(q) => q.id === questionId,
|
|
||||||
);
|
|
||||||
if (questionIndex !== -1)
|
|
||||||
state.questions.splice(
|
|
||||||
questionIndex,
|
|
||||||
1,
|
|
||||||
rawQuestionToQuestion(createdQuestion),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "createTypedQuestion",
|
|
||||||
question,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
updateQuestionOrders();
|
return;
|
||||||
} catch (error) {
|
|
||||||
devlog("Error creating question", error);
|
|
||||||
enqueueSnackbar("Не удалось создать вопрос");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
const questionIndex = state.questions.findIndex(
|
||||||
|
(q) => q.id === questionId,
|
||||||
|
);
|
||||||
|
if (questionIndex !== -1)
|
||||||
|
state.questions.splice(
|
||||||
|
questionIndex,
|
||||||
|
1,
|
||||||
|
rawQuestionToQuestion(createdQuestion),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "createTypedQuestion",
|
||||||
|
question,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
updateQuestionOrders();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteQuestion = async (questionId: string) =>
|
export const deleteQuestion = async (questionId: string) =>
|
||||||
@ -537,16 +544,17 @@ export const deleteQuestion = async (questionId: string) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const [_, deleteError] = await questionApi.delete(question.backendId);
|
||||||
await questionApi.delete(question.backendId);
|
|
||||||
|
|
||||||
removeQuestion(questionId);
|
if (deleteError) {
|
||||||
|
devlog("Error deleting question", deleteError);
|
||||||
updateQuestionOrders();
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error deleting question", error);
|
|
||||||
enqueueSnackbar("Не удалось удалить вопрос");
|
enqueueSnackbar("Не удалось удалить вопрос");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeQuestion(questionId);
|
||||||
|
|
||||||
|
updateQuestionOrders();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const copyQuestion = async (questionId: string, quizId: number) => {
|
export const copyQuestion = async (questionId: string, quizId: number) => {
|
||||||
@ -587,39 +595,39 @@ export const copyQuestion = async (questionId: string, quizId: number) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const [copiedQuestionResult, copiedQuestionError] =
|
||||||
const { updated: newQuestionId } = await questionApi.copy(
|
await questionApi.copy(question.backendId, quizId);
|
||||||
question.backendId,
|
|
||||||
quizId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const copiedQuestion = structuredClone(question);
|
if (copiedQuestionError || !copiedQuestionResult) {
|
||||||
copiedQuestion.backendId = newQuestionId;
|
devlog("Error copying question", copiedQuestionError);
|
||||||
copiedQuestion.id = frontId;
|
enqueueSnackbar(copiedQuestionError);
|
||||||
copiedQuestion.content.id = frontId;
|
|
||||||
copiedQuestion.content.rule = {
|
|
||||||
main: [],
|
|
||||||
parentId: "",
|
|
||||||
default: "",
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
setProducedState(
|
return;
|
||||||
(state) => {
|
|
||||||
state.questions.push(copiedQuestion);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "copyQuestion",
|
|
||||||
questionId,
|
|
||||||
quizId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
updateQuestionOrders();
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error copying question", error);
|
|
||||||
enqueueSnackbar("Не удалось скопировать вопрос");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copiedQuestion = structuredClone(question);
|
||||||
|
copiedQuestion.backendId = copiedQuestionResult.updated;
|
||||||
|
copiedQuestion.id = frontId;
|
||||||
|
copiedQuestion.content.id = frontId;
|
||||||
|
copiedQuestion.content.rule = {
|
||||||
|
main: [],
|
||||||
|
parentId: "",
|
||||||
|
default: "",
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
state.questions.push(copiedQuestion);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "copyQuestion",
|
||||||
|
questionId,
|
||||||
|
quizId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
updateQuestionOrders();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -704,30 +712,30 @@ export const createResult = async (
|
|||||||
);
|
);
|
||||||
content.rule.parentId = parentContentId;
|
content.rule.parentId = parentContentId;
|
||||||
|
|
||||||
try {
|
const [createdQuestion, createdQuestionError] = await questionApi.create({
|
||||||
const createdQuestion: RawQuestion = await questionApi.create({
|
quiz_id: quizId,
|
||||||
quiz_id: quizId,
|
type: "result",
|
||||||
type: "result",
|
title: "",
|
||||||
title: "",
|
description: "",
|
||||||
description: "",
|
page: 101,
|
||||||
page: 101,
|
required: true,
|
||||||
required: true,
|
content: JSON.stringify(content),
|
||||||
content: JSON.stringify(content),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setProducedState(
|
if (createdQuestionError || !createdQuestion) {
|
||||||
(state) => {
|
throw new Error(createdQuestionError);
|
||||||
state.questions.push(rawQuestionToQuestion(createdQuestion));
|
|
||||||
},
|
devlog("Error creating question", createdQuestionError);
|
||||||
{
|
enqueueSnackbar(createdQuestionError);
|
||||||
type: "createBackResult",
|
|
||||||
createdQuestion,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return createdQuestion;
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error creating question", error);
|
|
||||||
enqueueSnackbar("Не удалось создать вопрос");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
state.questions.push(rawQuestionToQuestion(createdQuestion));
|
||||||
|
},
|
||||||
|
{ type: "createBackResult", createdQuestion },
|
||||||
|
);
|
||||||
|
|
||||||
|
return createdQuestion;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,7 @@ export function useQuestions() {
|
|||||||
["questions", quiz?.backendId],
|
["questions", quiz?.backendId],
|
||||||
([, id]) => questionApi.getList({ quiz_id: id }),
|
([, id]) => questionApi.getList({ quiz_id: id }),
|
||||||
{
|
{
|
||||||
onSuccess: setQuestions,
|
onSuccess: ([questions]) => setQuestions(questions),
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const message = isAxiosError<string>(error)
|
const message = isAxiosError<string>(error)
|
||||||
? error.response?.data ?? ""
|
? error.response?.data ?? ""
|
||||||
|
@ -155,43 +155,44 @@ export const updateQuiz = (
|
|||||||
|
|
||||||
clearTimeout(requestTimeoutId);
|
clearTimeout(requestTimeoutId);
|
||||||
requestTimeoutId = setTimeout(async () => {
|
requestTimeoutId = setTimeout(async () => {
|
||||||
requestQueue
|
requestQueue.enqueue(`updateQuiz-${quizId}`, async () => {
|
||||||
.enqueue(`updateQuiz-${quizId}`, async () => {
|
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||||
const quiz = useQuizStore
|
if (!quiz) return;
|
||||||
.getState()
|
|
||||||
.quizes.find((q) => q.id === quizId);
|
|
||||||
if (!quiz) return;
|
|
||||||
|
|
||||||
const response = await quizApi.edit(quizToEditQuizRequest(quiz));
|
const [editedQuiz, editedQuizError] = await quizApi.edit(
|
||||||
|
quizToEditQuizRequest(quiz),
|
||||||
|
);
|
||||||
|
|
||||||
setQuizBackendId(quizId, response.updated);
|
if (editedQuizError || !editedQuiz) {
|
||||||
setEditQuizId(response.updated);
|
devlog("Error editing quiz", editedQuizError, quizId);
|
||||||
})
|
enqueueSnackbar(editedQuizError);
|
||||||
.catch((error) => {
|
|
||||||
if (isAxiosCanceledError(error)) return;
|
|
||||||
|
|
||||||
devlog("Error editing quiz", error, quizId);
|
return;
|
||||||
enqueueSnackbar("Не удалось сохранить настройки quiz");
|
}
|
||||||
});
|
|
||||||
|
setQuizBackendId(quizId, editedQuiz.updated);
|
||||||
|
setEditQuizId(editedQuiz.updated);
|
||||||
|
});
|
||||||
}, REQUEST_DEBOUNCE);
|
}, REQUEST_DEBOUNCE);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createQuiz = async (navigate: NavigateFunction) =>
|
export const createQuiz = async (navigate: NavigateFunction) =>
|
||||||
requestQueue.enqueue("createQuiz", async () => {
|
requestQueue.enqueue("createQuiz", async () => {
|
||||||
try {
|
const [rawQuiz, createQuizError] = await quizApi.create();
|
||||||
const rawQuiz = await quizApi.create();
|
|
||||||
const quiz = rawQuizToQuiz(rawQuiz);
|
|
||||||
|
|
||||||
addQuiz(quiz);
|
if (createQuizError || !rawQuiz) {
|
||||||
setEditQuizId(quiz.backendId);
|
devlog("Error creating quiz", createQuizError);
|
||||||
navigate("/edit");
|
enqueueSnackbar(createQuizError);
|
||||||
createUntypedQuestion(rawQuiz.id);
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error creating quiz", error);
|
|
||||||
|
|
||||||
const message = getMessageFromFetchError(error) ?? "";
|
return;
|
||||||
enqueueSnackbar(`Не удалось создать quiz. ${message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const quiz = rawQuizToQuiz(rawQuiz);
|
||||||
|
|
||||||
|
addQuiz(quiz);
|
||||||
|
setEditQuizId(quiz.backendId);
|
||||||
|
navigate("/edit");
|
||||||
|
createUntypedQuestion(rawQuiz.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteQuiz = async (quizId: string) =>
|
export const deleteQuiz = async (quizId: string) =>
|
||||||
@ -199,16 +200,17 @@ export const deleteQuiz = async (quizId: string) =>
|
|||||||
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||||
if (!quiz) return;
|
if (!quiz) return;
|
||||||
|
|
||||||
try {
|
const [_, deleteQuizError] = await quizApi.delete(quiz.backendId);
|
||||||
await quizApi.delete(quiz.backendId);
|
|
||||||
|
|
||||||
removeQuiz(quizId);
|
if (deleteQuizError) {
|
||||||
} catch (error) {
|
devlog("Error deleting quiz", deleteQuizError);
|
||||||
devlog("Error deleting quiz", error);
|
|
||||||
|
|
||||||
const message = getMessageFromFetchError(error) ?? "";
|
enqueueSnackbar(deleteQuizError);
|
||||||
enqueueSnackbar(`Не удалось удалить quiz. ${message}`);
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeQuiz(quizId);
|
||||||
});
|
});
|
||||||
export const updateRootContentId = (quizId: string, id: string) => {
|
export const updateRootContentId = (quizId: string, id: string) => {
|
||||||
if (id.length === 0) {
|
if (id.length === 0) {
|
||||||
@ -248,30 +250,28 @@ export const copyQuiz = async (quizId: string) =>
|
|||||||
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||||
if (!quiz) return;
|
if (!quiz) return;
|
||||||
|
|
||||||
try {
|
const [copiedQuiz, copyError] = await quizApi.copy(quiz.backendId);
|
||||||
const { updated } = await quizApi.copy(quiz.backendId);
|
|
||||||
let newQuiz: Quiz = {
|
|
||||||
...quiz,
|
|
||||||
id: String(updated),
|
|
||||||
session_count: 0,
|
|
||||||
passed_count: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
setProducedState(
|
if (copyError || !copiedQuiz) {
|
||||||
(state) => {
|
devlog("Error copying quiz", copyError);
|
||||||
state.quizes.unshift(newQuiz);
|
enqueueSnackbar(copyError);
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "addQuiz",
|
|
||||||
quiz,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error copying quiz", error);
|
|
||||||
|
|
||||||
const message = getMessageFromFetchError(error) ?? "";
|
return;
|
||||||
enqueueSnackbar(`Не удалось скопировать quiz. ${message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newQuiz: Quiz = {
|
||||||
|
...quiz,
|
||||||
|
id: String(copiedQuiz.updated),
|
||||||
|
session_count: 0,
|
||||||
|
passed_count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
state.quizes.unshift(newQuiz);
|
||||||
|
},
|
||||||
|
{ type: "addQuiz", quiz },
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const uploadQuizImage = async (
|
export const uploadQuizImage = async (
|
||||||
@ -282,28 +282,32 @@ export const uploadQuizImage = async (
|
|||||||
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||||
if (!quiz) return;
|
if (!quiz) return;
|
||||||
|
|
||||||
try {
|
const [addedImages, addImagesError] = await quizApi.addImages(
|
||||||
const response = await quizApi.addImages(quiz.backendId, blob);
|
quiz.backendId,
|
||||||
|
blob,
|
||||||
|
);
|
||||||
|
|
||||||
const values = Object.values(response);
|
if (addImagesError || !addedImages) {
|
||||||
if (values.length !== 1) {
|
devlog("Error uploading quiz image", addImagesError);
|
||||||
console.warn("Error uploading image");
|
enqueueSnackbar(addImagesError);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageId = values[0];
|
return;
|
||||||
|
|
||||||
updateQuiz(quizId, (quiz) => {
|
|
||||||
updateFn(
|
|
||||||
quiz,
|
|
||||||
`https://storage.yandexcloud.net/squizimages/${quiz.qid}/${imageId}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error uploading quiz image", error);
|
|
||||||
|
|
||||||
enqueueSnackbar("Не удалось загрузить изображение");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const values = Object.values(addedImages);
|
||||||
|
if (values.length !== 1) {
|
||||||
|
console.warn("Error uploading image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageId = values[0];
|
||||||
|
|
||||||
|
updateQuiz(quizId, (quiz) => {
|
||||||
|
updateFn(
|
||||||
|
quiz,
|
||||||
|
`https://storage.yandexcloud.net/squizimages/${quiz.qid}/${imageId}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function setProducedState<A extends string | { type: unknown }>(
|
function setProducedState<A extends string | { type: unknown }>(
|
||||||
|
@ -11,7 +11,7 @@ export function useQuizes() {
|
|||||||
"quizes",
|
"quizes",
|
||||||
() => quizApi.getList(),
|
() => quizApi.getList(),
|
||||||
{
|
{
|
||||||
onSuccess: setQuizes,
|
onSuccess: ([quizes]) => setQuizes(quizes),
|
||||||
onError: (error: unknown) => {
|
onError: (error: unknown) => {
|
||||||
const message = isAxiosError<string>(error)
|
const message = isAxiosError<string>(error)
|
||||||
? error.response?.data ?? ""
|
? error.response?.data ?? ""
|
||||||
|
@ -96,13 +96,15 @@ export const ExportResults = async (
|
|||||||
parseFilters(filterNew, filterDate),
|
parseFilters(filterNew, filterDate),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(typeof data)
|
console.log(typeof data);
|
||||||
|
|
||||||
const blob = new Blob([data as BlobPart], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8" });
|
const blob = new Blob([data as BlobPart], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = window.URL.createObjectURL(data as Blob);
|
link.href = window.URL.createObjectURL(data as Blob);
|
||||||
console.log(link)
|
console.log(link);
|
||||||
link.download = `report_${new Date().getTime()}.xlsx`;
|
link.download = `report_${new Date().getTime()}.xlsx`;
|
||||||
link.click();
|
link.click();
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
import {useEffect, useLayoutEffect, useRef, useState} from "react";
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
import { Box, Button, Modal, Typography } from "@mui/material";
|
import { Box, Button, Modal, Typography } from "@mui/material";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { getDiscounts } from "@api/discounts";
|
import { getDiscounts } from "@api/discounts";
|
||||||
|
|
||||||
import {clearUserData, OriginalUserAccount, setUserAccount, useUserStore} from "@root/user";
|
import {
|
||||||
|
clearUserData,
|
||||||
|
OriginalUserAccount,
|
||||||
|
setUserAccount,
|
||||||
|
useUserStore,
|
||||||
|
} from "@root/user";
|
||||||
import { parseAxiosError } from "@utils/parse-error";
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
import { useUserAccountFetcher } from "../App";
|
import { useUserAccountFetcher } from "../App";
|
||||||
import type { Discount } from "@model/discounts";
|
import type { Discount } from "@model/discounts";
|
||||||
import {clearAuthToken, createUserAccount, devlog, getMessageFromFetchError} from "@frontend/kitui";
|
import {
|
||||||
import {useNavigate} from "react-router-dom";
|
clearAuthToken,
|
||||||
import {isAxiosError} from "axios";
|
createUserAccount,
|
||||||
|
devlog,
|
||||||
|
getMessageFromFetchError,
|
||||||
|
} from "@frontend/kitui";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { isAxiosError } from "axios";
|
||||||
|
|
||||||
export function CheckFastlink() {
|
export function CheckFastlink() {
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
@ -21,9 +31,7 @@ export function CheckFastlink() {
|
|||||||
const [discounts, setDiscounts] = useState<Discount[]>([]);
|
const [discounts, setDiscounts] = useState<Discount[]>([]);
|
||||||
const [askToChange, setAskToChange] = useState(false);
|
const [askToChange, setAskToChange] = useState(false);
|
||||||
const [promocode, setPromocode] = useState("");
|
const [promocode, setPromocode] = useState("");
|
||||||
console.log(
|
console.log(user.userAccount, user.customerAccount);
|
||||||
user.userAccount,
|
|
||||||
user.customerAccount)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const get = async () => {
|
const get = async () => {
|
||||||
if (!user.userId) {
|
if (!user.userId) {
|
||||||
@ -53,7 +61,11 @@ export function CheckFastlink() {
|
|||||||
contentType: true,
|
contentType: true,
|
||||||
body: { fastLink: promocode },
|
body: { fastLink: promocode },
|
||||||
});
|
});
|
||||||
enqueueSnackbar(response.greetings !== "" ? response.greetings : "Промокод успешно активирован");
|
enqueueSnackbar(
|
||||||
|
response.greetings !== ""
|
||||||
|
? response.greetings
|
||||||
|
: "Промокод успешно активирован",
|
||||||
|
);
|
||||||
localStorage.setItem("fl", "");
|
localStorage.setItem("fl", "");
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const responseAccount = await makeRequest<never, any>({
|
const responseAccount = await makeRequest<never, any>({
|
||||||
@ -63,8 +75,8 @@ export function CheckFastlink() {
|
|||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
})
|
});
|
||||||
setUserAccount(responseAccount)
|
setUserAccount(responseAccount);
|
||||||
mutate("discounts");
|
mutate("discounts");
|
||||||
return response.greetings;
|
return response.greetings;
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
@ -104,8 +116,12 @@ export function CheckFastlink() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [user.userId, discounts, user.customerAccount?.createdAt, user.userAccount?.created_at]);
|
}, [
|
||||||
|
user.userId,
|
||||||
|
discounts,
|
||||||
|
user.customerAccount?.createdAt,
|
||||||
|
user.userAccount?.created_at,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
useTicketMessages,
|
useTicketMessages,
|
||||||
useTicketsFetcher,
|
useTicketsFetcher,
|
||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import makeRequest from "@api/makeRequest";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import FloatingSupportChat from "./FloatingSupportChat";
|
import FloatingSupportChat from "./FloatingSupportChat";
|
||||||
import { useUserStore } from "@root/user";
|
import { useUserStore } from "@root/user";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
@ -123,7 +123,7 @@ export default () => {
|
|||||||
const message = getMessageFromFetchError(error);
|
const message = getMessageFromFetchError(error);
|
||||||
if (message) enqueueSnackbar(message);
|
if (message) enqueueSnackbar(message);
|
||||||
},
|
},
|
||||||
onFetchStateChange: () => { },
|
onFetchStateChange: () => {},
|
||||||
enabled: Boolean(user),
|
enabled: Boolean(user),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user