.
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
NEXTAUTH_SECRET=";sadmfxflpdk"
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
91
app/api/auth/[...nextauth]/route.ts
Normal file
@ -0,0 +1,91 @@
|
||||
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 };
|
3
app/create-report/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import CreateReportPage from "@/Pages/CreateReportPage/CreateReportPage";
|
||||
|
||||
export default CreateReportPage;
|
3
app/profile/layout.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import ProfileLayout from "@/Pages/profile/ProfilePage/ProfileLayout";
|
||||
|
||||
export default ProfileLayout;
|
3
app/profile/my-reports/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import MyReportsPage from "@/Pages/profile/MyReportsPage/MyReportsPage";
|
||||
|
||||
export default MyReportsPage;
|
@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>;
|
||||
};
|
||||
|
||||
export default page;
|
3
app/profile/personal-data/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import PersonalDataPage from "@/Pages/profile/PersonalDataPage/PersonalDataPage";
|
||||
|
||||
export default PersonalDataPage;
|
3
app/report/[id]/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import ReportDetailsPage from "@/Pages/ReportDetailsPage/ReportDetailsPage";
|
||||
|
||||
export default ReportDetailsPage;
|
18
lib/next-auth.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
import NextAuth from "next-auth";
|
||||
|
||||
declare module "next-auth" {
|
||||
interface Session {
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
}
|
||||
}
|
||||
|
||||
import { JWT } from "next-auth/jwt";
|
||||
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
exp: number;
|
||||
}
|
||||
}
|
@ -10,17 +10,22 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"next": "14.1.0",
|
||||
"next-auth": "^4.24.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"sass": "^1.70.0",
|
||||
"swr": "^2.2.4",
|
||||
"use-debounce": "^10.0.0",
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/leaflet": "^1.9.8",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-leaflet": "^3.0.0",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"typescript": "^5"
|
||||
|
@ -2,6 +2,7 @@ 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"] });
|
||||
|
||||
@ -13,9 +14,11 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={montserrat.className}>
|
||||
<Providers>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
11
src/App/Providers.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
export const Providers = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
};
|
@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import "./NavAuthBtn.scss";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
const NavAuthBtn = () => {
|
||||
const auth = false;
|
||||
|
||||
return auth ? (
|
||||
<Link href="/profile" className="nav-profile">
|
||||
const session = useSession();
|
||||
return session.status === "authenticated" ? (
|
||||
<Link href="/profile/personal-data" className="nav-profile">
|
||||
Профиль
|
||||
</Link>
|
||||
) : (
|
||||
|
@ -1,13 +1,17 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.news-card {
|
||||
min-height: 420px;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
min-height: 160px;
|
||||
max-height: 160px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,13 @@ const NewsCard: React.FC<INewsCard> = ({
|
||||
description,
|
||||
date,
|
||||
}: INewsCard) => {
|
||||
const sliceTitle = (title: string) => {
|
||||
if (title.length > 35) {
|
||||
return `${title.slice(0, 35)}...`;
|
||||
}
|
||||
|
||||
return title;
|
||||
};
|
||||
const sliceDate = (date: string) => {
|
||||
return `${date.slice(8, 10)}/${date.slice(5, 7)}/${date.slice(
|
||||
0,
|
||||
@ -45,7 +52,7 @@ const NewsCard: React.FC<INewsCard> = ({
|
||||
|
||||
<div className="news-card__text">
|
||||
<h5 className="news-card__date">{sliceDate(date)}</h5>
|
||||
<h4>{title}</h4>
|
||||
<h4>{sliceTitle(title)}</h4>
|
||||
<p>{sliceDescription(description)}</p>
|
||||
</div>
|
||||
|
||||
|
@ -51,3 +51,11 @@
|
||||
letter-spacing: 0.25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.review-card {
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
src/Entities/RoadType/RoadType.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.road-type {
|
||||
padding: 4px 12px;
|
||||
width: fit-content;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
border-radius: 20px;
|
||||
}
|
19
src/Entities/RoadType/RoadType.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import "./RoadType.scss";
|
||||
|
||||
interface IRoadTypeProps {
|
||||
children: React.ReactNode;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const RoadType: React.FC<IRoadTypeProps> = ({
|
||||
children,
|
||||
color,
|
||||
}: IRoadTypeProps) => {
|
||||
return (
|
||||
<div className="road-type" style={{ backgroundColor: color }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoadType;
|
@ -1,16 +1,31 @@
|
||||
"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 }) => {
|
||||
const [toggle, setToggle] = useState(false);
|
||||
return (
|
||||
const Switch: React.FC<ISwitch> = ({
|
||||
color,
|
||||
onClick,
|
||||
defaultState,
|
||||
type,
|
||||
href,
|
||||
}: ISwitch) => {
|
||||
const [toggle, setToggle] = useState(defaultState);
|
||||
return type === ESwitch.BUTTON ? (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: !toggle
|
||||
@ -27,6 +42,25 @@ const Switch: React.FC<ISwitch> = ({ color, onClick }) => {
|
||||
>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
31
src/Features/CreateReportMap/CreateReportMap.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
80
src/Features/CreateReportMap/CreateReportMap.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
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;
|
5
src/Features/CreateReportMap/assets/pin.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<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>
|
After Width: | Height: | Size: 1.6 KiB |
@ -46,3 +46,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.create-review {
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.create-review {
|
||||
gap: 6px;
|
||||
|
||||
div {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
src/Features/HomeMap/HomeMap.scss
Normal file
@ -0,0 +1,44 @@
|
||||
.home-map {
|
||||
width: 100%;
|
||||
height: 580px;
|
||||
border-radius: 8px;
|
||||
|
||||
.leaflet-popup {
|
||||
margin-bottom: 30px;
|
||||
&-content-wrapper {
|
||||
border-radius: 8px;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
&-tip,
|
||||
&-close-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-content {
|
||||
padding: 12px 16px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: #3998e8;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.home-map {
|
||||
height: 416px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.home-map {
|
||||
height: 370px;
|
||||
}
|
||||
}
|
98
src/Features/HomeMap/HomeMap.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import "./HomeMap.scss";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import {
|
||||
MapContainer,
|
||||
Marker,
|
||||
Popup,
|
||||
TileLayer,
|
||||
} from "react-leaflet";
|
||||
import geo_green_icon from "./icons/geo-green.svg";
|
||||
import geo_orange_icon from "./icons/geo-orange.svg";
|
||||
import geo_pink_icon from "./icons/geo-pink.svg";
|
||||
import geo_purple_icon from "./icons/geo-purple.svg";
|
||||
import geo_red_icon from "./icons/geo-red.svg";
|
||||
import geo_yellow_icon from "./icons/geo-yellow.svg";
|
||||
import { DivIcon, Icon, LatLngTuple } from "leaflet";
|
||||
import { StaticImageData } from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
interface ILocation {
|
||||
id: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface IData {
|
||||
id: number;
|
||||
created_at: string;
|
||||
location: ILocation[];
|
||||
category: number;
|
||||
description: string;
|
||||
count_reviews: number;
|
||||
total_likes: number;
|
||||
}
|
||||
|
||||
interface IHomeMapProps {
|
||||
data: IData[] | undefined;
|
||||
}
|
||||
|
||||
const HomeMap: React.FC<IHomeMapProps> = ({
|
||||
data,
|
||||
}: IHomeMapProps) => {
|
||||
const createCustomIcon = (icon: StaticImageData) => {
|
||||
const customIcon = new Icon({
|
||||
iconUrl: icon.src,
|
||||
iconSize: [32, 32],
|
||||
});
|
||||
|
||||
return customIcon;
|
||||
};
|
||||
|
||||
const icons: Record<string, DivIcon> = {
|
||||
1: createCustomIcon(geo_red_icon),
|
||||
2: createCustomIcon(geo_pink_icon),
|
||||
3: createCustomIcon(geo_purple_icon),
|
||||
4: createCustomIcon(geo_orange_icon),
|
||||
5: createCustomIcon(geo_green_icon),
|
||||
6: createCustomIcon(geo_yellow_icon),
|
||||
};
|
||||
|
||||
const position = [42.8746, 74.606];
|
||||
|
||||
return (
|
||||
<MapContainer
|
||||
center={position as LatLngTuple}
|
||||
zoom={14}
|
||||
scrollWheelZoom={false}
|
||||
className="home-map"
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{data?.map((marker) => (
|
||||
<Marker
|
||||
key={marker.id}
|
||||
icon={icons[marker.category]}
|
||||
position={
|
||||
[
|
||||
+marker.location[0].latitude,
|
||||
+marker.location[0].longitude,
|
||||
] as LatLngTuple
|
||||
}
|
||||
>
|
||||
<Popup>
|
||||
<Link href={`/report/${marker.location[0].id}`}>
|
||||
{marker.location[0].address}
|
||||
</Link>
|
||||
</Popup>
|
||||
</Marker>
|
||||
))}
|
||||
</MapContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeMap;
|
5
src/Features/HomeMap/icons/geo-green.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#8FDE6A"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Features/HomeMap/icons/geo-orange.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#FFAC33"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Features/HomeMap/icons/geo-pink.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#C288E2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Features/HomeMap/icons/geo-purple.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#87289D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Features/HomeMap/icons/geo-red.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#E64452"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Features/HomeMap/icons/geo-yellow.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#FED363"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
10
src/Features/ReportLike/ReportLike.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.report-like {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
color: rgb(74, 192, 63);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
}
|
22
src/Features/ReportLike/ReportLike.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
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;
|
5
src/Features/ReportLike/icons/like-icon.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<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>
|
After Width: | Height: | Size: 929 B |
@ -5,17 +5,26 @@ 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 type="text" placeholder={placeholder} />
|
||||
<input
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
<button>Поиск</button>
|
||||
</div>
|
||||
|
@ -9,56 +9,56 @@ 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 {
|
||||
login,
|
||||
emailError,
|
||||
passwordError,
|
||||
error,
|
||||
loading,
|
||||
cleanRedirect,
|
||||
redirect,
|
||||
} = useSignIn();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (redirect) {
|
||||
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");
|
||||
cleanRedirect();
|
||||
} else {
|
||||
console.log(res?.error);
|
||||
}
|
||||
}, [redirect]);
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
className="sign-in-form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
login(email, password);
|
||||
}}
|
||||
>
|
||||
<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={emailError}
|
||||
error=""
|
||||
/>
|
||||
{emailError ? <p>{emailError}</p> : null}
|
||||
<InputWithLabel
|
||||
name="password"
|
||||
label="Пароль"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Введите пароль"
|
||||
secret
|
||||
error={passwordError}
|
||||
error=""
|
||||
/>
|
||||
{error ? <p>{error}</p> : null}
|
||||
</div>
|
||||
|
||||
<CustomLink
|
||||
@ -69,9 +69,7 @@ const SignInForm = () => {
|
||||
</CustomLink>
|
||||
|
||||
<div className="sign-in-form__btns">
|
||||
<Button type="submit">
|
||||
{!loading ? "Войти" : <DefaultLoader />}
|
||||
</Button>
|
||||
<Button type="submit">Войти</Button>
|
||||
<GoogleButton>Войти через Google</GoogleButton>
|
||||
</div>
|
||||
|
||||
|
133
src/Pages/CreateReportPage/CreateReportPage.scss
Normal file
@ -0,0 +1,133 @@
|
||||
.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;
|
||||
}
|
||||
}
|
94
src/Pages/CreateReportPage/CreateReportPage.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
"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;
|
6
src/Pages/CreateReportPage/icons/arrow-right.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<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>
|
After Width: | Height: | Size: 364 B |
5
src/Pages/CreateReportPage/icons/paperclip.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<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>
|
After Width: | Height: | Size: 883 B |
@ -5,13 +5,29 @@ import RatingSection from "@/Widgets/home/RatingSection/RatingSection";
|
||||
import StatisticsSection from "@/Widgets/home/StatisticsSection/StatisticsSection";
|
||||
import NewsSection from "@/Widgets/home/NewsSection/NewsSection";
|
||||
|
||||
const Homepage = () => {
|
||||
const Homepage = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
"тип-дороги": string;
|
||||
"карта-дорог": string;
|
||||
рейтинг: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div className="home">
|
||||
<Header />
|
||||
<StatisticsSection />
|
||||
<MapSection />
|
||||
<RatingSection />
|
||||
<MapSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["карта-дорог"]}
|
||||
queryRating={searchParams["рейтинг"]}
|
||||
/>
|
||||
<RatingSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["карта-дорог"]}
|
||||
queryRating={searchParams["рейтинг"]}
|
||||
/>
|
||||
<NewsSection />
|
||||
</div>
|
||||
);
|
||||
|
@ -12,33 +12,6 @@
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
&__image {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
&_main {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&__date-and-reviews {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: rgba(62, 50, 50, 0.75);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: #3e3232;
|
||||
font-size: 20px;
|
||||
@ -55,11 +28,13 @@
|
||||
@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,10 +1,9 @@
|
||||
import { newsDetailsStore } from "./store";
|
||||
import "./NewsDetailsPage.scss";
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import message_icon from "./icons/message-icon.svg";
|
||||
import calendar_icon from "./icons/calendar-icon.svg";
|
||||
import Image from "next/image";
|
||||
import NewsReviewsSection from "@/Widgets/NewsReviewsSection/NewsReviewsSection";
|
||||
|
||||
import ReviewsSection from "@/Widgets/general/ReviewsSection/ReviewsSection";
|
||||
import NewsHeader from "@/Widgets/NewsHeader/NewsHeader";
|
||||
|
||||
const NewsDetailsPage = async ({
|
||||
params,
|
||||
@ -13,54 +12,21 @@ const NewsDetailsPage = async ({
|
||||
}) => {
|
||||
const data = await newsDetailsStore.getNewsDetails(params.id);
|
||||
|
||||
console.log(data);
|
||||
|
||||
const months: Record<string, string> = {
|
||||
"01": "Январь",
|
||||
"02": "Февраль",
|
||||
"03": "Март",
|
||||
"04": "Апрель",
|
||||
"05": "Май",
|
||||
"06": "Июнь",
|
||||
"07": "Июль",
|
||||
"08": "Август",
|
||||
"09": "Сентябрь",
|
||||
"10": "Октябрь",
|
||||
"11": "Ноябрь",
|
||||
"12": "Декабрь",
|
||||
};
|
||||
|
||||
const year = data?.created_at.slice(0, 4);
|
||||
const month = data?.created_at.slice(5, 7);
|
||||
const day = data?.created_at.slice(8, 10);
|
||||
|
||||
return (
|
||||
<div className="news-details-page">
|
||||
<HeaderText>{data?.title}</HeaderText>
|
||||
<div className="news-details-page__wrapper">
|
||||
<div className="news-details-page__image">
|
||||
<img
|
||||
src={data?.image}
|
||||
alt="News Image"
|
||||
className="news-details-page__image_main"
|
||||
/>
|
||||
<div className="news-details-page__date-and-reviews">
|
||||
<span>
|
||||
<Image src={calendar_icon} alt="Calendar Icon" />
|
||||
{month && months[month]} {day}, {year}
|
||||
</span>
|
||||
<span>
|
||||
<Image src={message_icon} alt="Message Icon" />
|
||||
Комментарии: {data?.count_reviews}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<NewsReviewsSection id={data?.id} list={data?.news_review} />
|
||||
<ReviewsSection id={data?.id} endpoint="news" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
36
src/Pages/ReportDetailsPage/ReportDetailsPage.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
32
src/Pages/ReportDetailsPage/ReportDetailsPage.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
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;
|
57
src/Pages/ReportDetailsPage/report-details.store.ts
Normal file
@ -0,0 +1,57 @@
|
||||
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();
|
0
src/Pages/profile/MyReportsPage/MyReportsPage.scss
Normal file
7
src/Pages/profile/MyReportsPage/MyReportsPage.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import "./MyReportsPage.scss";
|
||||
|
||||
const MyReportsPage = () => {
|
||||
return <div className="my-reports-page">MyReportsPage</div>;
|
||||
};
|
||||
|
||||
export default MyReportsPage;
|
7
src/Pages/profile/PersonalDataPage/PersonalDataPage.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import "./PersonalDataPage.scss";
|
||||
|
||||
const PersonalDataPage = () => {
|
||||
return <div className="personal-data-page">PersonalDataPage</div>;
|
||||
};
|
||||
|
||||
export default PersonalDataPage;
|
24
src/Pages/profile/ProfilePage/ProfileLayout.scss
Normal file
@ -0,0 +1,24 @@
|
||||
.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;
|
||||
}
|
||||
}
|
17
src/Pages/profile/ProfilePage/ProfileLayout.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import "./ProfileLayout.scss";
|
||||
import ProfileNav from "@/Widgets/ProfileNav/ProfileNav";
|
||||
|
||||
export default function ProfileLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<div className="profile-layout">
|
||||
<HeaderText>Личный кабинет</HeaderText>
|
||||
<ProfileNav />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
0
src/Shared/helpers/sliceDate.ts
Normal file
@ -1,27 +0,0 @@
|
||||
import { useRef } from "react";
|
||||
import { DependencyList, EffectCallback, useEffect } from "react";
|
||||
|
||||
export function useIsFirstRender(): boolean {
|
||||
const isFirst = useRef(true);
|
||||
|
||||
if (isFirst.current) {
|
||||
isFirst.current = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return isFirst.current;
|
||||
}
|
||||
|
||||
export function useUpdateEffect(
|
||||
effect: EffectCallback,
|
||||
deps?: DependencyList
|
||||
) {
|
||||
const isFirst = useIsFirstRender();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFirst) {
|
||||
return effect();
|
||||
}
|
||||
}, deps);
|
||||
}
|
17
src/Shared/variables/road-types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const ROAD_TYPES_COLORS: Record<number, string> = {
|
||||
1: "#E64452",
|
||||
2: "#C288E2",
|
||||
3: "#87289D",
|
||||
4: "#FFAC33",
|
||||
5: "#8FDE6A",
|
||||
6: "#FED363",
|
||||
};
|
||||
|
||||
export const ROAD_TYPES: Record<number, string> = {
|
||||
1: "Разбитая дорога",
|
||||
2: "Очаг аварийности",
|
||||
3: "Локальный дефект",
|
||||
4: "В планах ремонта",
|
||||
5: "Отремонтировано",
|
||||
6: "Локальный дефект исправлен",
|
||||
};
|
55
src/Widgets/NewsHeader/NewsHeader.scss
Normal file
@ -0,0 +1,55 @@
|
||||
.news-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
&__main-image {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&__date-and-reviews {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: rgba(62, 50, 50, 0.75);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.news-header {
|
||||
gap: 25px;
|
||||
|
||||
&__main-image {
|
||||
height: 392px;
|
||||
}
|
||||
|
||||
&__date-and-reviews {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
.news-header {
|
||||
gap: 20px;
|
||||
|
||||
&__main-image {
|
||||
height: 231px;
|
||||
}
|
||||
|
||||
&__date-and-reviews {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
58
src/Widgets/NewsHeader/NewsHeader.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import "./NewsHeader.scss";
|
||||
import Image from "next/image";
|
||||
import message_icon from "./icons/message-icon.svg";
|
||||
import calendar_icon from "./icons/calendar-icon.svg";
|
||||
import React from "react";
|
||||
|
||||
interface INewsHeaderProps {
|
||||
date: string | undefined;
|
||||
image: string | undefined;
|
||||
count_reviews: number | undefined;
|
||||
}
|
||||
|
||||
const NewsHeader: React.FC<INewsHeaderProps> = ({
|
||||
date,
|
||||
image,
|
||||
count_reviews,
|
||||
}: INewsHeaderProps) => {
|
||||
const months: Record<string, string> = {
|
||||
"01": "Январь",
|
||||
"02": "Февраль",
|
||||
"03": "Март",
|
||||
"04": "Апрель",
|
||||
"05": "Май",
|
||||
"06": "Июнь",
|
||||
"07": "Июль",
|
||||
"08": "Август",
|
||||
"09": "Сентябрь",
|
||||
"10": "Октябрь",
|
||||
"11": "Ноябрь",
|
||||
"12": "Декабрь",
|
||||
};
|
||||
|
||||
const year = date?.slice(0, 4);
|
||||
const month = date?.slice(5, 7);
|
||||
const day = date?.slice(8, 10);
|
||||
|
||||
return (
|
||||
<div className="news-header">
|
||||
<img
|
||||
src={image}
|
||||
alt="News Image"
|
||||
className="news-header__main-image"
|
||||
/>
|
||||
<div className="news-header__date-and-reviews">
|
||||
<span>
|
||||
<Image src={calendar_icon} alt="Calendar Icon" />
|
||||
{month && months[month]} {day}, {year}
|
||||
</span>
|
||||
<span>
|
||||
<Image src={message_icon} alt="Message Icon" />
|
||||
Комментарии: {count_reviews}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsHeader;
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 747 B |
Before Width: | Height: | Size: 724 B After Width: | Height: | Size: 724 B |
@ -1,46 +0,0 @@
|
||||
import ReviewCard from "@/Entities/ReviewCard/ReviewCard";
|
||||
import "./NewsReviewsSection.scss";
|
||||
import CreateReview from "@/Features/CreateReview/CreateReview";
|
||||
|
||||
interface INewsReviewsSectionProps {
|
||||
id: number | null | undefined;
|
||||
list: IReview[] | undefined;
|
||||
}
|
||||
|
||||
interface IReview {
|
||||
id: number;
|
||||
author: IAuthor;
|
||||
review: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
const NewsReviewsSection: React.FC<INewsReviewsSectionProps> = ({
|
||||
id,
|
||||
list,
|
||||
}: INewsReviewsSectionProps) => {
|
||||
return (
|
||||
<div className="news-reviews-section">
|
||||
<CreateReview endpoint="news" id={id} />
|
||||
<div className="news-reviews-section__container">
|
||||
<h3>
|
||||
<span />
|
||||
Комментарии
|
||||
</h3>
|
||||
<div>
|
||||
{list?.map((item) => (
|
||||
<ReviewCard key={item.id} item={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsReviewsSection;
|
47
src/Widgets/ProfileNav/ProfileNav.scss
Normal file
@ -0,0 +1,47 @@
|
||||
.profile-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 46px;
|
||||
|
||||
&_right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
div {
|
||||
padding: 2px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 16px;
|
||||
background: rgb(224, 237, 248);
|
||||
|
||||
color: rgb(23, 92, 211);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(50, 48, 58);
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&__logout {
|
||||
padding: 10px;
|
||||
border: 1px solid rgb(230, 68, 82);
|
||||
border-radius: 8px;
|
||||
|
||||
color: rgb(230, 68, 82);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
51
src/Widgets/ProfileNav/ProfileNav.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import "./ProfileNav.scss";
|
||||
import Link from "next/link";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
const ProfileNav = () => {
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<div className="profile-nav">
|
||||
<div className="profile-nav__links">
|
||||
<Link
|
||||
style={{
|
||||
textDecoration: `${
|
||||
pathname === "/profile/personal-data" ? "underline" : ""
|
||||
}`,
|
||||
}}
|
||||
href="/profile/personal-data"
|
||||
>
|
||||
Личные данные
|
||||
</Link>
|
||||
<div className="profile-nav__links_right">
|
||||
<Link
|
||||
style={{
|
||||
textDecoration: `${
|
||||
pathname === "/profile/my-reports" ? "underline" : ""
|
||||
}`,
|
||||
}}
|
||||
href="/profile/my-reports"
|
||||
>
|
||||
Мои обращения
|
||||
</Link>
|
||||
<div>3</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() =>
|
||||
signOut({
|
||||
callbackUrl: "/",
|
||||
})
|
||||
}
|
||||
className="profile-nav__logout"
|
||||
>
|
||||
Выйти из аккаунта
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileNav;
|
42
src/Widgets/ReportImages/ReportImages.scss
Normal file
@ -0,0 +1,42 @@
|
||||
.report-images {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
|
||||
img {
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&-1 {
|
||||
width: 100%;
|
||||
grid-column: 1 / 5;
|
||||
height: 441px;
|
||||
}
|
||||
|
||||
&-default-1,
|
||||
&-default-2,
|
||||
&-default-3,
|
||||
&-default-4,
|
||||
&-default-5 {
|
||||
height: 102px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
|
||||
border-radius: 10px;
|
||||
background-color: rgb(209, 217, 226);
|
||||
|
||||
img {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
&-default-1 {
|
||||
grid-column: 1 / 5;
|
||||
height: 441px;
|
||||
}
|
||||
}
|
50
src/Widgets/ReportImages/ReportImages.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import Image from "next/image";
|
||||
import "./ReportImages.scss";
|
||||
import default_image_icon from "./icons/default-image.svg";
|
||||
|
||||
interface IImage {
|
||||
id: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface IReportImagesProps {
|
||||
images: IImage[];
|
||||
}
|
||||
|
||||
const ReportImages: React.FC<IReportImagesProps> = ({
|
||||
images,
|
||||
}: IReportImagesProps) => {
|
||||
const showImages = () => {
|
||||
let result = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (images.length > i) {
|
||||
result.push(
|
||||
<img
|
||||
className={`report-images-${i + 1}`}
|
||||
key={i}
|
||||
src={images[i].image}
|
||||
alt="Road Image"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
result.push(
|
||||
<div className={`report-images-default-${i + 1}`} key={i}>
|
||||
<Image
|
||||
src={default_image_icon}
|
||||
alt="Default Image Icon"
|
||||
/>
|
||||
{i + 1 === 1
|
||||
? "Пользователь не загрузил изображения"
|
||||
: ""}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
return <div className="report-images">{showImages()}</div>;
|
||||
};
|
||||
|
||||
export default ReportImages;
|
8
src/Widgets/ReportImages/icons/default-image.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="38.337891" height="38.336914" viewBox="0 0 38.3379 38.3369" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<desc>
|
||||
Created with Pixso.
|
||||
</desc>
|
||||
<defs/>
|
||||
<path id="Icon" d="M15.7637 0.834961L22.5762 0.834961C24.8477 0.834961 26.6543 0.834961 28.1113 0.954102C29.6035 1.07617 30.877 1.33105 32.043 1.9248C33.9238 2.88379 35.4531 4.41309 36.4121 6.29492C37.0059 7.46094 37.2617 8.7334 37.3828 10.2256C37.502 11.6836 37.502 13.4902 37.502 15.7617L37.502 22.5752C37.502 24.8467 37.502 26.6533 37.3828 28.1104C37.2617 29.6035 37.0059 30.876 36.4121 32.042C35.4531 33.9229 33.9238 35.4531 32.043 36.4121C30.877 37.0059 29.6035 37.2607 28.1113 37.3828C26.6543 37.502 24.8477 37.502 22.5762 37.502L15.7617 37.502C13.4902 37.502 11.6836 37.502 10.2266 37.3828C8.73438 37.2607 7.46094 37.0059 6.29688 36.4121C4.41406 35.4531 2.88477 33.9229 1.92578 32.042C1.33203 30.876 1.07617 29.6035 0.955078 28.1104C0.835938 26.6533 0.835938 24.8467 0.835938 22.5742L0.835938 15.7617C0.835938 13.4902 0.835938 11.6836 0.955078 10.2256C1.07617 8.7334 1.33203 7.46094 1.92578 6.29492C2.88477 4.41309 4.41406 2.88379 6.29688 1.9248C7.46094 1.33105 8.73438 1.07617 10.2266 0.954102C11.6836 0.834961 13.4922 0.834961 15.7637 0.834961ZM10.498 4.27637C9.21094 4.38184 8.42578 4.58008 7.80859 4.89453C6.55469 5.53418 5.53516 6.55371 4.89648 7.80859C4.58203 8.42578 4.38281 9.21094 4.27734 10.4971C4.16992 11.8037 4.16992 13.4736 4.16992 15.835L4.16992 17.502L6.8125 14.8584C8.11328 13.5566 10.2246 13.5566 11.5254 14.8584L21.9141 25.2461C22.2383 25.5713 22.7656 25.5713 23.0918 25.2461L26.8125 21.5254C28.1133 20.2236 30.2246 20.2236 31.5254 21.5254L34.168 24.166C34.1699 23.6523 34.1699 23.0986 34.1699 22.502L34.1699 15.835C34.1699 13.4736 34.168 11.8037 34.0605 10.4971C33.957 9.21094 33.7578 8.42578 33.4434 7.80859C32.8027 6.55371 31.7832 5.53418 30.5293 4.89453C29.9121 4.58008 29.127 4.38184 27.8398 4.27637C26.5352 4.16992 24.8633 4.16797 22.502 4.16797L15.8359 4.16797C13.4746 4.16797 11.8047 4.16992 10.498 4.27637ZM23.3359 7.50195C21.0352 7.50195 19.1699 9.36719 19.1699 11.668C19.1699 13.9697 21.0352 15.835 23.3359 15.835C25.6367 15.835 27.502 13.9697 27.502 11.668C27.502 9.36719 25.6367 7.50195 23.3359 7.50195Z" fill="#7C8B9D" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||
<path id="Icon" d="M23.3359 7.50195C25.6367 7.50195 27.502 9.36719 27.502 11.668C27.502 13.9697 25.6367 15.835 23.3359 15.835C21.0352 15.835 19.1699 13.9697 19.1699 11.668C19.1699 9.36719 21.0352 7.50195 23.3359 7.50195ZM10.2266 0.954102C8.73438 1.07617 7.46094 1.33105 6.29688 1.9248C4.41406 2.88379 2.88477 4.41309 1.92578 6.29492C1.33203 7.46094 1.07617 8.7334 0.955078 10.2256C0.835938 11.6836 0.835938 13.4902 0.835938 15.7617L0.835938 22.5742C0.835938 24.8467 0.835938 26.6533 0.955078 28.1104C1.07617 29.6035 1.33203 30.876 1.92578 32.042C2.88477 33.9229 4.41406 35.4531 6.29688 36.4121C7.46094 37.0059 8.73438 37.2607 10.2266 37.3828C11.6836 37.502 13.4902 37.502 15.7637 37.502L22.5762 37.502C24.8477 37.502 26.6543 37.502 28.1113 37.3828C29.6035 37.2607 30.877 37.0059 32.043 36.4121C33.9238 35.4531 35.4531 33.9229 36.4121 32.042C37.0059 30.876 37.2617 29.6035 37.3828 28.1104C37.502 26.6533 37.502 24.8467 37.502 22.5752L37.502 15.7617C37.502 13.4902 37.502 11.6836 37.3828 10.2256C37.2617 8.7334 37.0059 7.46094 36.4121 6.29492C35.4531 4.41309 33.9238 2.88379 32.043 1.9248C30.877 1.33105 29.6035 1.07617 28.1113 0.954102C26.6543 0.834961 24.8477 0.834961 22.5762 0.834961L15.7637 0.834961C13.4922 0.834961 11.6836 0.834961 10.2266 0.954102ZM10.498 4.27637C11.8047 4.16992 13.4746 4.16797 15.8359 4.16797L22.502 4.16797C24.8633 4.16797 26.5352 4.16992 27.8398 4.27637C29.127 4.38184 29.9121 4.58008 30.5293 4.89453C31.7832 5.53418 32.8027 6.55371 33.4434 7.80859C33.7578 8.42578 33.957 9.21094 34.0605 10.4971C34.168 11.8037 34.1699 13.4736 34.1699 15.835L34.1699 22.502C34.1699 23.0986 34.1699 23.6523 34.168 24.166L31.5254 21.5254C30.2246 20.2236 28.1133 20.2236 26.8125 21.5254L23.0918 25.2461C22.7656 25.5713 22.2383 25.5713 21.9141 25.2461L11.5254 14.8584C10.2246 13.5566 8.11328 13.5566 6.8125 14.8584L4.16992 17.502L4.16992 15.835C4.16992 13.4736 4.16992 11.8037 4.27734 10.4971C4.38281 9.21094 4.58203 8.42578 4.89648 7.80859C5.53516 6.55371 6.55469 5.53418 7.80859 4.89453C8.42578 4.58008 9.21094 4.38184 10.498 4.27637Z" stroke="#7C8B9D" stroke-opacity="1.000000" stroke-width="1.670000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
61
src/Widgets/ReportInfo/ReportInfo.scss
Normal file
@ -0,0 +1,61 @@
|
||||
@import "../../Shared/variables.scss";
|
||||
|
||||
.report-info {
|
||||
padding-top: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
&__date-and-likes {
|
||||
display: flex;
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
&__date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
color: rgba(62, 50, 50, 0.75);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(62, 50, 50);
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
color: rgb(102, 102, 102);
|
||||
|
||||
span {
|
||||
color: $light-blue;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&__show-map {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
span {
|
||||
color: rgb(102, 102, 102);
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
94
src/Widgets/ReportInfo/ReportInfo.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import "./ReportInfo.scss";
|
||||
import Image from "next/image";
|
||||
import RoadType from "@/Entities/RoadType/RoadType";
|
||||
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
|
||||
import ReportLike from "@/Features/ReportLike/ReportLike";
|
||||
import calendar_icon from "./icons/calendar-icon.svg";
|
||||
import map_pin_icon from "./icons/map-pin.svg";
|
||||
import {
|
||||
ROAD_TYPES,
|
||||
ROAD_TYPES_COLORS,
|
||||
} from "@/Shared/variables/road-types";
|
||||
|
||||
interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
govern_status: any;
|
||||
}
|
||||
|
||||
interface ILocation {
|
||||
id: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface IReportInfoProps {
|
||||
description: string | undefined;
|
||||
date: string | undefined;
|
||||
category: number | undefined;
|
||||
count_likes: number | undefined;
|
||||
author: IAuthor | undefined;
|
||||
location: ILocation[] | undefined;
|
||||
}
|
||||
|
||||
const ReportInfo: React.FC<IReportInfoProps> = ({
|
||||
description,
|
||||
date,
|
||||
category = 1,
|
||||
count_likes,
|
||||
author,
|
||||
location = [],
|
||||
}: IReportInfoProps) => {
|
||||
const months: Record<string, string> = {
|
||||
"01": "Январь",
|
||||
"02": "Февраль",
|
||||
"03": "Март",
|
||||
"04": "Апрель",
|
||||
"05": "Май",
|
||||
"06": "Июнь",
|
||||
"07": "Июль",
|
||||
"08": "Август",
|
||||
"09": "Сентябрь",
|
||||
"10": "Октябрь",
|
||||
"11": "Ноябрь",
|
||||
"12": "Декабрь",
|
||||
};
|
||||
|
||||
const year = date?.slice(0, 4);
|
||||
const month = date?.slice(5, 7);
|
||||
const day = date?.slice(8, 10);
|
||||
return (
|
||||
<div className="report-info">
|
||||
<RoadType color={ROAD_TYPES_COLORS[category]}>
|
||||
{ROAD_TYPES[category]}
|
||||
</RoadType>
|
||||
<HeaderText>{location[0].address}</HeaderText>
|
||||
|
||||
<div className="report-info__date-and-likes">
|
||||
<div className="report-info__date">
|
||||
<Image src={calendar_icon} alt="Calendar Icon" />
|
||||
{day} {months[month!]}, {year}
|
||||
</div>
|
||||
<ReportLike count_likes={count_likes!} />
|
||||
</div>
|
||||
|
||||
<p>{description}</p>
|
||||
|
||||
<h5>
|
||||
Автор обращения:{" "}
|
||||
<span>
|
||||
{author?.first_name}. {author?.last_name.slice(0, 1)}
|
||||
</span>
|
||||
</h5>
|
||||
|
||||
<button className="report-info__show-map">
|
||||
<Image src={map_pin_icon} alt="Map Pin Icon" />
|
||||
<span>Показать на карте</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportInfo;
|
8
src/Widgets/ReportInfo/icons/calendar-icon.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<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>
|
After Width: | Height: | Size: 747 B |
17
src/Widgets/ReportInfo/icons/map-pin.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<desc>
|
||||
Created with Pixso.
|
||||
</desc>
|
||||
<defs>
|
||||
<clipPath id="clip824_20705">
|
||||
<rect id="map-pin" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="map-pin" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip824_20705)">
|
||||
<path id="Vector" d="M21 10C21 17 12 23 12 23C12 23 3 17 3 10C3 7.61328 3.94727 5.32422 5.63672 3.63574C7.32422 1.94824 9.61328 1 12 1C14.3867 1 16.6758 1.94824 18.3633 3.63574C20.0527 5.32422 21 7.61328 21 10Z" fill="#3998E8" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||
<path id="Vector" d="M12 23C12 23 3 17 3 10C3 7.61328 3.94727 5.32422 5.63672 3.63574C7.32422 1.94824 9.61328 1 12 1C14.3867 1 16.6758 1.94824 18.3633 3.63574C20.0527 5.32422 21 7.61328 21 10C21 17 12 23 12 23Z" stroke="#3998E8" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M12 13C13.6562 13 15 11.6572 15 10C15 8.34277 13.6562 7 12 7C10.3438 7 9 8.34277 9 10C9 11.6572 10.3438 13 12 13Z" fill="#FFFFFF" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||
<path id="Vector" d="M12 13C10.3438 13 9 11.6572 9 10C9 8.34277 10.3438 7 12 7C13.6562 7 15 8.34277 15 10C15 11.6572 13.6562 13 12 13Z" stroke="#FFFFFF" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -2,7 +2,7 @@
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
z-index: 15;
|
||||
z-index: 10000;
|
||||
width: 100%;
|
||||
padding: 0 90px;
|
||||
height: 78px;
|
||||
|
@ -1,4 +1,4 @@
|
||||
.news-reviews-section {
|
||||
.reviews-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 70px;
|
||||
@ -20,15 +20,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
&__container,
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.reviews-section {
|
||||
gap: 40px;
|
||||
|
||||
&__list {
|
||||
gap: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.reviews-section {
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
&__list {
|
||||
gap: 13px;
|
||||
}
|
||||
}
|
||||
}
|
34
src/Widgets/general/ReviewsSection/ReviewsSection.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import "./ReviewsSection.scss";
|
||||
import ReviewCard from "@/Entities/ReviewCard/ReviewCard";
|
||||
import CreateReview from "@/Features/CreateReview/CreateReview";
|
||||
import { newsReviewStore } from "./store";
|
||||
|
||||
interface INewsReviewsSectionProps {
|
||||
id: number | null | undefined;
|
||||
endpoint: string;
|
||||
}
|
||||
|
||||
const ReviewsSection: React.FC<INewsReviewsSectionProps> = async ({
|
||||
id,
|
||||
endpoint,
|
||||
}: INewsReviewsSectionProps) => {
|
||||
const list = await newsReviewStore.getReviews(endpoint, id);
|
||||
return (
|
||||
<div className="reviews-section">
|
||||
<CreateReview endpoint="news" id={id} />
|
||||
<div className="reviews-section__container">
|
||||
<h3>
|
||||
<span />
|
||||
Комментарии
|
||||
</h3>
|
||||
<div className="reviews-section__list">
|
||||
{list?.results.map((item) => (
|
||||
<ReviewCard key={item.id} item={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReviewsSection;
|
46
src/Widgets/general/ReviewsSection/store.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import { IList } from "@/Shared/types";
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
interface IReview extends IList {
|
||||
results: IResult[];
|
||||
}
|
||||
|
||||
interface IResult {
|
||||
id: number;
|
||||
author: IAuthor;
|
||||
review: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
class NewsReviewsStore {
|
||||
error: string;
|
||||
constructor() {
|
||||
this.error = "";
|
||||
}
|
||||
|
||||
async getReviews(endpoint: string, id: number | null | undefined) {
|
||||
try {
|
||||
const response = await axios.get<IReview>(
|
||||
`${baseAPI}/${endpoint}/${id}/reviews/`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
this.error = error.message;
|
||||
} else {
|
||||
this.error = "An error ocured";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const newsReviewStore = new NewsReviewsStore();
|
@ -22,7 +22,7 @@ const Header = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link href="/send-report" className="header__report">
|
||||
<Link href="/create-report" className="header__report">
|
||||
Отметить разбитую дорогу
|
||||
<Image src={arrow_icon} alt="Arrow Right Icon" />
|
||||
</Link>
|
||||
|
@ -28,14 +28,6 @@
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
&__map {
|
||||
height: 580px;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
|
@ -1,52 +1,123 @@
|
||||
import Image from "next/image";
|
||||
"use client";
|
||||
|
||||
import "./MapSection.scss";
|
||||
import search_icon from "./icons/search-icon.svg";
|
||||
import Switch from "@/Entities/Switch/Switch";
|
||||
import map_image from "./assets/map.jpg";
|
||||
import SectionHeader from "@/Shared/UI/SectionHeader/SectionHeader";
|
||||
import SearchBar from "@/Features/SearchBar/SearchBar";
|
||||
import HomeMap from "@/Features/HomeMap/HomeMap";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMapStore } from "./map.store";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
enum ESwitch {
|
||||
BUTTON,
|
||||
A,
|
||||
}
|
||||
|
||||
interface IMapSection {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const MapSection: React.FC<IMapSection> = ({
|
||||
queryMap,
|
||||
queryRating,
|
||||
categories = "1,2,3,4,5,6",
|
||||
}: IMapSection) => {
|
||||
const [searchLocation, setSearchLocation] =
|
||||
useState<string>(queryMap);
|
||||
const [query] = useDebounce(searchLocation, 500);
|
||||
const router = useRouter();
|
||||
const switches = [
|
||||
{ title: "Разбитая дорога", category: "1", color: "#E64452" },
|
||||
{ title: "В планах ремонта", category: "4", color: "#FFAC33" },
|
||||
{ title: "Очаг аварийности", category: "2", color: "#C288E2" },
|
||||
{ title: "Отремонтировано", category: "5", color: "#8FDE6A" },
|
||||
{ title: "Локальный дефект", category: "3", color: "#87289D" },
|
||||
{
|
||||
title: "Локальный дефект исправлен'",
|
||||
category: "6",
|
||||
color: "#FED363",
|
||||
},
|
||||
];
|
||||
|
||||
const data = useMapStore(useShallow((state) => state.data));
|
||||
const getRatings = useMapStore(
|
||||
useShallow((state) => state.getRatings)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getRatings(categories);
|
||||
}, [categories]);
|
||||
|
||||
useEffect(() => {
|
||||
router.push(
|
||||
`/?тип-дороги=${categories}${
|
||||
query ? `&карта-дорог=${query}` : ""
|
||||
}${queryRating ? `&рейтинг=${queryRating}` : ""}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
);
|
||||
}, [query, router, categories]);
|
||||
|
||||
const setSearchParams = (category: string) => {
|
||||
const availableCategories: Record<string, number> = {
|
||||
"1": 1,
|
||||
"2": 2,
|
||||
"3": 3,
|
||||
"4": 4,
|
||||
"5": 5,
|
||||
"6": 6,
|
||||
};
|
||||
|
||||
if (!categories || !availableCategories[category])
|
||||
return categories;
|
||||
|
||||
if (categories?.includes(category)) {
|
||||
const updatedString = categories
|
||||
?.replace(category + ",", "")
|
||||
.replace("," + category, "")
|
||||
.replace(category, "");
|
||||
|
||||
return updatedString;
|
||||
} else {
|
||||
const newValue = category + ",";
|
||||
const updatedString = newValue + categories;
|
||||
return updatedString;
|
||||
}
|
||||
};
|
||||
|
||||
const MapSection = () => {
|
||||
return (
|
||||
<div className="map-section">
|
||||
<SectionHeader description="Yorem ipsum dolor sit amet, consectetur adipiscing elit.">
|
||||
Карта дорог
|
||||
</SectionHeader>
|
||||
|
||||
<SearchBar placeholder="Введите город, село или регион" />
|
||||
<SearchBar
|
||||
value={searchLocation}
|
||||
setValue={setSearchLocation}
|
||||
placeholder="Введите город, село или регион"
|
||||
/>
|
||||
|
||||
<div className="map-section__filters">
|
||||
<div className="map-section__switch">
|
||||
<Switch color="#E64452" />
|
||||
<h4>Разбитая дорога</h4>
|
||||
</div>
|
||||
<div className="map-section__switch">
|
||||
<Switch color="#FFAC33" />
|
||||
<h4>В планах ремонта</h4>
|
||||
</div>
|
||||
<div className="map-section__switch">
|
||||
<Switch color="#C288E2" />
|
||||
<h4>Очаг аварийности</h4>
|
||||
</div>
|
||||
<div className="map-section__switch">
|
||||
<Switch color="#FED363" />
|
||||
<h4>Локальный дефект исправлен</h4>
|
||||
</div>
|
||||
<div className="map-section__switch">
|
||||
<Switch color="#87289D" />
|
||||
<h4>Локальный дефект</h4>
|
||||
</div>
|
||||
<div className="map-section__switch">
|
||||
<Switch color="#8FDE6A" />
|
||||
<h4>Отремонтировано</h4>
|
||||
{switches.map((sw) => (
|
||||
<div key={sw.category} className="map-section__switch">
|
||||
<Switch
|
||||
defaultState={
|
||||
categories?.includes(sw.category) || categories === ""
|
||||
}
|
||||
href={`/?тип-дороги=${setSearchParams(sw.category)}`}
|
||||
type={ESwitch.A}
|
||||
color={sw.color}
|
||||
/>
|
||||
<h4>{sw.title}</h4>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Image
|
||||
className="map-section__map"
|
||||
src={map_image}
|
||||
alt="Map Image"
|
||||
/>
|
||||
<HomeMap data={data?.results} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
5
src/Widgets/home/MapSection/assets/geo-green.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#8FDE6A"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Widgets/home/MapSection/assets/geo-orange.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#FFAC33"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Widgets/home/MapSection/assets/geo-pink.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#C288E2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Widgets/home/MapSection/assets/geo-purple.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#87289D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Widgets/home/MapSection/assets/geo-red.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#E64452"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
5
src/Widgets/home/MapSection/assets/geo-yellow.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="38" height="50" viewBox="0 0 38 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 0C8.52334 0 0 8.74812 0 19.501C0 32.8456 17.0031 48.5901 17.7271 49.4175C18.407 50.1948 19.5942 50.1935 20.2729 49.4175C20.9969 48.5901 38 32.8456 38 19.501C37.9998 8.74812 29.4766 0 19 0Z" fill="#3998E8" fill-opacity="0.7"/>
|
||||
<ellipse cx="18.7867" cy="19" rx="9.88775" ry="10" fill="white"/>
|
||||
<circle cx="18.5" cy="18.5" r="7.5" fill="#FED363"/>
|
||||
</svg>
|
After Width: | Height: | Size: 461 B |
39
src/Widgets/home/MapSection/map.store.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { baseAPI } from "@/Shared/API/baseAPI";
|
||||
import { IFetch } from "@/Shared/types";
|
||||
import axios from "axios";
|
||||
import { create } from "zustand";
|
||||
import { ILocation, IReport } from "./types";
|
||||
|
||||
interface IMapStore extends IFetch {
|
||||
data: IReport;
|
||||
getRatings: (categories: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useMapStore = create<IMapStore>((set) => ({
|
||||
data: {
|
||||
count: 0,
|
||||
previous: null,
|
||||
next: null,
|
||||
results: [],
|
||||
},
|
||||
loading: false,
|
||||
error: "",
|
||||
getRatings: async (
|
||||
categories: string = "1,2,3,4,5,6",
|
||||
query: string = ""
|
||||
) => {
|
||||
try {
|
||||
set({ loading: true });
|
||||
|
||||
const response = await axios.get<IReport>(
|
||||
`${baseAPI}/report/?category=${categories}`
|
||||
);
|
||||
|
||||
set({ data: response.data });
|
||||
} catch (error: any) {
|
||||
set({ error: error.message });
|
||||
} finally {
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
}));
|
22
src/Widgets/home/MapSection/types.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { IList } from "@/Shared/types";
|
||||
|
||||
export interface ILocation {
|
||||
id: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface IResults {
|
||||
id: number;
|
||||
created_at: string;
|
||||
location: ILocation[];
|
||||
category: number;
|
||||
description: string;
|
||||
count_reviews: number;
|
||||
total_likes: number;
|
||||
}
|
||||
|
||||
export interface IReport extends IList {
|
||||
results: IResults[];
|
||||
}
|
@ -23,7 +23,7 @@ const NewsSection = () => {
|
||||
|
||||
<ul className="news-section__list">
|
||||
{data.map((card) => (
|
||||
<li key={card.id} className="news-card">
|
||||
<li key={card.id} className="news-section__card">
|
||||
<NewsCard
|
||||
id={card.id}
|
||||
title={card.title}
|
||||
|
@ -76,15 +76,6 @@
|
||||
|
||||
#report-type {
|
||||
min-width: 210px;
|
||||
|
||||
span {
|
||||
width: 120px;
|
||||
padding: 4px 12px;
|
||||
font-size: 14px;
|
||||
border-radius: 20px;
|
||||
background: rgba(230, 68, 82, 0.8);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
#report-description {
|
||||
|
@ -9,15 +9,45 @@ import like_icon from "./icons/like-icon.svg";
|
||||
import message_icon from "./icons/message-icon.svg";
|
||||
import Image from "next/image";
|
||||
import { useRating } from "./rating.store";
|
||||
import { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import RoadType from "@/Entities/RoadType/RoadType";
|
||||
import {
|
||||
ROAD_TYPES,
|
||||
ROAD_TYPES_COLORS,
|
||||
} from "@/Shared/variables/road-types";
|
||||
|
||||
const RatingSection = () => {
|
||||
interface IRatingSection {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const RatingSection: React.FC<IRatingSection> = ({
|
||||
categories = "1,2,3,4,5,6",
|
||||
queryMap,
|
||||
queryRating,
|
||||
}: IRatingSection) => {
|
||||
const { data: reports, getRatings } = useRating();
|
||||
const [search, setSearch] = useState<string>(queryRating);
|
||||
const [query] = useDebounce(search, 500);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
getRatings();
|
||||
}, []);
|
||||
getRatings(query);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
router.push(
|
||||
`/?тип-дороги=${categories}${
|
||||
queryMap ? `&карта-дорог=${queryMap}` : ""
|
||||
}${query ? `&рейтинг=${query}` : ""}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
);
|
||||
}, [query, router, categories]);
|
||||
|
||||
const sliceDate = (date: string) => {
|
||||
return `${date.slice(8, 10)}.${date.slice(5, 7)}.${date.slice(
|
||||
@ -45,7 +75,11 @@ const RatingSection = () => {
|
||||
<SectionHeader description="Yorem ipsum dolor sit amet, consectetur adipiscing elit.">
|
||||
Рейтинг
|
||||
</SectionHeader>
|
||||
<SearchBar placeholder="Введите адрес" />
|
||||
<SearchBar
|
||||
value={search}
|
||||
setValue={setSearch}
|
||||
placeholder="Введите адрес"
|
||||
/>
|
||||
|
||||
<div className="rating-section__table">
|
||||
<table>
|
||||
@ -89,7 +123,11 @@ const RatingSection = () => {
|
||||
</Link>
|
||||
</td>
|
||||
<td id="report-type">
|
||||
<span>Разбитая дорога</span>
|
||||
<RoadType
|
||||
color={ROAD_TYPES_COLORS[report.category]}
|
||||
>
|
||||
{ROAD_TYPES[report.category]}
|
||||
</RoadType>
|
||||
</td>
|
||||
<td id="report-description">
|
||||
<p>{sliceDescription(report.description)}</p>
|
||||
|
@ -6,23 +6,36 @@ import { IReport } from "./types";
|
||||
|
||||
interface IRatingStore extends IFetch {
|
||||
data: IReport;
|
||||
getRatings: () => Promise<void>;
|
||||
getRatings: (query: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useRating = create<IRatingStore>((set) => ({
|
||||
data: {
|
||||
count: 0,
|
||||
previous: null,
|
||||
next: null,
|
||||
results: [],
|
||||
},
|
||||
loading: false,
|
||||
error: "",
|
||||
getRatings: async () => {
|
||||
getRatings: async (query: string = "") => {
|
||||
try {
|
||||
set({ loading: true });
|
||||
|
||||
const response = await axios.get<IReport>(`${baseAPI}/report/`);
|
||||
const data = (await axios.get<IReport>(`${baseAPI}/report/`))
|
||||
.data;
|
||||
|
||||
set({ data: response.data });
|
||||
const searched = data.results.filter((rating) => {
|
||||
return rating.location.some((location) => {
|
||||
return location.address
|
||||
.toLowerCase()
|
||||
.includes(query.toLowerCase());
|
||||
});
|
||||
});
|
||||
|
||||
data.results = [...searched];
|
||||
|
||||
set({ data: data });
|
||||
} catch (error: any) {
|
||||
set({ error: error.message });
|
||||
} finally {
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { IList } from "@/Shared/types";
|
||||
|
||||
export interface ILocation {
|
||||
id: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface IReport {
|
||||
count: number;
|
||||
results: IResults[];
|
||||
}
|
||||
|
||||
interface IResults {
|
||||
id: number;
|
||||
created_at: string;
|
||||
@ -17,3 +16,7 @@ interface IResults {
|
||||
count_reviews: number;
|
||||
total_likes: number;
|
||||
}
|
||||
|
||||
export interface IReport extends IList {
|
||||
results: IResults[];
|
||||
}
|
||||
|
3
src/middleware.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default } from "next-auth/middleware";
|
||||
|
||||
export const config = { matcher: ["/profile/personal-data"] };
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
137
yarn.lock
@ -7,6 +7,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
|
||||
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
|
||||
|
||||
"@babel/runtime@^7.20.13":
|
||||
version "7.23.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.23.2":
|
||||
version "7.23.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||
@ -155,11 +162,21 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@panva/hkdf@^1.0.2":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d"
|
||||
integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==
|
||||
|
||||
"@pkgjs/parseargs@^0.11.0":
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@react-leaflet/core@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-2.1.0.tgz#383acd31259d7c9ae8fb1b02d5e18fe613c2a13d"
|
||||
integrity sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==
|
||||
|
||||
"@rushstack/eslint-patch@^1.3.3":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.7.0.tgz#b5bc1e081428794f6a4d239707b359404be35ce2"
|
||||
@ -172,11 +189,23 @@
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@types/geojson@*":
|
||||
version "7946.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e"
|
||||
integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||
|
||||
"@types/leaflet@^1.9.8":
|
||||
version "1.9.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.8.tgz#32162a8eaf305c63267e99470b9603b5883e63e8"
|
||||
integrity sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==
|
||||
dependencies:
|
||||
"@types/geojson" "*"
|
||||
|
||||
"@types/node@^20":
|
||||
version "20.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.5.tgz#be10c622ca7fcaa3cf226cf80166abc31389d86e"
|
||||
@ -196,6 +225,13 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-leaflet@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-leaflet/-/react-leaflet-3.0.0.tgz#b27a50abb6e3ae734d3c15399a26c77c161cab1c"
|
||||
integrity sha512-p8R9mVKbCDDqOdW+M6GyJJuFn6q+IgDFYavFiOIvaWHuOe5kIHZEtCy1pfM43JIA6JiB3D/aDoby7C51eO+XSg==
|
||||
dependencies:
|
||||
react-leaflet "*"
|
||||
|
||||
"@types/react@*", "@types/react@^18":
|
||||
version "18.2.48"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1"
|
||||
@ -526,7 +562,7 @@ chalk@^4.0.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
client-only@0.0.1, client-only@^0.0.1:
|
||||
client-only@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
|
||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||
@ -555,6 +591,11 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
cookie@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@ -1491,6 +1532,11 @@ jackspeak@^2.3.5:
|
||||
optionalDependencies:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
jose@^4.11.4, jose@^4.15.4:
|
||||
version "4.15.4"
|
||||
resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.4.tgz#02a9a763803e3872cf55f29ecef0dfdcc218cc03"
|
||||
integrity sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@ -1554,6 +1600,11 @@ language-tags@^1.0.9:
|
||||
dependencies:
|
||||
language-subtag-registry "^0.3.20"
|
||||
|
||||
leaflet@^1.9.4:
|
||||
version "1.9.4"
|
||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d"
|
||||
integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
@ -1662,6 +1713,21 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
next-auth@^4.24.5:
|
||||
version "4.24.5"
|
||||
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.24.5.tgz#1fd1bfc0603c61fd2ba6fd81b976af690edbf07e"
|
||||
integrity sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.13"
|
||||
"@panva/hkdf" "^1.0.2"
|
||||
cookie "^0.5.0"
|
||||
jose "^4.11.4"
|
||||
oauth "^0.9.15"
|
||||
openid-client "^5.4.0"
|
||||
preact "^10.6.3"
|
||||
preact-render-to-string "^5.1.19"
|
||||
uuid "^8.3.2"
|
||||
|
||||
next@14.1.0:
|
||||
version "14.1.0"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-14.1.0.tgz#b31c0261ff9caa6b4a17c5af019ed77387174b69"
|
||||
@ -1690,11 +1756,21 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
oauth@^0.9.15:
|
||||
version "0.9.15"
|
||||
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
|
||||
integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-hash@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
||||
|
||||
object-inspect@^1.13.1, object-inspect@^1.9.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
|
||||
@ -1760,6 +1836,11 @@ object.values@^1.1.6, object.values@^1.1.7:
|
||||
define-properties "^1.2.0"
|
||||
es-abstract "^1.22.1"
|
||||
|
||||
oidc-token-hash@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6"
|
||||
integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
@ -1767,6 +1848,16 @@ once@^1.3.0:
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
openid-client@^5.4.0:
|
||||
version "5.6.4"
|
||||
resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.6.4.tgz#b2c25e6d5338ba3ce00e04341bb286798a196177"
|
||||
integrity sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==
|
||||
dependencies:
|
||||
jose "^4.15.4"
|
||||
lru-cache "^6.0.0"
|
||||
object-hash "^2.2.0"
|
||||
oidc-token-hash "^5.0.3"
|
||||
|
||||
optionator@^0.9.3:
|
||||
version "0.9.3"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
|
||||
@ -1852,11 +1943,28 @@ postcss@8.4.31:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
preact-render-to-string@^5.1.19:
|
||||
version "5.2.6"
|
||||
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz#0ff0c86cd118d30affb825193f18e92bd59d0604"
|
||||
integrity sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==
|
||||
dependencies:
|
||||
pretty-format "^3.8.0"
|
||||
|
||||
preact@^10.6.3:
|
||||
version "10.19.3"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
||||
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
pretty-format@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
|
||||
|
||||
prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
@ -1894,6 +2002,13 @@ react-is@^16.13.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-leaflet@*, react-leaflet@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-4.2.1.tgz#c300e9eccaf15cb40757552e181200aa10b94780"
|
||||
integrity sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==
|
||||
dependencies:
|
||||
"@react-leaflet/core" "^2.1.0"
|
||||
|
||||
react@^18:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
@ -2193,14 +2308,6 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
swr@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07"
|
||||
integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
use-sync-external-store "^1.2.0"
|
||||
|
||||
tapable@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
@ -2316,11 +2423,21 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
|
||||
use-debounce@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.0.tgz#5091b18d6c16292605f588bae3c0d2cfae756ff2"
|
||||
integrity sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==
|
||||
|
||||
use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
|