creating my polls page
This commit is contained in:
parent
28882e7038
commit
08b22b07c6
16 changed files with 266 additions and 24 deletions
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
37
SurveyFrontend/src/api/AuthApi.ts
Normal file
37
SurveyFrontend/src/api/AuthApi.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
SurveyFrontend/src/api/BaseApi.ts
Normal file
50
SurveyFrontend/src/api/BaseApi.ts
Normal 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 };
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
60
SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx
Normal file
60
SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
5
SurveyFrontend/src/components/Results/Results.module.css
Normal file
5
SurveyFrontend/src/components/Results/Results.module.css
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
/*Results.module.css*/
|
||||||
|
|
||||||
|
.results{
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
10
SurveyFrontend/src/components/Results/Results.tsx
Normal file
10
SurveyFrontend/src/components/Results/Results.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
.info{
|
.info{
|
||||||
min-width: 373px;
|
min-width: 373px;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 35px; /*подумать нужно ли справа слева отступы*/
|
padding: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleSurvey{
|
.titleSurvey{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/*SurveyEditingPage.module.css*/
|
/*MySurveysPage.module.css*/
|
||||||
|
|
||||||
.layout{
|
.layout{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
12
SurveyFrontend/src/pages/MySurveysPage/MySurveysPage.tsx
Normal file
12
SurveyFrontend/src/pages/MySurveysPage/MySurveysPage.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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%;
|
||||||
|
}
|
||||||
|
|
@ -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 />
|
||||||
Loading…
Add table
Add a link
Reference in a new issue