Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cd7b2a67d | ||
|
|
d77846bbbb | ||
|
|
76b7a8e6b4 | ||
|
|
7a9ba30e8f | ||
|
|
16bb5a4a1a | ||
|
|
b0870824ba | ||
|
|
d4c49a1061 | ||
|
|
a12aaa1213 | ||
|
|
ed72067108 | ||
|
|
95ff16d48b | ||
|
|
e0e2f5470d | ||
|
|
d6c6504d1f | ||
|
|
f6ceb252b2 | ||
|
|
45a9d698cd | ||
|
|
1468fac1cc |
No files matched your search
@@ -1,2 +0,0 @@
|
||||
NEXTAUTH_SECRET=";sadmfxflpdk"
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
@@ -5,6 +5,7 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
.env
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import RootLayout from "@/app/App";
|
||||
|
||||
export default RootLayout;
|
||||
@@ -1,3 +0,0 @@
|
||||
import Home from "@/pages/Home/Home";
|
||||
|
||||
export default Home;
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>;
|
||||
};
|
||||
|
||||
export default page;
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>;
|
||||
};
|
||||
|
||||
export default page;
|
||||
@@ -4,6 +4,7 @@ declare module "next-auth" {
|
||||
interface Session {
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
expires_in: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +14,6 @@ declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
refresh_token: string;
|
||||
access_token: string;
|
||||
exp: number;
|
||||
expires_in: string;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
distDir: "build",
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.5",
|
||||
"dotenv": "^16.4.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"next": "14.1.0",
|
||||
"next-auth": "^4.24.5",
|
||||
@@ -17,12 +18,13 @@
|
||||
"react-dom": "^18",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"sass": "^1.70.0",
|
||||
"sharp": "^0.33.2",
|
||||
"use-debounce": "^10.0.0",
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/leaflet": "^1.9.8",
|
||||
"@types/node": "^20",
|
||||
"@types/node": "^20.11.16",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-leaflet": "^3.0.0",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap");
|
||||
|
||||
@import url("https://fonts.cdnfonts.com/css/arial");
|
||||
|
||||
@import url("../shared/fonts/TildaSans.css");
|
||||
|
||||
* {
|
||||
font-family: Tilda Sans;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol,
|
||||
li {
|
||||
list-style-type: none;
|
||||
list-style: none;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
.section-header {
|
||||
margin-bottom: 52px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
text-align: center;
|
||||
|
||||
h3 {
|
||||
font-size: 42px;
|
||||
font-weight: 500;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
line-height: 29px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.section-header {
|
||||
margin-bottom: 42px;
|
||||
|
||||
h3 {
|
||||
font-size: 36px;
|
||||
line-height: 43px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.section-header {
|
||||
margin-bottom: 42px;
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import "./SectionHeader.scss";
|
||||
|
||||
interface ISectionHeaderProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
style?: object;
|
||||
}
|
||||
|
||||
const SectionHeader: React.FC<ISectionHeaderProps> = ({
|
||||
title,
|
||||
description,
|
||||
style,
|
||||
}: ISectionHeaderProps) => {
|
||||
return (
|
||||
<div style={style} className="section-header">
|
||||
<h3>{title}</h3>
|
||||
{description && <p>{description}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionHeader;
|
||||
@@ -1,2 +0,0 @@
|
||||
export const baseAPI =
|
||||
"https://api.kgroaduat.fishrungames.com/api/v1";
|
||||
@@ -1,32 +0,0 @@
|
||||
export interface IAuthor {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
govern_status: unknown;
|
||||
}
|
||||
|
||||
export interface ILocation {
|
||||
id: 4;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface IReportImage {
|
||||
id: number;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface IReport {
|
||||
results: any;
|
||||
id: number;
|
||||
image: IReportImage[];
|
||||
description: string;
|
||||
author: IAuthor;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
location: ILocation[];
|
||||
category: number;
|
||||
total_likes: number;
|
||||
count_reviews: number;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
"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";
|
||||
import { ILocation } from "@/shared/types/report-type";
|
||||
|
||||
interface IData {
|
||||
id: number;
|
||||
location: ILocation[];
|
||||
category: 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;
|
||||
@@ -1,123 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import "./MapSection.scss";
|
||||
import SearchForm from "@/features/SearchBar/SearchForm";
|
||||
import { useEffect, useState } from "react";
|
||||
import SectionHeader from "@/entities/SectionHeader/SectionHeader";
|
||||
import {
|
||||
ROAD_TYPES,
|
||||
ROAD_TYPES_COLORS,
|
||||
} from "@/shared/variables/road-types";
|
||||
import HomeMap from "./HomeMap/HomeMap";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMapStore } from "./mapSectionStore";
|
||||
import Switch from "./Switch/Switch";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
|
||||
interface IMapSectionProps {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const MapSection: React.FC<IMapSectionProps> = ({
|
||||
categories = "1,2,3,4,5,6",
|
||||
queryMap,
|
||||
queryRating,
|
||||
}: IMapSectionProps) => {
|
||||
const [mapSearch, setMapSearch] = useState<string>(queryMap || "");
|
||||
const data = useMapStore(useShallow((state) => state.data));
|
||||
const getReports = useMapStore(
|
||||
useShallow((state) => state.getReports)
|
||||
);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
getReports(categories);
|
||||
}, [categories]);
|
||||
|
||||
const handleSubmit: React.FormEventHandler<
|
||||
HTMLFormElement
|
||||
> = async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
setMapSearch(formData.get("map-search") as string);
|
||||
|
||||
router.push(
|
||||
`/?тип-дороги=${categories}${
|
||||
mapSearch ? `&поиск-на-карте=${mapSearch}` : ""
|
||||
}${queryRating ? `&поиск-рейтинг=${queryRating}` : ""}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const setSearchParams = (category: string) => {
|
||||
const availableCategories = ["1", "2", "3", "4", "5", "6"];
|
||||
|
||||
if (!categories || !availableCategories.includes(category))
|
||||
return categories;
|
||||
|
||||
if (categories?.includes(category)) {
|
||||
const updatedCategories = categories
|
||||
?.replace(category + ",", "")
|
||||
.replace("," + category, "")
|
||||
.replace(category, "");
|
||||
|
||||
return updatedCategories;
|
||||
} else {
|
||||
const newValue = category + ",";
|
||||
const updatedCategories = newValue + categories;
|
||||
return updatedCategories;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="map-section">
|
||||
<SectionHeader
|
||||
title="Карта дорог"
|
||||
description="Будьте в курсе последних новостей о дорожном движении, строительствах и мероприятиях!"
|
||||
/>
|
||||
<SearchForm
|
||||
onChange={(e) => setMapSearch(e.target.value)}
|
||||
name="map-search"
|
||||
value={mapSearch}
|
||||
placeholder="Введите город, село или регион"
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
<div className="map-section__categories">
|
||||
<ul className="map-section__categories_left">
|
||||
{[1, 2, 3].map((sw) => (
|
||||
<li key={sw}>
|
||||
<Switch
|
||||
href={`/?тип-дороги=${setSearchParams(
|
||||
sw.toString()
|
||||
)}`}
|
||||
color={ROAD_TYPES_COLORS[sw]}
|
||||
defaultState={categories.includes(sw.toString())}
|
||||
/>
|
||||
<label>{ROAD_TYPES[sw]}</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="map-section__categories_right">
|
||||
{[4, 5, 6].map((sw) => (
|
||||
<li key={sw}>
|
||||
<Switch
|
||||
href={`/?тип-дороги=${setSearchParams(
|
||||
sw.toString()
|
||||
)}`}
|
||||
color={ROAD_TYPES_COLORS[sw]}
|
||||
defaultState={categories.includes(sw.toString())}
|
||||
/>
|
||||
<label>{ROAD_TYPES[sw]}</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<HomeMap data={data?.results} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapSection;
|
||||
@@ -1,41 +0,0 @@
|
||||
import { baseAPI } from "@/shared/api/baseAPI";
|
||||
import { IFetch } from "@/shared/types/fetch-type";
|
||||
import { IList } from "@/shared/types/list-type";
|
||||
import { IReport } from "@/shared/types/report-type";
|
||||
import axios from "axios";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface IFetchReports extends IList {
|
||||
results: IReport[];
|
||||
}
|
||||
|
||||
interface IMapStore extends IFetch {
|
||||
data: IFetchReports;
|
||||
getReports: (categories: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useMapStore = create<IMapStore>((set) => ({
|
||||
data: {
|
||||
count: 0,
|
||||
previous: null,
|
||||
next: null,
|
||||
results: [],
|
||||
},
|
||||
isLoading: false,
|
||||
error: "",
|
||||
getReports: async (categories: string = "1,2,3,4,5,6") => {
|
||||
try {
|
||||
set({ isLoading: true });
|
||||
|
||||
const response = await axios.get<IFetchReports>(
|
||||
`${baseAPI}/report/?category=${categories}`
|
||||
);
|
||||
|
||||
set({ data: response.data });
|
||||
} catch (error: any) {
|
||||
set({ error: error.message });
|
||||
} finally {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -1,133 +0,0 @@
|
||||
@import "../../shared/ui/variables.scss";
|
||||
|
||||
.rating-section {
|
||||
padding: 0 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__table {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-radius: 6px;
|
||||
thead {
|
||||
tr {
|
||||
border-bottom: 1px solid #ddd;
|
||||
th {
|
||||
padding: 15px 0;
|
||||
text-align: left;
|
||||
color: $gray-500;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#report-header-date,
|
||||
#report-header-comment,
|
||||
#report-header-like {
|
||||
cursor: pointer;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
td {
|
||||
padding: 15px 0;
|
||||
|
||||
a {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
#report-date {
|
||||
min-width: 150px;
|
||||
color: $gray-500;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#report-location {
|
||||
min-width: 200px;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
white-space: preserve nowrap;
|
||||
color: $light-blue;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
#report-type {
|
||||
min-width: 210px;
|
||||
}
|
||||
|
||||
#report-description {
|
||||
min-width: 378px;
|
||||
|
||||
p {
|
||||
width: 318px;
|
||||
color: #32303a;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.1px;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
#report-comment,
|
||||
#report-like {
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
td {
|
||||
text-align: center;
|
||||
font-size: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.rating-section {
|
||||
padding: 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.rating-section {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
"use client";
|
||||
import "./RatingSection.scss";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import SectionHeader from "@/entities/SectionHeader/SectionHeader";
|
||||
import SearchForm from "@/features/SearchBar/SearchForm";
|
||||
import arrow_down_icon from "./icons/arrow-down.svg";
|
||||
import arrow_up_icon from "./icons/arrow-up.svg";
|
||||
import like_icon from "./icons/like.svg";
|
||||
import message_icon from "./icons/message.svg";
|
||||
import { useRatingStore } from "./ratingSectionStore";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ROAD_TYPES,
|
||||
ROAD_TYPES_COLORS,
|
||||
} from "@/shared/variables/road-types";
|
||||
import RoadType from "@/entities/RoadType/RoadType";
|
||||
|
||||
interface IRatingSectionProps {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const RatingSection: React.FC<IRatingSectionProps> = ({
|
||||
categories = "1,2,3,4,5,6",
|
||||
queryMap,
|
||||
queryRating,
|
||||
}: IRatingSectionProps) => {
|
||||
const [ratingSearch, setRatingSearch] = useState<string>(
|
||||
queryRating || ""
|
||||
);
|
||||
const router = useRouter();
|
||||
|
||||
const reports = useRatingStore(useShallow((state) => state.data));
|
||||
const getReports = useRatingStore(
|
||||
useShallow((state) => state.getReports)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getReports(ratingSearch);
|
||||
}, []);
|
||||
|
||||
const handleSubmit: React.FormEventHandler<
|
||||
HTMLFormElement
|
||||
> = async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
setRatingSearch(formData.get("rating-search") as string);
|
||||
|
||||
router.push(
|
||||
`/?тип-дороги=${categories}${
|
||||
queryMap ? `&поиск-на-карте=${queryMap}` : ""
|
||||
}${ratingSearch ? `&поиск-рейтинг=${ratingSearch}` : ""}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
);
|
||||
|
||||
getReports(ratingSearch);
|
||||
};
|
||||
|
||||
const sliceDate = (date: string) => {
|
||||
return `${date.slice(8, 10)}.${date.slice(5, 7)}.${date.slice(
|
||||
0,
|
||||
4
|
||||
)}`;
|
||||
};
|
||||
|
||||
const sliceLocation = (location: string) => {
|
||||
if (location.length > 15) return `${location.slice(0, 15)}...`;
|
||||
|
||||
return location;
|
||||
};
|
||||
|
||||
const sliceDescription = (description: string) => {
|
||||
if (description.length > 49) {
|
||||
return `${description.slice(0, 50)}...`;
|
||||
}
|
||||
|
||||
return description;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rating-section">
|
||||
<SectionHeader
|
||||
title="Рейтинг"
|
||||
description="Обсуждаем дороги: рейтинг, опыт, комфорт в пути!"
|
||||
/>
|
||||
<SearchForm
|
||||
onChange={(e) => setRatingSearch(e.target.value)}
|
||||
name="rating-search"
|
||||
placeholder="Введите адрес"
|
||||
value={ratingSearch}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
<div className="rating-section__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="report-header-date" tabIndex={0}>
|
||||
<div>
|
||||
Дата
|
||||
<Image src={arrow_down_icon} alt="Arrow Down" />
|
||||
<Image src={arrow_up_icon} alt="Arrow Up" />
|
||||
</div>
|
||||
</th>
|
||||
<th>Адрес</th>
|
||||
<th>Статус</th>
|
||||
<th>Описание</th>
|
||||
<th id="report-header-comment" tabIndex={0}>
|
||||
<div>
|
||||
Комментарии
|
||||
<Image src={arrow_down_icon} alt="Arrow Down" />
|
||||
<Image src={arrow_up_icon} alt="Arrow Up" />
|
||||
</div>
|
||||
</th>
|
||||
<th id="report-header-like" tabIndex={0}>
|
||||
<div>
|
||||
Рейтинг
|
||||
<Image src={arrow_down_icon} alt="Arrow Down" />
|
||||
<Image src={arrow_up_icon} alt="Arrow Up" />
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{reports.results.length !== 0 ? (
|
||||
reports.results.map((report) => (
|
||||
<tr key={report.id}>
|
||||
<td id="report-date">
|
||||
{sliceDate(report.created_at)}
|
||||
</td>
|
||||
<td id="report-location">
|
||||
<Link href={`/report/${report.location[0].id}`}>
|
||||
{sliceLocation(report.location[0].address)}
|
||||
</Link>
|
||||
</td>
|
||||
<td id="report-type">
|
||||
<RoadType
|
||||
color={ROAD_TYPES_COLORS[report.category]}
|
||||
>
|
||||
{ROAD_TYPES[report.category]}
|
||||
</RoadType>
|
||||
</td>
|
||||
<td id="report-description">
|
||||
<p>{sliceDescription(report.description)}</p>
|
||||
</td>
|
||||
<td id="report-comment">
|
||||
<div>
|
||||
<Image src={message_icon} alt="Message Icon" />
|
||||
{report.count_reviews}
|
||||
</div>
|
||||
</td>
|
||||
<td id="report-like">
|
||||
<div>
|
||||
<Image src={like_icon} alt="Like Icon" />
|
||||
{report.total_likes}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr className="rating-section-error">
|
||||
<td colSpan={7}>Упс, адрес не найден</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RatingSection;
|
||||
@@ -1,14 +0,0 @@
|
||||
<svg width="16.000000" height="16.000000" viewBox="0 0 16 16" 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="clip183_53161">
|
||||
<rect id="arrow-down" width="16.000000" height="16.000000" transform="translate(16.000000 16.000000) rotate(180.000000)" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="arrow-down" width="16.000000" height="16.000000" transform="translate(16.000000 16.000000) rotate(180.000000)" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip183_53161)">
|
||||
<path id="Icon" d="M8 12.667L8 3.33398M3.33301 8L8 3.33398L12.6665 8" stroke="#667085" stroke-opacity="1.000000" stroke-width="1.333333" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 783 B |
@@ -1,14 +0,0 @@
|
||||
<svg width="16.000000" height="16.000000" viewBox="0 0 16 16" 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="clip161_49481">
|
||||
<rect id="arrow-down" width="16.000000" height="16.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="arrow-down" width="16.000000" height="16.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip161_49481)">
|
||||
<path id="Icon" d="M8 3.33301L8 12.666M12.667 8L8 12.666L3.3335 8" stroke="#667085" stroke-opacity="1.000000" stroke-width="1.333333" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 656 B |
@@ -1,51 +0,0 @@
|
||||
import { baseAPI } from "@/shared/api/baseAPI";
|
||||
import { IFetch } from "@/shared/types/fetch-type";
|
||||
import { IList } from "@/shared/types/list-type";
|
||||
import { IReport } from "@/shared/types/report-type";
|
||||
import axios from "axios";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface IFetchReports extends IList {
|
||||
results: IReport[];
|
||||
}
|
||||
|
||||
interface IRatingStore extends IFetch {
|
||||
data: IFetchReports;
|
||||
getReports: (categories: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useRatingStore = create<IRatingStore>((set) => ({
|
||||
data: {
|
||||
count: 0,
|
||||
previous: null,
|
||||
next: null,
|
||||
results: [],
|
||||
},
|
||||
isLoading: false,
|
||||
error: "",
|
||||
getReports: async (query: string = "") => {
|
||||
try {
|
||||
set({ isLoading: true });
|
||||
|
||||
const data = (
|
||||
await axios.get<IFetchReports>(`${baseAPI}/report/`)
|
||||
).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 {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ROAD_TYPES_STATS } from "@/shared/variables/road-types";
|
||||
import "./StatisticsSection.scss";
|
||||
import { statiscsSectionStore } from "./statisticsSection.store";
|
||||
|
||||
const StatisticsSection = async () => {
|
||||
// const { getStatsCount, getErrorMessage } = statiscsSectionStore();
|
||||
// const statistics = await getStatsCount();
|
||||
// const error = getErrorMessage();
|
||||
|
||||
const mockArray = [
|
||||
{ category: 1, count: 132 },
|
||||
{ category: 2, count: 12 },
|
||||
{ category: 3, count: 1432 },
|
||||
{ category: 4, count: 18 },
|
||||
{ category: 5, count: 95 },
|
||||
{ category: 6, count: 54 },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="statistics-section">
|
||||
<ul className="statistics-section__list">
|
||||
{mockArray.map((cat) => (
|
||||
<li>
|
||||
<p>{ROAD_TYPES_STATS[cat.category]}</p>
|
||||
<span>{cat.count}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticsSection;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { baseAPI } from "@/shared/api/baseAPI";
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
interface IStatsCount {
|
||||
category: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export const statiscsSectionStore = () => {
|
||||
let errorMessage = "";
|
||||
async function getStatsCount() {
|
||||
try {
|
||||
const response = await axios.get<IStatsCount[]>(
|
||||
`${baseAPI}/report/category_count/`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
errorMessage = error.message;
|
||||
} else {
|
||||
errorMessage = "An error ocured";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
return {
|
||||
getStatsCount,
|
||||
getErrorMessage,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
export const Providers = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
.about-us {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 40px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
img {
|
||||
align-self: center;
|
||||
margin-bottom: 65px;
|
||||
border-radius: 12px;
|
||||
max-width: 1072px;
|
||||
width: 100%;
|
||||
height: 598px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 29px;
|
||||
color: rgb(62, 50, 50);
|
||||
}
|
||||
|
||||
&__descriptions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
color: rgb(62, 50, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.about-us {
|
||||
h2 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-bottom: 30px;
|
||||
height: 392px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
&__descriptions {
|
||||
gap: 16px;
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.about-us {
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 231px;
|
||||
}
|
||||
|
||||
&__descriptions {
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 140%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="1072.000000" height="598.000000" viewBox="0 0 1072 598" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<desc>
|
||||
Created with Pixso.
|
||||
</desc>
|
||||
<defs>
|
||||
<pattern id="pattern_627_213000" patternContentUnits="objectBoundingBox" width="1.000000" height="1.000000">
|
||||
<use xlink:href="#image627_21300_0" transform="matrix(0.002315,0,0,0.004149,-0.056678,0)"/>
|
||||
</pattern>
|
||||
<image id="image627_21300_0" width="481.000000" height="241.000000" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
<rect id="unsplash:NrN5Rvl89Lo" width="1072.000000" height="598.000000" fill="url(#pattern_627_213000)" fill-opacity="1.000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 332 KiB |
@@ -0,0 +1,70 @@
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import "./AboutUs.scss";
|
||||
import Image from "next/image";
|
||||
import header from "./assets/header.svg";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | О нас",
|
||||
description:
|
||||
'Страница "О Нас" Kyrgyzstan Transperency International',
|
||||
};
|
||||
|
||||
const AboutUs = () => {
|
||||
return (
|
||||
<div className="about-us page-padding">
|
||||
<Typography element="h2">О нас</Typography>
|
||||
|
||||
<Image src={header} alt="Header Image" />
|
||||
|
||||
<h3>Don’t wait. The purpose of our lives is to be happy!</h3>
|
||||
|
||||
<div className="about-us__descriptions">
|
||||
<p>
|
||||
arrival, your senses will be rewarded with the pleasant
|
||||
scent of lemongrass oil used to clean the natural wood found
|
||||
throughout the room, creating a relaxing atmosphere within
|
||||
the space. A wonderful serenity has taken possession of my
|
||||
entire soul, like these sweet mornings of spring which I
|
||||
enjoy with my whole heart. I am alone, and feel the charm of
|
||||
existence in this spot, which was created for the bliss of
|
||||
souls like mine. I am so happy, my dear friend, so absorbed
|
||||
in the exquisite.
|
||||
</p>
|
||||
<p>
|
||||
When you are ready to indulge your sense of excitement,
|
||||
check out the range of water- sports opportunities at the
|
||||
resort’s on-site water-sports center. Want to leave your
|
||||
stress on the water? The resort has kayaks, paddleboards, or
|
||||
the low-key pedal boats. Snorkeling equipment is available
|
||||
as well, so you can experience the ever-changing undersea
|
||||
environment. Not only do visitors to a bed and breakfast get
|
||||
a unique perspective on the place they are visiting, they
|
||||
have options for special packages not available in other
|
||||
hotel settings.{" "}
|
||||
</p>
|
||||
<p>
|
||||
Bed and breakfasts can partner easily with local businesses
|
||||
for a smoothly organized and highly personalized vacation
|
||||
experience. The Fife and Drum Inn offers options such as the
|
||||
Historic Triangle Package that includes three nights at the
|
||||
Inn, breakfasts, and admissions to historic Williamsburg,
|
||||
Jamestown, and Yorktown. Bed and breakfasts also lend
|
||||
themselves to romance.
|
||||
</p>
|
||||
<p>
|
||||
Part of the charm of a bed and breakfast is the uniqueness;
|
||||
art, décor, and food are integrated to create a complete
|
||||
experience. For example, the Fife and Drum retains the
|
||||
colonial feel of the area in all its guest rooms. Special
|
||||
features include antique furnishings, elegant four poster
|
||||
beds in some guest rooms, as well folk art and artifacts
|
||||
from the restoration period of the historic area available
|
||||
for guests to enjoy.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUs;
|
||||
@@ -0,0 +1,6 @@
|
||||
import NextAuth from "next-auth";
|
||||
import { authConfig } from "@/shared/config/authConfig";
|
||||
|
||||
const handler = NextAuth(authConfig);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
@@ -0,0 +1,20 @@
|
||||
.create-report {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
h2 {
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.create-report {
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.create-report {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import "./CreateReport.scss";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Написать обращение",
|
||||
description:
|
||||
"Написать обращение Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const DynamicForm = dynamic(
|
||||
() => import("@/widgets/ReportForm/ReportForm"),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
const CreateReport = () => {
|
||||
return (
|
||||
<div className="create-report page-padding">
|
||||
<Typography element="h2">Написать обращение</Typography>
|
||||
|
||||
<DynamicForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateReport;
|
||||
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,60 @@
|
||||
@import url("https://fonts.cdnfonts.com/css/inter");
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
// font-family: "Tilda Sans";
|
||||
}
|
||||
|
||||
button,
|
||||
a,
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.page-padding {
|
||||
padding: 40px 90px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.page-padding {
|
||||
padding: 40px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.page-padding {
|
||||
padding: 40px 16px;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.scss";
|
||||
import "./fonts.scss";
|
||||
import "./App.scss";
|
||||
import Footer from "@/widgets/Footer/Footer";
|
||||
// import "@/shared/fonts/fonts.scss";
|
||||
import { Providers } from "./Providers";
|
||||
import Navbar from "@/widgets/Navbar/Navbar";
|
||||
import Footer from "@/widgets/Footer/Footer";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
@@ -12,9 +14,11 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<Navbar />
|
||||
<div className="app">{children}</div>
|
||||
<Footer />
|
||||
<Providers>
|
||||
<Navbar />
|
||||
<div className="app">{children}</div>
|
||||
<Footer />
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
.news {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
h2 {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.news {
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.news {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
.news-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
&__img {
|
||||
align-self: center;
|
||||
#news-img {
|
||||
margin-bottom: 30px;
|
||||
width: 100%;
|
||||
max-width: 1072px;
|
||||
height: 598px;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&__date-and-reviews {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
&__date,
|
||||
&__reviews {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgba(62, 50, 50, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
width: 100%;
|
||||
max-width: 1072px;
|
||||
align-self: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
color: rgb(62, 50, 50);
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.news-details {
|
||||
gap: 30px;
|
||||
|
||||
&__img {
|
||||
#news-img {
|
||||
margin-bottom: 25px;
|
||||
height: 392px;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.news-details {
|
||||
gap: 20px;
|
||||
|
||||
&__img {
|
||||
#news-img {
|
||||
margin-bottom: 20px;
|
||||
height: 231px;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 140%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg width="16.000000" height="16.000000" viewBox="0 0 16 16" 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="clip2340_50999">
|
||||
<rect id="calendar" width="16.000000" height="16.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="calendar" width="16.000000" height="16.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip2340_50999)">
|
||||
<path id="Vector" d="M12.666 2.66699C13.4023 2.66699 14 3.26367 14 4L14 13.334C14 14.0703 13.4023 14.667 12.666 14.667L3.33398 14.667C2.59766 14.667 2 14.0703 2 13.334L2 4C2 3.26367 2.59766 2.66699 3.33398 2.66699L12.666 2.66699Z" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M10.666 1.33301L10.666 4" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path id="Vector" d="M5.33398 1.33301L5.33398 4" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path id="Vector" d="M2 6.66699L14 6.66699" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg width="16.000000" height="16.000000" viewBox="0 0 16 16" 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="clip2340_51004">
|
||||
<rect id="message-circle" width="16.000000" height="16.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="message-circle" width="16.000000" height="16.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip2340_51004)">
|
||||
<path id="Vector" d="M13.4004 10.2002C12.9297 11.1416 12.207 11.9326 11.3125 12.4863C10.416 13.04 9.38477 13.333 8.33398 13.333C7.45312 13.3359 6.58594 13.1299 5.80078 12.7334L2 14L3.26758 10.2002C2.86914 9.41504 2.66406 8.54688 2.66602 7.66699C2.66797 6.61426 2.96094 5.58301 3.51367 4.68848C4.06641 3.79395 4.85938 3.07031 5.80078 2.59961C6.58594 2.20312 7.45312 1.99805 8.33398 2L8.66602 2C10.0566 2.07715 11.3691 2.66309 12.3535 3.64746C13.3359 4.63086 13.9238 5.94336 14 7.33301L14 7.66699C14.002 8.54688 13.7969 9.41504 13.4004 10.2002Z" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,75 @@
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import "./NewsDetails.scss";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { INews } from "@/shared/types/news-type";
|
||||
import Image from "next/image";
|
||||
import message from "./icons/message.svg";
|
||||
import calendar from "./icons/calendar.svg";
|
||||
import ReviewSection from "@/widgets/ReviewSection/ReviewSection";
|
||||
|
||||
const NewsDetails = async ({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string };
|
||||
}) => {
|
||||
const getNewsById = async () => {
|
||||
const response = await apiInstance.get<INews>(
|
||||
`/news/${params.id}/`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const data = await getNewsById();
|
||||
|
||||
const months: Record<string, string> = {
|
||||
"01": "Январь",
|
||||
"02": "Февраль",
|
||||
"03": "Март",
|
||||
"04": "Апрель",
|
||||
"05": "Май",
|
||||
"06": "Июнь",
|
||||
"07": "Июль",
|
||||
"08": "Август",
|
||||
"09": "Сентябрь",
|
||||
"10": "Октябрь",
|
||||
"11": "Ноябрь",
|
||||
"12": "Декабрь",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="news-details page-padding">
|
||||
<Typography element="h2">{data.title}</Typography>
|
||||
|
||||
<div className="news-details__img">
|
||||
<img id="news-img" src={data.image} alt="News Image" />
|
||||
<div className="news-details__date-and-reviews">
|
||||
<div className="news-details__date">
|
||||
<Image src={calendar} alt="Calendar Icon" />
|
||||
<p>
|
||||
{months[data.created_at.slice(5, 7)]}{" "}
|
||||
{data.created_at.slice(5, 7).slice(0, 1) === "0"
|
||||
? data.created_at.slice(6, 7)
|
||||
: data.created_at.slice(5, 7)}
|
||||
, {data.created_at.slice(0, 4)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="news-details__reviews">
|
||||
<Image src={message} alt="Message Icon" />
|
||||
<p>Комментарии: {data.count_reviews}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="news-details__text">
|
||||
<h3>{data.title}</h3>
|
||||
<p>{data.description}</p>
|
||||
</div>
|
||||
|
||||
<ReviewSection endpoint="news" id={data.id} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsDetails;
|
||||
@@ -0,0 +1,30 @@
|
||||
import "./News.scss";
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { INewsList } from "@/shared/types/news-type";
|
||||
import NewsList from "@/widgets/NewsList/NewsList";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Новости",
|
||||
description:
|
||||
"Страница новостей Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const News = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
["страница-новостей"]: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div className="news page-padding">
|
||||
<Typography element="h2">Новости</Typography>
|
||||
|
||||
<NewsList searchParams={searchParams} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default News;
|
||||
@@ -0,0 +1,35 @@
|
||||
import Header from "@/widgets/Header/Header";
|
||||
import StatisticsSection from "@/widgets/StatisticsSection/StatisticsSection";
|
||||
import RatingSection from "@/widgets/RatingSection/RatingSection";
|
||||
import NewsSection from "@/widgets/NewsSection/NewsSection";
|
||||
import MapSection from "@/widgets/MapSection/MapSection";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Главная",
|
||||
description:
|
||||
"Главная страница Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const Home = async ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
["тип-дороги"]: string;
|
||||
["поиск-на-карте"]: string;
|
||||
["поиск-рейтинг"]: string;
|
||||
["страница-рейтинга"]: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div className="home">
|
||||
<Header />
|
||||
<StatisticsSection />
|
||||
<MapSection searchParams={searchParams} />
|
||||
<RatingSection searchParams={searchParams} />
|
||||
<NewsSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { AxiosError } from "axios";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const AuthGuard = ({ children }: { children: React.ReactNode }) => {
|
||||
const session = useSession();
|
||||
const verifyToken = async () => {
|
||||
try {
|
||||
const data = {
|
||||
token: session.data?.access_token,
|
||||
};
|
||||
await apiInstance.post("/token/verify/", data);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
if (error.response?.data.code === "token_not_valid") {
|
||||
signOut({
|
||||
callbackUrl: "/",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (session.status === "loading") return;
|
||||
|
||||
verifyToken();
|
||||
}, [session.status]);
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default AuthGuard;
|
||||
@@ -0,0 +1,8 @@
|
||||
.profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import "./Profile.scss";
|
||||
import ProfileNav from "@/widgets/ProfileNav/ProfileNav";
|
||||
import AuthGuard from "./AuthGuard";
|
||||
import { AxiosError } from "axios";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authConfig } from "@/shared/config/authConfig";
|
||||
import { IProfile } from "@/shared/types/profile-type";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Профиль",
|
||||
description:
|
||||
"Страница профиля Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const Profile = async ({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) => {
|
||||
const session = await getServerSession(authConfig);
|
||||
|
||||
const getProfile = async () => {
|
||||
const Authorization = `Bearer ${session?.access_token}`;
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
};
|
||||
try {
|
||||
const response = await apiInstance.get<{
|
||||
report_count: number;
|
||||
}>("/users/profile/", config);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) console.log(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const data = await getProfile();
|
||||
|
||||
return (
|
||||
<div className="profile page-padding">
|
||||
<Typography element="h2">Личный кабинет</Typography>
|
||||
<ProfileNav report_count={data?.report_count as number} />
|
||||
|
||||
<AuthGuard>{children}</AuthGuard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
@@ -0,0 +1,16 @@
|
||||
import ProfileTable from "@/widgets/ProfileTable/ProfileTable";
|
||||
import React from "react";
|
||||
|
||||
const MyReports = async ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { ["страница-обращений"]: string };
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<ProfileTable searchParams={searchParams} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyReports;
|
||||
@@ -0,0 +1,48 @@
|
||||
"use server";
|
||||
|
||||
import ProfileAvatar from "@/features/ProfileAvatar/ProfileAvatar";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { authConfig } from "@/shared/config/authConfig";
|
||||
import { IProfile } from "@/shared/types/profile-type";
|
||||
import ProfileForm from "@/widgets/ProfileForm/ProfileForm";
|
||||
import { AxiosError } from "axios";
|
||||
import { getServerSession } from "next-auth";
|
||||
import React from "react";
|
||||
|
||||
const Personal = async () => {
|
||||
const session = await getServerSession(authConfig);
|
||||
const getProfile = async () => {
|
||||
const Authorization = `Bearer ${session?.access_token}`;
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
};
|
||||
try {
|
||||
const response = await apiInstance.get<IProfile>(
|
||||
"/users/profile/",
|
||||
config
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) console.log(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const data = await getProfile();
|
||||
|
||||
return (
|
||||
<div className="personal">
|
||||
<ProfileAvatar img={data?.image as string} />
|
||||
<ProfileForm
|
||||
id={data?.id as number}
|
||||
first_name={data?.first_name as string}
|
||||
last_name={data?.last_name as string}
|
||||
email={data?.email as string}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Personal;
|
||||
@@ -0,0 +1,180 @@
|
||||
.report-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 75px;
|
||||
&__container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-information {
|
||||
max-width: 646px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 4px;
|
||||
text-align: start;
|
||||
font-size: 42px;
|
||||
font-weight: 500;
|
||||
line-height: 50px;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
&__date-and-like {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
&__date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgba(62, 50, 50, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-bottom: 4px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
color: rgb(62, 50, 50);
|
||||
}
|
||||
|
||||
&__author {
|
||||
margin-bottom: 40px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
color: rgb(102, 102, 102);
|
||||
|
||||
span {
|
||||
color: rgb(72, 159, 225);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&__show-map {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 34px;
|
||||
text-decoration: underline;
|
||||
color: rgb(102, 102, 102);
|
||||
}
|
||||
}
|
||||
|
||||
.report-images {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
|
||||
&__exist {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&__default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(209, 217, 226);
|
||||
}
|
||||
|
||||
&__item1 {
|
||||
grid-column: 1 / 5;
|
||||
height: 441px;
|
||||
}
|
||||
|
||||
&__item2,
|
||||
&__item3,
|
||||
&__item4,
|
||||
&__item5 {
|
||||
height: 102px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.report-details {
|
||||
&__container {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-information {
|
||||
max-width: 100%;
|
||||
|
||||
h2 {
|
||||
font-size: 36px;
|
||||
line-height: 43px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.report-details {
|
||||
&__container {
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-information {
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: 16px;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
&__author {
|
||||
margin-bottom: 0px;
|
||||
font-size: 16px;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
&__show-map {
|
||||
font-size: 16px;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-images {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
&__item1 {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
|
||||
&__item1,
|
||||
&__item2,
|
||||
&__item3,
|
||||
&__item4,
|
||||
&__item5 {
|
||||
height: 162px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg width="16.000000" height="16.000000" viewBox="0 0 16 16" 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="clip2340_50999">
|
||||
<rect id="calendar" width="16.000000" height="16.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="calendar" width="16.000000" height="16.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip2340_50999)">
|
||||
<path id="Vector" d="M12.666 2.66699C13.4023 2.66699 14 3.26367 14 4L14 13.334C14 14.0703 13.4023 14.667 12.666 14.667L3.33398 14.667C2.59766 14.667 2 14.0703 2 13.334L2 4C2 3.26367 2.59766 2.66699 3.33398 2.66699L12.666 2.66699Z" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M10.666 1.33301L10.666 4" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path id="Vector" d="M5.33398 1.33301L5.33398 4" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path id="Vector" d="M2 6.66699L14 6.66699" stroke="#6E6565" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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 |
@@ -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 |
@@ -0,0 +1,138 @@
|
||||
import "./ReportDetails.scss";
|
||||
import Image from "next/image";
|
||||
import RoadType from "@/entities/RoadType/RoadType";
|
||||
import ReportLike from "@/features/ReportLike/ReportLike";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { IReport } from "@/shared/types/report-type";
|
||||
import {
|
||||
ROAD_TYPES,
|
||||
ROAD_TYPES_COLORS,
|
||||
} from "@/shared/variables/road-types";
|
||||
import calendar from "./icons/calendar.svg";
|
||||
import map_pin from "./icons/map-pin.svg";
|
||||
import def_image from "./icons/def_image.svg";
|
||||
import ReviewSection from "@/widgets/ReviewSection/ReviewSection";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Обращение",
|
||||
description:
|
||||
"Страница обращения Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const ReportDetails = async ({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string };
|
||||
}) => {
|
||||
const getReportDetails = async () => {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_BASE_API}/report/${params.id}/`,
|
||||
{ cache: "no-store" }
|
||||
);
|
||||
|
||||
return res.json();
|
||||
};
|
||||
const report: IReport = await getReportDetails();
|
||||
|
||||
const months: Record<string, string> = {
|
||||
"01": "Январь",
|
||||
"02": "Февраль",
|
||||
"03": "Март",
|
||||
"04": "Апрель",
|
||||
"05": "Май",
|
||||
"06": "Июнь",
|
||||
"07": "Июль",
|
||||
"08": "Август",
|
||||
"09": "Сентябрь",
|
||||
"10": "Октябрь",
|
||||
"11": "Ноябрь",
|
||||
"12": "Декабрь",
|
||||
};
|
||||
|
||||
const showImages = () => {
|
||||
const images = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (report.image[i]) {
|
||||
const image = (
|
||||
<img
|
||||
className={`report-images__exist report-images__item${
|
||||
i + 1
|
||||
}`}
|
||||
key={i}
|
||||
src={report.image[i].image}
|
||||
alt="Report Image"
|
||||
/>
|
||||
);
|
||||
images.push(image);
|
||||
} else {
|
||||
const defImage = (
|
||||
<div
|
||||
className={`report-images__default report-images__item${
|
||||
i + 1
|
||||
}`}
|
||||
key={i}
|
||||
>
|
||||
<Image src={def_image} alt="Default Image" />
|
||||
</div>
|
||||
);
|
||||
images.push(defImage);
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="report-details page-padding">
|
||||
<div className="report-details__container">
|
||||
<div className="report-information">
|
||||
<RoadType color={ROAD_TYPES_COLORS[report.category]}>
|
||||
{ROAD_TYPES[report.category]}
|
||||
</RoadType>
|
||||
<h2>{report.location[0].address}</h2>
|
||||
<div className="report-information__date-and-like">
|
||||
<div className="report-information__date">
|
||||
<Image src={calendar} alt="Calendar Icon" />
|
||||
<p>
|
||||
{months[report.created_at.slice(5, 7)]}{" "}
|
||||
{report.created_at.slice(5, 7).slice(0, 1) === "0"
|
||||
? report.created_at.slice(6, 7)
|
||||
: report.created_at.slice(5, 7)}
|
||||
, {report.created_at.slice(0, 4)}
|
||||
</p>
|
||||
</div>
|
||||
<ReportLike
|
||||
count={report.total_likes}
|
||||
report_id={report.id}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="report-information__description">
|
||||
{report.description}
|
||||
</p>
|
||||
|
||||
<p className="report-information__author">
|
||||
Автор обращения:{" "}
|
||||
<span>
|
||||
{report.author.first_name}{" "}
|
||||
{report.author.last_name.slice(0, 1)}.
|
||||
</span>
|
||||
</p>
|
||||
<button className="report-information__show-map">
|
||||
<Image src={map_pin} alt="Map Pin Icon" />
|
||||
Показать на карте
|
||||
</button>
|
||||
</div>
|
||||
<div className="report-images">
|
||||
{showImages().map((image) => image)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ReviewSection endpoint="report" id={+params.id} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportDetails;
|
||||
@@ -0,0 +1,12 @@
|
||||
import "@/shared/ui/auth-classes.scss";
|
||||
import ForgotPasswordForm from "@/widgets/ForgotPasswordForm/ForgotPasswordForm";
|
||||
|
||||
const ForgotPassword = () => {
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<ForgotPasswordForm />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPassword;
|
||||
@@ -0,0 +1,14 @@
|
||||
<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="clip51_17162">
|
||||
<rect id="log-in-04" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="log-in-04" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip51_17162)">
|
||||
<path id="Icon" d="M12 8L16 12L12 16M3 12L16 12M3.33789 7C5.06641 4.01074 8.29883 2 12 2C17.5225 2 22 6.47754 22 12C22 17.5225 17.5225 22 12 22C8.29883 22 5.06641 19.9893 3.33789 17" stroke="#000000" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 768 B |
@@ -0,0 +1,35 @@
|
||||
import "@/shared/ui/auth-classes.scss";
|
||||
import Image from "next/image";
|
||||
import sign_in_icon from "./icons/sign-in_icon.svg";
|
||||
import SignInForm from "@/widgets/SignInForm/SignInForm";
|
||||
import Link from "next/link";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Вход",
|
||||
description: "Страница входа Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const SignIn = () => {
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="auth-wrapper">
|
||||
<div className="auth-icon">
|
||||
<Image src={sign_in_icon} alt="Sign In Icon" />
|
||||
</div>
|
||||
<div className="auth-header">
|
||||
<h2>Войдите в аккаунт</h2>
|
||||
<p>Пожалуйста, введите свои данные</p>
|
||||
</div>
|
||||
|
||||
<SignInForm />
|
||||
<p className="auth-redirect">
|
||||
Еще нет аккаунта?{" "}
|
||||
<Link href="/sign-up">Зарегистрируйтесь</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignIn;
|
||||
@@ -0,0 +1,14 @@
|
||||
<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="clip1668_53329">
|
||||
<rect id="key" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="key" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip1668_53329)">
|
||||
<path id="Vector" d="M21 2L19 4L15.5 7.5L11.3906 11.6104L11.3896 11.6113C10.3516 10.6094 8.96289 10.0547 7.52051 10.0674C6.07812 10.0801 4.69922 10.6582 3.67969 11.6777C2.65918 12.6973 2.08105 14.0771 2.06836 15.5195C2.05566 16.9609 2.61035 18.3506 3.6123 19.3877C4.12207 19.9043 4.72852 20.3145 5.39746 20.5957C6.06543 20.877 6.7832 21.0225 7.50879 21.0254C8.23438 21.0273 8.95312 20.8867 9.62402 20.6104C10.2939 20.333 10.9033 19.9268 11.416 19.4141C11.9297 18.9014 12.3359 18.292 12.6123 17.6211C12.8887 16.9502 13.0293 16.2324 13.0273 15.5068C13.0244 14.7812 12.8789 14.0635 12.5977 13.3945C12.3174 12.7256 11.9062 12.1191 11.3906 11.6104M15.5 7.5L18.5 10.5L22 7L19 4" stroke="#489FE1" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,28 @@
|
||||
import "@/shared/ui/auth-classes.scss";
|
||||
import Image from "next/image";
|
||||
import key from "./icons/key.svg";
|
||||
import ResetCodeForm from "@/widgets/ResetCodeForm/ResetCodeForm";
|
||||
|
||||
const ResetCode = () => {
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="auth-wrapper">
|
||||
<div className="auth-icon2">
|
||||
<Image src={key} alt="Key Icon" />
|
||||
</div>
|
||||
|
||||
<div className="auth-header">
|
||||
<h2>Введите новый пароль</h2>
|
||||
<p>
|
||||
Пароль должен содерждать минимум 8 символов, 1 заглавная
|
||||
буква и цифра
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ResetCodeForm />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetCode;
|
||||
@@ -0,0 +1,14 @@
|
||||
<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="clip55_8430">
|
||||
<rect id="mail" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="mail" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip55_8430)">
|
||||
<path id="Icon" d="M20 4C21.0996 4 22 4.90039 22 6L22 18C22 19.0996 21.0996 20 20 20L4 20C2.90039 20 2 19.0996 2 18L2 6C2 4.90039 2.90039 4 4 4L20 4ZM22 6L12 13L2 6" stroke="#489FE1" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 739 B |
@@ -0,0 +1,29 @@
|
||||
import "@/shared/ui/auth-classes.scss";
|
||||
import Image from "next/image";
|
||||
import mail from "./icons/mail.svg";
|
||||
import ConfirmEmailForm from "@/widgets/ConfirmEmailForm/ConfirmEmailForm";
|
||||
|
||||
const ConfirmEmail = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
email: string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="auth-wrapper">
|
||||
<div className="auth-icon2">
|
||||
<Image src={mail} alt="Mail icon" width={56} height={56} />
|
||||
</div>
|
||||
<div className="auth-header">
|
||||
<h2>Проверьте свою почту</h2>
|
||||
<p>Мы отправили код на почту {searchParams.email}</p>
|
||||
</div>
|
||||
<ConfirmEmailForm email={searchParams.email} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmEmail;
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="20.317383" height="21.000977" viewBox="0 0 20.3174 21.001" 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="M12.2998 5.22266L18.2529 5.22266C18.7256 5.22266 18.9609 5.22266 19.0996 5.32129C19.2197 5.4082 19.2979 5.54102 19.3154 5.68848C19.335 5.85742 19.2207 6.06445 18.9912 6.47656L17.5703 9.03418C17.4873 9.18359 17.4453 9.25879 17.4297 9.33789C17.415 9.4082 17.415 9.48047 17.4297 9.55078C17.4453 9.62988 17.4873 9.70508 17.5703 9.85449L18.9912 12.4121C19.2207 12.8252 19.335 13.0312 19.3154 13.2002C19.2979 13.3477 19.2197 13.4805 19.0996 13.5674C18.9609 13.667 18.7256 13.667 18.2529 13.667L10.8213 13.667C10.2305 13.667 9.93457 13.667 9.70898 13.5518C9.51074 13.4502 9.34863 13.2891 9.24805 13.0898C9.13281 12.8643 9.13281 12.5693 9.13281 11.9775L9.13281 9.44434M10.6104 1C11.2021 1 11.4971 1 11.7227 1.11523C11.9219 1.21582 12.083 1.37793 12.1846 1.57617C12.2998 1.80176 12.2998 2.09766 12.2998 2.68848L12.2998 7.75586C12.2998 8.34668 12.2998 8.64258 12.1846 8.86816C12.083 9.06641 11.9219 9.22852 11.7227 9.3291C11.4971 9.44434 11.2021 9.44434 10.6104 9.44434L2.7998 9.44434L1.21289 3.09863C1.03418 2.38281 0.944336 2.02539 1.03125 1.74414C1.1084 1.49707 1.27246 1.28711 1.49316 1.15234C1.74512 1 2.11426 1 2.85156 1L10.6104 1ZM5.43848 20L1.21582 3.11133" stroke="#000000" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,37 @@
|
||||
import "@/shared/ui/auth-classes.scss";
|
||||
import Image from "next/image";
|
||||
import flag from "./icons/flag.svg";
|
||||
import Link from "next/link";
|
||||
import SignUpForm from "@/widgets/SignUpForm/SignUpForm";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Регистрация",
|
||||
description:
|
||||
"Страница регистрации Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const SignUp = () => {
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="auth-wrapper">
|
||||
<div className="auth-icon">
|
||||
<Image src={flag} alt="Flag Icon" />
|
||||
</div>
|
||||
<div className="auth-header">
|
||||
<h2>Регистрация</h2>
|
||||
<p>Пожалуйста, введите свои данные</p>
|
||||
</div>
|
||||
|
||||
<SignUpForm />
|
||||
|
||||
<p className="auth-redirect">
|
||||
Уже есть аккаунт?{" "}
|
||||
<Link href="/sign-in">Войти в аккаунт</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUp;
|
||||
@@ -0,0 +1,21 @@
|
||||
.statistics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
h2 {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.statistics {
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.statistics {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import "./Statistics.scss";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { IStatistics } from "@/shared/types/statistics-type";
|
||||
import { AxiosError } from "axios";
|
||||
import StatisticsTable from "@/widgets/StatisticsTable/StatisticsTable";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Статистика",
|
||||
description:
|
||||
"Страница статистики Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const Statistics = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { ["поиск-населенного-пункта"]: string };
|
||||
}) => {
|
||||
return (
|
||||
<div className="statistics page-padding">
|
||||
<Typography element="h2">Статистика</Typography>
|
||||
<StatisticsTable searchParams={searchParams} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Statistics;
|
||||
@@ -0,0 +1,21 @@
|
||||
.volunteers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
|
||||
h2 {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.volunteers {
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.volunteers {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||
import "./Volunteers.scss";
|
||||
import VolunteersTable from "@/widgets/VolunteersTable/VolunteersTable";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "KG ROAD | Волонтеры",
|
||||
description:
|
||||
"Страница волонтеров Kyrgyzstan Transperency International",
|
||||
};
|
||||
|
||||
const Volunteers = () => {
|
||||
return (
|
||||
<div className="volunteers page-padding">
|
||||
<Typography element="h2">Волонтеры</Typography>
|
||||
<VolunteersTable />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Volunteers;
|
||||
@@ -0,0 +1,55 @@
|
||||
@import "@/shared/ui/variables.scss";
|
||||
|
||||
.auth-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
&__field,
|
||||
&__field-with-error {
|
||||
padding: 10px 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgb(208, 213, 221);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||
background: rgb(255, 255, 255);
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: $gray-900;
|
||||
|
||||
::placeholder {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: $gray-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__field-with-error {
|
||||
border: 1px solid rgb(240, 68, 56);
|
||||
}
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: $red-500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import "./AuthInput.scss";
|
||||
import eye_off from "./icons/eye-off.svg";
|
||||
import eye_on from "./icons/eye-on.svg";
|
||||
import alert from "./icons/alert-circle.svg";
|
||||
import { useState } from "react";
|
||||
|
||||
interface IAuthInputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
isPassword?: boolean;
|
||||
label: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const AuthInput: React.FC<IAuthInputProps> = ({
|
||||
isPassword,
|
||||
label,
|
||||
error,
|
||||
placeholder,
|
||||
name,
|
||||
type,
|
||||
}: IAuthInputProps) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
return (
|
||||
<div className="auth-input">
|
||||
<label>{label}</label>
|
||||
<div
|
||||
className={`auth-input__field${error ? "-with-error" : ""}`}
|
||||
>
|
||||
<input
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
type={!isPassword ? type : isOpen ? type : "password"}
|
||||
/>
|
||||
{isPassword && (
|
||||
<button
|
||||
onClick={() => setIsOpen((prev) => !prev)}
|
||||
type="button"
|
||||
>
|
||||
<Image src={isOpen ? eye_on : eye_off} alt="Eye Icon" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{error ? (
|
||||
<p>
|
||||
{error} <Image src={alert} alt="Alert Icon" />
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthInput;
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="14.666992" height="14.666992" viewBox="0 0 14.667 14.667" 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="M7.33398 14C3.65137 14 0.666992 11.0156 0.666992 7.33398C0.666992 3.65137 3.65137 0.666992 7.33398 0.666992C11.0156 0.666992 14 3.65137 14 7.33398C14 11.0156 11.0156 14 7.33398 14ZM7.33398 4.66699L7.33398 7.33398M7.33398 10L7.33984 10" stroke="#F04438" stroke-opacity="1.000000" stroke-width="1.333333" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 566 B |
@@ -0,0 +1,15 @@
|
||||
<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="clip1668_50526">
|
||||
<rect id="eye-off" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="eye-off" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip1668_50526)">
|
||||
<path id="Vector" d="M17.9404 17.9404C16.2305 19.2432 14.1494 19.9648 12 20C5 20 1 12 1 12C2.24414 9.68164 3.96875 7.65625 6.05957 6.05957M9.90039 4.24023C10.5879 4.0791 11.293 3.99805 12 4C19 4 23 12 23 12C22.3926 13.1357 21.6689 14.2051 20.8398 15.1904M14.1201 14.1201C13.8457 14.415 13.5137 14.6514 13.1465 14.8154C12.7783 14.9795 12.3809 15.0674 11.9785 15.0742C11.5752 15.0811 11.1748 15.0078 10.8018 14.8564C10.4277 14.7061 10.0889 14.4814 9.80371 14.1963C9.51855 13.9111 9.29395 13.5723 9.14355 13.1982C8.99219 12.8252 8.91895 12.4248 8.92578 12.0215C8.93262 11.6191 9.02051 11.2217 9.18457 10.8535C9.34863 10.4863 9.58496 10.1543 9.87988 9.87988" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M1 1L23 23" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,15 @@
|
||||
<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="clip1668_50161">
|
||||
<rect id="eye" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="eye" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip1668_50161)">
|
||||
<path id="Vector" d="M12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12C1 12 5 4 12 4Z" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M12 15C10.3428 15 9 13.6572 9 12C9 10.3428 10.3428 9 12 9C13.6572 9 15 10.3428 15 12C15 13.6572 13.6572 15 12 15Z" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
@@ -0,0 +1,18 @@
|
||||
@import "@/shared/ui/variables.scss";
|
||||
|
||||
.google-btn {
|
||||
padding: 10px 16px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
border: 1px solid rgb(208, 213, 221);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||
background: rgb(255, 255, 255);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
color: $gray-700;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import Image from "next/image";
|
||||
import "./GoogleButton.scss";
|
||||
import google from "./icons/google.svg";
|
||||
|
||||
const GoogleButton = () => {
|
||||
return (
|
||||
<button className="google-btn">
|
||||
<Image src={google} alt="Google Icon" />
|
||||
Войти через Google
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoogleButton;
|
||||
@@ -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="clip51_26188">
|
||||
<rect id="Social icon" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect id="Social icon" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||
<g clip-path="url(#clip51_26188)">
|
||||
<path id="Vector" d="M23.7666 12.2764C23.7666 11.4609 23.7002 10.6406 23.5586 9.83789L12.2402 9.83789L12.2402 14.459L18.7217 14.459C18.4531 15.9492 17.5889 17.2676 16.3232 18.1055L16.3232 21.1035L20.1904 21.1035C22.4609 19.0137 23.7666 15.9268 23.7666 12.2764Z" fill="#4285F4" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||
<path id="Vector" d="M12.2402 24.001C15.4766 24.001 18.2061 22.9385 20.1943 21.1045L16.3271 18.1064C15.252 18.8379 13.8623 19.2529 12.2441 19.2529C9.11426 19.2529 6.45898 17.1406 5.50684 14.3008L1.5166 14.3008L1.5166 17.3916C3.55371 21.4443 7.70312 24.001 12.2402 24.001Z" fill="#34A853" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||
<path id="Vector" d="M5.50293 14.3008C5 12.8105 5 11.1963 5.50293 9.70605L5.50293 6.61523L1.5166 6.61523C-0.185547 10.0059 -0.185547 14.001 1.5166 17.3916L5.50293 14.3008Z" fill="#FBBC04" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||
<path id="Vector" d="M12.2402 4.75C13.9512 4.72363 15.6045 5.36719 16.8438 6.54883L20.2695 3.12305C18.1006 1.08594 15.2207 -0.0341797 12.2402 0.000976562C7.70312 0.000976562 3.55371 2.55859 1.5166 6.61523L5.50293 9.70605C6.4502 6.86133 9.10938 4.75 12.2402 4.75Z" fill="#EA4335" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,15 @@
|
||||
.logout-btn {
|
||||
padding: 10px;
|
||||
border: 1px solid rgb(230, 68, 82);
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 19px;
|
||||
color: rgb(230, 68, 82);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.logout-btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { signOut } from "next-auth/react";
|
||||
import "./LogoutButton.scss";
|
||||
|
||||
interface ILogoutButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const LogoutButton: React.FC<ILogoutButtonProps> = ({
|
||||
className,
|
||||
}: ILogoutButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`logout-btn ${className}`}
|
||||
onClick={() =>
|
||||
signOut({
|
||||
callbackUrl: "/",
|
||||
})
|
||||
}
|
||||
>
|
||||
Выйти из аккаунта
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoutButton;
|
||||
@@ -0,0 +1,48 @@
|
||||
.pagination {
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgb(208, 213, 221);
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
&__prev,
|
||||
&__prev-disabled {
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
}
|
||||
|
||||
&__next,
|
||||
&__next-disabled {
|
||||
border-radius: 0px 8px 8px 0px;
|
||||
}
|
||||
|
||||
&__next-disabled,
|
||||
&__prev-disabled {
|
||||
cursor: auto;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&__page,
|
||||
&__page_active {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: rgb(52, 64, 84);
|
||||
}
|
||||
|
||||
&__page_active {
|
||||
background-color: rgb(249, 250, 251) !important;
|
||||
}
|
||||
|
||||
&__page:hover,
|
||||
&__prev:hover,
|
||||
&__next:hover {
|
||||
background-color: rgb(249, 250, 251);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import "./Pagination.scss";
|
||||
import arrow_right from "./icons/arrow-right.svg";
|
||||
import arrow_left from "./icons/arrow-left.svg";
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
|
||||
interface IPaginationProps {
|
||||
setActivePage: (page: number | ((prev: number) => number)) => void;
|
||||
activePage: number;
|
||||
prev: string | null;
|
||||
next: string | null;
|
||||
count: number;
|
||||
current_count: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
const Pagination: React.FC<IPaginationProps> = ({
|
||||
setActivePage,
|
||||
activePage,
|
||||
prev,
|
||||
next,
|
||||
count,
|
||||
current_count,
|
||||
limit,
|
||||
}: IPaginationProps) => {
|
||||
const pages_count = Math.ceil(count / limit);
|
||||
const showPages = () => {
|
||||
const btns = [];
|
||||
|
||||
for (let i = 1; i <= pages_count; i++) {
|
||||
btns.push(
|
||||
<button
|
||||
onClick={() => setActivePage(i)}
|
||||
key={i}
|
||||
className={
|
||||
activePage === i
|
||||
? "pagination__page_active"
|
||||
: "pagination__page"
|
||||
}
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (current_count < 8 && activePage === 1) {
|
||||
return [...btns.slice(0, 1)];
|
||||
}
|
||||
|
||||
if (pages_count > 5) {
|
||||
if (activePage > 2) {
|
||||
if (
|
||||
activePage + 2 === pages_count - 1 ||
|
||||
activePage + 1 === pages_count - 1 ||
|
||||
activePage === pages_count - 1 ||
|
||||
activePage === pages_count
|
||||
) {
|
||||
const newBtns = [
|
||||
...btns.slice(0, 1),
|
||||
<button key="..." disabled>
|
||||
...
|
||||
</button>,
|
||||
...btns.slice(
|
||||
activePage -
|
||||
(activePage + 2 === pages_count - 1 ? 2 : 1)
|
||||
),
|
||||
];
|
||||
|
||||
if (newBtns.length < 6) {
|
||||
return [
|
||||
...btns.slice(0, 1),
|
||||
<button key="..." disabled>
|
||||
...
|
||||
</button>,
|
||||
...btns.slice(-4),
|
||||
];
|
||||
}
|
||||
|
||||
return newBtns;
|
||||
} else {
|
||||
return [
|
||||
...btns.slice(0, 1),
|
||||
<button key="...1" disabled>
|
||||
...
|
||||
</button>,
|
||||
|
||||
...btns.slice(
|
||||
activePage - (activePage === 3 ? 1 : 2),
|
||||
activePage + 1
|
||||
),
|
||||
|
||||
<button key="..." disabled>
|
||||
...
|
||||
</button>,
|
||||
...btns.slice(-2),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
...btns.slice(0, 3),
|
||||
<button key="..." disabled>
|
||||
...
|
||||
</button>,
|
||||
...btns.slice(-2),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return btns;
|
||||
};
|
||||
|
||||
const prevPage = () => {
|
||||
if (activePage - 1 === 0) return;
|
||||
setActivePage((prev) => prev - 1);
|
||||
};
|
||||
|
||||
const nextPage = () => {
|
||||
if (activePage + 1 > pages_count) return;
|
||||
setActivePage((prev) => prev + 1);
|
||||
};
|
||||
return (
|
||||
<div className="pagination">
|
||||
<button
|
||||
onClick={prevPage}
|
||||
disabled={prev === null}
|
||||
className={`pagination__prev${
|
||||
prev === null ? "-disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<Image src={arrow_left} alt="" />
|
||||
</button>
|
||||
{showPages().map((btn) => btn)}
|
||||
<button
|
||||
onClick={nextPage}
|
||||
disabled={next === null}
|
||||
className={`pagination__next${
|
||||
next === null ? "-disabled" : ""
|
||||
}`}
|
||||
>
|
||||
<Image src={arrow_right} alt="" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="13.336426" height="13.343750" viewBox="0 0 13.3364 13.3438" 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="M12.5015 6.67188L0.834961 6.67188M6.66846 12.5059L0.834961 6.67188L6.66846 0.838867" stroke="#344054" stroke-opacity="1.000000" stroke-width="1.670000" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="13.336426" height="13.343750" viewBox="0 0 13.3364 13.3438" 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="M0.834961 6.67188L12.5015 6.67188M6.66846 0.838867L12.5015 6.67188L6.66846 12.5059" stroke="#344054" stroke-opacity="1.000000" stroke-width="1.670000" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
@@ -0,0 +1,37 @@
|
||||
.profile-avatar {
|
||||
margin-bottom: 25px;
|
||||
position: relative;
|
||||
width: 135px;
|
||||
height: 135px;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0px 0px 9px 0px rgba(0, 0, 0, 0.1);
|
||||
background: rgb(255, 255, 255);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import "./ProfileAvatar.scss";
|
||||
import pen from "./icons/pen.svg";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface IProfileAvatarProps {
|
||||
img: string;
|
||||
}
|
||||
|
||||
const ProfileAvatar: React.FC<IProfileAvatarProps> = ({
|
||||
img,
|
||||
}: IProfileAvatarProps) => {
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
const changeImage: React.ChangeEventHandler<
|
||||
HTMLInputElement
|
||||
> = async (e) => {
|
||||
const Authorization = `Bearer ${session.data?.access_token}`;
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
};
|
||||
const formData = new FormData();
|
||||
if (e.target.files) {
|
||||
const image = Array.from(e.target.files);
|
||||
formData.append("image", image[0]);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await apiInstance.patch(
|
||||
"/users/update_image/",
|
||||
formData,
|
||||
config
|
||||
);
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="profile-avatar">
|
||||
<img
|
||||
className="profile-avatar__image"
|
||||
src={img}
|
||||
alt="User Image"
|
||||
/>
|
||||
<label htmlFor="profile-image">
|
||||
<Image src={pen} alt="Pen Icon" />
|
||||
</label>
|
||||
<input onChange={changeImage} id="profile-image" type="file" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileAvatar;
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg width="21.908203" height="22.000000" viewBox="0 0 21.9082 22" 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="Vector (Stroke)" d="M14.7539 1.22754C16.3906 -0.40918 19.041 -0.40918 20.6777 1.22754C22.3145 2.86426 22.3145 5.51562 20.6777 7.15234L8.5918 19.2383C8.45508 19.375 8.28516 19.4707 8.09766 19.5166L4.51562 20.3867C4.47852 20.3984 4.44141 20.4072 4.4043 20.4141L2.23047 20.9424C1.875 21.0293 1.5 20.9238 1.24023 20.665C0.982422 20.4062 0.876953 20.0312 0.962891 19.6758L2.38867 13.8076C2.43555 13.6211 2.53125 13.4502 2.66797 13.3145L14.7539 1.22754ZM4.53711 18.2236L7.31641 17.5479L19.1953 5.66895C20.0117 4.85156 20.0117 3.52832 19.1953 2.71094C18.377 1.89355 17.0547 1.89355 16.2363 2.71094L4.35742 14.5889L3.68359 17.3652L4.53711 18.2236Z" fill="#B2B5BE" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||
<path id="Union" d="M14.5332 4.09473C14.9434 3.68652 15.6074 3.6875 16.0176 4.09766L19.123 7.21191C19.5312 7.62207 19.5312 8.28613 19.1211 8.69531C18.7109 9.10449 18.0469 9.10352 17.6367 8.69336L14.5312 5.57812C14.123 5.16797 14.123 4.50391 14.5332 4.09473ZM8.77734 19.9238C8.77734 19.3447 9.24609 18.875 9.82617 18.875L20.8594 18.875C21.4375 18.875 21.9082 19.3447 21.9082 19.9238C21.9082 20.5029 21.4375 20.9727 20.8594 20.9727L9.82617 20.9727C9.24609 20.9727 8.77734 20.5029 8.77734 19.9238Z" fill="#B2B5BE" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,13 @@
|
||||
.report-like {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
|
||||
span {
|
||||
align-self: center;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
color: rgb(74, 192, 63);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import "./ReportLike.scss";
|
||||
import Image from "next/image";
|
||||
import like from "./icons/like.svg";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface IReportLikeProps {
|
||||
count: number;
|
||||
report_id: number;
|
||||
}
|
||||
|
||||
const ReportLike: React.FC<IReportLikeProps> = ({
|
||||
count,
|
||||
report_id,
|
||||
}: IReportLikeProps) => {
|
||||
const session = useSession();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const Authorization = `Bearer ${session?.data?.access_token}`;
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
};
|
||||
|
||||
const checkLike = async () => {
|
||||
const res = await apiInstance.get<{ detail: string }>(
|
||||
`/report/${report_id}/like/check/`,
|
||||
config
|
||||
);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
const likeReport = async () => {
|
||||
const isLiked = await checkLike();
|
||||
if (isLiked.detail === "Like does not exist!") {
|
||||
await apiInstance.post(
|
||||
`/report/${report_id}/like/`,
|
||||
{},
|
||||
config
|
||||
);
|
||||
} else {
|
||||
await apiInstance.post(
|
||||
`/report/${report_id}/unlike/`,
|
||||
{},
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={likeReport} className="report-like">
|
||||
<Image src={like} alt="Like Icon" />
|
||||
<span>{count}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportLike;
|
||||
@@ -2,12 +2,22 @@
|
||||
margin-bottom: 32px;
|
||||
height: 52px;
|
||||
width: 645px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
height: 100%;
|
||||
width: 100px;
|
||||
border-radius: 0px 6px 6px 0px;
|
||||
background: rgb(72, 159, 225);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&__input {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgb(197, 198, 197);
|
||||
@@ -18,22 +28,14 @@
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
padding-left: 5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
height: 100%;
|
||||
width: 100px;
|
||||
border-radius: 0px 6px 6px 0px;
|
||||
background: rgb(72, 159, 225);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
@@ -41,9 +43,3 @@
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.search-form {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,14 @@ const SearchForm: React.FC<ISearchFormProps> = ({
|
||||
placeholder,
|
||||
value,
|
||||
onChange,
|
||||
style,
|
||||
}: ISearchFormProps) => {
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="search-form">
|
||||
<form
|
||||
style={style}
|
||||
className="search-form"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="search-form__input">
|
||||
<Image src={search} alt="Search Icon" />
|
||||
<input
|
||||
@@ -0,0 +1,9 @@
|
||||
export { default } from "next-auth/middleware";
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
"/profile/personal",
|
||||
"/profile/my-reports",
|
||||
"/create-report",
|
||||
],
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import "./Home.scss";
|
||||
import Header from "@/widgets/Header/Header";
|
||||
import StatisticsSection from "@/widgets/StatisticsSection/StatisticsSection";
|
||||
import MapSection from "@/widgets/MapSection/MapSection";
|
||||
import RatingSection from "@/widgets/RatingSection/RatingSection";
|
||||
import NewsSection from "@/widgets/NewsSection/NewsSection";
|
||||
|
||||
const Home = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: {
|
||||
"тип-дороги": string;
|
||||
"поиск-на-карте": string;
|
||||
"поиск-рейтинг": string;
|
||||
};
|
||||
}) => {
|
||||
return (
|
||||
<div className="home">
|
||||
<Header />
|
||||
<StatisticsSection />
|
||||
<MapSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["поиск-на-карте"]}
|
||||
queryRating={searchParams["поиск-рейтинг"]}
|
||||
/>
|
||||
<RatingSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["поиск-на-карте"]}
|
||||
queryRating={searchParams["поиск-рейтинг"]}
|
||||
/>
|
||||
<NewsSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from "axios";
|
||||
|
||||
const API_URL = process.env["NEXT_PUBLIC_BASE_API"];
|
||||
|
||||
export const apiInstance = axios.create({
|
||||
baseURL: API_URL,
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import axios from "axios";
|
||||
import { AuthOptions } 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 authConfig: AuthOptions = {
|
||||
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.toString()[0] === "4") {
|
||||
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;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -5,4 +5,5 @@
|
||||
url("TildaSans-Black.woff2") format("woff2"),
|
||||
url("TildaSans-Black.woff") format("woff");
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
}
|
||||