diff --git a/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs b/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs index a4ee704..e516621 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/AuthController.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using SurveyBackend.Core.Contexts; +using SurveyBackend.Core.Services; using SurveyBackend.DTOs; +using SurveyBackend.DTOs.User; using SurveyBackend.Mappers; using IAuthorizationService = SurveyBackend.Core.Services.IAuthorizationService; @@ -14,14 +17,19 @@ namespace SurveyBackend.Controllers; public class AuthController : ControllerBase { private readonly IAuthorizationService _authorizationService; + private readonly IUserContext _userContext; + private readonly IUserService _userService; /// /// Нет ну вы прикалываетесь что ли мне ща каждый контроллер описывать? /// /// - public AuthController(IAuthorizationService authorizationService) + public AuthController(IAuthorizationService authorizationService, IUserContext userContext, + IUserService userService) { _authorizationService = authorizationService; + _userContext = userContext; + _userService = userService; } /// @@ -55,7 +63,19 @@ public class AuthController : ControllerBase public async Task Register([FromBody] UserRegistrationDto registerData) { var token = await _authorizationService.RegisterUser( - AuthMapper.UserRegistrationToModel(registerData)); + UserMapper.UserRegistrationToModel(registerData)); return Ok(new { token = token }); } + + [Authorize] + [HttpGet("me")] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetMe() + { + var userId = _userContext.UserId; + var user = await _userService.GetUserById(userId); + var result = UserMapper.ModelToOutput(user); + return Ok(result); + } } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs index e9ac6a0..41f6ac8 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs @@ -29,7 +29,7 @@ public class QuestionController : ControllerBase [AllowAnonymous] [HttpGet] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task GetBySurveyId([FromRoute] int surveyId) { var questions = await _questionService.GetQuestionsBySurveyIdAsync(surveyId); @@ -47,11 +47,31 @@ public class QuestionController : ControllerBase [Authorize] [HttpPost] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status201Created)] - public async Task AddQuestion(CreateQuestionDto dto, [FromRoute] int surveyId) + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task AddQuestion(QuestionCreateDto dto, [FromRoute] int surveyId) { var model = QuestionMapper.QuestionCreationToModel(dto, surveyId); await _questionService.AddQuestionAsync(model); - return Created(); + var result = QuestionMapper.ModelToQuestionDto(model); + return Ok(result); + } + + [Authorize] + [HttpPut("{id}")] + public async Task UpdateQuestion([FromBody] QuestionUpdateDto dto, [FromRoute] int id, + [FromRoute] int surveyId) + { + var question = QuestionMapper.QuestionUpdateToModel(dto, surveyId, id); + await _questionService.UpdateQuestionAsync(question); + var result = QuestionMapper.ModelToQuestionDto(question); + return Ok(result); + } + + [Authorize] + [HttpDelete("{id}")] + public async Task DeleteQuestion([FromRoute] int id, [FromRoute] int surveyId) + { + await _questionService.DeleteQuestionAsync(id); + return Ok(); } } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs index 24f1cd2..ca82219 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs @@ -32,7 +32,7 @@ public class SurveyController : ControllerBase /// [AllowAnonymous] [HttpGet] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task Get() { var surveys = await _surveyService.GetSurveysAsync(); @@ -49,7 +49,7 @@ public class SurveyController : ControllerBase [AllowAnonymous] [HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(OutputSurveyDto), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(SurveyOutputDto), StatusCodes.Status200OK)] public async Task Get(int id) { var survey = await _surveyService.GetSurveyAsync(id); @@ -65,14 +65,32 @@ public class SurveyController : ControllerBase /// [Authorize] [HttpPost] - [ProducesResponseType(StatusCodes.Status201Created)] - public async Task Post([FromBody] CreateSurveyDto dto) + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task Post([FromBody] SurveyCreateDto dto) { var userId = _userContext.UserId; var survey = SurveyMapper.CreateDtoToModel(dto, userId); await _surveyService.AddSurveyAsync(survey); - return Created(); + var result = SurveyMapper.ModelToOutputDto(survey); + return Ok(result); + } + + /// + /// Обновляет опрос целиком + /// + /// + /// + /// + [Authorize] + [HttpPut("{id}")] + public async Task Put(int id, [FromBody] SurveyUpdateDto dto) + { + var userId = _userContext.UserId; + var survey = SurveyMapper.UpdateDtoToModel(dto, userId, id); + await _surveyService.UpdateSurveyAsync(survey); + var result = SurveyMapper.ModelToOutputDto(survey); + return Ok(result); } /// @@ -100,7 +118,7 @@ public class SurveyController : ControllerBase [Authorize] [HttpGet("my")] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task GetMySurveys() { var userId = _userContext.UserId; diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionCreateDto.cs similarity index 63% rename from SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs rename to SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionCreateDto.cs index 5fc9692..5ba991e 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionCreateDto.cs @@ -3,19 +3,15 @@ namespace SurveyBackend.DTOs.Question; /// /// Схема для создания нового Question /// -public class CreateQuestionDto +public class QuestionCreateDto { /// /// Название вопроса /// public required string Title { get; set; } + /// /// Тип вопроса /// public required string QuestionType { get; set; } - - /// - /// Варианты ответа (только если вопрос с выбором) - /// - public List? AnswerVariants { get; set; } } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Question/OutputQuestionDto.cs b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionOutputDto.cs similarity index 95% rename from SurveyBackend/SurveyBackend.API/DTOs/Question/OutputQuestionDto.cs rename to SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionOutputDto.cs index f850365..96eac88 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/Question/OutputQuestionDto.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionOutputDto.cs @@ -3,7 +3,7 @@ namespace SurveyBackend.DTOs.Question; /// /// Выходнпя схема вопроса /// -public class OutputQuestionDto +public class QuestionOutputDto { /// /// ID вопроса diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionUpdateDto.cs b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionUpdateDto.cs new file mode 100644 index 0000000..fcefc31 --- /dev/null +++ b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionUpdateDto.cs @@ -0,0 +1,6 @@ +namespace SurveyBackend.DTOs.Question; + +public class QuestionUpdateDto : QuestionCreateDto +{ + +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Survey/CreateSurveyDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyCreateDto.cs similarity index 93% rename from SurveyBackend/SurveyBackend.API/DTOs/Survey/CreateSurveyDTO.cs rename to SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyCreateDto.cs index 0383257..c9f19e7 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/Survey/CreateSurveyDTO.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyCreateDto.cs @@ -3,7 +3,7 @@ namespace SurveyBackend.DTOs.Survey; /// /// Схема для создания нового опроса /// -public class CreateSurveyDto +public class SurveyCreateDto { /// /// Название опроса diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Survey/OutputSurveyDto.cs b/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyOutputDto.cs similarity index 95% rename from SurveyBackend/SurveyBackend.API/DTOs/Survey/OutputSurveyDto.cs rename to SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyOutputDto.cs index dea522b..6598707 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/Survey/OutputSurveyDto.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyOutputDto.cs @@ -3,7 +3,7 @@ namespace SurveyBackend.DTOs.Survey; /// /// Выходная схема опроса /// -public class OutputSurveyDto +public class SurveyOutputDto { /// /// ID опроса diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyUpdateDto.cs b/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyUpdateDto.cs new file mode 100644 index 0000000..5add4ca --- /dev/null +++ b/SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyUpdateDto.cs @@ -0,0 +1,5 @@ +namespace SurveyBackend.DTOs.Survey; + +public class SurveyUpdateDto : SurveyCreateDto +{ +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/DTOs/UserLoginDto.cs b/SurveyBackend/SurveyBackend.API/DTOs/User/UserLoginDto.cs similarity index 90% rename from SurveyBackend/SurveyBackend.API/DTOs/UserLoginDto.cs rename to SurveyBackend/SurveyBackend.API/DTOs/User/UserLoginDto.cs index 2cf7002..aa7a9b6 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/UserLoginDto.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/User/UserLoginDto.cs @@ -1,4 +1,4 @@ -namespace SurveyBackend.DTOs; +namespace SurveyBackend.DTOs.User; /// /// Схема авторизации пользователя diff --git a/SurveyBackend/SurveyBackend.API/DTOs/User/UserOutputDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/User/UserOutputDTO.cs new file mode 100644 index 0000000..814e91f --- /dev/null +++ b/SurveyBackend/SurveyBackend.API/DTOs/User/UserOutputDTO.cs @@ -0,0 +1,9 @@ +namespace SurveyBackend.DTOs.User; + +public class UserOutputDto +{ + public long Id { get; set; } + public required string Email { get; set; } + public required string FirstName { get; set; } + public string? LastName { get; set; } +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDto.cs b/SurveyBackend/SurveyBackend.API/DTOs/User/UserRegistrationDto.cs similarity index 79% rename from SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDto.cs rename to SurveyBackend/SurveyBackend.API/DTOs/User/UserRegistrationDto.cs index b67622e..3ddcf05 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/UserRegistrationDto.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/User/UserRegistrationDto.cs @@ -1,4 +1,4 @@ -namespace SurveyBackend.DTOs; +namespace SurveyBackend.DTOs.User; /// /// Схема регистрации пользователя @@ -10,11 +10,6 @@ public record UserRegistrationDto /// public required string Email { get; set; } - /// - /// Юзернейм - /// - public string Username { get; set; } - /// /// Имя /// diff --git a/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs index dab60b9..044c052 100644 --- a/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs +++ b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs @@ -17,7 +17,7 @@ public static class QuestionMapper /// /// /// - public static QuestionBase QuestionCreationToModel(CreateQuestionDto dto, int surveyId) + public static QuestionBase QuestionCreationToModel(QuestionCreateDto dto, int surveyId) { return dto.QuestionType.ToLower() switch { @@ -30,13 +30,13 @@ public static class QuestionMapper { Title = dto.Title, SurveyId = surveyId, - AnswerVariants = dto.AnswerVariants?.Select(v => new AnswerVariant { Text = v }).ToList() ?? [], + AnswerVariants = [], }, "multipleanswerquestion" => new MultipleAnswerQuestion { Title = dto.Title, SurveyId = surveyId, - AnswerVariants = dto.AnswerVariants?.Select(v => new AnswerVariant { Text = v }).ToList() ?? [] + AnswerVariants = [] }, _ => throw new BadRequestException("Unknown question type") }; @@ -47,10 +47,10 @@ public static class QuestionMapper /// /// /// - public static OutputQuestionDto ModelToQuestionDto(QuestionBase question) + public static QuestionOutputDto ModelToQuestionDto(QuestionBase question) { var withAnswerVariants = question.GetType() != typeof(TextQuestion); - return new OutputQuestionDto + return new QuestionOutputDto { Id = question.Id, SurveyId = question.SurveyId, @@ -60,6 +60,34 @@ public static class QuestionMapper }; } + public static QuestionBase QuestionUpdateToModel(QuestionCreateDto dto, int surveyId, int questionId) + { + return dto.QuestionType.ToLower() switch + { + "textquestion" => new TextQuestion + { + Id = questionId, + Title = dto.Title, + SurveyId = surveyId, + }, + "singleanswerquestion" => new SingleAnswerQuestion + { + Id = questionId, + Title = dto.Title, + SurveyId = surveyId, + AnswerVariants = [], + }, + "multipleanswerquestion" => new MultipleAnswerQuestion + { + Id = questionId, + Title = dto.Title, + SurveyId = surveyId, + AnswerVariants = [] + }, + _ => throw new BadRequestException("Unknown question type") + }; + } + private static List AnswerVariantsToDto(IEnumerable answerVariants) { return answerVariants.Select(av => new OutputAnswerVariantDto diff --git a/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs index bbcc51f..be88b9c 100644 --- a/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs +++ b/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs @@ -14,7 +14,7 @@ public static class SurveyMapper /// /// /// - public static Survey CreateDtoToModel(CreateSurveyDto dto, int userId) + public static Survey CreateDtoToModel(SurveyCreateDto dto, int userId) { return new Survey { @@ -24,14 +24,22 @@ public static class SurveyMapper }; } + public static Survey UpdateDtoToModel(SurveyUpdateDto dto, int userId, int surveyId) => new Survey + { + Id = surveyId, + Title = dto.Title, + Description = dto.Description, + CreatedBy = userId + }; + /// /// Модель в выходную схему /// /// /// - public static OutputSurveyDto ModelToOutputDto(Survey survey) + public static SurveyOutputDto ModelToOutputDto(Survey survey) { - return new OutputSurveyDto + return new SurveyOutputDto { Id = survey.Id, Title = survey.Title, diff --git a/SurveyBackend/SurveyBackend.API/Mappers/AuthMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/UserMapper.cs similarity index 68% rename from SurveyBackend/SurveyBackend.API/Mappers/AuthMapper.cs rename to SurveyBackend/SurveyBackend.API/Mappers/UserMapper.cs index 47a01e6..0d9a216 100644 --- a/SurveyBackend/SurveyBackend.API/Mappers/AuthMapper.cs +++ b/SurveyBackend/SurveyBackend.API/Mappers/UserMapper.cs @@ -1,12 +1,13 @@ using SurveyBackend.Core.Models; using SurveyBackend.DTOs; +using SurveyBackend.DTOs.User; namespace SurveyBackend.Mappers; /// /// Маппер всего связанного с авторизацией /// -public static class AuthMapper +public static class UserMapper { /// /// Перегнать схему регистрации в нового юзера @@ -20,4 +21,12 @@ public static class AuthMapper LastName = dto.LastName, Password = dto.Password, }; + + public static UserOutputDto ModelToOutput(User model) => new UserOutputDto + { + Id = model.Id, + FirstName = model.FirstName, + LastName = model.LastName, + Email = model.Email, + }; } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs b/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs index cd34c76..47828ec 100644 --- a/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs +++ b/SurveyBackend/SurveyBackend.Core/Services/IUserService.cs @@ -5,6 +5,7 @@ namespace SurveyBackend.Core.Services; public interface IUserService { public Task GetUserByEmail(string email); + public Task GetUserById(int id); public Task IsEmailTaken(string email); public Task CreateUserAsync(User user); } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.Services/Services/QuestionService.cs b/SurveyBackend/SurveyBackend.Services/Services/QuestionService.cs index d62a28f..261eb2a 100644 --- a/SurveyBackend/SurveyBackend.Services/Services/QuestionService.cs +++ b/SurveyBackend/SurveyBackend.Services/Services/QuestionService.cs @@ -10,12 +10,14 @@ public class QuestionService : IQuestionService { private readonly IQuestionRepository _questionRepository; private readonly ISurveyRepository _surveyRepository; + private readonly IUserContext _userContext; public QuestionService(IQuestionRepository questionRepository, ISurveyRepository surveyRepository, IUserContext userContext) { _questionRepository = questionRepository; _surveyRepository = surveyRepository; + _userContext = userContext; } public async Task AddQuestionAsync(QuestionBase question) diff --git a/SurveyBackend/SurveyBackend.Services/Services/UserService.cs b/SurveyBackend/SurveyBackend.Services/Services/UserService.cs index 9c52a64..9336478 100644 --- a/SurveyBackend/SurveyBackend.Services/Services/UserService.cs +++ b/SurveyBackend/SurveyBackend.Services/Services/UserService.cs @@ -19,6 +19,11 @@ public class UserService : IUserService return await _userRepository.GetUserByEmail(email) ?? throw new NotFoundException("Email not found"); } + public async Task GetUserById(int id) + { + return await _userRepository.GetByIdAsync(id) ?? throw new NotFoundException("User not found"); + } + public async Task IsEmailTaken(string email) { return await _userRepository.GetUserByEmail(email) != null; 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 ? ( -