just started rewritig the project, found a lot of fails
@ -1,3 +1,7 @@
|
||||
import AboutUsPage from "@/Pages/AboutUsPage/AboutUsPage";
|
||||
import React from "react";
|
||||
|
||||
export default AboutUsPage;
|
||||
const page = () => {
|
||||
return <div>page</div>;
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
@ -1,91 +0,0 @@
|
||||
import axios from "axios";
|
||||
import NextAuth, { NextAuthOptions } from "next-auth";
|
||||
import { JWT } from "next-auth/jwt";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
|
||||
interface IToken {
|
||||
access: string;
|
||||
}
|
||||
|
||||
const refreshToken = async (token: JWT): Promise<JWT> => {
|
||||
const data = {
|
||||
refresh: token.refresh_token,
|
||||
};
|
||||
|
||||
const response = await axios.post<IToken>(
|
||||
"https://api.kgroaduat.fishrungames.com/api/v1/token/refresh/",
|
||||
data
|
||||
);
|
||||
|
||||
return {
|
||||
...token,
|
||||
access_token: response.data.access,
|
||||
};
|
||||
};
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
email: {
|
||||
label: "Email",
|
||||
type: "text",
|
||||
placeholder: "jsmith@example.com",
|
||||
},
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
if (!credentials?.email || !credentials?.password)
|
||||
return null;
|
||||
const { email, password } = credentials as any;
|
||||
|
||||
const res = await fetch(
|
||||
"https://api.kgroaduat.fishrungames.com/api/v1/users/login/",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (res.status === 401) {
|
||||
console.log(res.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await res.json();
|
||||
return user;
|
||||
},
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: "/sign-in",
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
if (user) return { ...token, ...user };
|
||||
|
||||
return refreshToken(token);
|
||||
},
|
||||
|
||||
async session({ token, session }) {
|
||||
session.access_token = token.access_token;
|
||||
session.refresh_token = token.refresh_token;
|
||||
|
||||
return session;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
@ -1,3 +0,0 @@
|
||||
import CreateReportPage from "@/Pages/CreateReportPage/CreateReportPage";
|
||||
|
||||
export default CreateReportPage;
|
@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>;
|
||||
};
|
||||
|
||||
export default page;
|
@ -1,9 +1,3 @@
|
||||
import RootLayout from "@/App/App";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG Road",
|
||||
description: "Road in Kyrgyzstan",
|
||||
};
|
||||
import RootLayout from "@/app/App";
|
||||
|
||||
export default RootLayout;
|
||||
|
@ -1,3 +0,0 @@
|
||||
import NewsDetailsPage from "@/Pages/NewsDetailsPage/NewsDetailsPage";
|
||||
|
||||
export default NewsDetailsPage;
|
@ -1,3 +0,0 @@
|
||||
import NewsPage from "@/Pages/NewsPage/NewsPage";
|
||||
|
||||
export default NewsPage;
|
@ -1,3 +1,3 @@
|
||||
import Homepage from "@/Pages/Homepage/Homepage";
|
||||
import Home from "@/pages/Home/Home";
|
||||
|
||||
export default Homepage;
|
||||
export default Home;
|
||||
|
@ -1,3 +0,0 @@
|
||||
import ProfileLayout from "@/Pages/profile/ProfilePage/ProfileLayout";
|
||||
|
||||
export default ProfileLayout;
|
@ -1,3 +0,0 @@
|
||||
import MyReportsPage from "@/Pages/profile/MyReportsPage/MyReportsPage";
|
||||
|
||||
export default MyReportsPage;
|
@ -1,3 +0,0 @@
|
||||
import PersonalDataPage from "@/Pages/profile/PersonalDataPage/PersonalDataPage";
|
||||
|
||||
export default PersonalDataPage;
|
@ -1,3 +0,0 @@
|
||||
import ReportDetailsPage from "@/Pages/ReportDetailsPage/ReportDetailsPage";
|
||||
|
||||
export default ReportDetailsPage;
|
@ -1,3 +1,7 @@
|
||||
import SignInPage from "@/Pages/SignInPage/SignInPage";
|
||||
import React from "react";
|
||||
|
||||
export default SignInPage;
|
||||
const page = () => {
|
||||
return <div>page</div>;
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
@ -1,3 +0,0 @@
|
||||
import SignUpPage from "@/Pages/SignUpPage/SignUpPage";
|
||||
|
||||
export default SignUpPage;
|
@ -1,3 +0,0 @@
|
||||
import StatisticsPage from "@/Pages/StatisticsPage/StatisticsPage";
|
||||
|
||||
export default StatisticsPage;
|
@ -1,3 +0,0 @@
|
||||
import VolunteersPage from "@/Pages/VolunteersPage/VolunteersPage";
|
||||
|
||||
export default VolunteersPage;
|
@ -1,9 +1,9 @@
|
||||
.home {
|
||||
.app {
|
||||
padding-top: 78px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.home {
|
||||
.app {
|
||||
padding-top: 72px;
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
import { Montserrat } from "next/font/google";
|
||||
import "./globals.scss";
|
||||
import Navbar from "@/Widgets/general/Navbar/Navbar";
|
||||
import Footer from "@/Widgets/general/Footer/Footer";
|
||||
import { Providers } from "./Providers";
|
||||
|
||||
const montserrat = Montserrat({ subsets: ["latin"] });
|
||||
import "./fonts.scss";
|
||||
import "./App.scss";
|
||||
import Footer from "@/widgets/Footer/Footer";
|
||||
import Navbar from "@/widgets/Navbar/Navbar";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
@ -13,12 +11,10 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={montserrat.className}>
|
||||
<Providers>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
</Providers>
|
||||
<body>
|
||||
<Navbar />
|
||||
<div className="app">{children}</div>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
@ -1,11 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
export const Providers = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
};
|
9
src/App/fonts.scss
Normal file
@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap");
|
||||
|
||||
@import url("https://fonts.cdnfonts.com/css/arial");
|
||||
|
||||
@import url("../shared/fonts/TildaSans.css");
|
||||
|
||||
* {
|
||||
font-family: Tilda Sans;
|
||||
}
|
@ -4,25 +4,22 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
button,
|
||||
a {
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
ul,
|
||||
ol,
|
||||
li {
|
||||
list-style-type: none;
|
||||
list-style: none;
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.auth-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
|
||||
&__icon {
|
||||
padding: 12px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 10px;
|
||||
border: 1px solid #eaecf0;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
color: #101828;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-500;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import Image, { StaticImageData } from "next/image";
|
||||
import "./AuthHeader.scss";
|
||||
|
||||
interface IAuthHeaderProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: StaticImageData;
|
||||
}
|
||||
|
||||
const AuthHeader: React.FC<IAuthHeaderProps> = ({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
}: IAuthHeaderProps) => {
|
||||
return (
|
||||
<div className="auth-header">
|
||||
<div className="auth-header__icon">
|
||||
<Image src={icon} alt="Auth Icon" />
|
||||
</div>
|
||||
|
||||
<div className="auth-header__text">
|
||||
<h2>{title}</h2>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthHeader;
|
@ -1,61 +0,0 @@
|
||||
.change-language {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
&__btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__popUp {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
}
|
||||
&__item,
|
||||
&__item_active {
|
||||
button {
|
||||
width: 150px;
|
||||
padding: 10px 14px;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
background-color: #fff;
|
||||
line-height: 24px;
|
||||
|
||||
color: #101828;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&__item:first-child,
|
||||
&__item_active:first-child {
|
||||
button {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__item:last-child,
|
||||
&__item_active:last-child {
|
||||
button {
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__item_active {
|
||||
button {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.change-language {
|
||||
&__popUp {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import "./ChangeLanguage.scss";
|
||||
import Image from "next/image";
|
||||
import lang_icon from "./icons/lang-icon.svg";
|
||||
import chevron_icon from "./icons/chevron-down-icon.svg";
|
||||
import check_icon from "./icons/check-icon.svg";
|
||||
import { useState } from "react";
|
||||
|
||||
const ChangeLanguage = () => {
|
||||
const [lang, setLang] = useState<string>("ru");
|
||||
const [popUp, setPopUp] = useState<boolean>(false);
|
||||
const languages = [
|
||||
{ id: "ru", language: "Русский" },
|
||||
{ id: "kg", language: "Кыргыз" },
|
||||
{ id: "en", language: "English" },
|
||||
];
|
||||
return (
|
||||
<div className="change-language">
|
||||
<button
|
||||
onClick={() => setPopUp((prev) => !prev)}
|
||||
className="change-language__btn"
|
||||
>
|
||||
<Image src={lang_icon} alt="Language Icon" />
|
||||
<Image src={chevron_icon} alt="Chevron Down Icon" />
|
||||
</button>
|
||||
{popUp && (
|
||||
<ul className="change-language__popUp">
|
||||
{languages.map((language) => (
|
||||
<li
|
||||
key={language.id}
|
||||
className={
|
||||
lang === language.id
|
||||
? "change-language__item_active"
|
||||
: "change-language__item"
|
||||
}
|
||||
>
|
||||
<button onClick={() => setLang(language.id)}>
|
||||
{language.language}
|
||||
{language.id === lang ? (
|
||||
<Image src={check_icon} alt="Check Icon" />
|
||||
) : null}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeLanguage;
|
@ -1,5 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="check">
|
||||
<path id="Icon" d="M16.6663 5L7.49967 14.1667L3.33301 10" stroke="#3695D8" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 270 B |
@ -1,11 +0,0 @@
|
||||
<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="IMAGE" clip-path="url(#clip0_1290_49928)">
|
||||
<path id="Vector" d="M0.246139 1.97381C-0.0820464 1.63666 -0.0820464 1.09002 0.246139 0.752867C0.574324 0.415711 1.10642 0.415711 1.4346 0.752867L5.59423 5.02619C5.92241 5.36334 5.92241 5.90998 5.59423 6.24713C5.26604 6.58429 4.73395 6.58429 4.40576 6.24713L0.246139 1.97381Z" fill="#66727F"/>
|
||||
<path id="Vector_2" d="M8.56495 0.752867C8.89313 0.415711 9.42523 0.415711 9.75341 0.752867C10.0816 1.09002 10.0816 1.63666 9.75341 1.97382L5.59378 6.247C5.26559 6.58416 4.7335 6.58416 4.40531 6.247C4.07713 5.90985 4.07714 5.36334 4.40532 5.02619L8.56495 0.752867Z" fill="#66727F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1290_49928">
|
||||
<rect width="10" height="6" fill="white" transform="translate(0 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 865 B |
@ -1,7 +0,0 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="globe">
|
||||
<path id="Vector" d="M12 22.5C17.5228 22.5 22 18.0228 22 12.5C22 6.97715 17.5228 2.5 12 2.5C6.47715 2.5 2 6.97715 2 12.5C2 18.0228 6.47715 22.5 12 22.5Z" stroke="#66727F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M2 12.5H22" stroke="#66727F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_3" d="M12 2.5C14.5013 5.23835 15.9228 8.79203 16 12.5C15.9228 16.208 14.5013 19.7616 12 22.5C9.49872 19.7616 8.07725 16.208 8 12.5C8.07725 8.79203 9.49872 5.23835 12 2.5Z" stroke="#66727F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 750 B |
@ -1,7 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.custom-link {
|
||||
color: $blue;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import "./CustomLink.scss";
|
||||
|
||||
interface ICustomLink {
|
||||
children?: React.ReactNode;
|
||||
path: string;
|
||||
style?: object;
|
||||
}
|
||||
|
||||
const CustomLink: React.FC<ICustomLink> = ({
|
||||
children,
|
||||
path,
|
||||
style,
|
||||
}: ICustomLink) => {
|
||||
return (
|
||||
<Link href={path} className="custom-link" style={style}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomLink;
|
@ -1,19 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.google-btn {
|
||||
width: 100%;
|
||||
padding: 10px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
|
||||
border-radius: 8px;
|
||||
border: 1px solid $gray-300;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import "./GoogleButton.scss";
|
||||
import google_icon from "./icons/google-icon.svg";
|
||||
|
||||
interface IGoogleButton {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const GoogleButton = ({ children }: IGoogleButton) => {
|
||||
return (
|
||||
<div className="google-btn">
|
||||
<Image src={google_icon} alt="Google Icon" />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoogleButton;
|
@ -1,13 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Social icon" clip-path="url(#clip0_2134_15985)">
|
||||
<path id="Vector" d="M23.7663 12.2763C23.7663 11.4605 23.7001 10.6404 23.559 9.83789H12.2402V14.4589H18.722C18.453 15.9492 17.5888 17.2676 16.3233 18.1054V21.1037H20.1903C22.4611 19.0137 23.7663 15.9272 23.7663 12.2763Z" fill="#4285F4"/>
|
||||
<path id="Vector_2" d="M12.2401 24.0013C15.4766 24.0013 18.2059 22.9387 20.1945 21.1044L16.3276 18.106C15.2517 18.838 13.8627 19.2525 12.2445 19.2525C9.11388 19.2525 6.45946 17.1404 5.50705 14.3008H1.5166V17.3917C3.55371 21.4439 7.7029 24.0013 12.2401 24.0013Z" fill="#34A853"/>
|
||||
<path id="Vector_3" d="M5.50277 14.3007C5.00011 12.8103 5.00011 11.1965 5.50277 9.70618V6.61523H1.51674C-0.185266 10.006 -0.185266 14.0009 1.51674 17.3916L5.50277 14.3007Z" fill="#FBBC04"/>
|
||||
<path id="Vector_4" d="M12.2401 4.74966C13.9509 4.7232 15.6044 5.36697 16.8434 6.54867L20.2695 3.12262C18.1001 1.0855 15.2208 -0.034466 12.2401 0.000808666C7.7029 0.000808666 3.55371 2.55822 1.5166 6.61481L5.50264 9.70575C6.45064 6.86173 9.10947 4.74966 12.2401 4.74966Z" fill="#EA4335"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2134_15985">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,46 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.input-with-label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
label {
|
||||
color: $gray-700;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: $red-500;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
padding: 10px 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-radius: 8px;
|
||||
border: 1px solid $gray-300;
|
||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: $gray-500;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 24px;
|
||||
width: 24;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import "./InputWithLabel.scss";
|
||||
import eye_icon from "./icons/eye-icon.svg";
|
||||
import eye_off_icon from "./icons/eye-off-icon.svg";
|
||||
import { useState } from "react";
|
||||
|
||||
interface IInputWithLabel {
|
||||
value?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
name?: string;
|
||||
secret?: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const InputWithLabel: React.FC<IInputWithLabel> = ({
|
||||
placeholder,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
name,
|
||||
secret,
|
||||
error,
|
||||
}: IInputWithLabel) => {
|
||||
const [show, setShow] = useState<boolean>(false);
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="input-with-label">
|
||||
<label>{label}</label>
|
||||
|
||||
<div className="input-with-label__wrapper">
|
||||
<input
|
||||
onChange={handleChange}
|
||||
type={!secret ? "text" : show ? "text" : "password"}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
/>
|
||||
{secret && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShow((prev) => !prev)}
|
||||
>
|
||||
{show ? (
|
||||
<Image src={eye_icon} alt="Eye Opened Icon" />
|
||||
) : (
|
||||
<Image src={eye_off_icon} alt="Eye Closed Icon" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{error ? <p>{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputWithLabel;
|
@ -1,6 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="eye">
|
||||
<path id="Vector" d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z" stroke="#979797" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="#979797" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 518 B |
@ -1,11 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="eye-off" clip-path="url(#clip0_1668_50526)">
|
||||
<path id="Vector" d="M17.94 17.94C16.2306 19.243 14.1491 19.9649 12 20C5 20 1 12 1 12C2.24389 9.68192 3.96914 7.65663 6.06 6.06003M9.9 4.24002C10.5883 4.0789 11.2931 3.99836 12 4.00003C19 4.00003 23 12 23 12C22.393 13.1356 21.6691 14.2048 20.84 15.19M14.12 14.12C13.8454 14.4148 13.5141 14.6512 13.1462 14.8151C12.7782 14.9791 12.3809 15.0673 11.9781 15.0744C11.5753 15.0815 11.1752 15.0074 10.8016 14.8565C10.4281 14.7056 10.0887 14.4811 9.80385 14.1962C9.51897 13.9113 9.29439 13.572 9.14351 13.1984C8.99262 12.8249 8.91853 12.4247 8.92563 12.0219C8.93274 11.6191 9.02091 11.2219 9.18488 10.8539C9.34884 10.4859 9.58525 10.1547 9.88 9.88003" stroke="#979797" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M1 1L23 23" stroke="#979797" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1668_50526">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,18 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.nav-sign-in,
|
||||
.nav-profile {
|
||||
padding: 8px 16px;
|
||||
background-color: $light-blue;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.nav-sign-in,
|
||||
.nav-profile {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import "./NavAuthBtn.scss";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
const NavAuthBtn = () => {
|
||||
const session = useSession();
|
||||
return session.status === "authenticated" ? (
|
||||
<Link href="/profile/personal-data" className="nav-profile">
|
||||
Профиль
|
||||
</Link>
|
||||
) : (
|
||||
<Link href="/sign-in" className="nav-sign-in">
|
||||
Войти
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavAuthBtn;
|
@ -1,4 +1,4 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
@import "../../shared/ui/variables.scss";
|
||||
|
||||
.news-card {
|
||||
height: 100%;
|
||||
|
@ -6,7 +6,7 @@ import Link from "next/link";
|
||||
|
||||
interface INewsCard {
|
||||
id: number;
|
||||
image: StaticImageData;
|
||||
image: StaticImageData | string;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
|
@ -1,61 +0,0 @@
|
||||
.review-card {
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #c5c6c5;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
&_left {
|
||||
min-width: 60px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&_right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
h4 {
|
||||
color: #3e3232;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
color: rgba(62, 50, 50, 0.75);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.review-card {
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import "./ReviewCard.scss";
|
||||
import calendar_icon from "./icons/calendar-icon.svg";
|
||||
|
||||
interface IReviewCardProps {
|
||||
item: IReview;
|
||||
}
|
||||
|
||||
interface IReview {
|
||||
id: number;
|
||||
author: IAuthor;
|
||||
review: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
const ReviewCard: React.FC<IReviewCardProps> = ({
|
||||
item,
|
||||
}: IReviewCardProps) => {
|
||||
const months: Record<string, string> = {
|
||||
"01": "Январь",
|
||||
"02": "Февраль",
|
||||
"03": "Март",
|
||||
"04": "Апрель",
|
||||
"05": "Май",
|
||||
"06": "Июнь",
|
||||
"07": "Июль",
|
||||
"08": "Август",
|
||||
"09": "Сентябрь",
|
||||
"10": "Октябрь",
|
||||
"11": "Ноябрь",
|
||||
"12": "Декабрь",
|
||||
};
|
||||
|
||||
const year = item?.created_at.slice(0, 4);
|
||||
const month = item?.created_at.slice(5, 7);
|
||||
const day = item?.created_at.slice(8, 10);
|
||||
return (
|
||||
<div className="review-card">
|
||||
<div className="review-card__header">
|
||||
<img
|
||||
className="review-card__header_left"
|
||||
src={item.author.image}
|
||||
alt="Author Image"
|
||||
/>
|
||||
<div className="review-card__header_right">
|
||||
<h4>
|
||||
{item.author.first_name} {item.author.last_name}
|
||||
</h4>
|
||||
<span>
|
||||
<Image src={calendar_icon} alt="Calendar Icon" />
|
||||
{month && months[month]} {day}, {year}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>{item.review}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReviewCard;
|
@ -1,8 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="calendar">
|
||||
<path id="Vector" d="M12.6667 2.66699H3.33333C2.59695 2.66699 2 3.26395 2 4.00033V13.3337C2 14.07 2.59695 14.667 3.33333 14.667H12.6667C13.403 14.667 14 14.07 14 13.3337V4.00033C14 3.26395 13.403 2.66699 12.6667 2.66699Z" stroke="#6E6565" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M10.666 1.33301V3.99967" stroke="#6E6565" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_3" d="M5.33398 1.33301V3.99967" stroke="#6E6565" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_4" d="M2 6.66699H14" stroke="#6E6565" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 747 B |
@ -1,39 +1,47 @@
|
||||
.section-header {
|
||||
margin-bottom: 52px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
text-align: center;
|
||||
|
||||
h3 {
|
||||
font-size: 42px;
|
||||
font-weight: 500;
|
||||
color: #32303a;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
line-height: 29px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.section-header {
|
||||
gap: 20px;
|
||||
margin-bottom: 42px;
|
||||
|
||||
h3 {
|
||||
font-size: 36px;
|
||||
line-height: 43px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.section-header {
|
||||
gap: 16px;
|
||||
margin-bottom: 42px;
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
22
src/Entities/SectionHeader/SectionHeader.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import "./SectionHeader.scss";
|
||||
|
||||
interface ISectionHeaderProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
style?: object;
|
||||
}
|
||||
|
||||
const SectionHeader: React.FC<ISectionHeaderProps> = ({
|
||||
title,
|
||||
description,
|
||||
style,
|
||||
}: ISectionHeaderProps) => {
|
||||
return (
|
||||
<div style={style} className="section-header">
|
||||
<h3>{title}</h3>
|
||||
{description && <p>{description}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionHeader;
|
@ -1,67 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import "./Switch.scss";
|
||||
import { useState } from "react";
|
||||
|
||||
enum ESwitch {
|
||||
BUTTON,
|
||||
A,
|
||||
}
|
||||
|
||||
interface ISwitch {
|
||||
color?: string;
|
||||
onClick?: () => void;
|
||||
defaultState?: boolean;
|
||||
type?: ESwitch;
|
||||
href?: ESwitch extends ESwitch.A ? string : string | undefined;
|
||||
}
|
||||
|
||||
const Switch: React.FC<ISwitch> = ({
|
||||
color,
|
||||
onClick,
|
||||
defaultState,
|
||||
type,
|
||||
href,
|
||||
}: ISwitch) => {
|
||||
const [toggle, setToggle] = useState(defaultState);
|
||||
return type === ESwitch.BUTTON ? (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: !toggle
|
||||
? "#32303A"
|
||||
: color
|
||||
? color
|
||||
: "#e64452",
|
||||
}}
|
||||
onClick={() => {
|
||||
setToggle((prev) => !prev);
|
||||
onClick && onClick();
|
||||
}}
|
||||
className={`switch ${toggle ? "switch-active" : ""}`}
|
||||
>
|
||||
<div className="switch__thumb"></div>
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
scroll={false}
|
||||
href={href ? href : "?category"}
|
||||
style={{
|
||||
backgroundColor: !toggle
|
||||
? "#32303A"
|
||||
: color
|
||||
? color
|
||||
: "#e64452",
|
||||
}}
|
||||
onClick={() => {
|
||||
setToggle((prev) => !prev);
|
||||
onClick && onClick();
|
||||
}}
|
||||
className={`switch ${toggle ? "switch-active" : ""}`}
|
||||
>
|
||||
<div className="switch__thumb"></div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default Switch;
|
@ -1,31 +0,0 @@
|
||||
.create-report-map {
|
||||
height: 410px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 290px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #c5c6c5;
|
||||
|
||||
&__info {
|
||||
padding: 20px 5px 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 11px;
|
||||
|
||||
h4 {
|
||||
color: #32303a;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #32303a;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 140%;
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import "./CreateReportMap.scss";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import {
|
||||
MapContainer,
|
||||
Marker,
|
||||
TileLayer,
|
||||
useMapEvents,
|
||||
} from "react-leaflet";
|
||||
import { LatLng, LatLngTuple, LeafletMouseEvent } from "leaflet";
|
||||
import pin from "./assets/pin.svg";
|
||||
import Image from "next/image";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface ICreateReportMapProps {
|
||||
markers: LatLng[];
|
||||
setMarkers: Dispatch<SetStateAction<LatLng[]>>;
|
||||
}
|
||||
|
||||
const CreateReportMap: React.FC<ICreateReportMapProps> = ({
|
||||
markers,
|
||||
setMarkers,
|
||||
}: ICreateReportMapProps) => {
|
||||
const position = [42.8746, 74.606];
|
||||
|
||||
const handleMapClick = (e: LeafletMouseEvent) => {
|
||||
if (markers.length <= 2) {
|
||||
setMarkers((prev) => [...prev, e.latlng]);
|
||||
}
|
||||
};
|
||||
|
||||
const LocationMarker = () => {
|
||||
useMapEvents({
|
||||
click: handleMapClick,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{markers.map((marker) => (
|
||||
<Marker
|
||||
key={marker.lat}
|
||||
position={position as LatLngTuple}
|
||||
></Marker>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="create-report-map">
|
||||
<MapContainer
|
||||
center={position as LatLngTuple}
|
||||
zoom={13}
|
||||
scrollWheelZoom={false}
|
||||
className="create-report-map__container"
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
|
||||
<LocationMarker />
|
||||
</MapContainer>
|
||||
|
||||
<div className="create-report-map__info">
|
||||
<h4>Как отметить участок дороги?</h4>
|
||||
<Image src={pin} alt="Pin Image" />
|
||||
<p>
|
||||
Поставьте булавку и начните рисовать участок дороги{" "}
|
||||
<span>
|
||||
(он может состоять из любого количества ломаных линий)
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p>Чтобы удалить отрезок нажмите на точки повторно.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateReportMap;
|
@ -1,5 +0,0 @@
|
||||
<svg width="82" height="65" viewBox="0 0 82 65" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M32.8125 9.37536C32.8124 7.89715 32.3929 6.4493 31.6027 5.19999C30.8126 3.95067 29.6842 2.95117 28.3487 2.31755C27.0132 1.68394 25.5253 1.44223 24.0578 1.6205C22.5904 1.79876 21.2037 2.38969 20.0587 3.32464C18.9137 4.25959 18.0575 5.50019 17.5894 6.90235C17.1214 8.30451 17.0608 9.81069 17.4146 11.2459C17.7684 12.6812 18.5222 13.9866 19.5883 15.0106C20.6544 16.0345 21.9892 16.735 23.4375 17.0306V44.684C23.4377 45.0991 23.5203 45.51 23.6807 45.8929L24.6641 48.2367C24.697 48.297 24.7456 48.3473 24.8047 48.3824C24.8638 48.4174 24.9313 48.436 25 48.436C25.0687 48.436 25.1362 48.4174 25.1953 48.3824C25.2544 48.3473 25.303 48.297 25.3359 48.2367L26.3193 45.8929C26.4797 45.51 26.5623 45.0991 26.5625 44.684V17.0306C28.3259 16.6683 29.9104 15.709 31.0489 14.3145C32.1874 12.92 32.8103 11.1756 32.8125 9.37536ZM27.3438 9.37536C26.8802 9.37536 26.4271 9.2379 26.0416 8.98037C25.6562 8.72284 25.3558 8.35679 25.1784 7.92853C25.001 7.50026 24.9546 7.02901 25.045 6.57437C25.1355 6.11973 25.3587 5.70211 25.6865 5.37433C26.0142 5.04655 26.4319 4.82333 26.8865 4.7329C27.3412 4.64246 27.8124 4.68888 28.2407 4.86627C28.6689 5.04366 29.035 5.34407 29.2925 5.7295C29.55 6.11492 29.6875 6.56806 29.6875 7.03161C29.6875 7.65321 29.4406 8.24936 29.001 8.6889C28.5615 9.12843 27.9654 9.37536 27.3438 9.37536Z" fill="#E64452"/>
|
||||
<circle cx="25" cy="51" r="4.75" fill="white" stroke="black" stroke-width="0.5"/>
|
||||
<line x1="33.2944" y1="53.5292" x2="81.0099" y2="63.081" stroke="#E64452" stroke-width="3" stroke-dasharray="6 6"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,64 +0,0 @@
|
||||
.create-review {
|
||||
margin-top: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #3e3232;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
|
||||
span {
|
||||
width: 4px;
|
||||
height: 10px;
|
||||
border-radius: 12px;
|
||||
background: #3998e8;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
textarea {
|
||||
height: 258px;
|
||||
padding: 27px 18px;
|
||||
resize: none;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #c5c6c5;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
}
|
||||
::placeholder {
|
||||
color: #c5c6c5;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
button {
|
||||
width: fit-content;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.create-review {
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.create-review {
|
||||
gap: 6px;
|
||||
|
||||
div {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import "./CreateReview.scss";
|
||||
import { useState } from "react";
|
||||
import Button from "@/Shared/UI/Button/Button";
|
||||
import { createReviewAction } from "./actions";
|
||||
import { StaticImageData } from "next/image";
|
||||
|
||||
interface ICreateReviewProps {
|
||||
endpoint: string;
|
||||
id: number | null | undefined;
|
||||
}
|
||||
|
||||
const CreateReview: React.FC<ICreateReviewProps> = ({
|
||||
endpoint,
|
||||
id,
|
||||
}: ICreateReviewProps) => {
|
||||
const [review, setReview] = useState<string>("");
|
||||
|
||||
return (
|
||||
<div className="create-review">
|
||||
<h3>
|
||||
<span />
|
||||
Написать комментарий
|
||||
</h3>
|
||||
<div>
|
||||
<textarea
|
||||
onChange={(e) => setReview(e.target.value)}
|
||||
value={review}
|
||||
placeholder="Напишите комментарий"
|
||||
/>
|
||||
<Button
|
||||
disabled={createReviewAction.isLoading}
|
||||
onClick={() => {
|
||||
setReview("");
|
||||
createReviewAction.createReview(endpoint, id, review);
|
||||
}}
|
||||
>
|
||||
Отправить
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateReview;
|
@ -1,59 +0,0 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import { getTokens } from "@/Shared/helpers/getTokens";
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
class CreateReview {
|
||||
response: string;
|
||||
isLoading: boolean;
|
||||
error: string;
|
||||
constructor() {
|
||||
this.response = "";
|
||||
this.isLoading = false;
|
||||
this.error = "";
|
||||
}
|
||||
|
||||
async createReview(
|
||||
endpoint: string,
|
||||
id: number | null | undefined,
|
||||
review: string
|
||||
) {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
|
||||
const access_token = getTokens()?.access_token;
|
||||
|
||||
const Authorization = `Bearer ${access_token}`;
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
};
|
||||
|
||||
const body = {
|
||||
review,
|
||||
};
|
||||
|
||||
const response = await axios.post(
|
||||
`${baseAPI}/${endpoint}/${id}/reviews/`,
|
||||
body,
|
||||
config
|
||||
);
|
||||
|
||||
this.response = response.data;
|
||||
|
||||
console.log(response);
|
||||
} catch (error: unknown) {
|
||||
console.log(error);
|
||||
if (error instanceof AxiosError) {
|
||||
this.error = error.message;
|
||||
} else {
|
||||
this.error = "An error ocured";
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createReviewAction = new CreateReview();
|
@ -1,52 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.nav-menu-btn,
|
||||
.nav-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
padding: 48px 30px;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 72px;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
|
||||
&__pages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 26px;
|
||||
|
||||
&-item,
|
||||
&-item_active {
|
||||
a {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&-item_active {
|
||||
a {
|
||||
color: $light-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.nav-menu-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.nav-menu {
|
||||
padding: 40px 16px;
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import "./NavMenu.scss";
|
||||
import Image from "next/image";
|
||||
import menu_icon from "./icons/menu-icon.svg";
|
||||
import cross_icon from "./icons/cross-icon.svg";
|
||||
import { useState } from "react";
|
||||
import { pages } from "@/Shared/variables/pages";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
const NavMenu = () => {
|
||||
const [menu, setMenu] = useState<boolean>(false);
|
||||
const auth = false;
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="nav-menu-btn"
|
||||
onClick={() => setMenu((prev) => !prev)}
|
||||
>
|
||||
{menu ? (
|
||||
<Image src={cross_icon} alt="Cross Icon" />
|
||||
) : (
|
||||
<Image src={menu_icon} alt="Menu Icon" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{menu && (
|
||||
<div className="nav-menu">
|
||||
<ul className="nav-menu__pages">
|
||||
{pages.map((page) => (
|
||||
<li
|
||||
key={page.id}
|
||||
className={
|
||||
page.path === pathname
|
||||
? "nav-menu__pages-item_active"
|
||||
: "nav-menu__pages-item"
|
||||
}
|
||||
>
|
||||
<Link onClick={() => setMenu(false)} href={page.path}>
|
||||
{page.page}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
{auth ? (
|
||||
<li
|
||||
className={
|
||||
pathname === "/sign-in"
|
||||
? "nav-menu__pages-item_active"
|
||||
: "nav-menu__pages-item"
|
||||
}
|
||||
>
|
||||
<Link onClick={() => setMenu(false)} href="/sign-in">
|
||||
Войти
|
||||
</Link>
|
||||
</li>
|
||||
) : (
|
||||
<li
|
||||
className={
|
||||
pathname === "/profile"
|
||||
? "nav-menu__pages-item_active"
|
||||
: "nav-menu__pages-item"
|
||||
}
|
||||
>
|
||||
<Link onClick={() => setMenu(false)} href="/profile">
|
||||
Профиль
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavMenu;
|
@ -1,6 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="x">
|
||||
<path id="Vector" d="M18 6L6 18" stroke="#32303A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M6 6L18 18" stroke="#32303A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 353 B |
@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="menu">
|
||||
<path id="Icon" d="M3 12H21M3 6H21M3 18H21" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 249 B |
@ -1,10 +0,0 @@
|
||||
.report-like {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
color: rgb(74, 192, 63);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import "./ReportLike.scss";
|
||||
import Image from "next/image";
|
||||
import like_icon from "./icons/like-icon.svg";
|
||||
|
||||
interface IReportLikeProps {
|
||||
count_likes: number;
|
||||
}
|
||||
|
||||
const ReportLike: React.FC<IReportLikeProps> = ({
|
||||
count_likes,
|
||||
}: IReportLikeProps) => {
|
||||
return (
|
||||
<div className="report-like">
|
||||
<button>
|
||||
<Image src={like_icon} alt="Like Icon" />
|
||||
</button>
|
||||
{count_likes}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportLike;
|
@ -1,5 +0,0 @@
|
||||
<svg width="26" height="27" viewBox="0 0 26 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="thumbs-up">
|
||||
<path id="Vector" d="M7.58317 12.417L11.9165 2.66699C12.7785 2.66699 13.6051 3.0094 14.2146 3.6189C14.8241 4.22839 15.1665 5.05504 15.1665 5.91699V10.2503H21.2982C21.6122 10.2468 21.9233 10.3115 22.2099 10.4401C22.4964 10.5688 22.7516 10.7581 22.9577 10.9951C23.1638 11.2321 23.3159 11.5111 23.4035 11.8128C23.491 12.1144 23.512 12.4315 23.4648 12.742L21.9698 22.492C21.8915 23.0086 21.6291 23.4796 21.2309 23.818C20.8327 24.1564 20.3257 24.3396 19.8032 24.3337H7.58317M7.58317 12.417V24.3337M7.58317 12.417H4.33317C3.75853 12.417 3.20743 12.6453 2.80111 13.0516C2.39478 13.4579 2.1665 14.009 2.1665 14.5837V22.167C2.1665 22.7416 2.39478 23.2927 2.80111 23.6991C3.20743 24.1054 3.75853 24.3337 4.33317 24.3337H7.58317" stroke="#4AC03F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 929 B |
@ -1,34 +0,0 @@
|
||||
import "./SearchBar.scss";
|
||||
import Image from "next/image";
|
||||
import search_icon from "./icons/search-icon.svg";
|
||||
|
||||
interface ISearchBar {
|
||||
placeholder?: string;
|
||||
style?: object;
|
||||
value: string;
|
||||
setValue: (search: string) => any;
|
||||
}
|
||||
|
||||
const SearchBar: React.FC<ISearchBar> = ({
|
||||
placeholder,
|
||||
style,
|
||||
value,
|
||||
setValue,
|
||||
}: ISearchBar) => {
|
||||
return (
|
||||
<div style={style} className="search-bar">
|
||||
<div className="search-bar__input">
|
||||
<Image src={search_icon} alt="Search Icon" />
|
||||
<input
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
<button>Поиск</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
@ -1,6 +0,0 @@
|
||||
<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="search">
|
||||
<path id="Vector" d="M11 20.2796C15.4183 20.2796 19 16.5547 19 11.9596C19 7.36464 15.4183 3.63965 11 3.63965C6.58172 3.63965 3 7.36464 3 11.9596C3 16.5547 6.58172 20.2796 11 20.2796Z" stroke="#C5C6C5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M20.9999 22.3599L16.6499 17.8359" stroke="#C5C6C5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 531 B |
@ -1,44 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.sign-in-form {
|
||||
width: 360px;
|
||||
display: grid;
|
||||
gap: 36px;
|
||||
|
||||
&__inputs,
|
||||
&__btns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
p {
|
||||
color: $red-500;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__btns {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__no-account {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
|
||||
span {
|
||||
color: $gray-500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.sign-in-form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import "./SignInForm.scss";
|
||||
import { useEffect, useState } from "react";
|
||||
import InputWithLabel from "@/Entities/InputWithLabel/InputWithLabel";
|
||||
import Button from "@/Shared/UI/Button/Button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import CustomLink from "@/Entities/CustomLink/CustomLink";
|
||||
import GoogleButton from "@/Entities/GoogleButton/GoogleButton";
|
||||
import { useSignIn } from "./sign-in.store";
|
||||
import DefaultLoader from "@/Shared/UI/DefaultLoader/DefaultLoader";
|
||||
import { signIn } from "next-auth/react";
|
||||
|
||||
const SignInForm = () => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit: React.FormEventHandler<
|
||||
HTMLFormElement
|
||||
> = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
console.log(formData.get("email"), formData.get("email"));
|
||||
|
||||
const res = await signIn("credentials", {
|
||||
email: formData.get("email"),
|
||||
password: formData.get("password"),
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
if (res && !res.error) {
|
||||
router.push("/profile");
|
||||
} else {
|
||||
console.log(res?.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="sign-in-form" onSubmit={handleSubmit}>
|
||||
<div className="sign-in-form__inputs">
|
||||
<InputWithLabel
|
||||
name="email"
|
||||
label="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Введите email"
|
||||
error=""
|
||||
/>
|
||||
<InputWithLabel
|
||||
name="password"
|
||||
label="Пароль"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Введите пароль"
|
||||
secret
|
||||
error=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CustomLink
|
||||
path="/forgot-password"
|
||||
style={{ justifySelf: "flex-end" }}
|
||||
>
|
||||
Забыли пароль?
|
||||
</CustomLink>
|
||||
|
||||
<div className="sign-in-form__btns">
|
||||
<Button type="submit">Войти</Button>
|
||||
<GoogleButton>Войти через Google</GoogleButton>
|
||||
</div>
|
||||
|
||||
<div className="sign-in-form__no-account">
|
||||
<span>Еще нет аккаунта?</span>
|
||||
|
||||
<CustomLink path="/sign-up">Зарегистрируйтесь</CustomLink>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInForm;
|
@ -1,63 +0,0 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import { IFetch } from "@/Shared/types";
|
||||
import axios from "axios";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface SignInStore extends IFetch {
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
cleanRedirect: () => void;
|
||||
emailError: string;
|
||||
passwordError: string;
|
||||
redirect: boolean;
|
||||
}
|
||||
|
||||
export const useSignIn = create<SignInStore>((set) => ({
|
||||
loading: false,
|
||||
error: "",
|
||||
emailError: "",
|
||||
passwordError: "",
|
||||
redirect: false,
|
||||
login: async (email: string, password: string) => {
|
||||
if (!email.trim()) {
|
||||
set({ emailError: "Пожалуйста введите почту" });
|
||||
set({ passwordError: "" });
|
||||
|
||||
return;
|
||||
}
|
||||
if (!password.trim()) {
|
||||
set({ passwordError: "Пожалуйста введите пароль" });
|
||||
set({ emailError: "" });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = {
|
||||
email,
|
||||
password,
|
||||
};
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
|
||||
const response = await axios.post(
|
||||
`${baseAPI}/users/login/`,
|
||||
user
|
||||
);
|
||||
|
||||
localStorage.setItem("tokens", JSON.stringify(response.data));
|
||||
|
||||
set({ emailError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ error: "" });
|
||||
set({ redirect: true });
|
||||
} catch (error: any) {
|
||||
set({ emailError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ error: error.message });
|
||||
} finally {
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
cleanRedirect: () => {
|
||||
set({ redirect: false });
|
||||
},
|
||||
}));
|
@ -1,44 +0,0 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.sign-up-form {
|
||||
width: 360px;
|
||||
display: grid;
|
||||
gap: 36px;
|
||||
|
||||
&__inputs,
|
||||
&__btns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: $red-500;
|
||||
}
|
||||
}
|
||||
|
||||
&__btns {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__has-account {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
|
||||
span {
|
||||
color: $gray-500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.sign-up-form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import "./SignUpForm.scss";
|
||||
import InputWithLabel from "@/Entities/InputWithLabel/InputWithLabel";
|
||||
import Button from "@/Shared/UI/Button/Button";
|
||||
import CustomLink from "@/Entities/CustomLink/CustomLink";
|
||||
import { useRouter } from "next/navigation";
|
||||
import GoogleButton from "@/Entities/GoogleButton/GoogleButton";
|
||||
import { useSignUp } from "./sign-up.store";
|
||||
import DefaultLoader from "@/Shared/UI/DefaultLoader/DefaultLoader";
|
||||
|
||||
const SignUpForm = () => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>("");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
redirect,
|
||||
register,
|
||||
loading,
|
||||
emailError,
|
||||
passwordError,
|
||||
confirmPasswordError,
|
||||
matchPasswordError,
|
||||
error,
|
||||
} = useSignUp();
|
||||
|
||||
useEffect(() => {
|
||||
if (redirect) {
|
||||
router.push("/confirm-email");
|
||||
}
|
||||
}, [redirect]);
|
||||
|
||||
return (
|
||||
<form
|
||||
className="sign-up-form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
register(email, password, confirmPassword);
|
||||
}}
|
||||
>
|
||||
<div className="sign-up-form__inputs">
|
||||
<InputWithLabel
|
||||
label="Email"
|
||||
placeholder="Введите email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
error={emailError}
|
||||
/>
|
||||
<InputWithLabel
|
||||
label="Пароль"
|
||||
placeholder="Введите пароль"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
secret
|
||||
error={passwordError}
|
||||
/>
|
||||
<InputWithLabel
|
||||
label="Пароль Потверждения"
|
||||
placeholder="Повторите пароль"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
secret
|
||||
error={confirmPasswordError}
|
||||
/>
|
||||
{matchPasswordError ? <p>{matchPasswordError}</p> : null}
|
||||
{error ? <p>{error}</p> : null}
|
||||
</div>
|
||||
<div className="sign-up-form__btns">
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={() => router.push("/confirm-email")}
|
||||
>
|
||||
{loading ? <DefaultLoader /> : "Зарегистрироваться"}
|
||||
</Button>
|
||||
<GoogleButton>Войти через Google</GoogleButton>
|
||||
</div>
|
||||
|
||||
<div className="sign-up-form__has-account">
|
||||
<span>Уже есть аккаунт?</span>
|
||||
<CustomLink path="/sign-in">Войти в аккаунт</CustomLink>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpForm;
|
@ -1,121 +0,0 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import { IFetch } from "@/Shared/types";
|
||||
import axios from "axios";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface SignUpStore extends IFetch {
|
||||
register: (
|
||||
email: string,
|
||||
password: string,
|
||||
confirmPassword: string
|
||||
) => Promise<void>;
|
||||
cleanRedirect: () => void;
|
||||
emailError: string;
|
||||
passwordError: string;
|
||||
confirmPasswordError: string;
|
||||
matchPasswordError: string;
|
||||
redirect: boolean;
|
||||
}
|
||||
|
||||
export const useSignUp = create<SignUpStore>((set) => ({
|
||||
loading: false,
|
||||
error: "",
|
||||
emailError: "",
|
||||
passwordError: "",
|
||||
confirmPasswordError: "",
|
||||
matchPasswordError: "",
|
||||
redirect: false,
|
||||
register: async (
|
||||
email: string,
|
||||
password: string,
|
||||
confirmPassword: string
|
||||
) => {
|
||||
if (!email.trim()) {
|
||||
set({ passwordError: "" });
|
||||
set({ confirmPasswordError: "" });
|
||||
set({ matchPasswordError: "" });
|
||||
set({ emailError: "Пожалуйста введите почту" });
|
||||
|
||||
return;
|
||||
}
|
||||
if (!password.trim()) {
|
||||
set({ emailError: "" });
|
||||
set({ confirmPasswordError: "" });
|
||||
set({ matchPasswordError: "" });
|
||||
set({ passwordError: "Пожалуйста введите пароль" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirmPassword.trim()) {
|
||||
set({ emailError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ matchPasswordError: "" });
|
||||
set({ confirmPasswordError: "Пожалуйста введите пароль" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (validatePassword(password)) {
|
||||
set({ emailError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ matchPasswordError: "" });
|
||||
set({ confirmPasswordError: "" });
|
||||
set({ error: "Минимум 8 символов, 1 заглавная буква и цифра" });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
set({ emailError: "" });
|
||||
set({ confirmPasswordError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ matchPasswordError: "Пароли не совпадают" });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = {
|
||||
email,
|
||||
password,
|
||||
password2: confirmPassword,
|
||||
};
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
|
||||
const response = await axios.post(
|
||||
`${baseAPI}/users/register/`,
|
||||
user
|
||||
);
|
||||
|
||||
localStorage.setItem("tokens", JSON.stringify(response.data));
|
||||
|
||||
set({ emailError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ confirmPasswordError: "" });
|
||||
set({ matchPasswordError: "" });
|
||||
set({ error: "" });
|
||||
set({ redirect: true });
|
||||
} catch (error: any) {
|
||||
set({ emailError: "" });
|
||||
set({ passwordError: "" });
|
||||
set({ confirmPasswordError: "" });
|
||||
set({ matchPasswordError: "" });
|
||||
set({ error: error.message });
|
||||
} finally {
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
cleanRedirect: () => {
|
||||
set({ redirect: false });
|
||||
},
|
||||
}));
|
||||
|
||||
const validatePassword = (password: string) => {
|
||||
const regex = /[A-Z]/;
|
||||
const digitRegex = /\d/;
|
||||
if (password.length < 8) return true;
|
||||
console.log("1");
|
||||
if (!regex.test(password) || !digitRegex.test(password))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
@ -1,100 +0,0 @@
|
||||
.about-us-page {
|
||||
padding: 118px 90px 0px 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
&__image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 25px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
h3 {
|
||||
color: #3e3232;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
|
||||
p {
|
||||
color: #3e3232;
|
||||
font-feature-settings: "clig" off, "liga" off;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.about-us-page {
|
||||
padding: 112px 30px 0px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.about-us-page {
|
||||
padding: 112px 30px 0px 30px;
|
||||
gap: 30px;
|
||||
|
||||
&__image {
|
||||
margin-bottom: 0;
|
||||
img {
|
||||
height: 392px;
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div {
|
||||
gap: 20px;
|
||||
p {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.about-us-page {
|
||||
padding: 112px 16px 0px 16px;
|
||||
gap: 20px;
|
||||
|
||||
&__image {
|
||||
margin-bottom: 30px;
|
||||
img {
|
||||
height: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
div {
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import "./AboutUsPage.scss";
|
||||
import Image from "next/image";
|
||||
import image from "./assets/image.png";
|
||||
|
||||
const AboutUsPage = () => {
|
||||
return (
|
||||
<div className="about-us-page">
|
||||
<HeaderText>О нас</HeaderText>
|
||||
<div className="about-us-page__image">
|
||||
<Image src={image} alt="About Us Image" />
|
||||
</div>
|
||||
|
||||
<div className="about-us-page__container">
|
||||
<h3>Don’t wait. The purpose of our lives is to be happy!</h3>
|
||||
<div>
|
||||
<p>
|
||||
Upon arrival, your senses will be rewarded with the
|
||||
pleasant scent of lemongrass oil used to clean the natural
|
||||
wood found throughout the room, creating a relaxing
|
||||
atmosphere within the space. A wonderful serenity has
|
||||
taken possession of my entire soul, like these sweet
|
||||
mornings of spring which I enjoy with my whole heart. I am
|
||||
alone, and feel the charm of existence in this spot, which
|
||||
was created for the bliss of souls like mine. I am so
|
||||
happy, my dear friend, so absorbed in the exquisite.
|
||||
</p>
|
||||
<p>
|
||||
When you are ready to indulge your sense of excitement,
|
||||
check out the range of water- sports opportunities at the
|
||||
resort’s on-site water-sports center. Want to leave your
|
||||
stress on the water? The resort has kayaks, paddleboards,
|
||||
or the low-key pedal boats. Snorkeling equipment is
|
||||
available as well, so you can experience the ever-changing
|
||||
undersea environment. Not only do visitors to a bed and
|
||||
breakfast get a unique perspective on the place they are
|
||||
visiting, they have options for special packages not
|
||||
available in other hotel settings.{" "}
|
||||
</p>
|
||||
<p>
|
||||
bed and breakfasts can partner easily with local
|
||||
businesses for a smoothly organized and highly
|
||||
personalized vacation experience. The Fife and Drum Inn
|
||||
offers options such as the Historic Triangle Package that
|
||||
includes three nights at the Inn, breakfasts, and
|
||||
admissions to historic Williamsburg, Jamestown, and
|
||||
Yorktown. Bed and breakfasts also lend themselves to
|
||||
romance.
|
||||
</p>
|
||||
<p>
|
||||
Part of the charm of a bed and breakfast is the
|
||||
uniqueness; art, décor, and food are integrated to create
|
||||
a complete experience. For example, the Fife and Drum
|
||||
retains the colonial feel of the area in all its guest
|
||||
rooms. Special features include antique furnishings,
|
||||
elegant four poster beds in some guest rooms, as well folk
|
||||
art and artifacts from the restoration period of the
|
||||
historic area available for guests to enjoy.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUsPage;
|
Before Width: | Height: | Size: 1.0 MiB |
@ -1,133 +0,0 @@
|
||||
.create-report {
|
||||
padding: 118px 90px 0px 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 38px;
|
||||
|
||||
&__wrapper {
|
||||
padding: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #d5d5d5;
|
||||
|
||||
h5 {
|
||||
color: #32303a;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
max-width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22px;
|
||||
|
||||
input {
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #c5c6c5;
|
||||
background: #fff;
|
||||
|
||||
color: #32303a;
|
||||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
|
||||
::placeholder {
|
||||
color: #c5c6c5;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__add-image {
|
||||
max-width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
color: #32303a;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__images {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
div {
|
||||
width: 187px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 187px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
button {
|
||||
color: #32303a;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
text-decoration-line: underline;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__btn {
|
||||
width: 330px;
|
||||
padding: 15px;
|
||||
gap: 10px;
|
||||
|
||||
background-color: #489fe1;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.create-report {
|
||||
padding: 118px 30px 0px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.create-report {
|
||||
padding: 112px 30px 0px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.create-report {
|
||||
padding: 112px 16px 0px 16px;
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import "./CreateReportPage.scss";
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import CreateReportMap from "@/Features/CreateReportMap/CreateReportMap";
|
||||
import paperclip__icon from "./icons/paperclip.svg";
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import arrow_right_icon from "./icons/arrow-right.svg";
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
const CreateReportPage = () => {
|
||||
const [location, setLocation] = useState<LatLng[]>([]);
|
||||
const [images, setImages] = useState<File[]>();
|
||||
const [description, setdescription] = useState<string>("");
|
||||
|
||||
const handleImages = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setImages(Array.from(e.target.files));
|
||||
}
|
||||
};
|
||||
|
||||
const deleteImage = (name: string) => {
|
||||
setImages((prev) => prev?.filter((image) => image.name !== name));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="create-report">
|
||||
<HeaderText>Написать обращение</HeaderText>
|
||||
|
||||
<div className="create-report__wrapper">
|
||||
<div className="input-label">
|
||||
<h5>Адрес</h5>
|
||||
<input type="text" placeholder="Выберите точки на карте" />
|
||||
</div>
|
||||
|
||||
<CreateReportMap
|
||||
markers={location}
|
||||
setMarkers={setLocation}
|
||||
/>
|
||||
|
||||
<div className="input-label">
|
||||
<h5>Добавьте описание проблемы</h5>
|
||||
<input
|
||||
value={description}
|
||||
onChange={(e) => setdescription(e.target.value)}
|
||||
type="text"
|
||||
placeholder="Введите описание"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="create-report__add-image">
|
||||
<h5>Добавьте фотографии</h5>
|
||||
<p>
|
||||
Загрузите до 5 фотографии, связанные с дорогой, которую Вы
|
||||
хотите отметить. Фотографии помогут лучше понять проблему.
|
||||
</p>
|
||||
<input
|
||||
onChange={handleImages}
|
||||
type="file"
|
||||
id="report-add-image"
|
||||
multiple
|
||||
/>
|
||||
<label htmlFor="report-add-image">
|
||||
<Image src={paperclip__icon} alt="Paper Clip Icon" />
|
||||
Прикрепить файл <span>(до 5 МБ)</span>
|
||||
</label>
|
||||
|
||||
<div className="create-report__images">
|
||||
{images?.map((image) => (
|
||||
<div>
|
||||
<img
|
||||
src={URL.createObjectURL(image)}
|
||||
key={image.name}
|
||||
alt="Report Image"
|
||||
/>
|
||||
<button onClick={() => deleteImage(image.name)}>
|
||||
удалить
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="create-report__btn">
|
||||
Отправить на модерацию
|
||||
<Image src={arrow_right_icon} alt="Arrow Right Icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateReportPage;
|
@ -1,6 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="arrow-right">
|
||||
<path id="Vector" d="M5 12H19" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="Vector_2" d="M12 5L19 12L12 19" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 364 B |
@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="paperclip">
|
||||
<path id="Vector" d="M21.4403 11.0499L12.2503 20.2399C11.1244 21.3658 9.59747 21.9983 8.00529 21.9983C6.41311 21.9983 4.88613 21.3658 3.76029 20.2399C2.63445 19.1141 2.00195 17.5871 2.00195 15.9949C2.00195 14.4027 2.63445 12.8758 3.76029 11.7499L12.9503 2.55992C13.7009 1.80936 14.7188 1.3877 15.7803 1.3877C16.8417 1.3877 17.8597 1.80936 18.6103 2.55992C19.3609 3.31048 19.7825 4.32846 19.7825 5.38992C19.7825 6.45138 19.3609 7.46936 18.6103 8.21992L9.41029 17.4099C9.03501 17.7852 8.52602 17.996 7.99529 17.996C7.46456 17.996 6.95557 17.7852 6.58029 17.4099C6.20501 17.0346 5.99418 16.5256 5.99418 15.9949C5.99418 15.4642 6.20501 14.9552 6.58029 14.5799L15.0703 6.09992" stroke="#32303A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 883 B |
@ -1,36 +0,0 @@
|
||||
import "./Homepage.scss";
|
||||
import Header from "@/Widgets/home/Header/Header";
|
||||
import MapSection from "@/Widgets/home/MapSection/MapSection";
|
||||
import RatingSection from "@/Widgets/home/RatingSection/RatingSection";
|
||||
import StatisticsSection from "@/Widgets/home/StatisticsSection/StatisticsSection";
|
||||
import NewsSection from "@/Widgets/home/NewsSection/NewsSection";
|
||||
|
||||
const Homepage = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
"тип-дороги": string;
|
||||
"карта-дорог": string;
|
||||
рейтинг: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div className="home">
|
||||
<Header />
|
||||
<StatisticsSection />
|
||||
<MapSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["карта-дорог"]}
|
||||
queryRating={searchParams["рейтинг"]}
|
||||
/>
|
||||
<RatingSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["карта-дорог"]}
|
||||
queryRating={searchParams["рейтинг"]}
|
||||
/>
|
||||
<NewsSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Homepage;
|
@ -1,40 +0,0 @@
|
||||
.news-details-page {
|
||||
padding: 118px 90px 0 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
&__wrapper {
|
||||
max-width: 1070px;
|
||||
display: flex;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: #3e3232;
|
||||
font-size: 20px;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.news-details-page {
|
||||
padding: 118px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.news-details-page {
|
||||
padding: 112px 30px 0 30px;
|
||||
gap: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.news-details-page {
|
||||
padding: 112px 16px 0 16px;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import { newsDetailsStore } from "./store";
|
||||
import "./NewsDetailsPage.scss";
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
|
||||
import ReviewsSection from "@/Widgets/general/ReviewsSection/ReviewsSection";
|
||||
import NewsHeader from "@/Widgets/NewsHeader/NewsHeader";
|
||||
|
||||
const NewsDetailsPage = async ({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string };
|
||||
}) => {
|
||||
const data = await newsDetailsStore.getNewsDetails(params.id);
|
||||
|
||||
return (
|
||||
<div className="news-details-page">
|
||||
<HeaderText>{data?.title}</HeaderText>
|
||||
|
||||
<div className="news-details-page__wrapper">
|
||||
<NewsHeader
|
||||
date={data?.created_at}
|
||||
image={data?.image}
|
||||
count_reviews={data?.count_reviews}
|
||||
/>
|
||||
<p className="news-details-page__description">
|
||||
{data?.description}
|
||||
</p>
|
||||
|
||||
<ReviewsSection id={data?.id} endpoint="news" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsDetailsPage;
|
@ -1 +0,0 @@
|
||||
export * from "./store";
|
@ -1,54 +0,0 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { StaticImport } from "next/dist/shared/lib/get-img-props";
|
||||
import { StaticImageData } from "next/image";
|
||||
|
||||
interface IDetails {
|
||||
id: number | null;
|
||||
image: string;
|
||||
title: string;
|
||||
description: string;
|
||||
news_review: IReview[];
|
||||
created_at: string;
|
||||
count_reviews: number;
|
||||
}
|
||||
|
||||
interface IReview {
|
||||
id: number;
|
||||
author: IAuthor;
|
||||
review: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
class NewsDetailsStore {
|
||||
error: string;
|
||||
constructor() {
|
||||
this.error = "";
|
||||
}
|
||||
|
||||
async getNewsDetails(id: string) {
|
||||
try {
|
||||
const response = await axios.get<IDetails>(
|
||||
`${baseAPI}/news/${id}/`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
this.error = error.message;
|
||||
} else {
|
||||
console.log(error);
|
||||
this.error = "An error occured";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const newsDetailsStore = new NewsDetailsStore();
|
@ -1,24 +0,0 @@
|
||||
.news-page {
|
||||
padding: 118px 90px 0px 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 35px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.news-page {
|
||||
padding: 118px 30px 0px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.news-page {
|
||||
padding: 112px 30px 0px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.news-page {
|
||||
padding: 112px 16px 0px 16px;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import "./NewsPage.scss";
|
||||
import NewsList from "@/Widgets/NewsList/NewsList";
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
|
||||
const NewsPage = async () => {
|
||||
return (
|
||||
<div className="news-page">
|
||||
<HeaderText>Новости</HeaderText>
|
||||
<NewsList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsPage;
|
@ -1,36 +0,0 @@
|
||||
.report-details-page {
|
||||
padding: 118px 90px 0 90px;
|
||||
|
||||
&__container {
|
||||
display: grid;
|
||||
grid-template-columns: 52% 1fr;
|
||||
gap: 76px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.report-details-page {
|
||||
padding: 118px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.report-details-page {
|
||||
padding: 112px 30px 0 30px;
|
||||
|
||||
&__container {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 45px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.report-details-page {
|
||||
padding: 112px 16px 0px 16px;
|
||||
|
||||
&__container {
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import ReportInfo from "@/Widgets/ReportInfo/ReportInfo";
|
||||
import "./ReportDetailsPage.scss";
|
||||
import { reportDetailsStore } from "./report-details.store";
|
||||
import ReportImages from "@/Widgets/ReportImages/ReportImages";
|
||||
import ReviewsSection from "@/Widgets/general/ReviewsSection/ReviewsSection";
|
||||
|
||||
const ReportDetailsPage = async ({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string };
|
||||
}) => {
|
||||
const data = await reportDetailsStore.getReportDetails(params.id);
|
||||
|
||||
return (
|
||||
<div className="report-details-page">
|
||||
<div className="report-details-page__container">
|
||||
<ReportInfo
|
||||
description={data?.description}
|
||||
date={data?.created_at}
|
||||
category={data?.category}
|
||||
count_likes={data?.total_likes}
|
||||
author={data?.author}
|
||||
location={data?.location}
|
||||
/>
|
||||
<ReportImages images={data?.image!} />
|
||||
</div>
|
||||
<ReviewsSection id={data?.id} endpoint="report" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportDetailsPage;
|
@ -1,57 +0,0 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
govern_status: any;
|
||||
}
|
||||
|
||||
interface ILocation {
|
||||
id: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface IImage {
|
||||
id: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface IReport {
|
||||
id: number;
|
||||
created_at: string;
|
||||
location: ILocation[];
|
||||
category: number;
|
||||
description: string;
|
||||
total_likes: number;
|
||||
author: IAuthor;
|
||||
image: IImage[];
|
||||
}
|
||||
|
||||
class ReportDetailsStore {
|
||||
error: string;
|
||||
constructor() {
|
||||
this.error = "";
|
||||
}
|
||||
|
||||
async getReportDetails(id: string) {
|
||||
try {
|
||||
const response = await axios.get<IReport>(
|
||||
`${baseAPI}/report/${id}/`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
this.error = error.message;
|
||||
} else {
|
||||
this.error = "An error occured";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const reportDetailsStore = new ReportDetailsStore();
|
@ -1,15 +0,0 @@
|
||||
.sign-in-page {
|
||||
height: 100vh;
|
||||
min-height: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.sign-in-page {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import "./SignInPage.scss";
|
||||
import sign_in_icon from "./icons/sign-in-icon.svg";
|
||||
import AuthHeader from "@/Entities/AuthHeader/AuthHeader";
|
||||
import SignInForm from "@/Features/SignInForm/SignInForm";
|
||||
|
||||
const SignInPage = () => {
|
||||
return (
|
||||
<div className="sign-in-page">
|
||||
<AuthHeader
|
||||
title="Войдите в аккаунт"
|
||||
description="Пожалуйста, введите свои данные"
|
||||
icon={sign_in_icon}
|
||||
/>
|
||||
<SignInForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInPage;
|
@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="log-in-04">
|
||||
<path id="Icon" d="M12 8L16 12M16 12L12 16M16 12H3M3.33782 7C5.06687 4.01099 8.29859 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C8.29859 22 5.06687 19.989 3.33782 17" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 395 B |
@ -1,15 +0,0 @@
|
||||
.sign-up-page {
|
||||
height: 100vh;
|
||||
min-height: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.sign-up-page {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import "./SignUpPage.scss";
|
||||
import AuthHeader from "@/Entities/AuthHeader/AuthHeader";
|
||||
import flag_icon from "./icons/flag-icon.svg";
|
||||
import SignUpForm from "@/Features/SignUpForm/SignUpForm";
|
||||
|
||||
const SignUpPage = () => {
|
||||
return (
|
||||
<div className="sign-up-page">
|
||||
<AuthHeader
|
||||
title="Регистрация"
|
||||
description="Пожалуйста, введите свои данные"
|
||||
icon={flag_icon}
|
||||
/>
|
||||
<SignUpForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpPage;
|
@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="flag-05">
|
||||
<path id="Icon" d="M14.0914 6.72222H20.0451C20.5173 6.72222 20.7534 6.72222 20.8914 6.82149C21.0119 6.9081 21.0903 7.04141 21.1075 7.18877C21.1272 7.35767 21.0126 7.56403 20.7833 7.97677L19.3624 10.5343C19.2793 10.684 19.2377 10.7589 19.2214 10.8381C19.207 10.9083 19.207 10.9806 19.2214 11.0508C19.2377 11.13 19.2793 11.2049 19.3624 11.3545L20.7833 13.9121C21.0126 14.3248 21.1272 14.5312 21.1075 14.7001C21.0903 14.8475 21.0119 14.9808 20.8914 15.0674C20.7534 15.1667 20.5173 15.1667 20.0451 15.1667H12.6136C12.0224 15.1667 11.7269 15.1667 11.5011 15.0516C11.3024 14.9504 11.141 14.7889 11.0398 14.5903C10.9247 14.3645 10.9247 14.0689 10.9247 13.4778V10.9444M7.23027 21.5L3.00805 4.61111M4.59143 10.9444H12.4025C12.9937 10.9444 13.2892 10.9444 13.515 10.8294C13.7137 10.7282 13.8751 10.5667 13.9763 10.3681C14.0914 10.1423 14.0914 9.84672 14.0914 9.25556V4.18889C14.0914 3.59772 14.0914 3.30214 13.9763 3.07634C13.8751 2.87773 13.7137 2.71625 13.515 2.61505C13.2892 2.5 12.9937 2.5 12.4025 2.5H4.64335C3.90602 2.5 3.53735 2.5 3.2852 2.65278C3.0642 2.78668 2.89999 2.99699 2.82369 3.24387C2.73663 3.52555 2.82605 3.88321 3.00489 4.59852L4.59143 10.9444Z" stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1,24 +0,0 @@
|
||||
.statistics-page {
|
||||
padding: 118px 90px 0 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.statistics-page {
|
||||
padding: 118px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.statistics-page {
|
||||
padding: 112px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.statistics-page {
|
||||
padding: 112px 16px 0 16px;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import "./StatisticsPage.scss";
|
||||
import SearchBar from "@/Features/SearchBar/SearchBar";
|
||||
import StatisticsTable from "@/Widgets/StatisticsTable/StatisticsTable";
|
||||
|
||||
const StatisticsPage = () => {
|
||||
return (
|
||||
<div className="statistics-page">
|
||||
<HeaderText>Статистика</HeaderText>
|
||||
<SearchBar
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Введите населенный пункт"
|
||||
/>
|
||||
<StatisticsTable />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticsPage;
|
@ -1,24 +0,0 @@
|
||||
.volunteers-page {
|
||||
padding: 118px 90px 0 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.volunteers-page {
|
||||
padding: 118px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.volunteers-page {
|
||||
padding: 112px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.volunteers-page {
|
||||
padding: 112px 16px 0 16px;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import "./VolunteersPage.scss";
|
||||
import VolunteersTable from "@/Widgets/VolunteersTable/VolunteersTable";
|
||||
|
||||
const VolunteersPage = () => {
|
||||
return (
|
||||
<div className="volunteers-page">
|
||||
<HeaderText>Волонтеры</HeaderText>
|
||||
<VolunteersTable />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VolunteersPage;
|
@ -1,7 +0,0 @@
|
||||
import "./MyReportsPage.scss";
|
||||
|
||||
const MyReportsPage = () => {
|
||||
return <div className="my-reports-page">MyReportsPage</div>;
|
||||
};
|
||||
|
||||
export default MyReportsPage;
|
@ -1,7 +0,0 @@
|
||||
import "./PersonalDataPage.scss";
|
||||
|
||||
const PersonalDataPage = () => {
|
||||
return <div className="personal-data-page">PersonalDataPage</div>;
|
||||
};
|
||||
|
||||
export default PersonalDataPage;
|
@ -1,24 +0,0 @@
|
||||
.profile-layout {
|
||||
padding: 118px 90px 0 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 50px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.personal-data-page {
|
||||
padding: 118px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.personal-data-page {
|
||||
padding: 112px 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.personal-data-page {
|
||||
padding: 112px 16px 0 16px;
|
||||
}
|
||||
}
|