creating my polls page

This commit is contained in:
Tatiana Nikolaeva 2025-04-28 11:09:38 +05:00
parent 28882e7038
commit 08b22b07c6
16 changed files with 266 additions and 24 deletions

View file

@ -1,19 +1,32 @@
import './App.css' import './App.css'
import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom";
import {SurveyEditingPage} from "./pages/SurveyEditingPage/SurveyEditingPage.tsx"; import {SurveyCreateAndEditingPage} from "./pages/SurveyCreateAndEditingPage/SurveyCreateAndEditingPage.tsx";
import Survey from "./components/Survey/Survey.tsx"; import Survey from "./components/Survey/Survey.tsx";
import SettingSurvey from "./components/SettingSurvey/SettingSurvey.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";
const App = () => { const App = () => {
return( return(
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<Navigate to="/survey/edit/questions" replace />} /> <Route path="/" element={<Navigate to="/survey/create/questions" replace />} />
<Route path="survey/edit" element={<SurveyEditingPage />}> <Route path="survey/create" element={<SurveyCreateAndEditingPage />}>
<Route path="questions" element={<Survey />} /> <Route path="questions" element={<Survey />} />
<Route path="settings" element={<SettingSurvey />} /> <Route path="settings" element={<SettingSurvey />} />
</Route> </Route>
<Route path="my-surveys" element={<MySurveysPage />}>
<Route index element={<MySurveyList />} />
</Route>
<Route path='survey/:surveyId' element={<SurveyCreateAndEditingPage />}>
<Route path="questions" element={<Survey />} />
<Route path="settings" element={<SettingSurvey />} />
<Route path="results" element={<Results />} />
</Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
); );

View file

@ -0,0 +1,37 @@
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
interface IAuthData{
email: string;
password: string;
}
interface IRegistrationData extends IAuthData{
username: string;
firstName: string;
lastName: string;
}
export const registerUser = async (data: IRegistrationData) => {
try{
const response = await fetch(`${BASE_URL}/auth/register`, {
...createRequestConfig('POST'), body: JSON.stringify(data),
})
return await handleResponse(response);
} catch (error){
console.error("Registration error:", error);
throw error;
}
}
export const authUser = async (data: IAuthData) => {
try{
const response = await fetch(`${BASE_URL}/auth/login`, {
...createRequestConfig('POST'), body: JSON.stringify(data),
})
return await handleResponse(response);
}
catch(error){
console.error("Login error:", error);
throw error;
}
}

View file

@ -0,0 +1,50 @@
const BASE_URL = "https://survey.slavagm.ru/api";
interface RequestConfig {
method: string;
headers: Record<string, string>;
body?: BodyInit | null;
}
/**
* Создаёт конфигурацию для fetch-запроса
* @param method HTTP-метод (GET, POST, PUT, DELETE)
* @param isFormData Флаг, указывающий, что отправляется FormData
* @returns Конфигурация для fetch-запроса
*/
const createRequestConfig = (method: string, isFormData: boolean = false): RequestConfig => {
const token = localStorage.getItem("accessToken");
const config: RequestConfig = {
method,
headers: {},
};
// Добавляем заголовок авторизации, если есть токен
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Добавляем Content-Type, если это не FormData
if (!isFormData) {
config.headers["Content-Type"] = "application/json";
}
return config;
};
/**
* Обрабатывает ответ от сервера
* @param response Ответ от fetch
* @returns Распарсенные данные или ошибку
*/
const handleResponse = async (response: Response) => {
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || "Произошла ошибка");
}
return data;
};
export { BASE_URL, createRequestConfig, handleResponse };

View file

@ -1,7 +1,6 @@
/*AddQuestionButton.module.css*/ /*AddQuestionButton.module.css*/
.questionButton{ .questionButton{
display: block;
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -8,22 +8,23 @@ import {Link, useLocation} from "react-router-dom";
const Header: React.FC = () => { const Header: React.FC = () => {
const location = useLocation(); const location = useLocation();
const isCreateSurveyActive = location.pathname.includes('/survey/edit'); const isCreateSurveyActive = location.pathname.includes('/survey/create');
const isMySurveyActive = location.pathname === '/my-surveys'; const isSurveyPage = location.pathname.includes('/survey/') && !location.pathname.includes('/survey/create');
const isMySurveysPage = location.pathname === '/my-surveys' || isSurveyPage;
return ( return (
<div className={styles.header}> <div className={styles.header}>
<Logo href='/' /> <Logo href='/' />
<nav className={styles.pagesNav}> <nav className={styles.pagesNav}>
<Link to='/survey/edit/questions' <Link to='/survey/create/questions'
className={`${styles.pageLink} ${isCreateSurveyActive ? styles.active : ''}`}> className={`${styles.pageLink} ${isCreateSurveyActive ? styles.active : ''}`}>
Создать опрос Создать опрос
{isCreateSurveyActive && <hr className={styles.activeLine}/>} {isCreateSurveyActive && <hr className={styles.activeLine}/>}
</Link> </Link>
<Link to='/my-surveys' <Link to='/my-surveys'
className={`${styles.pageLink} ${isMySurveyActive ? styles.active : ''}`}> className={`${styles.pageLink} ${isMySurveysPage ? styles.active : ''}`}>
Мои опросы Мои опросы
{isMySurveyActive && <hr className={styles.activeLine}/>} {isMySurveysPage && <hr className={styles.activeLine}/>}
</Link> </Link>
</nav> </nav>
<Account <Account

View file

@ -0,0 +1,60 @@
import styles from './MySurveysList.module.css'
import {useNavigate} from "react-router-dom";
interface MySurveyItem{
id: string,
title: string,
description: string,
date: string
status: 'active' | 'completed'
}
export const MySurveyList = () => {
const navigate = useNavigate();
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',
}
]
const handleSurveyClick = (id: string) => {
navigate(`/survey/${id}/questions`)
}
return(
<div className={styles.main}>
{surveys.map((survey) => (
<div
key={survey.id}
className={styles.survey}
onClick={() => handleSurveyClick(survey.id)}
role="button"
tabIndex={0}
>
<div>
<h1 className={styles.title}>{survey.title}</h1>
<h2 className={styles.description}>{survey.description}</h2>
<span className={styles.date}>Дата создания: {survey.date}</span>
</div>
<div className={`${styles.status} ${
survey.status === 'active' ? styles.active : styles.completed
}`}>
{survey.status === 'active' ? 'Активен' : 'Завершён'}
</div>
</div>
))}
</div>
)
}

View file

@ -0,0 +1,34 @@
.main{
background-color: #F6F6F6;
width: 100%;
height: 100vh;
padding: 34px 8%;
}
.survey{
display: flex;
justify-content: space-between;
background-color: #FFFFFF;
width: 79%;
border-radius: 14px;
padding: 29px 36px 29px 54px;
margin-bottom: 23px;
}
.completed{
width: fit-content;
height: fit-content;
padding: 15px 47px;
border-radius: 15px;
background-color: #65B953;
color: #FFFFFF;
}
.active{
width: fit-content;
height: fit-content;
padding: 15px 47px;
border-radius: 15px;
background-color: #B0B0B0;
color: #FFFFFF;
}

View file

@ -4,22 +4,28 @@ import styles from './Navigation.module.css'
import NavigationItem from "../NavigationItem/NavigationItem.tsx"; import NavigationItem from "../NavigationItem/NavigationItem.tsx";
import SaveButton from "../SaveButton/SaveButton.tsx"; import SaveButton from "../SaveButton/SaveButton.tsx";
const Navigation: React.FC = () => { const Navigation: React.FC = () => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const activePage = location.pathname.split('/').pop() || 'questions' const isSurveyPage = /\/survey\/[^/]+/.test(location.pathname);
const isNotCreateSurvey = !location.pathname.includes('/survey/create');
const isMySurveysPage = isSurveyPage && isNotCreateSurvey;
const items = [ const activePage = location.pathname.split('/').pop() ?? 'questions';
const baseItems = [
{id: 'questions', title: 'Вопросы'}, {id: 'questions', title: 'Вопросы'},
{id: 'settings', title: 'Настройки'}, {id: 'settings', title: 'Настройки'}
{id: 'results', title: 'Результаты'} ];
]
const handleNavigationClick = (padeId: string) => { const items = isMySurveysPage
navigate(`${padeId}`); ? [...baseItems, {id: 'results', title: 'Результаты'}]
} : baseItems;
const handleNavigationClick = (pageId: string) => {
navigate(`${pageId}`, { relative: 'path' });
};
return ( return (
<div className={styles.navContainer}> <div className={styles.navContainer}>
@ -35,7 +41,6 @@ const Navigation: React.FC = () => {
))} ))}
</ul> </ul>
</nav> </nav>
<SaveButton /> <SaveButton />
</div> </div>
); );

View file

@ -0,0 +1,5 @@
/*Results.module.css*/
.results{
width: 85%;
}

View file

@ -0,0 +1,10 @@
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
import styles from './Results.module.css'
export const Results = () => {
return(
<div className={styles.results}>
<SurveyInfo />
</div>
)
}

View file

@ -13,7 +13,7 @@
.info{ .info{
min-width: 373px; min-width: 373px;
display: block; display: block;
padding: 35px; /*подумать нужно ли справа слева отступы*/ padding: 35px;
} }
.titleSurvey{ .titleSurvey{

View file

@ -4,7 +4,6 @@
width: 23%; width: 23%;
position: relative; position: relative;
display: inline-block; display: inline-block;
/*margin-right: 29px;*/
margin-left: auto; margin-left: auto;
} }

View file

@ -1,4 +1,4 @@
/*SurveyEditingPage.module.css*/ /*MySurveysPage.module.css*/
.layout{ .layout{
width: 100%; width: 100%;

View file

@ -0,0 +1,12 @@
import styles from "./MySurveysPage.module.css";
import {MySurveyList} from "../../components/MySurveyList/MySurveyList.tsx";
import Header from "../../components/Header/Header.tsx";
export const MySurveysPage = () => {
return (
<div className={styles.layout}>
<Header />
<MySurveyList />
</div>
)
}

View file

@ -0,0 +1,17 @@
/*SurveyCreateAndEditingPage.module.css*/
.layout{
width: 100%;
}
.main{
width: 100%;
min-height: 85vh;
display: flex;
background-color: #F6F6F6;
}
.content{
width: 100%;
margin-left: 8.9%;
}

View file

@ -1,9 +1,9 @@
import Header from "../../components/Header/Header.tsx"; import Header from "../../components/Header/Header.tsx";
import Navigation from "../../components/Navigation/Navigation.tsx"; import Navigation from "../../components/Navigation/Navigation.tsx";
import styles from './SurveyEditingPage.module.css' import styles from './SurveyCreateAndEditingPage.module.css'
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
export const SurveyEditingPage = () => { export const SurveyCreateAndEditingPage = () => {
return ( return (
<div className={styles.layout}> <div className={styles.layout}>
<Header /> <Header />