Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90acfefe80 | ||
|
|
e9509e9396 | ||
|
|
6837cfa46a | ||
|
|
c7f7380821 | ||
|
|
57fab57b9d | ||
|
|
ffe8c84d3d | ||
|
|
f38f7ad7bd | ||
|
|
d9ac888d48 | ||
|
|
b885c9c9bc | ||
|
|
2228025e0f | ||
|
|
046a17122d | ||
|
|
5fb5487ea9 | ||
|
|
80a3f658b9 | ||
|
|
45650996c3 | ||
|
|
36896b1fad | ||
|
|
779f37a1a3 | ||
|
|
a520214eb9 | ||
|
|
a01b35343f | ||
|
|
c54131197e | ||
|
|
006a1a7b7b | ||
|
|
18f173820f | ||
|
|
40a4116c03 | ||
|
|
0ba0f1df89 | ||
|
|
0221a79015 | ||
|
|
ad86f2e79e | ||
|
|
7d36510a9b | ||
|
|
7bc3e2ccb5 | ||
|
|
74b15060a1 | ||
|
|
bcc7279d39 | ||
|
|
5330f21a73 | ||
|
|
65a700dad7 | ||
|
|
0a7513dd89 | ||
|
|
6650a98503 | ||
|
|
e2db27b56f | ||
|
|
6637ab4eae | ||
|
|
d1440f3907 | ||
|
|
773a33e21a | ||
|
|
3c4661a9c8 | ||
|
|
82eb226810 | ||
|
|
f9754aca1a | ||
|
|
d933102065 | ||
|
|
5ae5ff9fd3 | ||
|
|
2e03fd9175 | ||
|
|
c3a94c920f | ||
|
|
721e8506dd | ||
|
|
566c4d4473 | ||
|
|
67b935b47f | ||
|
|
deb08e290e | ||
|
|
e3a5ed72e0 | ||
|
|
3b365d7742 |
No files matched your search
@@ -4,7 +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 {
|
||||||
|
refresh_token: string;
|
||||||
|
access_token: string;
|
||||||
|
expires_in: Date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"date": "Date",
|
||||||
|
"address": "Address",
|
||||||
|
"status": "Status",
|
||||||
|
"description": "Description",
|
||||||
|
"reviews": "Reviews",
|
||||||
|
"rating": "Rating",
|
||||||
|
"review": "Review",
|
||||||
|
"write_comment": "Write Comment",
|
||||||
|
"search": "Search",
|
||||||
|
"search_for": "Search For",
|
||||||
|
"city": "City",
|
||||||
|
"added_roads": "Added Roads",
|
||||||
|
"broken_roads": "Broken Roads",
|
||||||
|
"accident_hotspots": "Accident Hotspots",
|
||||||
|
"local_defects": "Local Defects",
|
||||||
|
"repair_plans": "Repair Plans",
|
||||||
|
"repaired": "Repaired",
|
||||||
|
"fixed_local_defects": "Fixed Local Defects",
|
||||||
|
"news": "News",
|
||||||
|
"details": "Details",
|
||||||
|
"navigation": "Navigation",
|
||||||
|
"contacts": "Contacts",
|
||||||
|
"download_our_app": "Download our app",
|
||||||
|
"back": "Back",
|
||||||
|
"save": "Save",
|
||||||
|
"saving": "Saving",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"cancellation": "Cancellation",
|
||||||
|
"save_changes": "Save Changes",
|
||||||
|
"send": "Send",
|
||||||
|
"receive": "Receive",
|
||||||
|
"delete": "Delete",
|
||||||
|
"show_on_map": "Show on Map",
|
||||||
|
"author_of_appeal": "Author of Appeal",
|
||||||
|
"enter_city": "Enter City",
|
||||||
|
"page_not_found": "Page Not Found (404)",
|
||||||
|
"incorrect_address_or_nonexistent_page": "Incorrect Address or Nonexistent Page.",
|
||||||
|
"home": "Home",
|
||||||
|
"first_name": "First Name",
|
||||||
|
"last_name": "Last Name",
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"home": "Home",
|
||||||
|
"about_us": "About Us",
|
||||||
|
"privacy": "Privacy Policy",
|
||||||
|
"support": "Support",
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"news": "News",
|
||||||
|
"volunteers": "Volunteers",
|
||||||
|
"profile": "Profile",
|
||||||
|
"login": "Login"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"title": "Roads of Kyrgyzstan",
|
||||||
|
"subtitle": "Let's Make Roads Safe!",
|
||||||
|
"info": "Current information about the state of roads",
|
||||||
|
"report_broken_road": "Report Broken Road",
|
||||||
|
"road_map": "Road Map",
|
||||||
|
"latest_news": "Stay informed about the latest news on traffic, construction, and events!",
|
||||||
|
"enter_location": "Enter city, village, or region",
|
||||||
|
"broken_roads": "Broken road",
|
||||||
|
"accident_hotspots": "Accident hotspot",
|
||||||
|
"local_defects": "Local defect",
|
||||||
|
"repair_plans": "In repair plan",
|
||||||
|
"repaired": "Repaired",
|
||||||
|
"fixed_local_defects": "Fixed local defect",
|
||||||
|
"rating": "Rating",
|
||||||
|
"road_discussions": "Discussing roads: rating, experience, comfort on the way!",
|
||||||
|
"enter_address": "Enter address",
|
||||||
|
"read_more": "Read More"
|
||||||
|
},
|
||||||
|
"about_us": {
|
||||||
|
"name": "Transparency International-Kyrgyzstan",
|
||||||
|
"description": "Branch of the international organization Transparency International in the Kyrgyz Republic.",
|
||||||
|
"mission": "Promoting effective public policy and good governance to prevent corruption and strengthen democracy in the country.",
|
||||||
|
"goals_and_priorities": {
|
||||||
|
"anti-corruption_education": "Anti-corruption education of the population, raising public awareness of the importance and significance of the fight against corruption in Kyrgyzstan;",
|
||||||
|
"study_of_corruption_practices": "Organization of the study of the practice and theory of combating corruption and the participation of civil society structures in Kyrgyzstan and other countries;",
|
||||||
|
"supporting_citizens_and_organizations": "Assistance to citizens and organizations in the implementation of their constitutional rights and freedoms;",
|
||||||
|
"international_experience": "Preferential orientation to international experience in reducing corruption, mastering its technologies and resources, as well as involving civil society structures in the international dialogue on combating corruption."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"volunteers": {
|
||||||
|
"activists": "Activists",
|
||||||
|
"received_votes": "Received Votes",
|
||||||
|
"left_votes": "Left Votes",
|
||||||
|
"rating": "Rating"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"personal_cabinet": "Personal Cabinet",
|
||||||
|
"personal_data": "Personal Data",
|
||||||
|
"my_appeals": "My Appeals",
|
||||||
|
"logout": "Logout",
|
||||||
|
"write_appeal": "Write Appeal",
|
||||||
|
"profile_photo": "Profile Photo",
|
||||||
|
"others_identification": "With a profile photo, other people will recognize you, and it will be easier for you to determine which account you logged into.",
|
||||||
|
"add_profile_photo": "Add Profile Photo",
|
||||||
|
"profile_photo_updated": "Profile Photo Updated",
|
||||||
|
"delete": "Delete",
|
||||||
|
"change": "Change"
|
||||||
|
},
|
||||||
|
"authorization": {
|
||||||
|
"change_password": "Change Password",
|
||||||
|
"old_password": "Old Password",
|
||||||
|
"enter_old_password": "Enter Old Password",
|
||||||
|
"new_password": "New Password",
|
||||||
|
"enter_new_password": "Enter New Password",
|
||||||
|
"confirm_new_password": "Confirm New Password",
|
||||||
|
"confirm_new_password_prompt": "Please confirm the new password",
|
||||||
|
"password": "Password",
|
||||||
|
"forgot_password": "Forgot Password?",
|
||||||
|
"login": "Login",
|
||||||
|
"register": "Register",
|
||||||
|
"sign_in_account": "Sign in to Account",
|
||||||
|
"enter_credentials": "Please enter your credentials",
|
||||||
|
"login_via_google": "Login via Google",
|
||||||
|
"enter_password": "Enter Password",
|
||||||
|
"password_requirements": "Minimum 8 characters, 1 uppercase letter, and 1 digit",
|
||||||
|
"no_account_yet": "Don't have an account yet? Register",
|
||||||
|
"registration": "Registration",
|
||||||
|
"register_now": "Register Now",
|
||||||
|
"already_have_account": "Already have an account? Sign in",
|
||||||
|
"enter_email": "Enter Email",
|
||||||
|
"enter_email_for_code": "Enter email, and we will send a code to reset the password",
|
||||||
|
"send_code": "Send Code",
|
||||||
|
"confirm_code": "Confirm Code",
|
||||||
|
"enter_code": "Enter Code",
|
||||||
|
"enter_reset_code": "Enter code to reset and recover the password",
|
||||||
|
"reset_code": "Reset Code",
|
||||||
|
"reset_password": "Reset Password",
|
||||||
|
"check_email": "Check Your Email",
|
||||||
|
"code_sent_to": "We sent a code to the email name@gmail.com",
|
||||||
|
"confirmation_code": "Confirmation Code",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"resend_code_in": "Resend Code in",
|
||||||
|
"resend_code": "Resend Code"
|
||||||
|
},
|
||||||
|
"send_report": {
|
||||||
|
"how_to_mark_road_section": "How to mark a road section?",
|
||||||
|
"mark_road_instructions": "Place a pin and start drawing a road section (it can consist of any number of broken lines).",
|
||||||
|
"remove_segment_instruction": "To remove a segment, click on the points again.",
|
||||||
|
"add_problem_description": "Add a problem description",
|
||||||
|
"enter_description": "Enter description",
|
||||||
|
"add_photos": "Add Photos",
|
||||||
|
"upload_photos_instructions": "Upload up to 5 photos related to the road you want to mark. Photos will help better understand the problem.",
|
||||||
|
"attach_file": "Attach File (up to 5 MB)",
|
||||||
|
"submit_for_moderation": "Submit for Moderation",
|
||||||
|
"appeal_submitted": "Your appeal has been submitted",
|
||||||
|
"thanks_for_appeal": "Thank you for your appeal. It is currently under moderation.",
|
||||||
|
"view_my_appeals": "View My Appeals"
|
||||||
|
},
|
||||||
|
"months": {
|
||||||
|
"january": "January",
|
||||||
|
"february": "February",
|
||||||
|
"march": "March",
|
||||||
|
"april": "April",
|
||||||
|
"may": "May",
|
||||||
|
"june": "June",
|
||||||
|
"july": "July",
|
||||||
|
"august": "August",
|
||||||
|
"september": "September",
|
||||||
|
"october": "October",
|
||||||
|
"november": "November",
|
||||||
|
"december": "December"
|
||||||
|
},
|
||||||
|
"validation_errors": {
|
||||||
|
"invalid_email_format": "Invalid email format.",
|
||||||
|
"passwords_do_not_match": "Passwords do not match.",
|
||||||
|
"required_field_not_filled": "Required field not filled.",
|
||||||
|
"exceeded_maximum_length": "Exceeded maximum length of the field.",
|
||||||
|
"login_required_before_commenting": "Please log in or register before leaving a comment.",
|
||||||
|
"login_required_before_like": "Please log in or register before liking."
|
||||||
|
},
|
||||||
|
"server_errors": {
|
||||||
|
"invalid_email_or_password": "Invalid email or password.",
|
||||||
|
"server_error_auth_attempt": "Server error during authentication attempt.",
|
||||||
|
"login_failed": "Failed to log in. Something went wrong, please try again later.",
|
||||||
|
"account_already_exists": "An account with this email already exists.",
|
||||||
|
"account_not_found": "Account not found.",
|
||||||
|
"invalid_activation_code": "Invalid activation code.",
|
||||||
|
"invalid_activation_code_reset": "Invalid activation code for reset.",
|
||||||
|
"invalid_password_reset_code": "Invalid password reset code.",
|
||||||
|
"invalid_code": "Invalid code."
|
||||||
|
},
|
||||||
|
"disclaimer": {
|
||||||
|
"text": "This website is funded by the European Union. Its contents are the sole responsibility of Transparency International Kyrgyzstan and do not necessarily reflect the views of the European Union."
|
||||||
|
},
|
||||||
|
"rights": {
|
||||||
|
"text": "All rights reserved"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"date": "Күн",
|
||||||
|
"address": "Дарек",
|
||||||
|
"status": "Статус",
|
||||||
|
"description": "Сүрөт",
|
||||||
|
"reviews": "Комментарийлер",
|
||||||
|
"rating": "Рейтинг",
|
||||||
|
"review": "Комментарий",
|
||||||
|
"write_comment": "Комментарий жазуу",
|
||||||
|
"search": "Издөө",
|
||||||
|
"search_for": "Издөө",
|
||||||
|
"city": "Шаар",
|
||||||
|
"added_roads": "Кошулган жолдор",
|
||||||
|
"broken_roads": "Тас тастаган жолдор",
|
||||||
|
"accident_hotspots": "Авариялуу жерлер",
|
||||||
|
"local_defects": "Жерги дефекттер",
|
||||||
|
"repair_plans": "Түзөө планттары",
|
||||||
|
"repaired": "Түзөлгөн",
|
||||||
|
"fixed_local_defects": "Жерги дефекттерди түзөлгөн",
|
||||||
|
"news": "Жаңылыктар",
|
||||||
|
"details": "Эчти маалымат",
|
||||||
|
"navigation": "Навигация",
|
||||||
|
"contacts": "Контакттар",
|
||||||
|
"download_our_app": "Биздин приложениямызды жүктөп алыңыз",
|
||||||
|
"back": "Кайтуу",
|
||||||
|
"save": "Сактоо",
|
||||||
|
"saving": "Сакталат",
|
||||||
|
"cancel": "Жокко чыгаруу",
|
||||||
|
"cancellation": "Жокко чыгаруу",
|
||||||
|
"save_changes": "Өзгөртүүлөрдү сактоо",
|
||||||
|
"send": "Жиберүү",
|
||||||
|
"receive": "Алуу",
|
||||||
|
"delete": "Жок кылуу",
|
||||||
|
"show_on_map": "Картада көрсөтүү",
|
||||||
|
"author_of_appeal": "Өтүнчүнүн автору",
|
||||||
|
"enter_city": "Шаарды киргизиңиз",
|
||||||
|
"page_not_found": "Бет табылган эмес (404)",
|
||||||
|
"incorrect_address_or_nonexistent_page": "Туура эмес дарек же бет жок",
|
||||||
|
"home": "Башкы бет",
|
||||||
|
"first_name": "Аты",
|
||||||
|
"last_name": "Фамилия",
|
||||||
|
"email": "Электрондук почта"
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"home": "Башкы бет",
|
||||||
|
"about_us": "Биз тууралуу",
|
||||||
|
"privacy": "Купуялык саясаты",
|
||||||
|
"support": "Колдоо",
|
||||||
|
"statistics": "Статистика",
|
||||||
|
"news": "Жаңылыктар",
|
||||||
|
"volunteers": "Волонтёрлер",
|
||||||
|
"profile": "Профиль",
|
||||||
|
"login": "Кириш"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"title": "Кыргызстандын жолдору",
|
||||||
|
"subtitle": "Жолдорду бекемделүү жасаңыз!",
|
||||||
|
"info": "Жолдордун жаңы күйү",
|
||||||
|
"report_broken_road": "Тас тастаган жолду турганды таратуу",
|
||||||
|
"road_map": "Жол картасы",
|
||||||
|
"latest_news": "Трафик, өндүрүү жана тапшыруудагы соңгосу турган жаңылыктардан кабардар болуңуз!",
|
||||||
|
"enter_location": "Шаар, айыл жана регионду киргизиңиз",
|
||||||
|
"broken_roads": "Жол кирпич",
|
||||||
|
"accident_hotspots": "Авариянын жатактоо жерлери",
|
||||||
|
"local_defects": "Жерги дефект",
|
||||||
|
"repair_plans": "Тозгоондоо жатактоо планында",
|
||||||
|
"repaired": "Тозотулду",
|
||||||
|
"fixed_local_defects": "Тозотулган жерги дефект",
|
||||||
|
"rating": "Рейтинг",
|
||||||
|
"road_discussions": "Жолдорду талкуулоо: рейтинг, тажрыйба, жолдоо боюнча комфорт!",
|
||||||
|
"enter_address": "Даректи киргизиңиз",
|
||||||
|
"read_more": "Көбүрөөк окуу"
|
||||||
|
},
|
||||||
|
"transparency_international_kyrgyzstan": {
|
||||||
|
"name": "Транспаренттыктык Интернационал-Кыргызстан",
|
||||||
|
"description": "Транспаренттыктык Интернационалдын Кыргызстан Республикасы бөлүмү.",
|
||||||
|
"mission": "Коррупциянын ыкмасы менен демократияны күтүтүп, эффективдүү жамааттык саясат жана жакшы мамлекеттүү башкаруунун бириктирилүү үчүн.",
|
||||||
|
"goals_and_priorities": {
|
||||||
|
"anti-corruption_education": "Коррупция менен борбордук бийикти ашуу, Кыргызстанда коррупцияга каршы болгондоо маанилүүдүн жана азаттыктардын маанилүүлүгү үчүн жалпы айткынуу;",
|
||||||
|
"study_of_corruption_practices": "Коррупцияга каршы борбордукты тартуу жана учуруу теориясы менен, Кыргызстан менен башка өлкөлердеги кызмат көрсөтүүсү үчүн азаттыктыктын долбоорлорун жана каттоого жаткантыруу;",
|
||||||
|
"supporting_citizens_and_organizations": "Граждандар менен биздин же компаниялардын конституциялык башкаруу менен жандуулаткануу;",
|
||||||
|
"international_experience": "Коррупциянын аздоого чейинки жыйынтыкты башкаруу үчүн көздөр тартуу, улуттуктары менен биргелеп, коррупцияга каршы көрсөткүчтөрдү мамлекеттик диалогга киргизүү."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"volunteers": {
|
||||||
|
"activists": "Активисттер",
|
||||||
|
"received_votes": "Алынган баллдар",
|
||||||
|
"left_votes": "Калган баллдар",
|
||||||
|
"rating": "Рейтинг"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"personal_cabinet": "Жеке кабинет",
|
||||||
|
"personal_data": "Жеке дайындар",
|
||||||
|
"my_appeals": "Менин жардам кылган жалпылыгым",
|
||||||
|
"logout": "Чыгуу",
|
||||||
|
"write_appeal": "Жардам кылуу",
|
||||||
|
"profile_photo": "Профиль сүрөтү",
|
||||||
|
"others_identification": "Профиль сүрөтү аркылуу башка адамдар сизди танышат, жана сизге кирген аккаунтту тандашуу өттүрүлгөн болот.",
|
||||||
|
"add_profile_photo": "Профиль сүрөтү кошуу",
|
||||||
|
"profile_photo_updated": "Профиль сүрөтү жаңыртылды",
|
||||||
|
"delete": "Жок кылуу",
|
||||||
|
"change": "Өзгөртүү"
|
||||||
|
},
|
||||||
|
"authorization": {
|
||||||
|
"change_password": "Сыр сөздү өзгөртүү",
|
||||||
|
"old_password": "Эски сыр сөз",
|
||||||
|
"enter_old_password": "Эски сыр сөздү киргизиңиз",
|
||||||
|
"new_password": "Жаңы сыр сөз",
|
||||||
|
"enter_new_password": "Жаңы сыр сөздү киргизиңиз",
|
||||||
|
"confirm_new_password": "Жаңы сыр сөздү растоо",
|
||||||
|
"confirm_new_password_prompt": "Жаңы сыр сөздү растоо, аны кайра чалыңыз",
|
||||||
|
"password": "Сыр сөз",
|
||||||
|
"forgot_password": "Сыр сөздү унуттуңузбу?",
|
||||||
|
"login": "Кириш",
|
||||||
|
"register": "Тизмеге кирүү",
|
||||||
|
"sign_in_account": "Аккаунтка кириңиз",
|
||||||
|
"enter_credentials": "Киргизген дайындарыңызды киргизиңиз",
|
||||||
|
"login_via_google": "Google аркылуу кириңиз",
|
||||||
|
"enter_password": "Сыр сөздү киргизиңиз",
|
||||||
|
"password_requirements": "Минимум 8 белги, 1 башкы буюк тамга жана 1 сандар",
|
||||||
|
"no_account_yet": "Өйткені, аккаунт жоок? Тизмеге кирүү",
|
||||||
|
"registration": "Тизмеге кирүү",
|
||||||
|
"register_now": "Азыр тизмеге кирүү",
|
||||||
|
"already_have_account": "Аккаунт бар болсо, кириңиз",
|
||||||
|
"enter_email": "Электрондук почтаны киргизиңиз",
|
||||||
|
"enter_email_for_code": "Электрондук почта киргизиңиз, биз сизге сыр сөздү калыпта тапшыруу үчүн код жөнөтөт",
|
||||||
|
"send_code": "Код жөнөтүү",
|
||||||
|
"confirm_code": "Кодду растоо",
|
||||||
|
"enter_code": "Кодду киргизиңиз",
|
||||||
|
"enter_reset_code": "Сыр сөздү өзгөртүп жаңыртуу үчүн кодду киргизиңиз",
|
||||||
|
"reset_code": "Сыр сөздү өзгөртүү коду",
|
||||||
|
"reset_password": "Сыр сөздү өзгөртүү",
|
||||||
|
"check_email": "Почтаны текшериңиз",
|
||||||
|
"code_sent_to": "Биз кодду name@gmail.com почтасына жөнөттүк",
|
||||||
|
"confirmation_code": "Тастыгы код",
|
||||||
|
"confirm": "Растоо",
|
||||||
|
"resend_code_in": "Кодду кайталап жөнөтүү",
|
||||||
|
"resend_code": "Кодду кайталап жөнөтүү"
|
||||||
|
},
|
||||||
|
"send_report": {
|
||||||
|
"how_to_mark_road_section": "Жол бөлүмүн белгилөө үчүн",
|
||||||
|
"mark_road_instructions": "Чек салып, жол бөлүмүн белгилөөгө ээсиңиз (ал булактардан турат).",
|
||||||
|
"remove_segment_instruction": "Жол бөлүмүн каттоо үчүн бир маандын жакшысына басыңыз.",
|
||||||
|
"add_problem_description": "Проблеманын сүрөтүн кошуңуз",
|
||||||
|
"enter_description": "Сүрөттөмөнү киргизиңиз",
|
||||||
|
"add_photos": "Фотографияларды кошуңуз",
|
||||||
|
"upload_photos_instructions": "Жолдун байланышты 5 фотосун жүктөп алыңыз, анткени жататат жана туура түшүнүүдү макул болот.",
|
||||||
|
"attach_file": "Файлды тиштөө (5 МБга чейин)",
|
||||||
|
"submit_for_moderation": "Модерацияга жиберүү",
|
||||||
|
"appeal_submitted": "Сиздин жалпылыгыңыз жиберилди",
|
||||||
|
"thanks_for_appeal": "Сиздин жалпылыгыңуз үчүн рахмат. Азырынча аны модерацияда.",
|
||||||
|
"view_my_appeals": "Менин жалпылыгымдарымды көрүү"
|
||||||
|
},
|
||||||
|
"months": {
|
||||||
|
"january": "Жанварь",
|
||||||
|
"february": "Февраль",
|
||||||
|
"march": "Март",
|
||||||
|
"april": "Апрель",
|
||||||
|
"may": "Май",
|
||||||
|
"june": "Июнь",
|
||||||
|
"july": "Июль",
|
||||||
|
"august": "Август",
|
||||||
|
"september": "Сентябрь",
|
||||||
|
"october": "Октябрь",
|
||||||
|
"november": "Ноябрь",
|
||||||
|
"december": "Декабрь"
|
||||||
|
},
|
||||||
|
"validation_errors": {
|
||||||
|
"invalid_email_format": "Туура эмес электрондук почта форматы.",
|
||||||
|
"passwords_do_not_match": "Сыр сөздөрдүн туура келмейт.",
|
||||||
|
"required_field_not_filled": "Милдеттүү талаа толтурулган жок.",
|
||||||
|
"exceeded_maximum_length": "Талаанын эң ылдам узундугу өткөнчү болду.",
|
||||||
|
"login_required_before_commenting": "Комментарий бир ар каайыпты таштоо алганда, ал кайталап киргизиңиз же тизмеге киргизиңиз.",
|
||||||
|
"login_required_before_like": "Лайк койгондо, кайталап киргизиңиз же тизмеге киргизиңиз керек."
|
||||||
|
},
|
||||||
|
"server_errors": {
|
||||||
|
"invalid_email_or_password": "Туура эмес почта же сыр сөз.",
|
||||||
|
"server_error_auth_attempt": "Авторизация учуруу учурастыктан кайталап сервердеги ката.",
|
||||||
|
"login_failed": "Кирүүгө мүмкүн болгон эмес. Негизги нече кайталап уруксат бериңиз.",
|
||||||
|
"account_already_exists": "Бул почтага ар бир аккаунт бар.",
|
||||||
|
"account_not_found": "Аккаунт табылган жок.",
|
||||||
|
"invalid_activation_code": "Четке калган иштеш коду.",
|
||||||
|
"invalid_activation_code_reset": "Сыр сөздү калыпта тапшыруу үчүн четке калган иштеш коду.",
|
||||||
|
"invalid_password_reset_code": "Сыр сөздү өзгөртүү коду четке калган эмес.",
|
||||||
|
"invalid_code": "Четке калган иштеш коду."
|
||||||
|
},
|
||||||
|
"disclaimer": {
|
||||||
|
"text": "Бул веб-сайт Европа Биримдиги тарабынан каржыланат. Анын мазмуну үчүн Трансперенси Интернешнл Кыргызстан гана жоопкерчиликтүү жана ал Европа Биримдигинин көз карашын сөзсүз түрдө чагылдырбайт."
|
||||||
|
},
|
||||||
|
"rights": {
|
||||||
|
"text": "Бардык укуктар корголгон"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"date": "Дата",
|
||||||
|
"address": "Адрес",
|
||||||
|
"status": "Статус",
|
||||||
|
"description": "Описание",
|
||||||
|
"reviews": "Комментарии",
|
||||||
|
"rating": "Рейтинг",
|
||||||
|
"review": "Комментарий",
|
||||||
|
"write_comment": "Написать комментарий",
|
||||||
|
"search": "Поиск",
|
||||||
|
"search_for": "Искать",
|
||||||
|
"city": "Город",
|
||||||
|
"added_roads": "Добавлено дорог",
|
||||||
|
"broken_roads": "Разбитых дорог",
|
||||||
|
"accident_hotspots": "Очагов аварийности",
|
||||||
|
"local_defects": "Локальных дефектов",
|
||||||
|
"repair_plans": "В планах ремонта",
|
||||||
|
"repaired": "Отремонтировано",
|
||||||
|
"fixed_local_defects": "Локальных дефектов исправлено",
|
||||||
|
"news": "Новости",
|
||||||
|
"details": "Подробнее",
|
||||||
|
"navigation": "Навигация",
|
||||||
|
"contacts": "Контакты",
|
||||||
|
"download_our_app": "Скачивай наше приложение",
|
||||||
|
"back": "Назад",
|
||||||
|
"save": "Сохранить",
|
||||||
|
"saving": "Сохранение",
|
||||||
|
"cancel": "Отменить",
|
||||||
|
"cancellation": "Отмена",
|
||||||
|
"save_changes": "Сохранить изменения",
|
||||||
|
"send": "Отправить",
|
||||||
|
"receive": "Получить",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"show_on_map": "Показать на карте",
|
||||||
|
"author_of_appeal": "Автор обращения",
|
||||||
|
"enter_city": "Введите населенный пункт",
|
||||||
|
"page_not_found": "Страница не найдена (404)",
|
||||||
|
"incorrect_address_or_nonexistent_page": "Неправильно набран адрес или такой страницы не существует.",
|
||||||
|
"home": "На главную",
|
||||||
|
"first_name": "Имя",
|
||||||
|
"last_name": "Фамилия",
|
||||||
|
"email": "Электронная почта"
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"home": "Главная",
|
||||||
|
"about_us": "О нас",
|
||||||
|
"privacy": "Политика конфиденциальности",
|
||||||
|
"support": "Поддержка",
|
||||||
|
"statistics": "Статистика",
|
||||||
|
"news": "Новости",
|
||||||
|
"volunteers": "Волонтеры",
|
||||||
|
"profile": "Профиль",
|
||||||
|
"login": "Войти"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"title": "Дороги Кыргызстана",
|
||||||
|
"subtitle": "Сделаем дороги безопасными!",
|
||||||
|
"info": "Актуальная информация о состоянии дорог",
|
||||||
|
"report_broken_road": "Отметить разбитую дорогу",
|
||||||
|
"road_map": "Карта дорог",
|
||||||
|
"latest_news": "Будьте в курсе последних новостей о дорожном движении, строительствах и мероприятиях!",
|
||||||
|
"enter_location": "Введите город, село или регион",
|
||||||
|
"broken_roads": "Разбитая дорога",
|
||||||
|
"accident_hotspots": "Очаг аварийности",
|
||||||
|
"local_defects": "Локальный дефект",
|
||||||
|
"repair_plans": "В плане ремонта",
|
||||||
|
"repaired": "Отремонтировано",
|
||||||
|
"fixed_local_defects": "Локальный дефект исправлен",
|
||||||
|
"rating": "Рейтинг",
|
||||||
|
"road_discussions": "Обсуждаем дороги: рейтинг, опыт, комфорт в пути!",
|
||||||
|
"enter_address": "Введите адрес",
|
||||||
|
"read_more": "Читать"
|
||||||
|
},
|
||||||
|
"about_us": {
|
||||||
|
"name": "Transparency International-Кыргызстан",
|
||||||
|
"description": "Филиал международной организации Transparency International в Кыргызской Республике.",
|
||||||
|
"mission": "Продвижение эффективной общественной политики и надлежащего управления в целях предотвращения коррупции и усиления демократии в стране.",
|
||||||
|
"goals_and_priorities": {
|
||||||
|
"anti-corruption_education": "Антикоррупционное просвещение населения, повышение общественного осознания значимости и важности борьбы с коррупцией в Кыргызстане;",
|
||||||
|
"study_of_corruption_practices": "Организация изучения практики и теории борьбы с коррупцией и участия в ней структур гражданского общества в Кыргызстане и других странах;",
|
||||||
|
"supporting_citizens_and_organizations": "Содействие гражданам и организациям в реализации их конституционных прав и свобод;",
|
||||||
|
"international_experience": "Преимущественная ориентация на международный опыт уменьшения коррупции, освоение его технологий, ресурсов, а также включение структур гражданского общества в международный диалог борьбы с коррупцией."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"volunteers": {
|
||||||
|
"activists": "Активисты",
|
||||||
|
"received_votes": "Получено голосов",
|
||||||
|
"left_votes": "Оставлено голосов",
|
||||||
|
"rating": "Рейтинг"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"personal_cabinet": "Личный кабинет",
|
||||||
|
"personal_data": "Личные данные",
|
||||||
|
"my_appeals": "Мои обращения",
|
||||||
|
"logout": "Выйти из аккаунта",
|
||||||
|
"write_appeal": "Написать обращение",
|
||||||
|
"profile_photo": "Фото профиля",
|
||||||
|
"others_identification": "По фото профиля другие люди смогут вас узнавать, а вам будет проще определять, в какой аккаунт вы вошли.",
|
||||||
|
"add_profile_photo": "Добавить фото профиля",
|
||||||
|
"profile_photo_updated": "Фото профиля обновлено",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"change": "Сменить"
|
||||||
|
},
|
||||||
|
"authorization": {
|
||||||
|
"change_password": "Изменить пароль",
|
||||||
|
"old_password": "Старый пароль",
|
||||||
|
"enter_old_password": "Введите старый пароль",
|
||||||
|
"new_password": "Новый пароль",
|
||||||
|
"enter_new_password": "Введите новый пароль",
|
||||||
|
"confirm_new_password": "Подтвердить новый пароль",
|
||||||
|
"confirm_new_password_prompt": "Пожалуйста, подтвердите новый пароль",
|
||||||
|
"password": "Пароль",
|
||||||
|
"forgot_password": "Забыли пароль?",
|
||||||
|
"login": "Войти",
|
||||||
|
"register": "Зарегистрироваться",
|
||||||
|
"sign_in_account": "Войдите в аккаунт",
|
||||||
|
"enter_credentials": "Пожалуйста, введите свои данные",
|
||||||
|
"login_via_google": "Войти через Google",
|
||||||
|
"enter_password": "Введите пароль",
|
||||||
|
"password_requirements": "Минимум 8 символов, 1 заглавная буква и цифра",
|
||||||
|
"no_account_yet": "Еще нет аккаунта? Зарегистрируйтесь",
|
||||||
|
"registration": "Регистрация",
|
||||||
|
"register_now": "Зарегистрировать",
|
||||||
|
"already_have_account": "Уже есть аккаунт? Войти в аккаунт",
|
||||||
|
"enter_email": "Введите электронную почту",
|
||||||
|
"enter_email_for_code": "Введите электронную почту и мы отправим код для восстановления пароля",
|
||||||
|
"send_code": "Отправить код",
|
||||||
|
"confirm_code": "Потвердить код",
|
||||||
|
"enter_code": "Введите код",
|
||||||
|
"enter_reset_code": "Введите код для сброса и восстановления пароля",
|
||||||
|
"reset_code": "Код сброса пароля",
|
||||||
|
"reset_password": "Сбросить пароль",
|
||||||
|
"check_email": "Проверьте свою почту",
|
||||||
|
"code_sent_to": "Мы отправили код на почту name@gmail.com",
|
||||||
|
"confirmation_code": "Код подтверждения",
|
||||||
|
"confirm": "Подтвердить",
|
||||||
|
"resend_code_in": "Отправить код повторно через",
|
||||||
|
"resend_code": "Отправить код повторно"
|
||||||
|
},
|
||||||
|
"send_report": {
|
||||||
|
"how_to_mark_road_section": "Как отметить участок дороги?",
|
||||||
|
"mark_road_instructions": "Поставьте булавку и начните рисовать участок дороги (он может состоять из любого количества ломаных линий).",
|
||||||
|
"remove_segment_instruction": "Чтобы удалить отрезок, нажмите на точки повторно.",
|
||||||
|
"add_problem_description": "Добавьте описание проблемы",
|
||||||
|
"enter_description": "Введите описание",
|
||||||
|
"add_photos": "Добавьте фотографии",
|
||||||
|
"upload_photos_instructions": "Загрузите до 5 фотографий, связанных с дорогой, которую вы хотите отметить. Фотографии помогут лучше понять проблему.",
|
||||||
|
"attach_file": "Прикрепить файл (до 5 МБ)",
|
||||||
|
"submit_for_moderation": "Отправить на модерацию",
|
||||||
|
"appeal_submitted": "Ваше обращение отправлено",
|
||||||
|
"thanks_for_appeal": "Спасибо за ваше обращение. На данный момент оно в модерации.",
|
||||||
|
"view_my_appeals": "Смотреть мои обращения"
|
||||||
|
},
|
||||||
|
"months": {
|
||||||
|
"january": "Январь",
|
||||||
|
"february": "Февраль",
|
||||||
|
"march": "Март",
|
||||||
|
"april": "Апрель",
|
||||||
|
"may": "Май",
|
||||||
|
"june": "Июнь",
|
||||||
|
"july": "Июль",
|
||||||
|
"august": "Август",
|
||||||
|
"september": "Сентябрь",
|
||||||
|
"october": "Октябрь",
|
||||||
|
"november": "Ноябрь",
|
||||||
|
"december": "Декабрь"
|
||||||
|
},
|
||||||
|
"validation_errors": {
|
||||||
|
"invalid_email_format": "Неверный формат электронной почты.",
|
||||||
|
"passwords_do_not_match": "Пароли не совпадают.",
|
||||||
|
"required_field_not_filled": "Обязательное поле не заполнено.",
|
||||||
|
"exceeded_maximum_length": "Превышена максимальная длина поля.",
|
||||||
|
"login_required_before_commenting": "Перед тем как оставить комментарий, пожалуйста, войдите или зарегистрируйтесь.",
|
||||||
|
"login_required_before_like": "Перед тем как поставить лайк, пожалуйста, войдите или зарегистрируйтесь."
|
||||||
|
},
|
||||||
|
"server_errors": {
|
||||||
|
"invalid_email_or_password": "Неверная почта или пароль.",
|
||||||
|
"server_error_auth_attempt": "Серверная ошибка при попытке авторизации.",
|
||||||
|
"login_failed": "Не удалось войти в систему. Что-то пошло не так, попробуйте еще раз немного позже.",
|
||||||
|
"account_already_exists": "Такая учетная запись уже существует.",
|
||||||
|
"account_not_found": "Такой учетной записи не существует.",
|
||||||
|
"invalid_activation_code": "Код не действителен.",
|
||||||
|
"invalid_activation_code_reset": "Код активации не действителен.",
|
||||||
|
"invalid_password_reset_code": "Код сброса пароля не действителен.",
|
||||||
|
"invalid_code": "Неверный код."
|
||||||
|
},
|
||||||
|
"disclaimer": {
|
||||||
|
"text": "Этот веб-сайт финансируется Европейским Союзом. Ответственность за его содержание лежит исключительно на Трансперенси Интернешнл Кыргызстан и не обязательно отражает точку зрения Европейского Союза."
|
||||||
|
},
|
||||||
|
"rights": {
|
||||||
|
"text": "Все права защищены"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
distDir: "build",
|
distDir: "build",
|
||||||
@@ -9,6 +12,10 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
env: {
|
||||||
|
CLIENT_ID: process.env.CLIENT_ID,
|
||||||
|
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default withNextIntl(nextConfig);
|
||||||
@@ -5,15 +5,17 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start -p 8003",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@next/third-parties": "^14.1.0",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
"dotenv": "^16.4.1",
|
"dotenv": "^16.4.1",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"next": "14.1.0",
|
"next": "^14.1.0",
|
||||||
"next-auth": "^4.24.5",
|
"next-auth": "^4.24.5",
|
||||||
|
"next-intl": "^3.9.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-leaflet": "^4.2.1",
|
"react-leaflet": "^4.2.1",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
&__image {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-bottom: 65px;
|
margin-bottom: 65px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -36,6 +36,22 @@
|
|||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
color: rgb(62, 50, 50);
|
color: rgb(62, 50, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
li {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 34px;
|
||||||
|
color: rgb(62, 50, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__author {
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
|
||||||
|
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 | О нас",
|
||||||
|
description:
|
||||||
|
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
|
||||||
|
openGraph: {
|
||||||
|
title: "KG ROAD | О нас",
|
||||||
|
description:
|
||||||
|
"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 = () => {
|
||||||
|
return (
|
||||||
|
<div className="about-us page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
<Typography element="h2">О нас</Typography>
|
||||||
|
|
||||||
|
<Image
|
||||||
|
className="about-us__image"
|
||||||
|
src={header}
|
||||||
|
alt="Header Image"
|
||||||
|
/>
|
||||||
|
<div className="about-us__descriptions">
|
||||||
|
<h3>
|
||||||
|
Transparency International-Кыргызстан - филиал международной
|
||||||
|
организации Transparency International в Кыргызской
|
||||||
|
Республике.
|
||||||
|
</h3>
|
||||||
|
<h3>Миссия ТИ-Кыргызстан</h3>
|
||||||
|
<p>
|
||||||
|
Продвижение эффективной общественной политики и надлежащего
|
||||||
|
управления в целях предотвращения коррупции и усиления
|
||||||
|
демократии в стране.{" "}
|
||||||
|
</p>
|
||||||
|
<h3>Цели и приоритеты ТИ-Кыргызстан:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
- антикоррупционное просвещение населения, повышение
|
||||||
|
общественного осознания значимости и важности борьбы с
|
||||||
|
коррупцией в Кыргызстане;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- организация изучения практики и теории борьбы с
|
||||||
|
коррупцией и участия в ней структур гражданского общества
|
||||||
|
в Кыргызстане и других странах;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- содействие гражданам и организациям в реализации их
|
||||||
|
конституционных прав и свобод;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- преимущественная ориентация на международный опыт
|
||||||
|
уменьшения коррупции, освоение его технологий, ресурсов, а
|
||||||
|
также включение структур гражданского общества в
|
||||||
|
международный диалог борьбы с коррупцией.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p className="about-us__author">
|
||||||
|
Photo By Mzximvs VdB from Brussels, belgium - Road to
|
||||||
|
Issyk-Kul (south shore), CC BY-SA 2.0
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AboutUs;
|
||||||
@@ -2,15 +2,15 @@ import Typography from "@/shared/ui/components/Typography/Typography";
|
|||||||
import "./CreateReport.scss";
|
import "./CreateReport.scss";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "KG ROAD | Написать обращение",
|
title: "KG ROAD | Написать обращение",
|
||||||
description:
|
description: "Написать обращение KG ROAD",
|
||||||
"Написать обращение Kyrgyzstan Transperency International",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DynamicForm = dynamic(
|
const DynamicForm = dynamic(
|
||||||
() => import("@/widgets/ReportForm/ReportForm"),
|
() => import("@/widgets/forms/ReportForm/ReportForm"),
|
||||||
{
|
{
|
||||||
ssr: false,
|
ssr: false,
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ const DynamicForm = dynamic(
|
|||||||
const CreateReport = () => {
|
const CreateReport = () => {
|
||||||
return (
|
return (
|
||||||
<div className="create-report page-padding">
|
<div className="create-report page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
<Typography element="h2">Написать обращение</Typography>
|
<Typography element="h2">Написать обращение</Typography>
|
||||||
|
|
||||||
<DynamicForm />
|
<DynamicForm />
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import "../globals.scss";
|
||||||
|
import "./App.scss";
|
||||||
|
import "@/shared/fonts/fonts.scss";
|
||||||
|
import Navbar from "@/widgets/Navbar/Navbar";
|
||||||
|
import Footer from "@/widgets/Footer/Footer";
|
||||||
|
import { NextIntlClientProvider, useMessages } from "next-intl";
|
||||||
|
import { Providers } from "./Providers";
|
||||||
|
|
||||||
|
export default function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: { locale: string };
|
||||||
|
}>) {
|
||||||
|
const messages = useMessages();
|
||||||
|
return (
|
||||||
|
<html lang={params.locale}>
|
||||||
|
<body>
|
||||||
|
<NextIntlClientProvider
|
||||||
|
locale={params.locale}
|
||||||
|
messages={messages}
|
||||||
|
>
|
||||||
|
<Providers>
|
||||||
|
<Navbar />
|
||||||
|
<div className="app">{children}</div>
|
||||||
|
<Footer />
|
||||||
|
</Providers>
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
.news {
|
.news {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 40px;
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.news {
|
.news {
|
||||||
gap: 30px;
|
h2 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 550px) {
|
@media screen and (max-width: 550px) {
|
||||||
.news {
|
.news {
|
||||||
gap: 20px;
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import NotFound from "@/widgets/NotFound/NotFound";
|
||||||
|
|
||||||
|
const error = () => {
|
||||||
|
return <NotFound />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default error;
|
||||||
@@ -6,11 +6,33 @@ import Image from "next/image";
|
|||||||
import message from "./icons/message.svg";
|
import message from "./icons/message.svg";
|
||||||
import calendar from "./icons/calendar.svg";
|
import calendar from "./icons/calendar.svg";
|
||||||
import ReviewSection from "@/widgets/ReviewSection/ReviewSection";
|
import ReviewSection from "@/widgets/ReviewSection/ReviewSection";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { id: string };
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const response = await apiInstance.get<INews>(
|
||||||
|
`/news/${params.id}/`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: response.data.title,
|
||||||
|
description: response.data.description,
|
||||||
|
openGraph: {
|
||||||
|
images: [response.data.image],
|
||||||
|
type: "article",
|
||||||
|
publishedTime: response.data.created_at,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const NewsDetails = async ({
|
const NewsDetails = async ({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: { id: string };
|
params: { id: string; новость: string };
|
||||||
}) => {
|
}) => {
|
||||||
const getNewsById = async () => {
|
const getNewsById = async () => {
|
||||||
const response = await apiInstance.get<INews>(
|
const response = await apiInstance.get<INews>(
|
||||||
@@ -39,6 +61,7 @@ const NewsDetails = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="news-details page-padding">
|
<div className="news-details page-padding">
|
||||||
|
<BreadCrumbs />
|
||||||
<Typography element="h2">{data.title}</Typography>
|
<Typography element="h2">{data.title}</Typography>
|
||||||
|
|
||||||
<div className="news-details__img">
|
<div className="news-details__img">
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import "./News.scss";
|
||||||
|
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||||
|
import NewsList from "@/widgets/NewsList/NewsList";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
|
||||||
|
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 | Новости",
|
||||||
|
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 = ({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: {
|
||||||
|
["страница-новостей"]: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="news page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
|
||||||
|
<Typography element="h2">Новости</Typography>
|
||||||
|
|
||||||
|
<NewsList searchParams={searchParams} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default News;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import NotFound from "@/widgets/NotFound/NotFound";
|
||||||
|
import "../globals.scss";
|
||||||
|
import "@/shared/fonts/fonts.scss";
|
||||||
|
|
||||||
|
export default function NotFoundPage() {
|
||||||
|
return <NotFound />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import Header from "@/widgets/home/Header/Header";
|
||||||
|
import StatisticsSection from "@/widgets/home/StatisticsSection/StatisticsSection";
|
||||||
|
import RatingSection from "@/widgets/home/RatingSection/RatingSection";
|
||||||
|
import NewsSection from "@/widgets/home/NewsSection/NewsSection";
|
||||||
|
import MapSection from "@/widgets/home/MapSection/MapSection";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
|
||||||
|
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 | Главная",
|
||||||
|
description:
|
||||||
|
"Главная страница KG ROAD | Сделаем дороги безопасными!",
|
||||||
|
openGraph: {
|
||||||
|
title: "KG ROAD | Главная",
|
||||||
|
description:
|
||||||
|
"Главная страница KG ROAD | Сделаем дороги безопасными!",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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 ({
|
||||||
|
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,102 @@
|
|||||||
|
.privacy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
li {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 34px;
|
||||||
|
color: rgb(62, 50, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__author {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.privacy {
|
||||||
|
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) {
|
||||||
|
.privacy {
|
||||||
|
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,iVBORw0KGgoAAAANSUhEUgAAAeEAAADxCAYAAAD86w8hAAAABHNCSVQICAgIfAhkiAAAIABJREFUeJysvVmPLUlyJvaZeZzc7q17q5vN5rREScRAgIDRwwDzNNC79NP1QA0JisMhNc2tSQ63atZyt8yzhJvpwczczT3iZGY1x4FC3TwR4Yu5uy2fmZvTH/5/f6ufH1cAK8AFqgoAKMSoKmAQ2H6CEAAIAAZDwMyoWsBYEU+ICKQKIgKYARGIfd6eqRC4CAQMEoKi2gvMELF6QQIFwLXYv6MTyt4/goiAFBAmKAGkAKMX6+lcZPrb3lDVNvbW12iDCBQ0gFr/AEDU2k3vxbe5rlzys2gzvpvbtb+jnt7P/P7mN6d5/22mwvW/7Rvd+RvYo2R8q6pQVrByG1u0rTqOLfd3Hsu1v2f6jm2MdJjbYV9TUIaibr65Vp6bwzz2536Z69ibu5fK3phyYRCqzmv65TL39RpNr/Un3iUFlHwtE4Gx3Q/WoIIKA6tAeKRFp5OAqKT9Udv6mddL64fvQTCDtLZnrW5lGCfB5lmmRu9zSRQav6m19r0P44csi//b3mddYFzigv09M64LEQGhNP7G3l5vnQGI1aTcvyVBTXMR/DCvBFK1ulFB8HGRNB4a9Qz0zN/HN/k31nFufV8xLY2GoiuYjC7B2wkFQsaXFt+TNpYKgIf6hr5p8Je+RgBOMkmcQp3a878BBolCKNo0mRb9iTV7kQsWWlBJQBo1MFSr0df/DcB4HSrU50MJYJS+jsvSeLhq9foEStY7k5uCh/sDlsul4vHpCQQAtKJCwXp90RMRiCueE28xqOGbxtyNMWdmD2WAVu+colDe6H0h5Elo/VMAvGXogAnlPUH3UpkF5fzbHkPM9Hqeef/ryktjea79TvdRwF3v774ac61NEtssL76/894u435Fm8+Vf43Q25a81TurIzIFENLXt0DBGJWHrBDYhh3Xy2uF32+yvqL+plB6W1UAcmX6N61z7t/eMwCNJntjCAXanq2DQhzv/pj+saIx7b39H7/tj/vywnhrn1MCSM+Dogmc2oihBaCa6sgKqSYF4wyR2ANrU0Tinb19KvFv/z8zo0a/5PVz2vgcxnljIgCXZAxslaGhP3Jsbdo6Pz/TaJrwl/oW63VeA3uGSBqPCVl7jysgrJDgeWm8rf8EsChUL52/v9S36W8hNOUg12uvKnw4jQRaFbXeYlFVQBXEDAVQQPC90gnP6j+59QqGACjaLUPTcASqxRm9wmhHacEpWuWZudMKSAHYBDCjoK1XyYRvQ4Nq/16rtInKC9YmgrDH4DQt3nmT7wnXTI/8bP77mqCeN9LMgPc2zK5ikTYDgIGxznVsrUrdraO/nxkDATua8F4f2/uFQDvPNt/sCOofKzCfYzDPCbZBsUzCMH87vD+8Q+2/Ya4VvrvsnZLqzJZTa1sVxNTWXl4rRXjcyb7+pXAXpIWh9fXWb2OsPCpUhbuSO6AG07fzGt0TjFnRiPryuBcelWlmBnzfCgAhbUpz7nOmY1Zoniti3Nh4va/IYMpEBdSsVkfRdsYyj3HgKyFsGq/c609C+Fq98/7s1rDV6Sgggn82cYF5yOzoA3HQqYKgIBCIAVMUFUTlWeW28QL7o/0eBg+3vdoYsr+mIOqKqfVbnOdXf6cjG7aGCoAVoIR47pRZ8Wg7a6DfTMv+f21zD7AqKlvbPH1lBqVCVkUhNiGZeBMJANLN7F4zwkrI0bSH+la2Dq3akVVyY3cRBJwrBhVTiFgjEEOh/m8BNyuXfaCFCaqGgqoWX6QZOiip49lWjhKWrQIKVDJSEcisYu4DDsFAFGqO2x4TUw/iyAs8PSY5FkQwufmdPeGZ28p/b5h4Ykxz/+bv8qTGN1kT3RPYMyO/1odZk53H6P9qm+66NX1dWM59fE752Rt3Xtx7ysc8N/H7nkWWlau53va+KrLWMFvjRAXQOrwTa7iPo8NMlBRDIhPGUSp1A2DTn9UEK4iAAhCxQZ8gKJPXBUABJkOLZktgb/x79GqjYHLNfxSsswU1r50oWdgSEbgqqJQGTyONUeGoFNtYwWTjCFSGbFyhWFxTfEspm/1wrRjzTmveBbNJXqcBheAYmf41y/uasra3/vbW3VzHTMvn6txT6vO7e0oSkYllq36iV7NG99EuU8jzL3tr6fr6ykq8CMAcKJz9VsqolI3rzAR8h3ODp1xtbmo31cvhMnE5owoqsH0d3xQXiswmdCnWGCPTTQyuAMiUDQKgLmQ095XZ+6qg+JYJpBVFGUowxKMUkzv/95/8pX7/4QkhJ4ioidxOmHGSQghSYah0eLpbZUGtELCjRSUiwySISIOqjCgMWgVS1HjgCxbSvCGfExTXBNJLdcwIynObbG9T7v39nNBS1YHpvDS211gIc5tWX0W2hnLbuW+zNq1qPr4ZVZqZRP4urJECcphmHEuMdc96nJ/tMaN5jOH7yYx2VQErd0WOo/7om6JCmwAd90B3pWRov/kl4T4qOSDDkM/NRVi6ZhGq+U5daMRzpe13slZ7v3Rl7arSE4xIFYsSVtrS7traCat+T2EUEYN+YXt2rkMIKDL2Y0C20t/zGHM7AFDR3wtFZob2e/tGiwUE9ZiFC8StHfJxaWPS1wTnho54/T7zAQKQBjFbPdT6l+v+MZb+3L9X8zCdLXvFrKeN81wgsu7Wfe2315fRrXOtzAbHHo1iTVwzlKKEsSUiIFGgLAArwi8NUQgTStRfGaCKsMzD1ZRqTOMQiIxK+NB/MV5Uw1L3dffV/Q3o9//kV/rD50/NyjVfxoJw2I/N5UaTX5byoM15zbyMgwdtfh8I6Bs2W6/hN35pop+zhK6V1wjt54T5a0soHM9ZJdf695JluCfA94RTfm8PAh3/rSDZh/ArdFN/CLcfO769cb625LHQaoxYkqXJuq9sKfcgw12robpFerW4AlBXEyBADzgiMYU0aFmlv7PXfw8SU1UoFWMKw0s9gKYQYxUPcFETeCJiAswtZVnrMJ6ahlHE+Alr32t7/qtdRqvjs7xf83Ol5+sJATArUeLKBteX/b5mQcjgzmBFo0P0R1Ud5YhOCkDF4l1gwUvstA2FS0jAKxrysDo8nvuf1926rm3tz+O8XkKR238nIzF778y/7wnd1ygLe3t4o0BtlLoIwsxC+opynurtz9mV1+v9v1auGTYZ7Zp54WuVAwsq9OAw7YGm196N9rvxYrFMWalQdSUV6AIdQEYdBABE8ObNPej3/+Sv9MOX4zCQHChFym6ed8FrBDSfiICASThbKz7RO9bStXItsCcLj2vCNYSdqjZLa45mvKbp/vcQtm0MLyyA5wTmc8J2v2y1yWxlgsnmaNoo5Eyf2YSFEtu/UxSliABlAdfaNUzaWtLZ6jQh1xGUVYGFzA8yjCdZSHtj3at/jx7DxkR1oeUQo0XnQIWbVRpBSCLShJeuRkeBtv5zkY5WKVvwA2BqLCXhWmkUPqwWM6HVFFmIRWQKtf6Y/9iEgvU5lFJpwlk19pa2+VDVbkXuWZBJEKlqE24igkWpCeF4T5VQiN1K27H+kpKRy64QnlxGpGgWQ363vQ/sCu3ddR7jCkRAFLpwV1rS89zHmSbtmWrv8xUrfFZQ6sYruC0NGUj86TVIw6ZvaX1HH/YQgfn3jNQJtKEFWUH+MVbrc3xnVrpfY2hcQxj+e5VZybvWl725sSJNeDZDRSs0R+snIdz4kwKCCFQWU6gbD9P2+6Y7LmMf7u4tMGujGbGiBT6xO/uJWwQfNKJABYaMs2++AiIL5W/MWoE9YbFrqfnRBV6KbTYd/an7Gpb9XUppfwtGSHWP6HmyAhqfBfNeX/eez78/p5HFxrgmcPO/96w1733+qsO8TuJCBGJuke6leGReC7RSqEkDsH+kWCwiMkLwmaF6Abj4/BN60BaQ55OZUS8rChcgje/ggm5ZE6MMBpgZJdCEYht3KHG8ZWSDIhb+G4tDMW3bpA1AAJdi/RYFisGTFHN/qaaocIddwQqVAtAIqZeFIBGU4rEPSh5sQmunRVWAi3NEwkKL9aeNR/376sEw2gJ4VD0CQyMw8QIId4WBndZQj14FoCkqMzEGcpoUMIT6iQd7QCACVKoJtEAOnkFJYr81BhWrjzuCoFVAC4OYQMF10lRnQdmsyiyw9/ZZOylCILE+Qmz1tYA4yo1YmzbfU/Cm/00BQTtMD+1rMPrUqlIYrP2Sjk4EFMbqBjgrAIWfGBRQg8KfQYya5e79I4ClH4sBWxCPUpwqCf4LrOgoRSHzfRof7LEJRq5QrqwvK+kQu9AJBlT0NZBjDGbeFfzTUE5pxs8en57LjCq8DknILs8gGQ3rtlnJs3KyM9ZKbHo2OwDl4xPlcVkFy2Ee1nIcTTL5uPp+7ftOlcDOpGOta6kgh6/p9//kr/SHz09OkG5KhxBrRcaNsgeFzMR4rlyzBufnLdQ82vENttePPcFp0Xlb/9xz1u9e5N41OK3D752BiQgKShKeFvEd/hWA3RfhMJgyhFaIC7siGWrpvptAHzQxjYBDRcIq0QGmHJWhkY7k/hCrM/n43fexB7VFX1r/pFg8gW9qY1ZujWKFYtndNM3d4dZhtrBm6+ml0ufSNykutvBpab9tvklwMDAGMhH8bLoqSjF/8cgcetSn1dGP7HVFp8NvLUq2WWaOKrkHuiFIohDdi5r2bxJE3c5QunCy80YzEwt7+kphs+xVCjJz6/R/nd/uNaVZT75em6IVCoYHZW2t5kDiZhSJwNBm9cGVgabscQGkdjpN6ykUlYYapd83wpLsgBWqQLm0tUwKVBXMAqSNh7sCw6DBoqc4tNW+Mzi0sjSoPFAT90SMyn0ej2jaNwwoQbHuC7Wk9Ga+sBd3sCcQr+3NQF/2fg+5En/H/omLine 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,114 @@
|
|||||||
|
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||||
|
import "./Privacy.scss";
|
||||||
|
import Image from "next/image";
|
||||||
|
import header from "./assets/header.svg";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
|
||||||
|
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 | О нас",
|
||||||
|
description:
|
||||||
|
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
|
||||||
|
openGraph: {
|
||||||
|
title: "KG ROAD | О нас",
|
||||||
|
description:
|
||||||
|
"Transparency International - Кыргызстан - филиал международной организации Transparency International в Кыргызской Республике.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadata = data.filter((tag) => tag.page === "privacy")[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 Privacy = () => {
|
||||||
|
return (
|
||||||
|
<div className="privacy page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
<Typography element="h2">Политика конфиденциальности</Typography>
|
||||||
|
<div className="privacy__descriptions">
|
||||||
|
<ol>
|
||||||
|
<li><span> Общие положения</span></li>
|
||||||
|
</ol>
|
||||||
|
<p><span>Настоящие Положение о политике конфиденциальности определяет порядок обработки и защиты информации о физических лицах (далее — Пользователи), пользующихся почтовой рассылкой, формой обратной связи сайта, электронными почтовыми адресами, расположенными на доменном имени </span><a href="https://kgroad.org/"><span>https://kgroad.org/</span></a><span> (далее — Сайт).</span></p>
|
||||||
|
<p><span>Мы разработали Политику Конфиденциальности, которая описывает, как мы осуществляем обработку персональных данных — любые действия (операции) или совокупность действий (операций), совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение (обновление, изменение), извлечение, использование, передачу (распространение, предоставление, доступ), обезличивание, блокирование, удаление, уничтожение персональных данных.</span></p>
|
||||||
|
<p><span>Отношения, связанные с обработкой персональных данных и информации о пользователях Сайта, регулируются настоящим Положением.</span></p>
|
||||||
|
<p><span>Настоящая Политика Конфиденциальности регулирует любой вид обработки персональных данных и информации личного характера (любой информации, позволяющей установить личность, и любой иной информации, связанной с этим) о физических лицах, которые являются пользователями Сайта.</span></p>
|
||||||
|
<p><span>Настоящая Политика распространяется на обработку личных, персональных данных, собранных любыми средствами, как активными, так и пассивными, как через Интернет, так и без его использования, от лиц, находящихся в любой точке мира.</span></p>
|
||||||
|
<p>2. Сбор персональных данных</p>
|
||||||
|
<p><span>Целью обработки персональных данных является выполнение обязательств Оператора перед Пользователями в отношении использования Сайта и его сервисов.</span></p>
|
||||||
|
<p><span>Обработка персональных данных пользователей осуществляется с согласия субъекта персональных данных на обработку его персональных данных.</span></p>
|
||||||
|
<p><span>Под персональными данными понимается любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу (субъекту персональных данных) и которая может быть использована для идентификации определенного лица либо связи с ним.</span></p>
|
||||||
|
<p><span>Мы можем запросить у Вас имя, фамилию, отчество и адрес электронной почты для того, чтобы включить Вас в почтовую рассылку с Вашего согласия, а также для того, чтобы ответить Вам, если Вы обратились к нам через форму обратной связи на Сайте.</span></p>
|
||||||
|
<p><span>3. Хранение и использование персональных данных</span></p>
|
||||||
|
<p><span>Персональные данные Пользователей хранятся исключительно на электронных носителях и обрабатываются с использованием автоматизированных систем.</span></p>
|
||||||
|
<p><span>КАК МЫ ИСПОЛЬЗУЕМ ВАШУ ПЕРСОНАЛЬНУЮ ИНФОРМАЦИЮ</span></p>
|
||||||
|
<p><span>Собираемые нами персональные данные позволяют направлять Вам по электронной почте уведомления о новостях, выходе новых антикоррупционных расследований и различных событиях. Также ваши персональные данные могут быть использованы при ответе на ваше обращение, направленное через форму обратной связи или на наш адрес электронной почты, расположенные на Сайте. Если Вы не желаете быть включенным в наш список рассылки, Вы можете в любое время отказаться от рассылки путём информирования нас по указанным контактам для обратной связи.</span></p>
|
||||||
|
<p><span>СБОР И ИСПОЛЬЗОВАНИЕ ИНФОРМАЦИИ, НЕ ЯВЛЯЮЩЕЙСЯ ПЕРСОНАЛЬНОЙ</span></p>
|
||||||
|
<p><span>Мы также собираем персональные данные, не являющиеся персональными − данные, не позволяющие прямо ассоциировать их с каким-либо определённым лицом. Мы можем собирать, использовать, передавать и раскрывать информацию, не являющуюся персональной, для любых целей. Ниже приведены примеры информации, не являющейся персональной, которую мы собираем, и как мы можем её использовать.</span></p>
|
||||||
|
<p><span>Мы можем собирать персональные данные, такие как: источник перехода на Сайт, особенности поведения пользователя на Сайте, количество просмотренных на Сайте страниц. Такие данные обезличены и не являются персональными. Эти данные собираются для того, чтобы лучше понимать, какая информация интересна посетителям Сайта и улучшать качество контента.</span></p>
|
||||||
|
<p>4. Передача персональных данных</p>
|
||||||
|
<p><span>Персональные данные Пользователей не передаются каким-либо третьим лицам. Пользователи, которые передали нам свои персональные данные до вступления в силу настоящих Правил автоматически соглашаются на использование свои персональных данных. В тоже время, они всегда могут отозвать свое согласие на использование персональных данных через форму обратной связи.</span></p>
|
||||||
|
<p><span>Оператор осуществляет блокирование персональных данных, относящихся к соответствующему Пользователю, с момента обращения или запроса Пользователя или его законного представителя, в случае выявления недостоверных персональных данных или неправомерных действий.</span></p>
|
||||||
|
<p>5. Уничтожение персональных данных</p>
|
||||||
|
<p><span>Персональные данные пользователя уничтожаются при отзыве субъектом персональных данных согласия на обработку персональных данных.</span></p>
|
||||||
|
<p><span>ИДЕНТИФИКАЦИОННЫЕ ФАЙЛЫ (СOOKIES) И ИНЫЕ ТЕХНОЛОГИИ</span></p>
|
||||||
|
<p><span>Веб-сайт, интерактивные услуги и приложения, сообщения электронной почты и любые иные коммуникации от нашего лица могут использовать</span><a href="https://www.re-store.ru/oferta/cookies/"> <span>идентификационные файлы cookies</span></a><span> и иные технологии, такие как пиксельные ярлыки (pixel tags), веб-маяки (web beacons). Соответственно при пользовании веб-сайтом и иными услугами на Вашем устройстве может возникнуть всплывающее окно с уведомлением об использовании cookies.</span></p>
|
||||||
|
<p><span>Указанные технологии помогают нам лучше понимать поведение пользователей, сообщают нам, какие разделы нашего сайта были посещены пользователями, и измеряют эффективность рекламы и сетевых поисков. Мы рассматриваем информацию, собираемую файлами cookies и иными технологиями как информацию, не являющуюся персональной.</span></p>
|
||||||
|
<p><span>Вы можете контролировать cookies следуя инструкциям Вашего браузера или устройства. Как правило, Вы можете изменить настройки cookies в разделе «Настройки» или «Системные настройки».</span></p>
|
||||||
|
<p><span>Как и в случае большинства веб-сайтов, мы собираем некоторую информацию автоматически и храним её в файлах статистики. Такая информация включает в себя адрес Интернет-протокола (IP-адрес), тип и язык браузера, информацию о поставщике Интернет-услуг, страницы отсылки и выхода, сведения об операционной системе, отметку даты и времени, а также сведения о посещениях. Мы используем такую информацию для понимания и анализа тенденций, администрирования сайта, изучения поведения пользователей на сайте и сбора демографической информации о нашем основном контингенте пользователей в целом. Организация может использовать такую информацию в своих маркетинговых целях.</span></p>
|
||||||
|
<p><span>В некоторых наших сообщениях электронной почты мы используем интерактивные ссылки на информацию, размещённую на Сайте. Когда пользователи проходят по таким ссылкам, прежде чем они попадают на страницу назначения на нашем веб-сайте, их запросы проходят отдельную регистрацию. Мы отслеживаем такие «проходные» данные, для того чтобы помочь нам определить интерес к отдельным темам и измерить эффективность наших коммуникаций с пользователями. Если Вы предпочитаете, чтобы Ваши обращения не отслеживались подобным образом, Вы не должны проходить по текстовым или графическим ссылкам в сообщениях электронной почты.</span></p>
|
||||||
|
<p><span>Пиксельные ярлыки позволяют нам направлять сообщения электронной почты в формате, читаемом потребителями, и сообщают нам, были ли такие сообщения прочитаны. Мы можем использовать такую информацию для ограничения количества направляемых потребителям сообщений или прекращения их направления.</span></p>
|
||||||
|
<p>6. Защита персональных данных</p>
|
||||||
|
<p><span>Мы предпринимает меры предосторожности для обеспечения защиты Ваших персональных данных в целях обеспечения защиты персональных данных Пользователя от неправомерного или случайного доступа к ним, уничтожения, изменения, блокирования, копирования, распространения, а также от иных неправомерных действий третьих лиц.</span></p>
|
||||||
|
<p><span>ЦЕЛОСТНОСТЬ И СОХРАНЕНИЕ ПЕРСОНАЛЬНОЙ ИНФОРМАЦИИ</span></p>
|
||||||
|
<p><span>Мы будем хранить Ваши персональные данные и информацию в течение срока, необходимого для выполнения целей, описываемых в настоящей Политике Конфиденциальности.</span></p>
|
||||||
|
<p><span>Мы не собираем персональные данные о несовершеннолетних. Если нам станет известно о том, что мы получили персональные данные о несовершеннолетнем, мы предпримем меры для удаления такой информации в максимально короткий срок.</span></p>
|
||||||
|
<p><span>Мы настоятельно рекомендуем родителям и иным лицам, под чьим присмотром находятся несовершеннолетние (законные представители — родители, усыновители или попечители), контролировать использование несовершеннолетними веб-сайтов.</span></p>
|
||||||
|
<p><span>Оператор не несет ответственности за действия третьих лиц, получивших в результате использования Интернета или Услуг Сайта доступ к информации о Пользователе и за последствия использования данных и информации, которые, в силу природы Сайта, доступны любому пользователю сети Интернет.</span></p>
|
||||||
|
<p><span>ВОПРОСЫ ОТНОСИТЕЛЬНО КОНФИДЕНЦИАЛЬНОСТИ</span></p>
|
||||||
|
<p><span>Если у вас возн</span><span>икнут вопросы в отношении настоящей Политики Конфиденциальности или обработки данных, Вы можете связаться с нами по контактам обратной связи office@ti-russia.org.</span></p>
|
||||||
|
<p><span>7. Обращения пользователей</span></p>
|
||||||
|
<p><span>Пользователи вправе направлять Оператору свои запросы, в том числе запросы относительно использования их персональных данных, направления отзыва согласия на обработку персональных данных в письменной форме по адресу, указанному в разделе Общие положения настоящего положения, или в форме электронного документа, отправленного посредством </span><a href="https://kgroad.org/ru/support/"><span>формы обратной связи.</span></a></p>
|
||||||
|
<p><span>Оператор обязуется рассмотреть и направить ответ на поступивший запрос Пользователя в течение 30 дней с момента поступления обращения.</span></p>
|
||||||
|
<p><span>ДРУГОЕ</span></p>
|
||||||
|
<p><span>Посетитель сайта Организации, предоставляющий свои персональные данные и информацию, тем самым соглашается с положениями данной Политики Конфиденциальности.</span></p>
|
||||||
|
<p><span>Мы оставляем за собой право вносить любые изменения в Политику в любое время по своему усмотрению с целью дальнейшего совершенствования системы защиты от несанкционированного доступа к сообщаемым Пользователями персональным данным без согласия Пользователя. Когда мы вносим существенные изменения в Политику Конфиденциальности, на нашем сайте размещается соответствующее уведомление вместе с обновлённой версией Политики Конфиденциальности.</span></p>
|
||||||
|
<p><span>Действие настоящей Политики не распространяется на действия и интернет-ресурсы третьих лиц.</span></p>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Privacy;
|
||||||
@@ -14,13 +14,9 @@ const AuthGuard = ({ children }: { children: React.ReactNode }) => {
|
|||||||
};
|
};
|
||||||
await apiInstance.post("/token/verify/", data);
|
await apiInstance.post("/token/verify/", data);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
signOut({
|
||||||
if (error.response?.data.code === "token_not_valid") {
|
callbackUrl: "/",
|
||||||
signOut({
|
});
|
||||||
callbackUrl: "/",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6,13 +6,12 @@ import { AxiosError } from "axios";
|
|||||||
import { apiInstance } from "@/shared/config/apiConfig";
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { authConfig } from "@/shared/config/authConfig";
|
import { authConfig } from "@/shared/config/authConfig";
|
||||||
import { IProfile } from "@/shared/types/profile-type";
|
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "KG ROAD | Профиль",
|
title: "KG ROAD | Профиль",
|
||||||
description:
|
description: "Страница профиля KG ROAD",
|
||||||
"Страница профиля Kyrgyzstan Transperency International",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Profile = async ({
|
const Profile = async ({
|
||||||
@@ -44,6 +43,7 @@ const Profile = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile page-padding">
|
<div className="profile page-padding">
|
||||||
|
<BreadCrumbs />
|
||||||
<Typography element="h2">Личный кабинет</Typography>
|
<Typography element="h2">Личный кабинет</Typography>
|
||||||
<ProfileNav report_count={data?.report_count as number} />
|
<ProfileNav report_count={data?.report_count as number} />
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import ProfileTable from "@/widgets/ProfileTable/ProfileTable";
|
import ProfileTable from "@/widgets/tables/ProfileTable/ProfileTable";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const MyReports = async ({
|
const MyReports = async ({
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRouter } from "@/shared/config/navigation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
useEffect(() => {
|
||||||
|
router.push("/profile/personal");
|
||||||
|
}, []);
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
||||||
@@ -4,7 +4,7 @@ import ProfileAvatar from "@/features/ProfileAvatar/ProfileAvatar";
|
|||||||
import { apiInstance } from "@/shared/config/apiConfig";
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
import { authConfig } from "@/shared/config/authConfig";
|
import { authConfig } from "@/shared/config/authConfig";
|
||||||
import { IProfile } from "@/shared/types/profile-type";
|
import { IProfile } from "@/shared/types/profile-type";
|
||||||
import ProfileForm from "@/widgets/ProfileForm/ProfileForm";
|
import ProfileForm from "@/widgets/forms/ProfileForm/ProfileForm";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.report-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 75px;
|
||||||
|
&__container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.05fr 1fr;
|
||||||
|
gap: 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__map {
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1024px) {
|
||||||
|
.report-details {
|
||||||
|
&__container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__map {
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
grid-column: 1 / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 550px) {
|
||||||
|
.report-details {
|
||||||
|
&__container {
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import NotFound from "@/widgets/NotFound/NotFound";
|
||||||
|
|
||||||
|
const error = () => {
|
||||||
|
return <NotFound />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default error;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import "./ReportDetails.scss";
|
||||||
|
import { IReport } from "@/shared/types/report-type";
|
||||||
|
import ReviewSection from "@/widgets/ReviewSection/ReviewSection";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import ReportInformation from "@/widgets/report-details/ReportInformation/ReportInformation";
|
||||||
|
import ReportImages from "@/widgets/report-details/ReportImages/ReportImages";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
|
||||||
|
const DynamicMap = dynamic(
|
||||||
|
() => import("@/widgets/report-details/ReportMap/ReportMap"),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { id: string };
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const response = await apiInstance.get<IReport>(
|
||||||
|
`/report/${params.id}/`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `KG ROAD | ${response.data.description}`,
|
||||||
|
description: `${response.data.location[0].address}, ${response.data.description}`,
|
||||||
|
openGraph: {
|
||||||
|
title: `KG ROAD | ${response.data.description}`,
|
||||||
|
description: `${response.data.location[0].address}, ${response.data.description}`,
|
||||||
|
images: [response.data.image[0].image],
|
||||||
|
type: "article",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReportDetails = async ({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { id: string };
|
||||||
|
}) => {
|
||||||
|
const getReportDetails = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_BASE_API}/report/${params.id}/`,
|
||||||
|
{ cache: "no-store" }
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const report: IReport = await getReportDetails();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="report-details page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
<div className="report-details__container">
|
||||||
|
<ReportInformation
|
||||||
|
id={report.id}
|
||||||
|
location={report.location[0]}
|
||||||
|
date={report.created_at}
|
||||||
|
description={report.description}
|
||||||
|
category={report.category}
|
||||||
|
total_likes={report.total_likes}
|
||||||
|
author={report.author}
|
||||||
|
/>
|
||||||
|
<div className="report-details__map">
|
||||||
|
<DynamicMap
|
||||||
|
location={report.location}
|
||||||
|
category={report.category}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ReportImages images={report.image} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ReviewSection endpoint="report" id={+params.id} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportDetails;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import "@/shared/ui/auth-classes.scss";
|
||||||
|
import ForgotPasswordForm from "@/widgets/forms/ForgotPasswordForm/ForgotPasswordForm";
|
||||||
|
|
||||||
|
const ForgotPassword = () => {
|
||||||
|
return (
|
||||||
|
<div className="page-padding">
|
||||||
|
<BreadCrumbs />
|
||||||
|
<div className="auth-page">
|
||||||
|
<ForgotPasswordForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForgotPassword;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import "@/shared/ui/auth-classes.scss";
|
import "@/shared/ui/auth-classes.scss";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import sign_in_icon from "./icons/sign-in_icon.svg";
|
import sign_in_icon from "./icons/sign-in_icon.svg";
|
||||||
import SignInForm from "@/widgets/SignInForm/SignInForm";
|
import SignInForm from "@/widgets/forms/SignInForm/SignInForm";
|
||||||
import Link from "next/link";
|
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
import { Link } from "@/shared/config/navigation";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "KG ROAD | Вход",
|
title: "KG ROAD | Вход",
|
||||||
description: "Страница входа Kyrgyzstan Transperency International",
|
description: "Страница входа KG ROAD",
|
||||||
};
|
};
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "@/shared/ui/auth-classes.scss";
|
import "@/shared/ui/auth-classes.scss";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import key from "./icons/key.svg";
|
import key from "./icons/key.svg";
|
||||||
import ResetCodeForm from "@/widgets/ResetCodeForm/ResetCodeForm";
|
import ResetCodeForm from "@/widgets/forms/ResetCodeForm/ResetCodeForm";
|
||||||
|
|
||||||
const ResetCode = () => {
|
const ResetCode = () => {
|
||||||
return (
|
return (
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import "@/shared/ui/auth-classes.scss";
|
||||||
|
import Image from "next/image";
|
||||||
|
import mail from "./icons/mail.svg";
|
||||||
|
import ConfirmEmailForm from "@/widgets/forms/ConfirmEmailForm/ConfirmEmailForm";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
|
||||||
|
const ConfirmEmail = ({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="page-padding">
|
||||||
|
<BreadCrumbs />
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmEmail;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import "@/shared/ui/auth-classes.scss";
|
import "@/shared/ui/auth-classes.scss";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import flag from "./icons/flag.svg";
|
import flag from "./icons/flag.svg";
|
||||||
import Link from "next/link";
|
import SignUpForm from "@/widgets/forms/SignUpForm/SignUpForm";
|
||||||
import SignUpForm from "@/widgets/SignUpForm/SignUpForm";
|
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
import { Link } from "@/shared/config/navigation";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "KG ROAD | Регистрация",
|
title: "KG ROAD | Регистрация",
|
||||||
description:
|
description:
|
||||||
"Страница регистрации Kyrgyzstan Transperency International",
|
"Страница регистрации KG ROAD",
|
||||||
};
|
};
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
.statistics {
|
.statistics {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 40px;
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.statistics {
|
.statistics {
|
||||||
gap: 30px;
|
h2 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 550px) {
|
@media screen and (max-width: 550px) {
|
||||||
.statistics {
|
.statistics {
|
||||||
gap: 20px;
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||||
|
import "./Statistics.scss";
|
||||||
|
import StatisticsTable from "@/widgets/tables/StatisticsTable/StatisticsTable";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
|
||||||
|
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 | Статистика",
|
||||||
|
description: `Статистика по населенным пунктам Кыргызстана`,
|
||||||
|
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 = ({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: { ["поиск-населенного-пункта"]: string };
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="statistics page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
<Typography element="h2">Статистика</Typography>
|
||||||
|
<StatisticsTable searchParams={searchParams} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Statistics;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
.support {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 135%;
|
||||||
|
color: #686b7c;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
input, textarea {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 957px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid rgb(221, 222, 226);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 135%;
|
||||||
|
color: #09090b;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 276px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn,
|
||||||
|
&__btn_active {
|
||||||
|
margin-top: 50px;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: rgb(158, 167, 175);
|
||||||
|
cursor: auto;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 19px;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btn_active {
|
||||||
|
background-color: rgb(57, 152, 232);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__logout {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 550px) {
|
||||||
|
.support {
|
||||||
|
&__btn,
|
||||||
|
&__btn_active {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__logout {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,126 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||||
|
import "./Support.scss";
|
||||||
|
import Image from "next/image";
|
||||||
|
import header from "./assets/header.svg";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const Support = () => {
|
||||||
|
|
||||||
|
const [isSent, setIsSent] = useState(false);
|
||||||
|
const [userName, setUserName] = useState("");
|
||||||
|
const [phone, setPhone] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit: React.MouseEventHandler<
|
||||||
|
HTMLFormElement
|
||||||
|
> = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: userName,
|
||||||
|
phone: phone,
|
||||||
|
email: email,
|
||||||
|
message: message
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const res = await apiInstance.post(
|
||||||
|
"/support/",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
setIsSent(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function buttonIsActive()
|
||||||
|
{
|
||||||
|
return message === "" || userName === "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="support page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
<Typography element="h2">Поддержка</Typography>
|
||||||
|
<div className="support__descriptions">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<span id="blue-point" /> Напишите нам
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{!isSent ? (
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="support">
|
||||||
|
<div className="support__input">
|
||||||
|
<label>Как вас зовут*:</label>
|
||||||
|
<div>
|
||||||
|
<input name="name"
|
||||||
|
type="text"
|
||||||
|
value={userName}
|
||||||
|
onChange={(e) => setUserName(e.target.value)}
|
||||||
|
/></div>
|
||||||
|
</div>
|
||||||
|
<div className="support__input">
|
||||||
|
<label>Телефон:</label>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
name="phone"
|
||||||
|
type="phone"
|
||||||
|
value={phone}
|
||||||
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="support__input">
|
||||||
|
<label>Email:</label>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="support__input">
|
||||||
|
<label>Сообщение*:</label>
|
||||||
|
<div>
|
||||||
|
<div className="support__input">
|
||||||
|
<textarea name="review"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
|
||||||
|
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
className={`support__btn${
|
||||||
|
buttonIsActive() ? "" : "_active"
|
||||||
|
}`}
|
||||||
|
disabled={buttonIsActive()}>Отправить</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<p>Спасибо за ваше сообщение!</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Support;
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
.volunteers {
|
.volunteers {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 40px;
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.volunteers {
|
.volunteers {
|
||||||
gap: 30px;
|
h2 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 550px) {
|
@media screen and (max-width: 550px) {
|
||||||
.volunteers {
|
.volunteers {
|
||||||
gap: 20px;
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import Typography from "@/shared/ui/components/Typography/Typography";
|
||||||
|
import "./Volunteers.scss";
|
||||||
|
import VolunteersTable from "@/widgets/tables/VolunteersTable/VolunteersTable";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import BreadCrumbs from "@/features/BreadCrumbs/BreadCrumbs";
|
||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { IMetatag } from "@/shared/types/metatag-type";
|
||||||
|
|
||||||
|
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 | Волонтеры",
|
||||||
|
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 = () => {
|
||||||
|
return (
|
||||||
|
<div className="volunteers page-padding">
|
||||||
|
<BreadCrumbs homeRequired />
|
||||||
|
|
||||||
|
<Typography element="h2">Волонтеры</Typography>
|
||||||
|
<VolunteersTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Volunteers;
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -4,10 +4,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont,
|
font-family: "Tilda Sans";
|
||||||
"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
|
||||||
"Helvetica Neue", sans-serif;
|
|
||||||
// font-family: "Tilda Sans";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import type { Metadata } from "next";
|
import { ReactNode } from "react";
|
||||||
import "./globals.scss";
|
|
||||||
import "./App.scss";
|
|
||||||
// import "@/shared/fonts/fonts.scss";
|
|
||||||
import { Providers } from "./Providers";
|
|
||||||
import Navbar from "@/widgets/Navbar/Navbar";
|
|
||||||
import Footer from "@/widgets/Footer/Footer";
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
<<<<<<< HEAD
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -13,6 +12,8 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Providers>
|
<Providers>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@@ -20,6 +21,12 @@ export default function RootLayout({
|
|||||||
<Footer />
|
<Footer />
|
||||||
</Providers>
|
</Providers>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
{<GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS || ""} />}
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
=======*/
|
||||||
|
export default function RootLayout({ children }: Props) {
|
||||||
|
return children;
|
||||||
|
//>>>>>>> ali
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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,15 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import NotFound from "@/widgets/NotFound/NotFound";
|
||||||
|
import "./globals.scss";
|
||||||
|
import "@/shared/fonts/fonts.scss";
|
||||||
|
|
||||||
|
export default function NotFoundPage() {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<NotFound />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const page = () => {
|
|
||||||
return <div>page</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default page;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import "@/shared/ui/auth-classes.scss";
|
|
||||||
import ForgotPasswordForm from "@/widgets/ForgotPasswordForm/ForgotPasswordForm";
|
|
||||||
|
|
||||||
const ForgotPassword = () => {
|
|
||||||
return (
|
|
||||||
<div className="auth-page">
|
|
||||||
<ForgotPasswordForm />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ForgotPassword;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import Image, { StaticImageData } from "next/image";
|
import Image, { StaticImageData } from "next/image";
|
||||||
import "./NewsCard.scss";
|
import "./NewsCard.scss";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface INewsCard {
|
interface INewsCard {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -19,6 +20,7 @@ const NewsCard: React.FC<INewsCard> = ({
|
|||||||
description,
|
description,
|
||||||
date,
|
date,
|
||||||
}: INewsCard) => {
|
}: INewsCard) => {
|
||||||
|
const t = useTranslations("general");
|
||||||
const sliceTitle = (title: string) => {
|
const sliceTitle = (title: string) => {
|
||||||
if (title.length > 35) {
|
if (title.length > 35) {
|
||||||
return `${title.slice(0, 35)}...`;
|
return `${title.slice(0, 35)}...`;
|
||||||
@@ -56,8 +58,14 @@ const NewsCard: React.FC<INewsCard> = ({
|
|||||||
<p>{sliceDescription(description)}</p>
|
<p>{sliceDescription(description)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link href={`/news/${id}`} className="news-card__more-btn">
|
<Link
|
||||||
Подробнее
|
href={{
|
||||||
|
pathname: `/news/${id}`,
|
||||||
|
query: { новость: title },
|
||||||
|
}}
|
||||||
|
className="news-card__more-btn"
|
||||||
|
>
|
||||||
|
{t("details")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
.breadcrumbs {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
span,
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
gap: 8px;
|
||||||
|
color: rgb(72, 159, 225);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: rgb(50, 48, 58);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Link, usePathname } from "@/shared/config/navigation";
|
||||||
|
import "./BreadCrumbs.scss";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import Image from "next/image";
|
||||||
|
import chevron from "./icons/chevron-right.svg";
|
||||||
|
|
||||||
|
interface IBreadcrumbsProps {
|
||||||
|
homeRequired?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BreadCrumbs: React.FC<IBreadcrumbsProps> = ({
|
||||||
|
homeRequired,
|
||||||
|
}: IBreadcrumbsProps) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const query = useSearchParams().get("новость");
|
||||||
|
const routes = pathname.split("/").filter((route) => route !== "");
|
||||||
|
const tRoutes: Record<string, string> = {
|
||||||
|
"about-us": "О нас",
|
||||||
|
"create-report": "Написать обращение",
|
||||||
|
news: "Новости",
|
||||||
|
profile: "Профиль",
|
||||||
|
"my-reports": "Мои обращения",
|
||||||
|
personal: "Личные данные",
|
||||||
|
report: "Обращение",
|
||||||
|
"sign-in": "Войти в аккаунт",
|
||||||
|
"forgot-password": "Восстановление пароля",
|
||||||
|
"reset-code": "Сброс пароля",
|
||||||
|
"sign-up": "Регистрация",
|
||||||
|
"confirm-email": "Потверждение почты",
|
||||||
|
statistics: "Статистика",
|
||||||
|
volunteers: "Волонтеры",
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="breadcrumbs">
|
||||||
|
{homeRequired && (
|
||||||
|
<Link href="/">
|
||||||
|
Главная <Image src={chevron} alt="Chevron Right Icon" />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{routes.map((route, i, array) => {
|
||||||
|
if (routes.length === 1 && routes[0] === "profile")
|
||||||
|
return null;
|
||||||
|
if (parseInt(route)) {
|
||||||
|
if (routes[0] === "news") {
|
||||||
|
return <span key={query}>{query}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i === array.length - 1) {
|
||||||
|
return <span key={route}>{tRoutes[route]}</span>;
|
||||||
|
} else {
|
||||||
|
return route === "report" ? (
|
||||||
|
<span key={route}>{tRoutes[route]}</span>
|
||||||
|
) : (
|
||||||
|
<Link key={route} href={`/${route}`}>
|
||||||
|
{tRoutes[route]}
|
||||||
|
<Image src={chevron} alt="Chevron Right Icon" />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BreadCrumbs;
|
||||||
@@ -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="clip7_52716">
|
||||||
|
<rect id="chevron-right" width="16.000000" height="16.000000" fill="white" fill-opacity="0"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<rect id="chevron-right" width="16.000000" height="16.000000" fill="#FFFFFF" fill-opacity="0"/>
|
||||||
|
<g clip-path="url(#clip7_52716)">
|
||||||
|
<path id="Vector" d="M6 4L10 8L6 12" stroke="#667085" stroke-opacity="1.000000" stroke-width="1.000000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 651 B |
@@ -1,10 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import "./GoogleButton.scss";
|
import "./GoogleButton.scss";
|
||||||
import google from "./icons/google.svg";
|
import google from "./icons/google.svg";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
|
||||||
const GoogleButton = () => {
|
const GoogleButton = () => {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const callbackUrl =
|
||||||
|
searchParams.get("callbackUrl") || "/profile/personal";
|
||||||
|
|
||||||
|
const googleLogin = async () => {
|
||||||
|
await signIn("google", { callbackUrl });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className="google-btn">
|
<button
|
||||||
|
type="button"
|
||||||
|
className="google-btn"
|
||||||
|
onClick={googleLogin}
|
||||||
|
>
|
||||||
<Image src={google} alt="Google Icon" />
|
<Image src={google} alt="Google Icon" />
|
||||||
Войти через Google
|
Войти через Google
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -11,11 +11,7 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
&__change-btn {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -34,4 +30,126 @@
|
|||||||
height: 22px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__modal {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
position: relative;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
color: rgb(51, 65, 85);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid rgb(226, 232, 240);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
width: 90%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
color: rgb(100, 116, 139);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__user-img {
|
||||||
|
margin: 24px 0;
|
||||||
|
width: 130px;
|
||||||
|
height: 130px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btns {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button,
|
||||||
|
label {
|
||||||
|
min-width: 110px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__blue-btn {
|
||||||
|
color: white;
|
||||||
|
background-color: rgb(72, 159, 225);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__gray-btn {
|
||||||
|
color: rgb(51, 65, 85);
|
||||||
|
border: 1px solid rgb(226, 232, 240);
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__message {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 32px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 302px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,14 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import "./ProfileAvatar.scss";
|
import "./ProfileAvatar.scss";
|
||||||
import pen from "./icons/pen.svg";
|
import pen from "./icons/pen.svg";
|
||||||
import { apiInstance } from "@/shared/config/apiConfig";
|
import close from "./icons/close.svg";
|
||||||
import { useSession } from "next-auth/react";
|
import close_white from "./icons/close-white.svg";
|
||||||
|
import { authInstanse } from "@/shared/config/apiConfig";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import Loader from "@/shared/ui/components/Loader/Loader";
|
||||||
|
|
||||||
interface IProfileAvatarProps {
|
interface IProfileAvatarProps {
|
||||||
img: string;
|
img: string;
|
||||||
@@ -14,35 +19,109 @@ interface IProfileAvatarProps {
|
|||||||
const ProfileAvatar: React.FC<IProfileAvatarProps> = ({
|
const ProfileAvatar: React.FC<IProfileAvatarProps> = ({
|
||||||
img,
|
img,
|
||||||
}: IProfileAvatarProps) => {
|
}: IProfileAvatarProps) => {
|
||||||
|
const [modal, setModal] = useState<boolean>(false);
|
||||||
|
const [display_image, setDisplayImage] = useState<File | string>(
|
||||||
|
img
|
||||||
|
);
|
||||||
|
const [isDeleting, setIsDeleting] = useState<boolean>(false);
|
||||||
|
const [message, setMessage] = useState<boolean>(false);
|
||||||
|
const def = process.env.NEXT_PUBLIC_PROFILE_AVATAR_URL as string;
|
||||||
|
|
||||||
|
|
||||||
|
const [success, setSuccess] = useState<string>("");
|
||||||
|
const [loader, setLoader] = useState<boolean>(false);
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const router = useRouter();
|
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 handleChange: React.ChangeEventHandler<HTMLInputElement> = (
|
||||||
const res = await apiInstance.patch(
|
e
|
||||||
"/users/update_image/",
|
) => {
|
||||||
formData,
|
if (e.target.files) {
|
||||||
config
|
setDisplayImage(e.target.files[0]);
|
||||||
);
|
|
||||||
router.refresh();
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeImage = async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
if (session.status === "unauthenticated") return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof display_image === typeof "string" ||
|
||||||
|
display_image === img
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
formData.append("image", display_image);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoader(true);
|
||||||
|
const res = await authInstanse(
|
||||||
|
session.data?.access_token as string
|
||||||
|
).patch("/users/update_image/", formData);
|
||||||
|
|
||||||
|
setError("");
|
||||||
|
setSuccess("Фото профиля обновлено");
|
||||||
|
setMessage(true);
|
||||||
|
router.refresh();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage(false);
|
||||||
|
}, 3000);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
setSuccess("");
|
||||||
|
setError(error.message);
|
||||||
|
setMessage(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage(false);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoader(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const returnDefaultImage = async () => {
|
||||||
|
if (session.status === "unauthenticated") return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoader(true);
|
||||||
|
const res = await authInstanse(
|
||||||
|
session.data?.access_token as string
|
||||||
|
).patch("/users/delete_image/", {});
|
||||||
|
|
||||||
|
setError("");
|
||||||
|
setSuccess("Фото профиля удалено");
|
||||||
|
setMessage(true);
|
||||||
|
setDisplayImage(def);
|
||||||
|
router.refresh();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage(false);
|
||||||
|
}, 3000);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
setSuccess("");
|
||||||
|
setError(error.message);
|
||||||
|
setMessage(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage(false);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoader(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageIsString =
|
||||||
|
typeof display_image === "string"
|
||||||
|
? display_image
|
||||||
|
: typeof display_image === "undefined"
|
||||||
|
? ""
|
||||||
|
: URL.createObjectURL(display_image as File);
|
||||||
return (
|
return (
|
||||||
<div className="profile-avatar">
|
<div className="profile-avatar">
|
||||||
<img
|
<img
|
||||||
@@ -50,10 +129,123 @@ const ProfileAvatar: React.FC<IProfileAvatarProps> = ({
|
|||||||
src={img}
|
src={img}
|
||||||
alt="User Image"
|
alt="User Image"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="profile-image">
|
<button
|
||||||
|
onClick={() => setModal(true)}
|
||||||
|
className="profile-avatar__change-btn"
|
||||||
|
>
|
||||||
<Image src={pen} alt="Pen Icon" />
|
<Image src={pen} alt="Pen Icon" />
|
||||||
</label>
|
</button>
|
||||||
<input onChange={changeImage} id="profile-image" type="file" />
|
|
||||||
|
{modal && (
|
||||||
|
<div
|
||||||
|
onClick={() => setModal(false)}
|
||||||
|
className="profile-avatar__modal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="profile-avatar__wrapper"
|
||||||
|
>
|
||||||
|
<div className="profile-avatar__text">
|
||||||
|
<div>
|
||||||
|
<h4>Фото профиля</h4>
|
||||||
|
<button onClick={() => setModal(false)}>
|
||||||
|
<Image src={close} alt="Close Icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
По фото профиля другие люди смогут вас узнавать, а вам
|
||||||
|
будет проще определять, в какой аккаунт вы вошли.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
className="profile-avatar__user-img"
|
||||||
|
src={imageIsString}
|
||||||
|
alt="User image"
|
||||||
|
/>
|
||||||
|
<div className="profile-avatar__btns">
|
||||||
|
{img === def && display_image === def ? (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
className="profile-avatar__blue-btn"
|
||||||
|
htmlFor="change-image"
|
||||||
|
>
|
||||||
|
Добавить фото профиля
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
onChange={handleChange}
|
||||||
|
id="change-image"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : isDeleting ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsDeleting(false)}
|
||||||
|
className="profile-avatar__gray-btn"
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={returnDefaultImage}
|
||||||
|
disabled={loader}
|
||||||
|
className="profile-avatar__blue-btn"
|
||||||
|
>
|
||||||
|
{loader ? <Loader /> : "Удалить"}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : img === display_image || success !== "" ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsDeleting(true)}
|
||||||
|
className="profile-avatar__gray-btn"
|
||||||
|
>
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
<label
|
||||||
|
className="profile-avatar__blue-btn"
|
||||||
|
htmlFor="change-image"
|
||||||
|
>
|
||||||
|
Сменить
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
onChange={handleChange}
|
||||||
|
id="change-image"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setDisplayImage(img)}
|
||||||
|
className="profile-avatar__gray-btn"
|
||||||
|
>
|
||||||
|
Назад
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={loader}
|
||||||
|
onClick={changeImage}
|
||||||
|
className="profile-avatar__blue-btn"
|
||||||
|
>
|
||||||
|
{loader ? <Loader /> : "Сохранить"}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{message && (
|
||||||
|
<div className="profile-avatar__message">
|
||||||
|
<p>
|
||||||
|
{success} {error}
|
||||||
|
</p>
|
||||||
|
<button onClick={() => setMessage(false)}>
|
||||||
|
<Image src={close_white} alt="Close Icon White" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<svg width="20.000000" height="20.000000" viewBox="0 0 20 20" 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="clip2704_56355">
|
||||||
|
<rect id="icon" width="20.000000" height="20.000000" fill="white" fill-opacity="0"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#clip2704_56355)">
|
||||||
|
<path id="Vector" d="M14.375 5.625L5.625 14.375" stroke="#FFFFFF" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
<path id="Vector" d="M5.625 5.625L14.375 14.375" stroke="#FFFFFF" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 730 B |
@@ -0,0 +1,14 @@
|
|||||||
|
<svg width="12.000000" height="12.000000" viewBox="0 0 12 12" 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="clip4_25648">
|
||||||
|
<rect id="close" width="12.000000" height="12.000000" fill="white" fill-opacity="0"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#clip4_25648)">
|
||||||
|
<path id="Vector" d="M8.625 3.375L3.375 8.625" stroke="#334155" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
<path id="Vector" d="M3.375 3.375L8.625 8.625" stroke="#334155" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 721 B |
@@ -4,9 +4,9 @@ import "./ReportLike.scss";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import like from "./icons/like.svg";
|
import like from "./icons/like.svg";
|
||||||
import { apiInstance } from "@/shared/config/apiConfig";
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
import { useRouter } from "next/navigation";
|
import { signIn, useSession } from "next-auth/react";
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { usePathname, useRouter } from "@/shared/config/navigation";
|
||||||
|
|
||||||
interface IReportLikeProps {
|
interface IReportLikeProps {
|
||||||
count: number;
|
count: number;
|
||||||
@@ -20,6 +20,7 @@ const ReportLike: React.FC<IReportLikeProps> = ({
|
|||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
const Authorization = `Bearer ${session?.data?.access_token}`;
|
const Authorization = `Bearer ${session?.data?.access_token}`;
|
||||||
const config = {
|
const config = {
|
||||||
@@ -29,6 +30,10 @@ const ReportLike: React.FC<IReportLikeProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const checkLike = async () => {
|
const checkLike = async () => {
|
||||||
|
if (session.status === "unauthenticated") {
|
||||||
|
signIn(undefined, { callbackUrl: pathname });
|
||||||
|
}
|
||||||
|
|
||||||
const res = await apiInstance.get<{ detail: string }>(
|
const res = await apiInstance.get<{ detail: string }>(
|
||||||
`/report/${report_id}/like/check/`,
|
`/report/${report_id}/like/check/`,
|
||||||
config
|
config
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
.report-message {
|
||||||
|
padding: 16px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 30000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0px 8px 8px -4px rgba(16, 24, 40, 0.04),
|
||||||
|
0px 20px 24px -4px rgba(16, 24, 40, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: rgb(16, 24, 40);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: rgb(71, 84, 103);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 10px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid rgb(72, 159, 225);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||||
|
background: rgb(72, 159, 225);
|
||||||
|
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Link, useRouter } from "@/shared/config/navigation";
|
||||||
|
import "./ReportMessage.scss";
|
||||||
|
import Image from "next/image";
|
||||||
|
import close from "./icons/close.svg";
|
||||||
|
import check from "./icons/check.svg";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
interface IReportMessageProps {
|
||||||
|
setShowMessage: (value: boolean) => void;
|
||||||
|
showMessage: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReportMessage: React.FC<IReportMessageProps> = ({
|
||||||
|
setShowMessage,
|
||||||
|
showMessage,
|
||||||
|
}: IReportMessageProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
useEffect(() => {
|
||||||
|
if (showMessage) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowMessage(false);
|
||||||
|
router.push("/");
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}, [showMessage]);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => setShowMessage(false)}
|
||||||
|
className="report-message"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="report-message__wrapper"
|
||||||
|
>
|
||||||
|
<div className="report-message__header">
|
||||||
|
<Image src={check} alt="Check Icon" />
|
||||||
|
<button onClick={() => setShowMessage(false)} type="button">
|
||||||
|
<Image src={close} alt="Close Icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="report-message__text">
|
||||||
|
<h4>Ваше обращение отправлено</h4>
|
||||||
|
<p>
|
||||||
|
Спасибо за ваше обращение. На данный момент оно в
|
||||||
|
модерации.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Link href="/profile/my-reports">Смотреть мои обращения</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportMessage;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="4" y="4" width="48" height="48" rx="24" fill="#D1FADF"/>
|
||||||
|
<rect x="4" y="4" width="48" height="48" rx="24" stroke="#ECFDF3" stroke-width="8"/>
|
||||||
|
<path d="M23.5 28L26.5 31L32.5 25M38 28C38 33.5228 33.5228 38 28 38C22.4772 38 18 33.5228 18 28C18 22.4772 22.4772 18 28 18C33.5228 18 38 22.4772 38 28Z" stroke="#039855" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 491 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13 1L1 13M1 1L13 13" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 217 B |
@@ -3,6 +3,7 @@
|
|||||||
import "./SearchForm.scss";
|
import "./SearchForm.scss";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import search from "./icons/search.svg";
|
import search from "./icons/search.svg";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface ISearchFormProps
|
interface ISearchFormProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
@@ -17,6 +18,7 @@ const SearchForm: React.FC<ISearchFormProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
style,
|
style,
|
||||||
}: ISearchFormProps) => {
|
}: ISearchFormProps) => {
|
||||||
|
const t = useTranslations("general");
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
style={style}
|
style={style}
|
||||||
@@ -33,7 +35,7 @@ const SearchForm: React.FC<ISearchFormProps> = ({
|
|||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Поиск</button>
|
<button type="submit">{t("search")}</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import map_pin from "./icons/map-pin.svg";
|
||||||
|
import { useReportStore } from "@/widgets/report-details/reportStore";
|
||||||
|
|
||||||
|
const ShowMapButton = () => {
|
||||||
|
const { setShowMap, showMap } = useReportStore();
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowMap(!showMap)}
|
||||||
|
className="report-information__show-map"
|
||||||
|
>
|
||||||
|
<Image src={map_pin} alt="Map Pin Icon" />
|
||||||
|
Показать на карте
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowMapButton;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
import { locales } from "@/shared/config/navigation";
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ locale }) => {
|
||||||
|
if (!locales.includes(locale as any)) notFound();
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,9 +1,51 @@
|
|||||||
export { default } from "next-auth/middleware";
|
import { withAuth } from "next-auth/middleware";
|
||||||
|
import createIntlMiddleware from "next-intl/middleware";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { locales, localePrefix } from "./shared/config/navigation";
|
||||||
|
|
||||||
|
const privatePages = [
|
||||||
|
"/profile",
|
||||||
|
"/profile/personal",
|
||||||
|
"/profile/my-reports",
|
||||||
|
"/create-report",
|
||||||
|
];
|
||||||
|
|
||||||
|
const intlMiddleware = createIntlMiddleware({
|
||||||
|
locales,
|
||||||
|
localePrefix,
|
||||||
|
defaultLocale: "ru",
|
||||||
|
});
|
||||||
|
|
||||||
|
const authMiddleware = withAuth(
|
||||||
|
function onSuccess(req) {
|
||||||
|
return intlMiddleware(req);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
callbacks: {
|
||||||
|
authorized: ({ token }) => token != null,
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: "/sign-in",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function middleware(req: NextRequest) {
|
||||||
|
const publicPathnameRegex = RegExp(
|
||||||
|
`^(/(${locales.join("|")}))?(${privatePages
|
||||||
|
.flatMap((p) => (p === "/" ? ["", "/"] : p))
|
||||||
|
.join("|")})/?$`,
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname);
|
||||||
|
|
||||||
|
if (!isPublicPage) {
|
||||||
|
return intlMiddleware(req);
|
||||||
|
} else {
|
||||||
|
return (authMiddleware as any)(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: ["/((?!api|_next|.*\\..*).*)"],
|
||||||
"/profile/personal",
|
|
||||||
"/profile/my-reports",
|
|
||||||
"/create-report",
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
@@ -5,3 +5,13 @@ const API_URL = process.env["NEXT_PUBLIC_BASE_API"];
|
|||||||
export const apiInstance = axios.create({
|
export const apiInstance = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const authInstanse = (access_token: string) => {
|
||||||
|
return axios.create({
|
||||||
|
baseURL: API_URL,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
},
|
||||||
|
method: "",
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2,24 +2,32 @@ import axios from "axios";
|
|||||||
import { AuthOptions } from "next-auth";
|
import { AuthOptions } from "next-auth";
|
||||||
import { JWT } from "next-auth/jwt";
|
import { JWT } from "next-auth/jwt";
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
|
import { apiInstance } from "./apiConfig";
|
||||||
interface IToken {
|
import { IRefresh, ITokens } from "../types/token-type";
|
||||||
access: string;
|
import GoogleProvider from "next-auth/providers/google";
|
||||||
}
|
|
||||||
|
|
||||||
const refreshToken = async (token: JWT): Promise<JWT> => {
|
const refreshToken = async (token: JWT): Promise<JWT> => {
|
||||||
|
const UTC = new Date();
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
refresh: token.refresh_token,
|
refresh: token.refresh_token,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axios.post<IToken>(
|
const response = await apiInstance.post<IRefresh>(
|
||||||
"https://api.kgroaduat.fishrungames.com/api/v1/token/refresh/",
|
"/users/refresh/",
|
||||||
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: expirationTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +35,6 @@ export const authConfig: AuthOptions = {
|
|||||||
providers: [
|
providers: [
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
name: "Credentials",
|
name: "Credentials",
|
||||||
|
|
||||||
credentials: {
|
credentials: {
|
||||||
email: {
|
email: {
|
||||||
label: "Email",
|
label: "Email",
|
||||||
@@ -36,35 +43,47 @@ export const authConfig: AuthOptions = {
|
|||||||
},
|
},
|
||||||
password: { label: "Password", type: "password" },
|
password: { label: "Password", type: "password" },
|
||||||
},
|
},
|
||||||
|
async authorize(credentials, req): Promise<any> {
|
||||||
async authorize(credentials, req) {
|
|
||||||
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 = {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await apiInstance.post<ITokens>(
|
||||||
"https://api.kgroaduat.fishrungames.com/api/v1/users/login/",
|
"/users/login/",
|
||||||
{
|
data
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.status.toString()[0] === "4") {
|
if ([200, 201].includes(res.status)) {
|
||||||
return null;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await res.json();
|
return null;
|
||||||
return user;
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
GoogleProvider({
|
||||||
|
clientId: process.env.CLIENT_ID as string,
|
||||||
|
clientSecret: process.env.CLIENT_SECRET as string,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
pages: {
|
pages: {
|
||||||
signIn: "/sign-in",
|
signIn: "/sign-in",
|
||||||
@@ -73,15 +92,57 @@ export const authConfig: AuthOptions = {
|
|||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
async signIn({ account, user }) {
|
||||||
|
if (account?.provider === "google") {
|
||||||
|
const data = {
|
||||||
|
auth_token: account?.id_token,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await apiInstance.post<ITokens>(
|
||||||
|
"/users/google/",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
if (![200, 201].includes(res.status)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTime = new Date();
|
||||||
|
|
||||||
|
const expirationTime = new Date(
|
||||||
|
currentTime.getTime() + 15 * 60000
|
||||||
|
);
|
||||||
|
|
||||||
|
user.access_token = res.data.access_token;
|
||||||
|
user.refresh_token = res.data.refresh_token;
|
||||||
|
user.expires_in = expirationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
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;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createSharedPathnamesNavigation } from "next-intl/navigation";
|
||||||
|
|
||||||
|
export const locales = ["en", "ru", "kg"] as const;
|
||||||
|
export const localePrefix = "always";
|
||||||
|
|
||||||
|
export const { Link, redirect, usePathname, useRouter } =
|
||||||
|
createSharedPathnamesNavigation({ locales, localePrefix });
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
@import "./TildaSans-Medium/TildaSans-Medium.css";
|
@import "./TildaSans-Medium/TildaSans-Medium.css";
|
||||||
@import "./TildaSans-Semibold/TildaSans-Semibold.css";
|
@import "./TildaSans-Semibold/TildaSans-Semibold.css";
|
||||||
@import "./TildaSans-Bold/TildaSans-Bold.css";
|
@import "./TildaSans-Bold/TildaSans-Bold.css";
|
||||||
@import "./TildaSans-Extrabold/TildaSans-Extrabold.css";
|
@import "./TildaSans-ExtraBold/TildaSans-ExtraBold.css";
|
||||||
@import "./TildaSans-Black/TildaSans-Black.css";
|
@import "./TildaSans-Black/TildaSans-Black.css";
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface IList {
|
export interface IList {
|
||||||
count: number | null;
|
count: number;
|
||||||
previous: string | null;
|
previous: string | null;
|
||||||
next: string | null;
|
next: string | null;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface ILocation {
|
export interface ILocation {
|
||||||
id: 4;
|
id: number;
|
||||||
latitude: string;
|
latitude: string;
|
||||||
longitude: string;
|
longitude: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface IMetatag {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
keywords: string;
|
||||||
|
og_image?: string;
|
||||||
|
page: string;
|
||||||
|
}
|
||||||
@@ -15,3 +15,17 @@ export interface IReport {
|
|||||||
total_likes: number;
|
total_likes: number;
|
||||||
count_reviews: number;
|
count_reviews: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IRatingReport {
|
||||||
|
created_at: string;
|
||||||
|
category: null;
|
||||||
|
author: {
|
||||||
|
id: number;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
govern_status: null;
|
||||||
|
};
|
||||||
|
total_likes: number;
|
||||||
|
count_reviews: number;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
@@ -3,3 +3,8 @@ export interface ITokens {
|
|||||||
access_token: string;
|
access_token: string;
|
||||||
expires_in: string;
|
expires_in: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IRefresh {
|
||||||
|
access: string;
|
||||||
|
expires_at: string;
|
||||||
|
}
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.auth-icon2 {
|
.auth-icon2 {
|
||||||
width: 60px;
|
min-width: 60px;
|
||||||
height: 60px;
|
min-height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -90,7 +90,6 @@
|
|||||||
@media screen and (max-width: 550px) {
|
@media screen and (max-width: 550px) {
|
||||||
.auth-page {
|
.auth-page {
|
||||||
padding: 48px 16px;
|
padding: 48px 16px;
|
||||||
min-height: 600px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-wrapper {
|
.auth-wrapper {
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import "./Loader.scss";
|
import "./Loader.scss";
|
||||||
|
|
||||||
const Loader = () => {
|
interface ILoader {
|
||||||
return <span className="loader"></span>;
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Loader: React.FC<ILoader> = ({ color }: ILoader) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="loader"
|
||||||
|
style={{ borderTop: `3px solid ${color}` }}
|
||||||
|
></span>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Loader;
|
export default Loader;
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
export const LINKS = [
|
import { useTranslations } from "next-intl";
|
||||||
{ id: 1, pagename: "Главная", pathname: "/" },
|
|
||||||
{ id: 2, pagename: "О нас", pathname: "/about-us" },
|
export const LINKS = () => {
|
||||||
{ id: 3, pagename: "Статистика", pathname: "/statistics" },
|
const t = useTranslations("navigation");
|
||||||
{ id: 4, pagename: "Новости", pathname: "/news" },
|
|
||||||
{ id: 5, pagename: "Волонтеры", pathname: "/volunteers" },
|
const LINKS = [
|
||||||
];
|
{ id: 1, pagename: t("home"), pathname: "/" },
|
||||||
|
{ id: 2, pagename: t("about_us"), pathname: "/about-us" },
|
||||||
|
{ id: 4, pagename: t("statistics"), pathname: "/statistics" },
|
||||||
|
{ id: 5, pagename: t("news"), pathname: "/news" },
|
||||||
|
{ id: 6, pagename: t("volunteers"), pathname: "/volunteers" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return LINKS;
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export const LINKS = () => {
|
||||||
|
const t = useTranslations("navigation");
|
||||||
|
|
||||||
|
const LINKS = [
|
||||||
|
{ id: 1, pagename: t("home"), pathname: "/" },
|
||||||
|
{ id: 2, pagename: t("about_us"), pathname: "/about-us" },
|
||||||
|
{ id: 3, pagename: t("privacy"), pathname: "/privacy" },
|
||||||
|
{ id: 3, pagename: t("support"), pathname: "/support" },
|
||||||
|
{ id: 4, pagename: t("statistics"), pathname: "/statistics" },
|
||||||
|
{ id: 5, pagename: t("news"), pathname: "/news" },
|
||||||
|
{ id: 6, pagename: t("volunteers"), pathname: "/volunteers" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return LINKS;
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const MONTHS: Record<string, string> = {
|
||||||
|
"01": "Январь",
|
||||||
|
"02": "Февраль",
|
||||||
|
"03": "Март",
|
||||||
|
"04": "Апрель",
|
||||||
|
"05": "Май",
|
||||||
|
"06": "Июнь",
|
||||||
|
"07": "Июль",
|
||||||
|
"08": "Август",
|
||||||
|
"09": "Сентябрь",
|
||||||
|
"10": "Октябрь",
|
||||||
|
"11": "Ноябрь",
|
||||||
|
"12": "Декабрь",
|
||||||
|
};
|
||||||
@@ -1,20 +1,4 @@
|
|||||||
export const ROAD_TYPES_STATS: Record<number, string> = {
|
import { getTranslations } from "next-intl/server";
|
||||||
1: "Разбитых дорог",
|
|
||||||
2: "Очагов аварийности",
|
|
||||||
3: "Локальных дефектов",
|
|
||||||
4: "В планах ремонта",
|
|
||||||
5: "Отремонтировано",
|
|
||||||
6: "Локальных дефектов исправлено",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ROAD_TYPES: Record<number, string> = {
|
|
||||||
1: "Разбитая дорога",
|
|
||||||
2: "Очаг аварийности",
|
|
||||||
3: "Локальный дефект",
|
|
||||||
4: "В планах ремонта",
|
|
||||||
5: "Отремонтировано",
|
|
||||||
6: "Локальный дефект исправлен",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ROAD_TYPES_COLORS: Record<number, string> = {
|
export const ROAD_TYPES_COLORS: Record<number, string> = {
|
||||||
1: "rgb(230, 68, 82)",
|
1: "rgb(230, 68, 82)",
|
||||||
|
|||||||
@@ -4,6 +4,19 @@
|
|||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
gap: 30px;
|
gap: 30px;
|
||||||
background-color: rgb(15, 23, 42);
|
background-color: rgb(15, 23, 42);
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a,
|
a,
|
||||||
li,
|
li,
|
||||||
h4 {
|
h4 {
|
||||||
|
|||||||