From 9a3f05ef60065f1aacc8f34460d07b80b9cf4a1b Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Thu, 8 May 2025 18:22:44 +0500 Subject: [PATCH 01/12] add components register and login --- SurveyFrontend/src/App.tsx | 10 ++- .../components/LoginForm/LoginForm.module.css | 85 +++++++++++++++++++ .../src/components/LoginForm/LoginForm.tsx | 39 +++++++++ .../RegisterForm/RegisterForm.module.css | 84 ++++++++++++++++++ .../components/RegisterForm/RegisterForm.tsx | 57 +++++++++++++ .../src/pages/AuthForm/AuthForm.module.css | 12 +++ .../src/pages/AuthForm/AuthForm.tsx | 18 ++++ 7 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 SurveyFrontend/src/components/LoginForm/LoginForm.module.css create mode 100644 SurveyFrontend/src/components/LoginForm/LoginForm.tsx create mode 100644 SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css create mode 100644 SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx create mode 100644 SurveyFrontend/src/pages/AuthForm/AuthForm.module.css create mode 100644 SurveyFrontend/src/pages/AuthForm/AuthForm.tsx diff --git a/SurveyFrontend/src/App.tsx b/SurveyFrontend/src/App.tsx index 8acf6f3..a231648 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 LoginForm from "./components/LoginForm/LoginForm.tsx"; +import AuthForm from "./pages/AuthForm/AuthForm.tsx"; const App = () => { return( - } /> + } /> }> } /> @@ -27,6 +29,10 @@ const App = () => { } /> } /> + + } /> + } /> + } /> ); diff --git a/SurveyFrontend/src/components/LoginForm/LoginForm.module.css b/SurveyFrontend/src/components/LoginForm/LoginForm.module.css new file mode 100644 index 0000000..fca2331 --- /dev/null +++ b/SurveyFrontend/src/components/LoginForm/LoginForm.module.css @@ -0,0 +1,85 @@ +.loginContainer{ + width: 31%; + background-color: #FFFFFF; + padding: 42.5px 65px; + margin: auto; + border-radius: 43px; + margin-bottom: 0; +} + +.title{ + text-align: center; + font-weight: 600; + font-size: 40px; + line-height: 88%; + padding: 0; + margin-bottom: 80px; + margin-top: 0; +} + +.form{ + text-align: center; + display: flex; + flex-direction: column; + gap: 80px; + margin-bottom: 80px; +} + +.input { + font-size: 24px; + font-weight: 600; + line-height: 88%; + color: #000000; /* Цвет текста по умолчанию */ + outline: none; + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); /* Нижняя граница с прозрачностью */ + padding: 5px 0; + opacity: 1; /* Установите opacity в 1 для input, а для placeholder используйте opacity */ +} + +.input::placeholder { + font-size: 24px; + font-weight: 600; + line-height: 88%; + color: #000000; + opacity: 0.2; /* Прозрачность placeholder */ +} + +.input:focus::placeholder { + opacity: 0; /* Убираем placeholder при фокусе */ +} + +/* Отключаем стиль для input, когда в нём есть данные */ +.input:not(:placeholder-shown) { + color: black; + opacity: 1; +} + +.input:focus { + border-bottom: 1px solid black; /* Чёрная граница при фокусе */ +} + +.signIn{ + margin: auto; + padding: 26.5px 67px; + width: fit-content; + border-radius: 24px; + background-color: #3788D6; + color: #FFFFFF; + font-size: 24px; + font-weight: 600; + line-height: 120%; + border: none; + outline: none; +} + +.recommendation{ + text-align: center; + font-size: 18px; + font-weight: 500; +} + +.recommendationLink{ + color: #3788D6; + margin-left: 5px; +} \ No newline at end of file diff --git a/SurveyFrontend/src/components/LoginForm/LoginForm.tsx b/SurveyFrontend/src/components/LoginForm/LoginForm.tsx new file mode 100644 index 0000000..b560c2c --- /dev/null +++ b/SurveyFrontend/src/components/LoginForm/LoginForm.tsx @@ -0,0 +1,39 @@ +import { Link } from "react-router-dom"; +import styles from './LoginForm.module.css'; +import { useState } from 'react'; + +const RegisterForm = () => { + const [focused, setFocused] = useState({ + email: false, + password: false + }); + return ( +
+

С возвращением!

+
+ setFocused({ ...focused, email: true })} + onBlur={() => setFocused({ ...focused, email: false })} + style={{ color: focused.email ? 'black' : 'inherit' }} + /> + setFocused({ ...focused, password: true })} + onBlur={() => setFocused({ ...focused, password: false })} + style={{ color: focused.password ? 'black' : 'inherit' }} + /> + +
+

Еще не с нами? + Зарегистрируйтесь! +

+
+ ); +} + +export default RegisterForm; diff --git a/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css b/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css new file mode 100644 index 0000000..d2ac3b1 --- /dev/null +++ b/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css @@ -0,0 +1,84 @@ +.registerContainer{ + width: 31%; + background-color: #FFFFFF; + padding: 94px 80px; + margin: auto; + border-radius: 43px; +} + +.title{ + text-align: center; + font-weight: 600; + font-size: 40px; + line-height: 88%; + padding: 0; + margin-bottom: 80px; + margin-top: 0; +} + +.form{ + text-align: center; + display: flex; + flex-direction: column; + gap: 80px; + margin-bottom: 80px; +} + +.input { + font-size: 24px; + font-weight: 600; + line-height: 88%; + color: #000000; /* Цвет текста по умолчанию */ + outline: none; + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); /* Нижняя граница с прозрачностью */ + padding: 5px 0; + opacity: 1; /* Установите opacity в 1 для input, а для placeholder используйте opacity */ +} + +.input::placeholder { + font-size: 24px; + font-weight: 600; + line-height: 88%; + color: #000000; + opacity: 0.2; /* Прозрачность placeholder */ +} + +.input:focus::placeholder { + opacity: 0; /* Убираем placeholder при фокусе */ +} + +/* Отключаем стиль для input, когда в нём есть данные */ +.input:not(:placeholder-shown) { + color: black; + opacity: 1; +} + +.input:focus { + border-bottom: 1px solid black; /* Чёрная граница при фокусе */ +} + +.signUp{ + margin: auto; + padding: 25.5px 16px; + width: fit-content; + border-radius: 24px; + background-color: #3788D6; + color: #FFFFFF; + font-size: 24px; + font-weight: 600; + line-height: 120%; + border: none; + outline: none; +} + +.recommendation{ + text-align: center; + font-size: 18px; + font-weight: 500; +} + +.recommendationLink{ + color: #3788D6; + margin-left: 5px; +} \ No newline at end of file diff --git a/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx b/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx new file mode 100644 index 0000000..6cb0238 --- /dev/null +++ b/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx @@ -0,0 +1,57 @@ +import { Link } from "react-router-dom"; +import styles from './RegisterForm.module.css'; +import { useState } from 'react'; + +const RegisterForm = () => { + const [focused, setFocused] = useState({ + name: false, + surname: false, + email: false, + password: false + }); + return ( +
+

Регистрация

+
+ setFocused({ ...focused, name: true })} + onBlur={() => setFocused({ ...focused, name: false })} + style={{ color: focused.name ? 'black' : 'inherit' }} + /> + setFocused({ ...focused, surname: true })} + onBlur={() => setFocused({ ...focused, surname: false })} + style={{ color: focused.surname ? 'black' : 'inherit' }} + /> + setFocused({ ...focused, email: true })} + onBlur={() => setFocused({ ...focused, email: false })} + style={{ color: focused.email ? 'black' : 'inherit' }} + /> + setFocused({ ...focused, password: true })} + onBlur={() => setFocused({ ...focused, password: false })} + style={{ color: focused.password ? 'black' : 'inherit' }} + /> + +
+

Уже с нами? + Войдите! +

+
+ ); +} + +export default RegisterForm; diff --git a/SurveyFrontend/src/pages/AuthForm/AuthForm.module.css b/SurveyFrontend/src/pages/AuthForm/AuthForm.module.css new file mode 100644 index 0000000..bae0307 --- /dev/null +++ b/SurveyFrontend/src/pages/AuthForm/AuthForm.module.css @@ -0,0 +1,12 @@ +.page{ + width: 100%; + min-height: 100vh; + background-color: #F6F6F6; + padding: 61.5px 0; +} + +.pageLogin{ + width: 100%; + background-color: #F6F6F6; + padding: 157px 0; +} \ No newline at end of file diff --git a/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx b/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx new file mode 100644 index 0000000..9f4e5d4 --- /dev/null +++ b/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx @@ -0,0 +1,18 @@ +import styles from './AuthForm.module.css'; +import LoginForm from "../../components/LoginForm/LoginForm.tsx"; +import RegisterForm from "../../components/RegisterForm/RegisterForm.tsx"; +import {useLocation} from "react-router-dom"; + + +const AuthForm = () => { + const location = useLocation(); + const isLogin = location.pathname === '/login'; + + return ( +
+ {isLogin ? : } +
+ ); +} + +export default AuthForm; \ No newline at end of file From bc293f6370eafdf8af05cc0a137b5b1b9d4e72f2 Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Sat, 10 May 2025 15:56:50 +0500 Subject: [PATCH 02/12] requests for auth/register --- SurveyFrontend/src/App.tsx | 6 +- SurveyFrontend/src/api/AuthApi.ts | 30 +++-- SurveyFrontend/src/api/BaseApi.ts | 35 +++++- SurveyFrontend/src/api/QuestionApi.ts | 29 +++++ SurveyFrontend/src/api/SurveyApi.ts | 117 ++++++++++++++++++ .../src/components/LoginForm/LoginForm.tsx | 38 +++++- .../components/MySurveyList/MySurveyList.tsx | 69 +++++++---- .../components/RegisterForm/RegisterForm.tsx | 62 ++++++++-- .../src/pages/AuthForm/AuthForm.tsx | 25 +++- 9 files changed, 348 insertions(+), 63 deletions(-) create mode 100644 SurveyFrontend/src/api/QuestionApi.ts create mode 100644 SurveyFrontend/src/api/SurveyApi.ts diff --git a/SurveyFrontend/src/App.tsx b/SurveyFrontend/src/App.tsx index a231648..fa8d8ba 100644 --- a/SurveyFrontend/src/App.tsx +++ b/SurveyFrontend/src/App.tsx @@ -6,14 +6,14 @@ 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 LoginForm from "./components/LoginForm/LoginForm.tsx"; import AuthForm from "./pages/AuthForm/AuthForm.tsx"; const App = () => { return( - } /> + } /> + } /> }> } /> @@ -31,8 +31,6 @@ const App = () => { } /> - } /> - } /> ); diff --git a/SurveyFrontend/src/api/AuthApi.ts b/SurveyFrontend/src/api/AuthApi.ts index 60e3a84..207da21 100644 --- a/SurveyFrontend/src/api/AuthApi.ts +++ b/SurveyFrontend/src/api/AuthApi.ts @@ -16,7 +16,13 @@ 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); + } + + return responseData; } catch (error){ console.error("Registration error:", error); throw error; @@ -24,14 +30,22 @@ 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); + } + + 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/LoginForm/LoginForm.tsx b/SurveyFrontend/src/components/LoginForm/LoginForm.tsx index b560c2c..553cd37 100644 --- a/SurveyFrontend/src/components/LoginForm/LoginForm.tsx +++ b/SurveyFrontend/src/components/LoginForm/LoginForm.tsx @@ -1,20 +1,45 @@ -import { Link } from "react-router-dom"; +import {Link, useNavigate} from "react-router-dom"; import styles from './LoginForm.module.css'; -import { useState } from 'react'; +import {useRef, useState} from 'react'; +import {authUser} from "../../api/AuthApi.ts"; -const RegisterForm = () => { +const LoginForm = () => { const [focused, setFocused] = useState({ email: false, password: false }); + + const navigate = useNavigate(); + + const emailRef = useRef(null); // ref для поля email + const passwordRef = useRef(null); // ref для поля password + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const email = emailRef.current?.value || ''; + const password = passwordRef.current?.value || ''; + + try{ + const responseData = await authUser({email, password}); + if (responseData && !responseData.error) + navigate('/my-surveys'); + else + console.error('Ошибка аутентификации:', responseData); + } + catch(err){ + console.error('Ошибка при отправке запроса:', err); + } + } + return (

С возвращением!

-
+ setFocused({ ...focused, email: true })} onBlur={() => setFocused({ ...focused, email: false })} style={{ color: focused.email ? 'black' : 'inherit' }} @@ -23,11 +48,12 @@ const RegisterForm = () => { className={`${styles.input} ${styles.password}`} type='password' placeholder='Пароль' + ref={passwordRef} onFocus={() => setFocused({ ...focused, password: true })} onBlur={() => setFocused({ ...focused, password: false })} style={{ color: focused.password ? 'black' : 'inherit' }} /> - +

Еще не с нами? Зарегистрируйтесь! @@ -36,4 +62,4 @@ const RegisterForm = () => { ); } -export default RegisterForm; +export default LoginForm; diff --git a/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx b/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx index 678d30d..210996e 100644 --- a/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx +++ b/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx @@ -1,35 +1,58 @@ import styles from './MySurveysList.module.css' import {useNavigate} from "react-router-dom"; +import {useEffect, useState} from "react"; +import {getMySurveys, ISurvey} from "../../api/SurveyApi.ts"; -interface MySurveyItem{ - id: string, - title: string, - description: string, - date: string + +interface MySurveyItem extends ISurvey{ status: 'active' | 'completed' } export const MySurveyList = () => { const navigate = useNavigate(); + const [surveys, setSurveys] = useState([]); - const surveys: MySurveyItem[] = [ - { - id: '1', - title: 'Опрос 1', - description: 'Описание опроса 1', - date: '27-04-2025', - status: 'active', - }, - { - id: '2', - title: 'Опрос 2', - description: 'Описание опроса 2', - date: '01-01-2025', - status: 'completed', - } - ] + useEffect(() => { + const fetchSurvey = async () => { + try { + const mySurveys = await getMySurveys(); + const surveysWithStatus: MySurveyItem[] = mySurveys.map((survey: ISurvey) => ({ + ...survey, + status: 'active', + })); + setSurveys(surveysWithStatus); + } catch (error) { + console.error('Ошибка при получении списка опросов:', error); - const handleSurveyClick = (id: string) => { + if (error instanceof Error && error.message.includes("401")) { + // Если ошибка 401, перенаправляем на страницу входа + navigate('/login'); + } else { + alert("Ошибка при загрузке опросов: " + (error instanceof Error && error.message)); + } + } + }; + fetchSurvey(); + }, [navigate]); + + // const surveys: MySurveyItem[] = [ + // { + // id: '1', + // title: 'Опрос 1', + // description: 'Описание опроса 1', + // createdBy: '27-04-2025', + // status: 'active', + // }, + // { + // id: '2', + // title: 'Опрос 2', + // description: 'Описание опроса 2', + // createdBy: '01-01-2025', + // status: 'completed', + // } + // ] + + const handleSurveyClick = (id: number) => { navigate(`/survey/${id}/questions`) } @@ -46,7 +69,7 @@ export const MySurveyList = () => {

{survey.title}

{survey.description}

- Дата создания: {survey.date} + Дата создания: {survey.createdBy}
{ const [focused, setFocused] = useState({ - name: false, - surname: false, + firstName: false, + lastName: false, email: false, password: false }); + + const nameRef = useRef(null); + const surnameRef = useRef(null); + const emailRef = useRef(null); + const passwordRef = useRef(null); + + const navigate = useNavigate(); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const firstName = nameRef.current?.value || ''; + const lastName = surnameRef.current?.value || ''; + const email = emailRef.current?.value || ''; + const password = passwordRef.current?.value || ''; + const username = firstName + lastName || ''; + + try{ + const responseData = await registerUser({username, firstName, lastName, email, password}); + console.log(responseData); //проверка вывода данных + if (responseData && !responseData.error) { + console.log('Регистрация успешна') + navigate('/my-surveys'); + } + else { + console.error(`Ошибка регистрации: ${responseData}`); + console.log('Регистраиця не удалась'); + } + } + catch (err) { + console.error(`Ошибка при отправке запроса ${err}`); + } + } + return (

Регистрация

-
+ setFocused({ ...focused, name: true })} - onBlur={() => setFocused({ ...focused, name: false })} - style={{ color: focused.name ? 'black' : 'inherit' }} + ref={nameRef} + onFocus={() => setFocused({ ...focused, firstName: true })} + onBlur={() => setFocused({ ...focused, firstName: false })} + style={{ color: focused.firstName ? 'black' : 'inherit' }} /> setFocused({ ...focused, surname: true })} - onBlur={() => setFocused({ ...focused, surname: false })} - style={{ color: focused.surname ? 'black' : 'inherit' }} + ref={surnameRef} + onFocus={() => setFocused({ ...focused, lastName: true })} + onBlur={() => setFocused({ ...focused, lastName: false })} + style={{ color: focused.lastName ? 'black' : 'inherit' }} /> setFocused({ ...focused, email: true })} onBlur={() => setFocused({ ...focused, email: false })} style={{ color: focused.email ? 'black' : 'inherit' }} @@ -41,11 +78,12 @@ const RegisterForm = () => { className={`${styles.input} ${styles.password}`} type='password' placeholder='Пароль' + ref={passwordRef} onFocus={() => setFocused({ ...focused, password: true })} onBlur={() => setFocused({ ...focused, password: false })} style={{ color: focused.password ? 'black' : 'inherit' }} /> - +

Уже с нами? Войдите! diff --git a/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx b/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx index 9f4e5d4..7d52d08 100644 --- a/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx +++ b/SurveyFrontend/src/pages/AuthForm/AuthForm.tsx @@ -5,13 +5,30 @@ import {useLocation} from "react-router-dom"; const AuthForm = () => { + // const location = useLocation(); + // const isLogin = location.pathname === '/login'; + // + // return ( + //

+ // {isLogin ? : } + //
+ // ); + const location = useLocation(); - const isLogin = location.pathname === '/login'; + const isLoginPage = location.pathname === '/login'; + const isRegisterPage = location.pathname === '/register'; + + let content; + if (isLoginPage) { + content = ; + } else if (isRegisterPage) { + content = ; + } else { + content = ; // По умолчанию показываем LoginForm + } return ( -
- {isLogin ? : } -
+
{content}
); } From 98b38f645fee801eac27796f48e4ff661c5c7957 Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 18:08:51 +0500 Subject: [PATCH 03/12] return created objects --- .../SurveyBackend.API/Controllers/QuestionController.cs | 5 +++-- .../SurveyBackend.API/Controllers/SurveyController.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs index e9ac6a0..d7240cc 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs @@ -47,11 +47,12 @@ public class QuestionController : ControllerBase [Authorize] [HttpPost] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task AddQuestion(CreateQuestionDto dto, [FromRoute] int surveyId) { var model = QuestionMapper.QuestionCreationToModel(dto, surveyId); await _questionService.AddQuestionAsync(model); - return Created(); + var result = QuestionMapper.ModelToQuestionDto(model); + return Ok(result); } } \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs index 24f1cd2..fcc3e13 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs @@ -65,14 +65,15 @@ public class SurveyController : ControllerBase /// [Authorize] [HttpPost] - [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task Post([FromBody] CreateSurveyDto 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); } /// From ae1a8d2b97490d9ef9d08011c5c094cb7e928bac Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 18:09:06 +0500 Subject: [PATCH 04/12] add "me" endpoint --- .../Controllers/AuthController.cs | 24 +++++++++++++++++-- .../DTOs/{ => User}/UserLoginDto.cs | 2 +- .../DTOs/User/UserOutputDTO.cs | 9 +++++++ .../DTOs/{ => User}/UserRegistrationDto.cs | 7 +----- .../Mappers/{AuthMapper.cs => UserMapper.cs} | 11 ++++++++- .../Services/IUserService.cs | 1 + .../Services/UserService.cs | 5 ++++ 7 files changed, 49 insertions(+), 10 deletions(-) rename SurveyBackend/SurveyBackend.API/DTOs/{ => User}/UserLoginDto.cs (90%) create mode 100644 SurveyBackend/SurveyBackend.API/DTOs/User/UserOutputDTO.cs rename SurveyBackend/SurveyBackend.API/DTOs/{ => User}/UserRegistrationDto.cs (79%) rename SurveyBackend/SurveyBackend.API/Mappers/{AuthMapper.cs => UserMapper.cs} (68%) 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/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/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/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; From dc6bca6b6e4797c25d1ca8cf2b77da86ad9aa74c Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 18:46:14 +0500 Subject: [PATCH 05/12] added unsecure survey edits --- .../Controllers/SurveyController.cs | 17 +++++++++++++++++ .../DTOs/Survey/UpdateSurveyDTO.cs | 5 +++++ .../SurveyBackend.API/Mappers/SurveyMapper.cs | 8 ++++++++ 3 files changed, 30 insertions(+) create mode 100644 SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs diff --git a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs index fcc3e13..6e61320 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs @@ -76,6 +76,23 @@ public class SurveyController : ControllerBase return Ok(result); } + /// + /// Обновляет опрос целиком + /// + /// + /// + /// + [Authorize] + [HttpPut("{id}")] + public async Task Put(int id, [FromBody] UpdateSurveyDto dto) + { + var userId = _userContext.UserId; + var survey = SurveyMapper.UpdateDtoToModel(dto, userId, id); + await _surveyService.UpdateSurveyAsync(survey); + var result = SurveyMapper.ModelToOutputDto(survey); + return Ok(result); + } + /// /// Удалить опрос по ID /// diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs new file mode 100644 index 0000000..0873176 --- /dev/null +++ b/SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs @@ -0,0 +1,5 @@ +namespace SurveyBackend.DTOs.Survey; + +public class UpdateSurveyDto : CreateSurveyDto +{ +} \ No newline at end of file diff --git a/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs index bbcc51f..55d90b3 100644 --- a/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs +++ b/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs @@ -24,6 +24,14 @@ public static class SurveyMapper }; } + public static Survey UpdateDtoToModel(UpdateSurveyDto dto, int userId, int surveyId) => new Survey + { + Id = surveyId, + Title = dto.Title, + Description = dto.Description, + CreatedBy = userId + }; + /// /// Модель в выходную схему /// From c39fd4192ab8b8a9f7e037af5054649ab28f7609 Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 19:09:08 +0500 Subject: [PATCH 06/12] delete answer variants for logic rework --- .../SurveyBackend.API/Controllers/QuestionController.cs | 8 ++++++++ .../SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs | 6 +----- SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs | 4 ++-- .../SurveyBackend.Services/Services/QuestionService.cs | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs index d7240cc..24367f6 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs @@ -55,4 +55,12 @@ public class QuestionController : ControllerBase var result = QuestionMapper.ModelToQuestionDto(model); 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/DTOs/Question/CreateQuestionDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs index 5fc9692..1f40d06 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs @@ -9,13 +9,9 @@ public class CreateQuestionDto /// Название вопроса /// 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/Mappers/QuestionMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs index dab60b9..e68b311 100644 --- a/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs +++ b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs @@ -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") }; 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) From cc2d98c710f1ee1c43923b649b97ba8cdde8f8c9 Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 19:15:36 +0500 Subject: [PATCH 07/12] naming fix --- .../Question/{CreateQuestionDTO.cs => QuestionCreateDto.cs} | 2 +- .../Question/{OutputQuestionDto.cs => QuestionOutputDto.cs} | 2 +- .../DTOs/Survey/{CreateSurveyDTO.cs => SurveyCreateDto.cs} | 2 +- .../DTOs/Survey/{OutputSurveyDto.cs => SurveyOutputDto.cs} | 2 +- .../SurveyBackend.API/DTOs/Survey/SurveyUpdateDto.cs | 5 +++++ .../SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs | 5 ----- 6 files changed, 9 insertions(+), 9 deletions(-) rename SurveyBackend/SurveyBackend.API/DTOs/Question/{CreateQuestionDTO.cs => QuestionCreateDto.cs} (92%) rename SurveyBackend/SurveyBackend.API/DTOs/Question/{OutputQuestionDto.cs => QuestionOutputDto.cs} (95%) rename SurveyBackend/SurveyBackend.API/DTOs/Survey/{CreateSurveyDTO.cs => SurveyCreateDto.cs} (93%) rename SurveyBackend/SurveyBackend.API/DTOs/Survey/{OutputSurveyDto.cs => SurveyOutputDto.cs} (95%) create mode 100644 SurveyBackend/SurveyBackend.API/DTOs/Survey/SurveyUpdateDto.cs delete mode 100644 SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs diff --git a/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionCreateDto.cs similarity index 92% rename from SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs rename to SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionCreateDto.cs index 1f40d06..5ba991e 100644 --- a/SurveyBackend/SurveyBackend.API/DTOs/Question/CreateQuestionDTO.cs +++ b/SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionCreateDto.cs @@ -3,7 +3,7 @@ namespace SurveyBackend.DTOs.Question; /// /// Схема для создания нового Question /// -public class CreateQuestionDto +public class QuestionCreateDto { /// /// Название вопроса 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/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/Survey/UpdateSurveyDTO.cs b/SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs deleted file mode 100644 index 0873176..0000000 --- a/SurveyBackend/SurveyBackend.API/DTOs/Survey/UpdateSurveyDTO.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace SurveyBackend.DTOs.Survey; - -public class UpdateSurveyDto : CreateSurveyDto -{ -} \ No newline at end of file From 2cb7fdbfb70774f2836886959378cae19fc7b1b7 Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 19:15:45 +0500 Subject: [PATCH 08/12] mappers to new naming --- SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs | 6 +++--- SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs index e68b311..bede586 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 { @@ -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, diff --git a/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/SurveyMapper.cs index 55d90b3..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,7 +24,7 @@ public static class SurveyMapper }; } - public static Survey UpdateDtoToModel(UpdateSurveyDto dto, int userId, int surveyId) => new Survey + public static Survey UpdateDtoToModel(SurveyUpdateDto dto, int userId, int surveyId) => new Survey { Id = surveyId, Title = dto.Title, @@ -37,9 +37,9 @@ public static class SurveyMapper /// /// /// - public static OutputSurveyDto ModelToOutputDto(Survey survey) + public static SurveyOutputDto ModelToOutputDto(Survey survey) { - return new OutputSurveyDto + return new SurveyOutputDto { Id = survey.Id, Title = survey.Title, From 9555497d4e288b97206b8883f43e588703370dc5 Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 19:16:20 +0500 Subject: [PATCH 09/12] controllers to naming --- .../Controllers/QuestionController.cs | 4 ++-- .../SurveyBackend.API/Controllers/SurveyController.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs index 24367f6..fb72465 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); @@ -48,7 +48,7 @@ public class QuestionController : ControllerBase [HttpPost] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task AddQuestion(CreateQuestionDto dto, [FromRoute] int surveyId) + public async Task AddQuestion(QuestionCreateDto dto, [FromRoute] int surveyId) { var model = QuestionMapper.QuestionCreationToModel(dto, surveyId); await _questionService.AddQuestionAsync(model); diff --git a/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs b/SurveyBackend/SurveyBackend.API/Controllers/SurveyController.cs index 6e61320..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); @@ -66,7 +66,7 @@ public class SurveyController : ControllerBase [Authorize] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task Post([FromBody] CreateSurveyDto dto) + public async Task Post([FromBody] SurveyCreateDto dto) { var userId = _userContext.UserId; @@ -84,7 +84,7 @@ public class SurveyController : ControllerBase /// [Authorize] [HttpPut("{id}")] - public async Task Put(int id, [FromBody] UpdateSurveyDto dto) + public async Task Put(int id, [FromBody] SurveyUpdateDto dto) { var userId = _userContext.UserId; var survey = SurveyMapper.UpdateDtoToModel(dto, userId, id); @@ -118,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; From 7cbff22d8a28311c7ea641bb3b44e0e75b23100c Mon Sep 17 00:00:00 2001 From: shept Date: Tue, 13 May 2025 19:19:43 +0500 Subject: [PATCH 10/12] update question --- .../Controllers/QuestionController.cs | 11 ++++++++ .../DTOs/Question/QuestionUpdateDto.cs | 6 ++++ .../Mappers/QuestionMapper.cs | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 SurveyBackend/SurveyBackend.API/DTOs/Question/QuestionUpdateDto.cs diff --git a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs index fb72465..41f6ac8 100644 --- a/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs +++ b/SurveyBackend/SurveyBackend.API/Controllers/QuestionController.cs @@ -56,6 +56,17 @@ public class QuestionController : ControllerBase 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) 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/Mappers/QuestionMapper.cs b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs index bede586..044c052 100644 --- a/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs +++ b/SurveyBackend/SurveyBackend.API/Mappers/QuestionMapper.cs @@ -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 From fa5fe30c34753b61cfdad3621a85bee1c8e7af78 Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Wed, 14 May 2025 17:59:36 +0500 Subject: [PATCH 11/12] correction of working capacity --- SurveyFrontend/src/api/AuthApi.ts | 12 ++++ .../AnswerOption/AnswerOption.module.css | 49 +++++++++---- .../components/AnswerOption/AnswerOption.tsx | 35 +++++++--- .../src/components/Header/Header.tsx | 14 ++-- SurveyFrontend/src/components/Logo/Logo.tsx | 5 +- .../QuestionItem/QuestionItem.module.css | 3 + .../components/QuestionItem/QuestionItem.tsx | 8 +++ .../components/RegisterForm/RegisterForm.tsx | 6 +- .../SettingSurvey/SettingSurvey.module.css | 1 + .../SurveyInfo/SurveyInfo.module.css | 60 +++++++++------- .../src/components/SurveyInfo/SurveyInfo.tsx | 69 ++++++++++++------- 11 files changed, 180 insertions(+), 82 deletions(-) diff --git a/SurveyFrontend/src/api/AuthApi.ts b/SurveyFrontend/src/api/AuthApi.ts index 207da21..2855bad 100644 --- a/SurveyFrontend/src/api/AuthApi.ts +++ b/SurveyFrontend/src/api/AuthApi.ts @@ -20,6 +20,10 @@ export const registerUser = async (data: IRegistrationData) => { if (responseData.accessToken) { localStorage.setItem("token", responseData.accessToken); + localStorage.setItem("user", JSON.stringify({ + firstName: data.firstName, + lastName: data.lastName + })); } return responseData; @@ -41,6 +45,14 @@ export const authUser = async (data: IAuthData) => { 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; 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 ? ( -