diff --git a/SurveyFrontend/src/App.tsx b/SurveyFrontend/src/App.tsx index 8acf6f3..fa8d8ba 100644 --- a/SurveyFrontend/src/App.tsx +++ b/SurveyFrontend/src/App.tsx @@ -1,17 +1,19 @@ import './App.css' -import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; +import {BrowserRouter, Route, Routes} from "react-router-dom"; import {SurveyCreateAndEditingPage} from "./pages/SurveyCreateAndEditingPage/SurveyCreateAndEditingPage.tsx"; import Survey from "./components/Survey/Survey.tsx"; import SettingSurvey from "./components/SettingSurvey/SettingSurvey.tsx"; import {MySurveysPage} from "./pages/MySurveysPage/MySurveysPage.tsx"; import {Results} from "./components/Results/Results.tsx"; import {MySurveyList} from "./components/MySurveyList/MySurveyList.tsx"; +import AuthForm from "./pages/AuthForm/AuthForm.tsx"; const App = () => { return( - } /> + } /> + } /> }> } /> @@ -27,6 +29,8 @@ const App = () => { } /> } /> + + } /> ); diff --git a/SurveyFrontend/src/api/AuthApi.ts b/SurveyFrontend/src/api/AuthApi.ts index 60e3a84..b46fff6 100644 --- a/SurveyFrontend/src/api/AuthApi.ts +++ b/SurveyFrontend/src/api/AuthApi.ts @@ -16,7 +16,17 @@ export const registerUser = async (data: IRegistrationData) => { const response = await fetch(`${BASE_URL}/auth/register`, { ...createRequestConfig('POST'), body: JSON.stringify(data), }) - return await handleResponse(response); + const responseData = await handleResponse(response); + + if (responseData.accessToken) { + localStorage.setItem("token", responseData.accessToken); + localStorage.setItem("user", JSON.stringify({ + firstName: data.firstName, + lastName: data.lastName + })); + } + + return responseData; } catch (error){ console.error("Registration error:", error); throw error; @@ -24,14 +34,30 @@ export const registerUser = async (data: IRegistrationData) => { } export const authUser = async (data: IAuthData) => { - try{ + try { const response = await fetch(`${BASE_URL}/auth/login`, { - ...createRequestConfig('POST'), body: JSON.stringify(data), - }) - return await handleResponse(response); - } - catch(error){ + ...createRequestConfig('POST'), + body: JSON.stringify(data), + }); + const responseData = await handleResponse(response); + console.log("Полный ответ сервера:", responseData); + + const token = responseData.accessToken || responseData.token; + if (token) { + localStorage.setItem("token", token); + const user = localStorage.getItem("user"); + if (!responseData.user && user) { + responseData.user = JSON.parse(user); + } + + if (responseData.user) { + localStorage.setItem("user", JSON.stringify(responseData.user)); + } + } + + return responseData; + } catch (error) { console.error("Login error:", error); throw error; } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/SurveyFrontend/src/api/BaseApi.ts b/SurveyFrontend/src/api/BaseApi.ts index 88861bb..3be4ee4 100644 --- a/SurveyFrontend/src/api/BaseApi.ts +++ b/SurveyFrontend/src/api/BaseApi.ts @@ -13,7 +13,8 @@ interface RequestConfig { * @returns Конфигурация для fetch-запроса */ const createRequestConfig = (method: string, isFormData: boolean = false): RequestConfig => { - const token = localStorage.getItem("accessToken"); + const token = localStorage.getItem("token"); + const config: RequestConfig = { method, headers: {}, @@ -37,14 +38,36 @@ const createRequestConfig = (method: string, isFormData: boolean = false): Reque * @param response Ответ от fetch * @returns Распарсенные данные или ошибку */ -const handleResponse = async (response: Response) => { - const data = await response.json(); +// const handleResponse = async (response: Response) => { +// const data = await response.json(); +// +// if (!response.ok) { +// throw new Error(data.message || "Произошла ошибка"); +// } +// +// return data; +// }; - if (!response.ok) { - throw new Error(data.message || "Произошла ошибка"); +const handleResponse = async (response: Response) => { + // Проверяем, есть ли контент в ответе + const responseText = await response.text(); + + if (!responseText) { + if (response.ok) { + return null; // Если ответ пустой, но статус 200, возвращаем null + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`); } - return data; + try { + const data = JSON.parse(responseText); + if (!response.ok) { + throw new Error(data.message || `HTTP ${response.status}`); + } + return data; + } catch (e) { + throw new Error(`Не удалось разобрать ответ сервера: ${e}`); + } }; export { BASE_URL, createRequestConfig, handleResponse }; \ No newline at end of file diff --git a/SurveyFrontend/src/api/QuestionApi.ts b/SurveyFrontend/src/api/QuestionApi.ts new file mode 100644 index 0000000..ba3c2a8 --- /dev/null +++ b/SurveyFrontend/src/api/QuestionApi.ts @@ -0,0 +1,29 @@ +import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts"; + +export interface INewQuestion{ + title: string; + questionType: string; + answerVariants: string[]; +} +// +// export interface IErrorQuestionResponse { +// +// } + +export const addNewQuestion = async (surveyId: number, question: INewQuestion) => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error("Токен отсутствует"); + } + + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions`, { + ...createRequestConfig('POST'), + body: JSON.stringify(question), + }) + return await handleResponse(response) + } catch (error){ + throw new Error(`Error when adding a new question: ${error}`); + } +} + diff --git a/SurveyFrontend/src/api/SurveyApi.ts b/SurveyFrontend/src/api/SurveyApi.ts new file mode 100644 index 0000000..95b6032 --- /dev/null +++ b/SurveyFrontend/src/api/SurveyApi.ts @@ -0,0 +1,117 @@ +import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts"; + +export interface ISurvey { + id: number; + title: string; + description: string; + createdBy: number; +} + +export interface INewSurvey{ + title: string; + description: string; +} + +/** + * getMySurveys - запрос на получение моих опросов + */ +export const getMySurveys = async (): Promise => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error("Токен отсутствует"); + } + + try { + const response = await fetch(`${BASE_URL}/surveys/my`, { + ...createRequestConfig('GET'), + }); + return await handleResponse(response); + } catch (error) { + console.error("Error receiving surveys:", error); + throw error; + } +} + +/** + * getAllSurvey - запрос на получение всех опросов + */ +export const getAllSurveys = async (): Promise => { + try{ + const response = await fetch(`${BASE_URL}/surveys`, { + ...createRequestConfig('GET'), + }) + return await handleResponse(response); + } catch (error) { + console.error("Error receiving surveys:", error); + throw error; + } +} + +/** + * postNewSurvey - добавление нового опроса + * @param survey + */ +export const postNewSurvey = async (survey: INewSurvey): Promise => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error("Токен отсутствует"); + } + + try{ + const response = await fetch(`${BASE_URL}/surveys`, { + ...createRequestConfig('POST'), + body: JSON.stringify(survey) + }) + + // return await handleResponse(response); + + if (response.status === 201) { + return await handleResponse(response); + } + throw new Error(`Ожидался код 201, получен ${response.status}`); + } catch (error) { + console.error(`Error when adding a new survey: ${error}`); + throw error; + } +} + +/** + * Запрос на получение опроса по заданному ID + * @param surveyId - ID опроса + */ +export const getSurveyById = async (surveyId: number): Promise => { + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}`, { + ...createRequestConfig('GET'), + }) + return await handleResponse(response); + } catch (error){ + console.error(`Error finding the survey by id: ${error}`); + throw error; + } +} + +/** + * Запрос на удаление опроса + * @param surveyId - ID выбранного опроса + */ +export const deleteSurvey = async (surveyId: string) => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error("Токен отсутствует"); + } + + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}`, { + ...createRequestConfig('DELETE'), + }) + const responseData = await handleResponse(response); + if (response.ok && !responseData){ + return {success: true}; + } + return responseData; + } catch (error){ + console.error(`Error deleting a survey: ${error}`); + throw error; + } +} \ No newline at end of file diff --git a/SurveyFrontend/src/components/AnswerOption/AnswerOption.module.css b/SurveyFrontend/src/components/AnswerOption/AnswerOption.module.css index 6a0ec90..9f0ce82 100644 --- a/SurveyFrontend/src/components/AnswerOption/AnswerOption.module.css +++ b/SurveyFrontend/src/components/AnswerOption/AnswerOption.module.css @@ -1,46 +1,67 @@ -/*AnswerOption.module.css*/ +/*!*AnswerOption.module.css*!*/ -.answer{ +.answer { width: 100%; display: flex; gap: 10px; margin-bottom: 17px; + align-items: flex-start; } -.textAnswer{ +.textAnswer { text-align: left; border: none; - background-color: #ffffff; + background: none; font-size: 18px; font-weight: 500; word-break: break-word; width: 70%; + padding: 0; + line-height: 24px; + cursor: text; + margin-top: 2px; } -.buttonMarker{ +.buttonMarker { padding: 0; border: none; - background-color: transparent; + background: none; + position: relative; + top: 0; + transition: top 0.1s ease; + cursor: pointer; + height: 24px; } -.answerIcon{ - vertical-align: middle; +.buttonMarker.editing { + top: 3px; +} + +.answerIcon { width: 24px; + height: 24px; + display: block; } -.answerInput{ - vertical-align: middle; +.answerInput { font-size: 18px; font-weight: 500; outline: none; border: none; resize: none; - display: flex; - align-items: center; - box-sizing: border-box; + width: 70%; + padding: 0; + margin-top: 2px; + font-family: inherit; + min-height: 24px; + height: auto; + overflow-y: hidden; + line-height: 1.5; + white-space: pre-wrap; + word-wrap: break-word; } -.deleteButton{ +.deleteButton { margin-left: auto; border: none; background-color: transparent; diff --git a/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx b/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx index e0c78b1..c08c213 100644 --- a/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx +++ b/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx @@ -32,8 +32,22 @@ const AnswerOption: React.FC = ({index, value, onChange, onDe const handleTextareaChange = (event: React.ChangeEvent) => { setCurrentValue(event.target.value); + // Автоматическое изменение высоты + if (textAreaRef.current) { + textAreaRef.current.style.height = 'auto'; + textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`; + } }; + useEffect(() => { + if (isEditing && textAreaRef.current) { + textAreaRef.current.focus(); + // Установка начальной высоты + textAreaRef.current.style.height = 'auto'; + textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`; + } + }, [isEditing]); + const handleSave = () => { setIsEditing(false); onChange(currentValue); @@ -66,7 +80,10 @@ const AnswerOption: React.FC = ({index, value, onChange, onDe return (
- {isEditing ? ( -