fixed sort in profile-reports table and in statistics table, improved error messages, improved metatags by fetching metadata from server

This commit is contained in:
Alibek 2024-03-16 02:19:33 +06:00
parent 0221a79015
commit 0ba0f1df89
25 changed files with 413 additions and 171 deletions

6
lib/next-auth.d.ts vendored
View File

@ -4,13 +4,13 @@ declare module "next-auth" {
interface Session { interface Session {
refresh_token: string; refresh_token: string;
access_token: string; access_token: string;
expires_in?: string; expires_in: Date;
} }
interface User { interface User {
refresh_token: string; refresh_token: string;
access_token: string; access_token: string;
expires_in?: string; expires_in: Date;
} }
} }
@ -20,6 +20,6 @@ declare module "next-auth/jwt" {
interface JWT { interface JWT {
refresh_token: string; refresh_token: string;
access_token: string; access_token: string;
expires_in?: string; expires_in: Date;
} }
} }

View File

@ -4,27 +4,53 @@ import Image from "next/image";
import header from "./assets/header.svg"; import header from "./assets/header.svg";
import { Metadata } from "next"; import { Metadata } from "next";
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs"; import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
import { apiInstance } from "@/shared/config/apiConfig";
import { IMetatag } from "@/shared/types/metatag-type";
export const metadata: Metadata = { export async function generateMetadata(): Promise<Metadata> {
const data = await apiInstance
.get<IMetatag[]>("/metatags/")
.then((res) => res.data)
.catch((e) => console.log(e));
if (!data)
return {
title: "KG ROAD | О нас", title: "KG ROAD | О нас",
description: description:
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.", "Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
keywords: [
"Миссия Transparency International - Кыргызстан",
"Цели и приоритеты ТИ-Кыргызстан",
],
openGraph: { openGraph: {
images: [
{
url: header.src,
},
],
title: "KG ROAD | О нас", title: "KG ROAD | О нас",
description: description:
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.", "Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
}, },
}; };
const metadata = data.filter((tag) => tag.page === "about-us")[0];
if (!metadata) {
return {
title: "KG ROAD | О нас",
description:
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
openGraph: {
title: "KG ROAD | О нас",
description:
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
},
};
}
return {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
keywords: metadata.keywords.split(","),
openGraph: {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
type: "website",
},
};
}
const AboutUs = () => { const AboutUs = () => {
return ( return (
<div className="about-us page-padding"> <div className="about-us page-padding">

View File

@ -26,7 +26,6 @@ export async function generateMetadata({
type: "article", type: "article",
publishedTime: response.data.created_at, publishedTime: response.data.created_at,
}, },
keywords: ["Новости КР", "Кыргызстан"],
}; };
} }

View File

@ -3,12 +3,41 @@ import "./News.scss";
import Typography from "@/shared/ui/components/Typography/Typography"; import Typography from "@/shared/ui/components/Typography/Typography";
import NewsList from "@/widgets/NewsList/NewsList"; import NewsList from "@/widgets/NewsList/NewsList";
import { Metadata } from "next"; import { Metadata } from "next";
import { apiInstance } from "@/shared/config/apiConfig";
import { IMetatag } from "@/shared/types/metatag-type";
export const metadata: Metadata = { export async function generateMetadata(): Promise<Metadata> {
const data = await apiInstance
.get<IMetatag[]>("/metatags/")
.then((res) => res.data)
.catch((e) => console.log(e));
if (!data)
return {
title: "KG ROAD | Новости", title: "KG ROAD | Новости",
description: "Страница новостей KG ROAD", description: "Страница новостей KG ROAD",
}; };
const metadata = data.filter((tag) => tag.page === "news")[0];
if (!metadata) {
return {
title: "KG ROAD | Новости",
description: "Страница новостей KG ROAD",
};
}
return {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
keywords: metadata.keywords.split(","),
openGraph: {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
type: "website",
},
};
}
const News = ({ const News = ({
searchParams, searchParams,
}: { }: {

View File

@ -4,22 +4,20 @@ import RatingSection from "@/widgets/home/RatingSection/RatingSection";
import NewsSection from "@/widgets/home/NewsSection/NewsSection"; import NewsSection from "@/widgets/home/NewsSection/NewsSection";
import MapSection from "@/widgets/home/MapSection/MapSection"; import MapSection from "@/widgets/home/MapSection/MapSection";
import { Metadata } from "next"; import { Metadata } from "next";
import { apiInstance } from "@/shared/config/apiConfig";
import { IMetatag } from "@/shared/types/metatag-type";
export const metadata: Metadata = { export async function generateMetadata(): Promise<Metadata> {
const data = await apiInstance
.get<IMetatag[]>("/metatags/")
.then((res) => res.data)
.catch((e) => console.log(e));
if (!data)
return {
title: "KG ROAD | Главная", title: "KG ROAD | Главная",
description: description:
"Главная страница KG ROAD | Сделаем дороги безопасными!", "Главная страница KG ROAD | Сделаем дороги безопасными!",
keywords: [
"Новости",
"Разбитые дороги",
"Очаги аварийности",
"Локальные дефекты",
"Дороги В планах ремонта",
"Отремонтированные дороги",
"Исправленные локальные дефекты",
"Карта дорог",
"Рейтинг",
],
openGraph: { openGraph: {
title: "KG ROAD | Главная", title: "KG ROAD | Главная",
description: description:
@ -28,6 +26,34 @@ export const metadata: Metadata = {
}, },
}; };
const metadata = data.filter((tag) => tag.page === "home")[0];
if (!metadata) {
return {
title: "KG ROAD | Главная",
description:
"Главная страница KG ROAD | Сделаем дороги безопасными!",
openGraph: {
title: "KG ROAD | Главная",
description:
"Главная страница KG ROAD | Сделаем дороги безопасными!",
type: "website",
},
};
}
return {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
keywords: metadata.keywords.split(","),
openGraph: {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
type: "website",
},
};
}
const Home = async ({ const Home = async ({
searchParams, searchParams,
}: { }: {

View File

@ -11,6 +11,7 @@ import React from "react";
const Personal = async () => { const Personal = async () => {
const session = await getServerSession(authConfig); const session = await getServerSession(authConfig);
console.log(session?.expires_in);
const getProfile = async () => { const getProfile = async () => {
const Authorization = `Bearer ${session?.access_token}`; const Authorization = `Bearer ${session?.access_token}`;
const config = { const config = {

View File

@ -32,7 +32,7 @@ export async function generateMetadata({
title: `KG ROAD | ${response.data.location[0].address}`, title: `KG ROAD | ${response.data.location[0].address}`,
description: response.data.description, description: response.data.description,
images: [response.data.image[0].image], images: [response.data.image[0].image],
type: "website", type: "article",
}, },
}; };
} }

View File

@ -3,18 +3,42 @@ import "./Statistics.scss";
import StatisticsTable from "@/widgets/tables/StatisticsTable/StatisticsTable"; import StatisticsTable from "@/widgets/tables/StatisticsTable/StatisticsTable";
import { Metadata } from "next"; import { Metadata } from "next";
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs"; import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
import { IMetatag } from "@/shared/types/metatag-type";
import { apiInstance } from "@/shared/config/apiConfig";
export async function generateMetadata({ export async function generateMetadata(): Promise<Metadata> {
searchParams, const data = await apiInstance
}: { .get<IMetatag[]>("/metatags/")
searchParams: { "поиск-населенного-пункта": string }; .then((res) => res.data)
}): Promise<Metadata> { .catch((e) => console.log(e));
if (!data)
return { return {
title: "KG ROAD | Статистика", title: "KG ROAD | Статистика",
description: `Статистика по ${searchParams["поиск-населенного-пункта"]} KG ROAD`, description: `Статистика по населенным пунктам Кыргызстана`,
keywords: ["Бишкек", "Чуй", "Кыргызстан", "Дороги"], keywords: ["Бишкек", "Чуй", "Кыргызстан", "Дороги"],
}; };
const metadata = data.filter((tag) => tag.page === "statistics")[0];
if (!metadata) {
return {
title: "KG ROAD | Статистика",
description: `Статистика по населенным пунктам Кыргызстана`,
};
} }
return {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
keywords: metadata.keywords.split(","),
openGraph: {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
type: "website",
},
};
}
const Statistics = ({ const Statistics = ({
searchParams, searchParams,
}: { }: {

View File

@ -3,12 +3,43 @@ import "./Volunteers.scss";
import VolunteersTable from "@/widgets/tables/VolunteersTable/VolunteersTable"; import VolunteersTable from "@/widgets/tables/VolunteersTable/VolunteersTable";
import { Metadata } from "next"; import { Metadata } from "next";
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs"; import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
import { apiInstance } from "@/shared/config/apiConfig";
import { IMetatag } from "@/shared/types/metatag-type";
export const metadata: Metadata = { export async function generateMetadata(): Promise<Metadata> {
const data = await apiInstance
.get<IMetatag[]>("/metatags/")
.then((res) => res.data)
.catch((e) => console.log(e));
if (!data)
return {
title: "KG ROAD | Волонтеры", title: "KG ROAD | Волонтеры",
description: "Страница лучших волонтеров Кыргызской Республики!", description:
"Страница лучших волонтеров Кыргызской Республики!",
}; };
const metadata = data.filter((tag) => tag.page === "volunteers")[0];
if (!metadata) {
return {
title: "KG ROAD | Волонтеры",
description:
"Страница лучших волонтеров Кыргызской Республики!",
};
}
return {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
keywords: metadata.keywords.split(","),
openGraph: {
title: `KG ROAD | ${metadata.title}`,
description: metadata.description,
type: "website",
},
};
}
const Volunteers = () => { const Volunteers = () => {
return ( return (
<div className="volunteers page-padding"> <div className="volunteers page-padding">

View File

@ -19,16 +19,7 @@ const verifyToken = async (access_token: string) => {
}; };
const refreshToken = async (token: JWT): Promise<JWT> => { const refreshToken = async (token: JWT): Promise<JWT> => {
// const date = new Date().toLocaleTimeString(); const UTC = new Date();
// const expire = new Date(token.expires_in).toLocaleTimeString();
const verify = await verifyToken(token.access_token);
if (verify) {
return {
...token,
};
}
const data = { const data = {
refresh: token.refresh_token, refresh: token.refresh_token,
@ -39,10 +30,16 @@ const refreshToken = async (token: JWT): Promise<JWT> => {
data data
); );
const expirationTime = new Date(UTC.getTime() + 14 * 60000);
expirationTime.setTime(
expirationTime.getTime() +
expirationTime.getTimezoneOffset() * 60 * 1000 * -1
);
return { return {
...token, ...token,
access_token: response.data.access, access_token: response.data.access,
expires_in: response.data.expires_at, expires_in: expirationTime,
}; };
}; };
@ -58,20 +55,37 @@ export const authConfig: AuthOptions = {
}, },
password: { label: "Password", type: "password" }, password: { label: "Password", type: "password" },
}, },
async authorize(credentials, req) { async authorize(credentials, req): Promise<any> {
if (!credentials?.email || !credentials?.password) if (!credentials?.email || !credentials?.password)
return null; return null;
const { email, password } = credentials as any; const { email, password } = credentials;
const data = { const data = {
email, email,
password, password,
}; };
const res = await apiInstance.post("/users/login/", data); const res = await apiInstance.post<ITokens>(
"/users/login/",
data
);
if ([200, 201].includes(res.status)) { if ([200, 201].includes(res.status)) {
const user = res.data; const currentTime = new Date();
const expirationTime = new Date(
currentTime.getTime() + 14 * 60000
);
expirationTime.setTime(
expirationTime.getTime() +
expirationTime.getTimezoneOffset() * 60 * 1000 * -1
);
const user = {
refresh_token: res.data.refresh_token,
access_token: res.data.access_token,
expires_in: expirationTime,
};
return user; return user;
} }
@ -105,9 +119,15 @@ export const authConfig: AuthOptions = {
return false; return false;
} }
const currentTime = new Date();
const expirationTime = new Date(
currentTime.getTime() + 15 * 60000
);
user.access_token = res.data.access_token; user.access_token = res.data.access_token;
user.refresh_token = res.data.refresh_token; user.refresh_token = res.data.refresh_token;
user.expires_in = res.data.expires_in; user.expires_in = expirationTime;
} }
return true; return true;
@ -115,12 +135,26 @@ export const authConfig: AuthOptions = {
async jwt({ token, user }) { async jwt({ token, user }) {
if (user) return { ...token, ...user }; if (user) return { ...token, ...user };
return refreshToken(token); const UTC = new Date();
const currentTime = new Date(UTC.getTime());
currentTime.setTime(
currentTime.getTime() +
currentTime.getTimezoneOffset() * 60 * 1000 * -1
);
const isValid =
new Date(currentTime).getTime() <=
new Date(token.expires_in).getTime();
if (isValid) return token;
return await refreshToken(token);
}, },
async session({ token, session }) { async session({ token, session }) {
session.access_token = token.access_token; session.access_token = token.access_token;
session.refresh_token = token.refresh_token; session.refresh_token = token.refresh_token;
session.expires_in = token.expires_in;
return session; return session;
}, },

View File

@ -0,0 +1,8 @@
export interface IMetatag {
id: number;
title: string;
description: string;
keywords: string;
og_image?: string;
page: string;
}

View File

@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import { apiInstance } from "@/shared/config/apiConfig"; import { apiInstance } from "@/shared/config/apiConfig";
import Loader from "@/shared/ui/components/Loader/Loader"; import Loader from "@/shared/ui/components/Loader/Loader";
import { useRouter } from "@/shared/config/navigation"; import { useRouter } from "@/shared/config/navigation";
import { AxiosError } from "axios";
interface IConfirmEmailFormProps { interface IConfirmEmailFormProps {
email: string; email: string;
@ -54,14 +55,23 @@ const ConfirmEmailForm: React.FC<IConfirmEmailFormProps> = ({
if (response.status === 200 || response.status === 201) { if (response.status === 200 || response.status === 201) {
router.push("/sign-in"); router.push("/sign-in");
} }
} catch (error) { } catch (error: unknown) {
setError("Возникла ошибка"); if (error instanceof AxiosError) {
if ([400, 404].includes(error.response?.status as number)) {
setError("Неверный код подтверждения");
} else {
setError("Ошибка на стороне сервера");
}
} else {
setError("Произошла непредвиденная ошибка");
}
} finally { } finally {
setLoader(false); setLoader(false);
} }
}; };
const handleClick = async () => { const handleClick = async () => {
try {
const data = { const data = {
email, email,
}; };
@ -70,9 +80,14 @@ const ConfirmEmailForm: React.FC<IConfirmEmailFormProps> = ({
data data
); );
if (response.status === 200 || response.status === 201) { if ([200, 201].includes(response.status)) {
setMinutes(1); setMinutes(1);
} }
} catch (error) {
setError(
"Проблема на стороне сервера или вы достигли максимальное количество попыток"
);
}
}; };
useEffect(() => { useEffect(() => {

View File

@ -7,6 +7,7 @@ import { apiInstance } from "@/shared/config/apiConfig";
import Loader from "@/shared/ui/components/Loader/Loader"; import Loader from "@/shared/ui/components/Loader/Loader";
import { ITokens } from "@/shared/types/token-type"; import { ITokens } from "@/shared/types/token-type";
import { useRouter } from "@/shared/config/navigation"; import { useRouter } from "@/shared/config/navigation";
import { AxiosError } from "axios";
interface IConfirmCodeProps { interface IConfirmCodeProps {
setChangeForm: (boolean: boolean) => void; setChangeForm: (boolean: boolean) => void;
@ -45,8 +46,16 @@ const ConfirmCode: React.FC<IConfirmCodeProps> = ({
router.push("/sign-in/reset-code"); router.push("/sign-in/reset-code");
} }
setLoader(false); setLoader(false);
} catch (error) { } catch (error: unknown) {
setError("An error ocured"); if (error instanceof AxiosError) {
if (error.response?.status === 400) {
setError("Неверный код");
} else {
setError("Ошибка на стороне сервера");
}
} else {
setError("Произошла непредвиденная ошибка");
}
setLoader(false); setLoader(false);
} }
}; };

View File

@ -5,6 +5,7 @@ import "./send-email.scss";
import { useState } from "react"; import { useState } from "react";
import { apiInstance } from "@/shared/config/apiConfig"; import { apiInstance } from "@/shared/config/apiConfig";
import Loader from "@/shared/ui/components/Loader/Loader"; import Loader from "@/shared/ui/components/Loader/Loader";
import { AxiosError } from "axios";
interface ISendEmailProps { interface ISendEmailProps {
setChangeForm: (boolean: boolean) => void; setChangeForm: (boolean: boolean) => void;
@ -34,11 +35,21 @@ const SendEmail: React.FC<ISendEmailProps> = ({
formData formData
); );
if (res.status === 200 || res.status === 201) { if ([200, 201].includes(res.status)) {
console.log(res.data);
setChangeForm(true); setChangeForm(true);
} }
} catch (error) { } catch (error: unknown) {
setError("An error ocured"); if (error instanceof AxiosError) {
console.log(error);
if (error.response?.status === 400) {
setError("Пользователь с таким email не найден");
} else {
setError("Ошибка на стороне сервера");
}
} else {
setError("Произошла непредвиденная ошибка");
}
} finally { } finally {
setLoader(false); setLoader(false);
} }

View File

@ -5,6 +5,7 @@ import { useState } from "react";
import { apiInstance } from "@/shared/config/apiConfig"; import { apiInstance } from "@/shared/config/apiConfig";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import Loader from "@/shared/ui/components/Loader/Loader"; import Loader from "@/shared/ui/components/Loader/Loader";
import { AxiosError } from "axios";
interface IChangePasswordProps { interface IChangePasswordProps {
closeWindow: (bool: boolean) => void; closeWindow: (bool: boolean) => void;
@ -90,8 +91,16 @@ const ChangePassword: React.FC<IChangePasswordProps> = ({
); );
if ([200, 201].includes(res.status)) return setSuccess(true); if ([200, 201].includes(res.status)) return setSuccess(true);
} catch (error: any) { } catch (error: unknown) {
setError(error.message); if (error instanceof AxiosError) {
if (error.response?.status === 400) {
setError(
"Некорректный старый пароль или недопустимый новый пароль"
);
}
} else {
setError("Произошла непредвиденная ошибка");
}
} finally { } finally {
setLoader(false); setLoader(false);
} }

View File

@ -74,7 +74,11 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
} }
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
setError(error.message); if (error.response?.status === 400) {
setError("Были введены неккоректные данные");
} else {
setError("Ошибка на стороне сервера");
}
} else { } else {
setError("Возникла непредвиденная ошибка"); setError("Возникла непредвиденная ошибка");
} }

View File

@ -1,7 +0,0 @@
import "./CreateReportMap.scss";
const CreateReportMap = () => {
return <div>CreateReportMap</div>;
};
export default CreateReportMap;

View File

@ -171,9 +171,10 @@
button[type="submit"] { button[type="submit"] {
margin-top: 40px; margin-top: 40px;
padding: 15px; padding: 15px;
width: fit-content; max-width: 320px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
gap: 10px; gap: 10px;
border: 1px solid rgb(72, 159, 225), rgb(72, 159, 225); border: 1px solid rgb(72, 159, 225), rgb(72, 159, 225);
border-radius: 5px; border-radius: 5px;
@ -182,6 +183,10 @@
font-size: 22px; font-size: 22px;
font-weight: 400; font-weight: 400;
line-height: 26px; line-height: 26px;
div {
flex: 1;
}
} }
} }

View File

@ -6,6 +6,7 @@ import Image from "next/image";
import { import {
MapContainer, MapContainer,
Marker, Marker,
Popup,
TileLayer, TileLayer,
useMapEvents, useMapEvents,
} from "react-leaflet"; } from "react-leaflet";
@ -66,7 +67,7 @@ const ReportForm = () => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
if (displayLatLng.length === 0) { if (displayLatLng.length === 0 || latLng.length === 0) {
setDescriptionWarning(""); setDescriptionWarning("");
setImageWarning(""); setImageWarning("");
setError(""); setError("");
@ -109,8 +110,10 @@ const ReportForm = () => {
}); });
formData.append("latitude1", latLng[0].lat.toString()); formData.append("latitude1", latLng[0].lat.toString());
formData.append("longitude1", latLng[0].lng.toString()); formData.append("longitude1", latLng[0].lng.toString());
// if (latLng.length === 2) {
formData.append("latitude2", latLng[1].lat.toString()); formData.append("latitude2", latLng[1].lat.toString());
formData.append("longitude2", latLng[1].lng.toString()); formData.append("longitude2", latLng[1].lng.toString());
// }
try { try {
setLoader(true); setLoader(true);
@ -125,6 +128,7 @@ const ReportForm = () => {
} }
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
console.log(error);
setError(error.message); setError(error.message);
} else { } else {
setError("Произошла непредвиденная ошибка"); setError("Произошла непредвиденная ошибка");
@ -144,12 +148,20 @@ const ReportForm = () => {
.get( .get(
`https://nominatim.openstreetmap.org/reverse?lat=${e.latlng.lat}&lon=${e.latlng.lng}&format=json` `https://nominatim.openstreetmap.org/reverse?lat=${e.latlng.lat}&lon=${e.latlng.lng}&format=json`
) )
.then((res) => .then((res) => {
console.log(res.data);
if (res.data.address.country_code !== "kg") {
setLocationWarning("Выберите точку в Кыргызстане");
return;
} else if (res.data.address.road === undefined) {
setLocationWarning("Выберите точку на дороге");
return;
}
setDisplayLatLng([ setDisplayLatLng([
...displayLatLng, ...displayLatLng,
res.data.display_name, res.data.display_name,
]) ]);
) })
.catch((error) => console.log(error)); .catch((error) => console.log(error));
} }
}, },
@ -272,8 +284,14 @@ const ReportForm = () => {
) : null} ) : null}
</div> </div>
<button disabled={loader} type="submit"> <button disabled={loader} type="submit">
{loader ? <Loader /> : "Отправить на модерацию"} {loader ? (
<Loader />
) : (
<>
Отправить на модерацию
<Image src={arrow_right} alt="Arrow Right Icon" /> <Image src={arrow_right} alt="Arrow Right Icon" />
</>
)}
</button> </button>
{error ? <p className="report-form__error">{error}</p> : null} {error ? <p className="report-form__error">{error}</p> : null}
</form> </form>

View File

@ -7,6 +7,7 @@ import { apiInstance } from "@/shared/config/apiConfig";
import { ITokens } from "@/shared/types/token-type"; import { ITokens } from "@/shared/types/token-type";
import Loader from "@/shared/ui/components/Loader/Loader"; import Loader from "@/shared/ui/components/Loader/Loader";
import { useRouter } from "@/shared/config/navigation"; import { useRouter } from "@/shared/config/navigation";
import { AxiosError } from "axios";
const ResetCodeForm = () => { const ResetCodeForm = () => {
const [passwordWarning, setPasswordWarning] = useState<string>(""); const [passwordWarning, setPasswordWarning] = useState<string>("");
@ -62,12 +63,28 @@ const ResetCodeForm = () => {
config config
); );
if (response.status === 200 || response.status === 201) { if ([200, 201].includes(response.status)) {
localStorage.removeItem("transitional"); localStorage.removeItem("transitional");
router.push("/sign-in"); router.push("/sign-in");
} }
} catch (error) { } catch (error) {
setError("An error ocured"); if (error instanceof AxiosError) {
if (
[500, 501, 502, 503, 504].includes(
error.response?.status as number
)
) {
setError("Ошибка на стороне сервера");
} else if (
[400, 404].includes(error.response?.status as number)
) {
setError(
"Слабый пароль, прошу избегайте очевидных паролей"
);
}
} else {
setError("Произошла непредвиденная ошибка");
}
} finally { } finally {
setLoader(false); setLoader(false);
} }

View File

@ -5,10 +5,11 @@ import AuthInput from "@/features/AuthInput/AuthInput";
import GoogleButton from "@/features/GoogleButton/GoogleButton"; import GoogleButton from "@/features/GoogleButton/GoogleButton";
import { Link, useRouter } from "@/shared/config/navigation"; import { Link, useRouter } from "@/shared/config/navigation";
import Loader from "@/shared/ui/components/Loader/Loader"; import Loader from "@/shared/ui/components/Loader/Loader";
import { signIn } from "next-auth/react"; import { signIn, useSession } from "next-auth/react";
import { useState } from "react"; import { useState } from "react";
const SignInForm = () => { const SignInForm = () => {
const session = useSession();
const [emailWarning, setEmailWarning] = useState<string>(""); const [emailWarning, setEmailWarning] = useState<string>("");
const [passwordWarning, setPasswordWarning] = useState<string>(""); const [passwordWarning, setPasswordWarning] = useState<string>("");
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
@ -49,8 +50,8 @@ const SignInForm = () => {
setLoader(false); setLoader(false);
if (res?.ok && !res.error) { if (res?.ok && !res.error) {
router.push("/profile/personal"); // router.push("/profile/personal");
} else if (res?.status.toString().slice(0, 1) === "4") { } else if ([400, 401, 404].includes(res?.status as number)) {
setError("Неверный Email или Пароль"); setError("Неверный Email или Пароль");
} else { } else {
setError("Произошла непредвиденная ошибка"); setError("Произошла непредвиденная ошибка");

View File

@ -29,7 +29,8 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
} = useProfileReportsStore(); } = useProfileReportsStore();
const session = useSession(); const session = useSession();
const router = useRouter(); const router = useRouter();
const [sort, setSort] = useState("date"); const [sort, setSort] = useState("");
const [status, setStatus] = useState<number>(2);
const [activePage, setActivePage] = useState<number>( const [activePage, setActivePage] = useState<number>(
+searchParams["страница-обращений"] || 1 +searchParams["страница-обращений"] || 1
); );
@ -37,11 +38,19 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
{ {
param: "Дата", param: "Дата",
handleClick() { handleClick() {
setSort("date"); setSort("");
}, },
}, },
{ param: "Адрес" }, { param: "Адрес" },
{ param: "Статус" }, {
param: "Статус",
handleClick() {
setStatus((prev) =>
prev === 1 ? 2 : prev === 2 ? 3 : prev === 3 ? 1 : 2
);
setSort("status");
},
},
{ {
param: "Комментарии", param: "Комментарии",
handleClick() { handleClick() {
@ -51,21 +60,13 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
{ {
param: "Рейтинг", param: "Рейтинг",
handleClick() { handleClick() {
setSort("rating"); setSort("likes");
}, },
}, },
]; ];
useEffect(() => { useEffect(() => {
if (session.status === "loading") return; if (session.status === "loading") return;
getMyReports(
sort,
activePage,
session.data?.access_token as string
);
}, [session]);
useEffect(() => {
router.push( router.push(
`/profile/my-reports?страница-обращений=${activePage}`, `/profile/my-reports?страница-обращений=${activePage}`,
{ scroll: false } { scroll: false }
@ -73,9 +74,10 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
getMyReports( getMyReports(
sort, sort,
activePage, activePage,
session.data?.access_token as string session.data?.access_token as string,
status
); );
}, [activePage, sort]); }, [activePage, sort, status, session]);
return ( return (
<div className="profile-table"> <div className="profile-table">

View File

@ -7,17 +7,13 @@ import {
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { create } from "zustand"; import { create } from "zustand";
const filterCategories: Record<string, string> = {
count_reviews: "count_reviews",
total_likes: "total_likes",
};
interface IProfileReportsStore extends IFetch { interface IProfileReportsStore extends IFetch {
data: IMyReportsList; data: IMyReportsList;
getMyReports: ( getMyReports: (
filter: string, filter: string,
page: number, page: number,
access_token: string access_token: string,
status: number
) => void; ) => void;
} }
@ -34,7 +30,8 @@ export const useProfileReportsStore = create<IProfileReportsStore>(
getMyReports: async ( getMyReports: async (
sort: string, sort: string,
page: number, page: number,
access_token: string access_token: string,
status: number
) => { ) => {
try { try {
const Authorization = `Bearer ${access_token}`; const Authorization = `Bearer ${access_token}`;
@ -44,27 +41,21 @@ export const useProfileReportsStore = create<IProfileReportsStore>(
}, },
}; };
set({ isLoading: true }); set({ isLoading: true });
const data = await apiInstance let data;
if (sort === "status") {
data = await apiInstance
.get<IMyReportsList>( .get<IMyReportsList>(
`/users/reports/?page=${page}&page_size=8`, `/users/reports/?page=${page}&page_size=8&status=${status}`,
config
)
.then((res) => res.data);
} else {
data = await apiInstance
.get<IMyReportsList>(
`/users/reports/?page=${page}&page_size=8&sort_by=${sort}`,
config config
) )
.then((res) => res.data); .then((res) => res.data);
if (sort === "date") {
data.results = data.results.sort((a, b) => {
const dateA = new Date(b.created_at) as unknown as number;
const dateB = new Date(a.created_at) as unknown as number;
return dateA - dateB;
});
} else if (sort === "reviews") {
data.results = data.results.sort(
(a, b) => b.count_reviews - a.count_reviews
);
} else if (sort === "rating") {
data.results = data.results.sort(
(a, b) => b.total_likes - a.total_likes
);
} }
set({ data: data }); set({ data: data });

View File

@ -5,15 +5,6 @@ import { IStatistics } from "@/shared/types/statistics-type";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { create } from "zustand"; import { create } from "zustand";
const filterCategories: Record<string, string> = {
broken_road_1: "broken_road_1",
hotbed_of_accidents_2: "hotbed_of_accidents_2",
local_defect_3: "local_defect_3",
repair_plans_4: "repair_plans_4",
repaired_5: "repaired_5",
local_defect_fixed_6: "local_defect_fixed_6",
};
interface IStatisticsStore extends IFetch { interface IStatisticsStore extends IFetch {
data: IStatistics[]; data: IStatistics[];
getStatistics: ( getStatistics: (
@ -35,16 +26,14 @@ export const useStatisticsStore = create<IStatisticsStore>((set) => ({
try { try {
set({ isLoading: true }); set({ isLoading: true });
const response = await apiInstance.get<IStatistics[]>( const response = await apiInstance.get<IStatistics[]>(
`/report/${endpoint}/stats` `/report/${endpoint}/stats?sort_by=${sort}`
); );
let data = response.data.filter((loc) => let data = response.data.filter((loc) =>
loc.name.toLowerCase().includes(query.toLowerCase()) loc.name.toLowerCase().includes(query.toLowerCase())
); );
const sorted = data.sort((a: any, b: any) => a[sort] - b[sort]); set({ data: data });
set({ data: sorted });
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
set({ error: error.message }); set({ error: error.message });