75 Commits
Author SHA1 Message Date
Vladislav Khorev c2282ee5b0 Production 2024-09-11 10:19:13 +00:00
anime 775e238e77 z-index for nav-language 2024-09-11 16:00:27 +06:00
anime 5eff43ef61 Fix error in footer 2024-09-11 15:55:18 +06:00
Vladislav Khorev 3c810dad82 Remove netkg tracker for uat 2024-09-10 17:39:02 +00:00
Vladislav Khorev b10ab93bf1 Merge 2024-09-10 17:37:17 +00:00
Vladislav Khorev 49cb64a368 fixing minor issues 2024-09-10 17:35:22 +00:00
Vladislav Khorev cbe5c0656c Remove netkg tracker from uat 2024-09-10 16:25:58 +00:00
anime 1a606f4ecd Fix news details 2024-09-09 22:23:59 +06:00
anime 19397689b1 Fix news details 2024-09-09 21:39:48 +06:00
Vladislav Khorev 922d8386c6 Merge 2024-09-08 14:16:41 +00:00
Vladislav Khorev dcac729914 merge 2024-09-08 14:15:44 +00:00
Vladislav Khorev ecd3848ec3 package lock added 2024-09-08 14:14:22 +00:00
Vladislav Khorev 32761ce479 Merge 2024-09-08 14:14:05 +00:00
anime 54ec09afe0 Fix description for news 2024-09-03 11:35:13 +06:00
anime ea58e4f2d1 fix dates of news 2024-09-03 10:58:38 +06:00
Vladislav Khorev c3697c9e55 Update readme 2024-07-28 21:01:16 +00:00
anime b85d1e8f30 fixing navbar 2024-07-09 09:41:47 +06:00
Vladislav Khorev 1363519e75 fixing translations 2024-07-03 17:52:47 +00:00
altynai bea7ec82c2 Merge pull request 'updated navbar, fixed map' (#3) from kg-road-fix into ali
Reviewed-on: http://gitea.fishrungames.com/Transparency/kgroad-frontend2/pulls/3
2024-06-13 11:41:41 +00:00
anime 6508bc0258 updated navbar, fixed map 2024-06-13 17:36:48 +06:00
vladislav.khorev 981c5863ee Merge pull request 'fix' (#1) from fix into ali
Reviewed-on: http://gitea.fishrungames.com/Transparency/kgroad-frontend2/pulls/1
2024-05-30 10:34:32 +00:00
anime 5b5feaa43f added name surname to volunteers page 2024-05-29 15:51:41 +06:00
anime 545285f1c3 added kg text, removed image from about, added links in descriptions of posts 2024-05-29 15:27:00 +06:00
Vlad aa4bf468cd fix date 2024-05-24 17:11:43 +06:00
Vladislav Khorev 4f26c31c36 Working on translations 2024-05-24 08:47:59 +00:00
Vladislav Khorev 90acfefe80 Fixing profile url 2024-04-13 18:57:36 +00:00
Vladislav Khorev e9509e9396 Fixing bug 2024-04-11 11:52:21 +00:00
Vladislav Khorev 6837cfa46a Added backend calls to support request 2024-04-11 11:41:33 +00:00
Vladislav Khorev c7f7380821 moving profile photo to env and fix bug 2024-04-10 21:03:40 +00:00
Vladislav Khorev 57fab57b9d moving profile photo to env 2024-04-10 20:59:20 +00:00
Vladislav Khorev ffe8c84d3d Working with links in footer 2024-04-10 20:37:16 +00:00
Vladislav Khorev f38f7ad7bd MErge with ali 2024-04-10 20:31:01 +00:00
Vladislav Khorev d9ac888d48 Merge branch 'ali' into release005 2024-04-10 20:27:46 +00:00
Vladislav Khorev b885c9c9bc Merge branch 'ali' of gitea.fishrungames.com:Transparency/kgroad-frontend2 into ali 2024-04-10 20:26:17 +00:00
Vladislav Khorev 2228025e0f Added privacy and some support 2024-04-10 20:26:07 +00:00
Vladislav Khorev 046a17122d Fixing compilation erorr 2024-03-22 08:49:03 +00:00
Vladislav Khorev 5fb5487ea9 Merge branch 'ali' into release005 2024-03-22 08:40:14 +00:00
Vladislav Khorev 80a3f658b9 release004 2024-03-22 08:39:52 +00:00
Alibek 45650996c3 made language in menu 2024-03-20 17:18:10 +06:00
Alibek 36896b1fad fixed metadata in report page 2024-03-18 17:30:09 +06:00
Alibek 779f37a1a3 fixed message 2024-03-18 17:08:49 +06:00
Alibek a520214eb9 made report message 2024-03-18 16:59:05 +06:00
Vladislav Khorev a01b35343f release changes 2024-03-18 10:53:48 +00:00
Alibek c54131197e fixed bug with latitude and longitude 2024-03-18 16:33:57 +06:00
Alibek 006a1a7b7b fixed redirect to profile 2024-03-16 19:04:37 +06:00
Vladislav Khorev 18f173820f Fixing bugs 2024-03-16 12:20:50 +00:00
Alibek 40a4116c03 mistake in sign-up form corrected 2024-03-16 02:21:47 +06:00
Alibek 0ba0f1df89 fixed sort in profile-reports table and in statistics table, improved error messages, improved metatags by fetching metadata from server 2024-03-16 02:19:33 +06:00
Alibek 0221a79015 made metatags 2024-03-15 15:34:11 +06:00
Alibek ad86f2e79e improved seo, made not-found page, fixed bugs in news details and in report datails pages 2024-03-14 04:13:08 +06:00
Alibek 7d36510a9b fixed bug with sign-in 2024-03-13 16:48:13 +06:00
Alibek 7bc3e2ccb5 added policy checkbox in sign-up form, fixed height bug in sign-up page 2024-03-12 04:04:41 +06:00
Alibek 74b15060a1 fixed bugs with sort 2024-03-10 04:38:46 +06:00
Alibek bcc7279d39 fixed few bugs 2024-03-02 02:07:30 +06:00
Alibek 5330f21a73 made breadcrumbs 2024-03-01 19:54:03 +06:00
Alibek 65a700dad7 added dynamic pagination in reviews-section 2024-03-01 14:14:32 +06:00
Alibek 0a7513dd89 added change-image feature in profile, sign-up validating password and shows correct error, if user already exists 2024-02-29 20:45:09 +06:00
Alibek 6650a98503 resoled conflicts 2024-02-29 14:22:42 +06:00
Alibek e2db27b56f resolved 2024-02-29 14:14:16 +06:00
Alibek 6637ab4eae added next-intl, composed next-auth and next-intl in middleware, fixed some bugs, completed details page, added google auth 2024-02-29 14:09:37 +06:00
Vladislav Khorev d1440f3907 yarn lock 2024-02-25 19:27:10 +00:00
Vladislav Khorev 773a33e21a uat changes 2024-02-25 19:23:13 +00:00
Vladislav Khorev 3c4661a9c8 Fixing google analytics, adding some changes from prod 2024-02-25 19:20:22 +00:00
Vladislav Khorev 82eb226810 Release version 2024-02-25 16:20:52 +00:00
Vladislav Khorev f9754aca1a Merge 2024-02-25 16:08:56 +00:00
Vladislav Khorev d933102065 Merge changes 2024-02-25 16:08:02 +00:00
Vladislav Khorev 5ae5ff9fd3 Merge branch 'ali' into release003 2024-02-25 16:03:48 +00:00
Vladislav Khorev 2e03fd9175 Added google analytics and net.kg analytics 2024-02-24 14:01:03 +00:00
Vladislav Khorev c3a94c920f For prod 2024-02-24 13:22:15 +00:00
Vladislav Khorev 721e8506dd Merge branch 'release003' into ali 2024-02-24 13:02:06 +00:00
Vladislav Khorev 566c4d4473 Fixing bug with comments/reviews 2024-02-24 13:00:24 +00:00
Vladislav Khorev 67b935b47f Fixing things 2024-02-24 12:07:50 +00:00
Vladislav Khorev deb08e290e Some changes for uat 2024-02-24 11:44:35 +00:00
Vladislav Khorev e3a5ed72e0 Make release version prep 2024-02-24 11:38:42 +00:00
Vladislav Khorev 3b365d7742 Make release 003 2024-02-24 10:42:58 +00:00
224 changed files with 9182 additions and 1952 deletions

No files matched your search

+9
View File
@@ -1,3 +1,12 @@
## TL;DR
```
git pull
yarn install
yarn build
yarn start
```
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started ## Getting Started
+8 -2
View File
@@ -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;
} }
} }
+224
View File
@@ -0,0 +1,224 @@
{
"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"
},
"PrivacyPolicy":{
"titel0":"Privacy policy",
"title1": "General provisions",
"title2": "Collection of personal data",
"title3": "Storage and use of personal data",
"title4": "Transfer of personal data",
"title5": "Destruction of personal data",
"title6": "Protection of personal data",
"title7": "User requests",
"p1": "These Privacy Policy Regulations define the procedure for processing and protecting information about individuals (hereinafter referred to as Users) using the mailing list, the feedback form of the site, and e-mail addresses located on the domain name ",
"p1o":" (hereinafter referred to as the Website). We have developed a Privacy Policy that describes how we process personal data — any actions (operations) or a set of actions (operations) performed using automation tools or without using such tools with personal data, including collection, recording, systematization, accumulation, storage, clarification (updating, modification), extraction, use, transfer (distribution, provision, access), depersonalization, blocking, deletion, destruction of personal data. The relations related to the processing of personal data and information about the users of the Website are governed by this Regulation. This Privacy Policy regulates any type of processing of personal data and personal information (any personally identifiable information and any other related information) about individuals who are users of the Website. This Policy applies to the processing of personal, personal data collected by any means, both active and passive, both via the Internet and without its use, from persons located anywhere in the world.",
"p2": "The purpose of processing personal data is to fulfill the Operator's obligations to Users regarding the use of the Website and its services. The processing of personal data of users is carried out with the consent of the personal data subject to the processing of his personal data. Personal data refers to any information relating directly or indirectly to a specific or identifiable individual (the subject of personal data) and which can be used to identify or communicate with a particular person. We may request your first name, last name, patronymic and e-mail address to include you in the mailing list with your consent and to respond to you if you have contacted us through the feedback form on the Website.",
"p3": "Users' personal data is stored exclusively on electronic media and processed using automated systems.",
"p4": "HOW WE USE YOUR PERSONAL INFORMATION",
"p5": "The personal data we collect allows us to send you e-mail notifications about news and various events in the field of anti-corruption. Your personal data may also be used when responding to your request sent via the feedback form or to our e-mail address located on the Website. If you do not wish to be included in our mailing list, you can unsubscribe at any time by informing us at the specified contacts for feedback.",
"p6":"COLLECTION AND USE OF NON-PERSONAL INFORMATION",
"p7":"We also collect personal data that is not personal − data that does not allow us to directly associate it with any person. We may collect, use, transfer and disclose non-personal information for any purpose. The following are examples of non-personal information that we collect and how we can use it. We may collect personal data, such as: the source of the link to the Website, the characteristics of user behavior on the Website, the number of pages viewed on the Website. Such data is depersonalized and is not personal. This data is collected in order to better understand what information is of interest to Website visitors and improve the quality of the content.",
"p8": "Personal data of Users is not transferred to any third parties. Users who have submitted their personal data to us before the entry into force of these Rules automatically agree to the use of their personal data. At the same time, they can always withdraw their consent to the use of personal data through the feedback form. The Operator blocks personal data related to the relevant User from the moment of the request or request of the User or his legal representative, in case of identification of false personal data or illegal actions.",
"p9": "The user's personal data is destroyed when the personal data subject withdraws consent to the processing of personal data.",
"p10":"IDENTIFICATION FILES (COOKIES) AND OTHER TECHNOLOGIES",
"p11":"The Website, interactive services and applications, e-mail messages and any other communications on our behalf may use identification cookies and other technologies such as pixel tags, web beacons. Accordingly, when using the website and other services, a pop-up window may appear on your device notifying you about the use of cookies. These technologies help us better understand user behavior, tell us which sections of our site have been visited by users, and measure the effectiveness of advertising and online searches. We consider the information collected by cookies and other technologies as non-personal information. You can control cookies by following the instructions of your browser or device. As a rule, you can change your cookie settings in the 'Settings' or 'System Settings' section. As with most websites, we collect some information automatically and store it in statistics files. Such information includes the Internet Protocol address (IP address), browser type and language, information about the Internet service provider, sending and exiting pages, information about the operating system, date and time stamp, as well as information about visits. We use such information to understand and analyze trends, administer the website, study user behavior on it, and collect demographic information about our core user base. The organization may use such information for its own marketing purposes. In some of our e-mail messages, we use interactive links to information posted on the Website. When users follow such links, before they get to the destination page on our website, their requests go through a separate registration. We track such 'passing' data to help us identify interest in individual topics and measure the effectiveness of our communications with users. If you prefer that your requests are not tracked in this way, you should not follow text or image links in e-mail messages. Pixel labels allow us to send e-mail messages in a format that is readable by consumers and tell us whether such messages have been read. We may use such information to limit the number of messages sent to consumers or to stop sending them.",
"p12": "We take precautions to ensure the protection of your personal data in order to ensure the protection of the User's personal data from unauthorized or accidental access to it, destruction, modification, blocking, copying, distribution, as well as from other illegal actions of third parties.",
"p13":"INTEGRITY AND PRESERVATION OF PERSONAL INFORMATION",
"p14":"We will store your personal data and information for as long as necessary to fulfill the purposes described in this Privacy Policy. We do not collect personal data about minors. If we become aware that we have received personal data about a minor, we will take measures to delete such information as soon as possible. We strongly recommend that parents and other persons under whose supervision minors are (legal representatives — parents, adoptive parents or guardians) monitor the use of websites by minors. The Operator is not responsible for the actions of third parties who have gained access to User information as a result of using the Internet or the Website's Services and for the consequences of using data and information that, due to the nature of the Website, are available to any Internet user.",
"p15":"QUESTIONS ABOUT PRIVACY",
"p16":"If you have any questions regarding this Privacy Policy or data processing, you can contact us using the feedback contacts kyrgyzstan@transparency.org.",
"p17": "Users have the right to send their requests to the Operator, including requests regarding the use of their personal data, sending a withdrawal of consent to the processing of personal data in writing to the address specified in the General Provisions section of this regulation, or in the form of an electronic document sent via the feedback form. The Operator undertakes to review and send a response to the User's request within 30 days from the date of receipt of the request.",
"p18":"OTHER",
"p19":"A visitor to the Organization's website who provides his personal data and information thereby agrees to the provisions of this Privacy Policy. We reserve the right to make any changes to the Policy at any time at our discretion in order to further improve the system of protection against unauthorized access to personal data reported by Users without the User's consent. When we make significant changes to the Privacy Policy, a corresponding notification is posted on our website along with the updated version of the Privacy Policy.This Policy does not apply to the actions and Internet resources of third parties."
}
}
+226
View File
@@ -0,0 +1,226 @@
{
"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": "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 башкы буюк тамга жана 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": "Бардык укуктар корголгон"
},
"PrivacyPolicy":{
"titel0":"Купуялык саясаты",
"title1": "Жалпы жоболор",
"title2": "Жеке маалыматтарды чогултуу",
"title3": "Жеке маалыматтарды сактоо жана пайдалануу",
"title4": "Жеке маалыматтарды өткөрүп берүү",
"title5": "Жеке маалыматтарды жок кылуу",
"title6": "Жеке маалыматтарды коргоо",
"title7": "Колдонуучунун кайрылуулары",
"p1":"Бул Купуялык саясатынын билдирүүсү почта тизмесин, сайттын пикир формасын жана домендик аталышта жайгашкан электрондук почта даректерин колдонгон жеке адамдар (мындан ары - Колдонуучулар) жөнүндө маалыматты иштеп чыгуу жана коргоо тартибин аныктайт.",
"p1o":" (мындан ары Сайт деп аталат) домендик аталышта жайгашкан почталык таркатма, сайттын пикир билдирүү формасын жана электрондук почта даректерин колдонгон жеке адамдар (мындан ары - Колдонуучулар) жөнүндө маалыматты колдонуу жана коргоо тартибин аныктайт. Биз жеке маалыматтарды кантип иштетээрибизди сүрөттөгөн Купуялык саясатын иштеп чыктык - автоматташтыруу куралдарын колдонуу менен же мындай куралдарды колдонбостон аткарылган ар кандай аракеттер (операциялар) же аракеттердин (операциялардын) жыйындысы, анын ичинде жеке маалыматтарды чогултуу, жазып алуу, системалаштыруу, топтоо, сактоо, тактоо (жаңыртуу, өзгөртүү),  чыгаруу, пайдалануу, берүү (тараттуу,  көрсөтүү, жетүү),  өздүксүздөндүрүү,  жаап коюу,  өчүрүү, жок кылуу. Жеке маалыматтарды жана Сайттын колдонуучулары жөнүндө маалыматтарды иштетүүгө байланышкан мамилелер ушул Жобо менен жөнгө салынат. Бул Купуялык саясаты Сайттын колдонуучулары болуп саналган адамдар жөнүндө персоналдык маалыматтарды жана жеке мүнөздөгү маалыматтарды ( өздүктү тактаганга мүмкүндүк берген каалаган маалымат жана ага байланыштуу каалаган башка маалымат) иштетүүнүн бардык түрүн жөнгө салат. Бул Саясат дүйнөнүн каалаган жеринде жайгашкан адамдардан Интернет аркылуу да, аны колдонбостон да активдүү жана пассивдүү ар кандай каражаттар менен чогултулган жеке, персоналдык маалыматтарды иштетүүгө карата колдонулат.",
"p2":"Жеке маалыматтарды  иштетүүнүн максаты Оператор тарабынан Сайтты жана анын кызматтарын пайдалануу боюнча Колдонуучулардын алдындагы милдеттерин аткаруу болуп саналат. Колдонуучунун жеке маалыматтарын иштетүү жеке маалыматтар субъектинин анын жеке маалыматтарын иштетүүгө макулдугу менен жүзөгө ашырылат. Жеке маалыматтар  деп, аныкталган же аныкталуучу адамга (жеке маалыматтар субъектисине) түздөн-түз же кыйыр түрдө тиешелүү жана аныкталган адамды идентификациялоо же аны менен байланыштыруу үчүн колдонулушу мүмкүн болгон  каалаган  маалымат. Сиздин макулдугуңуз менен сизди почталык таркатмага киргизүү, ошондой эле Сайттагы пикир билдирүү формасы аркылуу биз менен байланышсаңыз, сизге жооп берүү үчүн атыңызды, фамилияңызды, атаңыздын атыңыздын жана электрондук почта дарегиңизди сурашыбыз мүмкүн.",
"p3": "Колдонуучулардын жеке маалыматтары электрондук алып жүрүүчүлөрдө гана сакталат жана автоматташтырылган системалардын жардамы менен иштетилет.",
"p4": "БИЗ СИЗДИН ЖЕКЕ МААЛЫМАТТАРЫНЫЗДЫ КАНТИП КОЛДОНОБУЗ",
"p5":"Биз чогулткан жеке маалыматтар бизге коррупцияга каршы күрөшүү тармагындагы жаңылыктар жана ар кандай окуялар жөнүндө электрондук почта билдирүүлөрүн жөнөтүүгө мүмкүндүк берет. Ошондой эле, сиздин жеке маалыматтарыңыз пикир билдирүү формасы аркылуу же Сайтта жайгашкан биздин электрондук почта дарегибизге жөнөтүлгөн сурооңузга жооп берүү учурунда колдонулушу мүмкүн. Эгерде сиз биздин почта таркатма тизмебизге кирүүнү каалабасаңыз, анда көрсөтүлгөн пикир билдирүү байланыштар аркылуу бизге маалымдоо аркылуу жазылууну каалаган убакта баш тартсаңыз болот.",
"p6":"ЖЕКЕ ЭМЕС МААЛЫМАТТАРДЫ ЧОГУЛТУУ ЖАНА КОЛДОНУУ",
"p7":"Биз ошондой эле жеке эмес саналган персоналдык маалыматтарды чогултабыз - кандайдыр бир аныкталган адам менен түздөн-түз байланышта болбогон маалымат. Биз ар кандай максатта жеке эмес маалыматты чогултабыз, колдонобуз, өткөрүп бере алабыз жана ачып бере алабыз. Төмөндө биз чогулткан жеке эмес маалыматтын мисалдары жана биз аны кантип колдонорубуз көрсөтүлгөн. Биз Сайтка өтүүнүн булагы, Сайттагы колдонуучунун жүрүм-турум өзгөчүлүктөрү, Сайтта каралган барактардын саны сыяктуу персоналдык маалыматтарды чогултушубуз мүмкүн. Мындай маалыматтар анонимдүү жана жеке эмес. Бул маалыматтар Сайттын конокторун кандай маалымат кызыктырарын жакшыраак түшүнүү жана мазмундун сапатын жакшыртуу максатында чогултулат.",
"p8":"Колдонуучулардын жеке маалыматтары үчүнчү жактарга берилбейт. Ушул Эрежелер күчүнө киргенге чейин бизге өздөрүнүн жеке маалыматтарын берген колдонуучулар автоматтык түрдө өздөрүнүн жеке маалыматтарын пайдаланууга макул болушат. Ошол эле учурда алар ар дайым пикир билдирүү формасы аркылуу жеке маалыматтарды колдонууга макулдугун кайта чакырып ала алышат. Оператор  Колдонуучудан же анын мыйзамдуу өкүлүнөн арыз же суроо-талап түшкөн учурдан тартып, такталбаган жеке маалыматтар же мыйзамсыз аракеттер аныкталган учурда  тиешелүү Колдонуучуга таандык жеке маалыматтарды жаап коет.",
"p9": "Жеке маалыматтардын субъекти жеке маалыматтарды иштетүүгө макулдугун  кайта чакырып алса, колдонуучунун жеке маалыматтары жок кылынат.",
"p10":"ИДЕНФИКАЦИЯЛЫК ФАЙЛДАР (КУКИ) ЖАНА БАШКА ТЕХНОЛОГИЯЛАР",
"p11":"Вебсайт, интерактивдүү кызматтар жана тиркемелер, электрондук почта билдирүүлөрү жана биздин атыбыздан башка коммуникациялар идентификациялык куки файлдарын (cookies) жана башка пиксел энбелгилерин (pixel tags), веб-маяктар (web beacons) сыяктуу башка технологияларды колдонушу мүмкүн. Демек, веб-сайтты жана башка кызматтарды колдонууда, сиздин түзмөгүңүздө кукилерди колдонуу жөнүндө эскерткен калкыма терезе пайда болушу мүмкүн. Бул технологиялар бизге колдонуучунун жүрүм-турумун жакшыраак түшүнүүгө, биздин сайттын кайсы бөлүктөрүнө колдонуучулар киргенин айтып берүүгө жана жарнаманын жана онлайн издөөлөрдүн натыйжалуулугун өлчөөгө жардам берет. Биз кукилер жана башка технологиялар тарабынан чогултулган маалыматты жеке эмес маалымат деп эсептейбиз. Сиз браузериңиздеги же түзмөгүңүздөгү нускамаларды аткаруу менен cookie файлдарын көзөмөлдөй аласыз. Адатта, сиз куки жөндөөлөрүңүздү 'Жөндөөлөр' же 'Система жөндөөлөрү' бөлүмүнөн өзгөртө аласыз. Көпчүлүк веб-сайттардагыдай эле, биз кээ бир маалыматты автоматтык түрдө чогултуп, статистикалык файлдарда сактайбыз. Бул маалыматка Интернет Протоколунун (IP) дареги, браузердин түрү жана тили, Интернет-кызмат провайдери, шилтеме берүү жана чыгуу баракчалары, операциялык тутум маалыматы, дата жана убакыт маалыматы жана кирип чыгуу маалыматы кирет. Биз мындай маалыматты тенденцияларды түшүнүү жана талдоо, сайтты башкаруу, сайттагы колдонуучунун жүрүм-турумун изилдөө жана жалпы колдонуучуларыбыз жамааты жөнүндө демографиялык маалыматты чогултуу үчүн колдонобуз. Уюм мындай маалыматты өзүнүн маркетингдик максаттары үчүн колдоно алат. Кээ бир электрондук почта билдирүүлөрүндө биз Сайтта жайгаштырылган маалыматка интерактивдүү шилтемелерди колдонобуз. Колдонуучулар биздин веб-сайттагы көздөгөн бетке жеткенге чейин мындай шилтемелер боюнча өткөндө, алардын суроо-талаптары өзүнчө катталат. Биз белгилүү бир темаларга болгон кызыгууну аныктоого жана колдонуучулар менен болгон байланышыбыздын натыйжалуулугун өлчөөгө жардам берүү үчүн бул 'өтүү' маалыматына көз салабыз. Эгер сиз өзүңүздүн байланыштарыңызга ушундай жол менен көз салууну каалабасаңыз, электрондук почта билдирүүлөрүндөгү тексттик же сүрөт шилтемелерин боюнча өтпөшүңүз керек . Пиксел энбелгилери бизге электрондук почта билдирүүлөрүн керектөөчү окуй турган форматта жеткирүүгө жана мындай билдирүүлөр окулгандыгы жөнүндө маалымат берет. Биз мындай маалыматты керектөөчүлөргө жөнөткөн байланыштарды чектөө же токтотуу үчүн колдонушубуз мүмкүн.",
"p12":"Колдонуучунун жеке маалыматтарын уруксатсыз же кокустан  жетүүдөн, жок кылуудан, өзгөртүүдөн,  жаап коюудан, көчүрүүдөн, жайылтуудан, ошондой эле үчүнчү жактардын башка мыйзамсыз аракеттеринен коргоону камсыз кылуу үчүн биз сиздин жеке маалыматтарыңыздын корголушун камсыз кылуу боюнча чараларды көрөбүз.",
"p13":"ЖЕКЕ МААЛЫМАТТАРДЫН БҮТҮНДҮГҮ ЖАНА САКТАЛЫШЫ",
"p14":"Биз сиздин жеке маалыматтарыңызды ушул Купуялык саясатында сүрөттөлгөн максаттарды аткаруу үчүн зарыл болгон мезгил ичинде сактайбыз. Биз жашы жете электердин жеке маалыматын чогултпайбыз. Эгер биз жашы жете элек бала жөнүндө жеке маалыматты чогултканыбызды билсек, биз мындай маалыматты мүмкүн болушунча тезирээк жок кылуу үчүн чараларды көрөбүз. Биз ата-энелерге жана көзөмөлүндө жашы жете элек балдар бар башка адамдарга (мыйзамдуу өкүлдөр - ата-энелер, асырап алуучулар же камкорчулар) жашы жете электердин веб-сайттарды колдонуусун көзөмөлдөөнү туруктуу сунуштайбыз. Оператор Интернетти же Сайттын Кызматтарын колдонуунун натыйжасында Колдонуучу жөнүндө маалыматка жетүү мүмкүнчүлүгүнө ээ болгон үчүнчү жактардын аракеттери үчүн жана Сайттын мүнөзүнө байланыштуу каалаган интернет колдонуучуга жеткиликтүү маалыматтарды колдонуунун кесепеттери үчүн жоопкерчилик тартпайт.",
"p15":"КУПУЯЛЫК ЖӨНҮНДӨ СУРООЛОРУ",
"p16":"Бул Купуялык саясатына же маалыматтарды иштетүүгө байланыштуу суроолоруңуз болсо, kyrgyzstan@transparency.org байланыш дареги аркылуу биз менен байланышсаңыз болот.",
"p17":"Колдонуучулар Операторго өздөрүнүн суроо-талаптарын, анын ичинде алардын жеке маалыматтарын колдонууга, жеке маалыматтарды иштетүүгө макулдукту жокко чыгарууга байланыштуу суроо-талаптарды ушул жобонун Жалпы жоболор бөлүмүндө көрсөтүлгөн дарек боюнча жазуу жүзүндө же пикир билдирүү формасы аркылуу жөнөтүлгөн электрондук документ формасында жөнөтүүгө укуктуу. Оператор суроо-талапты алган күндөн тартып 30 күндүн ичинде Колдонуучунун суроо-талабын карап чыгууга жана ага жооп жөнөтүүгө милдеттенет.",
"p18":"БАШКАЛАР",
"p19":"Уюмдун веб-сайтына өзүнүн жеке маалыматтарын жана маалыматын берген конок ушул Купуялык саясатынын жоболоруна макул болот. Колдонуучунун макулдугусуз Колдонуучулар тарабынан билдирилген жеке маалыматтарга уруксатсыз кирүүдөн коргоо тутумун андан ары өркүндөтүү максатында, биз каалаган убакта ушул Саясатка өзгөртүүлөрдү киргизүү укугун сактайбыз. Купуялык саясатыбызга олуттуу өзгөртүүлөрдү киргизгенде, биз Купуялык саясатыбыздын жаңыртылган версиясы менен бирге веб-сайтыбызга билдирүү жарыялайбыз. Бул Саясат үчүнчү жактардын аракеттерине жана интернет-ресурстарына жайылтылбайт."
}
}
+224
View File
@@ -0,0 +1,224 @@
{
"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": "Все права защищены"
},
"PrivacyPolicy":{
"titel0":"Политика конфиденциальности",
"title1": "Общие положения",
"title2": "Сбор персональных данных",
"title3": "Хранение и использование персональных данных",
"title4": "Передача персональных данных",
"title5": "Уничтожение персональных данных",
"title6": "Защита персональных данных",
"title7": "Обращения пользователей ",
"p1":"Настоящее Положение о политике конфиденциальности определяет порядок обработки и защиты информации о физических лицах (далее — Пользователи), пользующихся почтовой рассылкой, формой обратной связи сайта, электронными почтовыми адресами, расположенными на доменном имени ",
"p1o":" (далее — Сайт). Мы разработали Политику Конфиденциальности, которая описывает, как мы осуществляем обработку персональных данных — любые действия (операции) или совокупность действий (операций), совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение (обновление, изменение), извлечение, использование, передачу (распространение, предоставление, доступ), обезличивание, блокирование, удаление, уничтожение персональных данных. Отношения, связанные с обработкой персональных данных и информации о пользователях Сайта, регулируются настоящим Положением. Настоящая Политика Конфиденциальности регулирует любой вид обработки персональных данных и информации личного характера (любой информации, позволяющей установить личность, и любой иной информации, связанной с этим) о физических лицах, которые являются пользователями Сайта. Настоящая Политика распространяется на обработку личных, персональных данных, собранных любыми средствами, как активными, так и пассивными, как через Интернет, так и без его использования, от лиц, находящихся в любой точке мира.",
"p2": "Целью обработки персональных данных является выполнение обязательств Оператора перед Пользователями в отношении использования Сайта и его сервисов. Обработка персональных данных пользователей осуществляется с согласия субъекта персональных данных на обработку его персональных данных. Под персональными данными понимается любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу (субъекту персональных данных) и которая может быть использована для идентификации определенного лица либо связи с ним. Мы можем запросить у Вас имя, фамилию, отчество и адрес электронной почты для того, чтобы включить Вас в почтовую рассылку с Вашего согласия, а также для того, чтобы ответить Вам, если Вы обратились к нам через форму обратной связи на Сайте.",
"p3": "Персональные данные Пользователей хранятся исключительно на электронных носителях и обрабатываются с использованием автоматизированных систем. ",
"p4": "КАК МЫ ИСПОЛЬЗУЕМ ВАШУ ПЕРСОНАЛЬНУЮ ИНФОРМАЦИЮ",
"p5":"Собираемые нами персональные данные позволяют направлять Вам по электронной почте уведомления о новостях и различных событиях в сфере антикоррупции. Также ваши персональные данные могут быть использованы при ответе на ваше обращение, направленное через форму обратной связи или на наш адрес электронной почты, расположенные на Сайте. Если Вы не желаете быть включенным в наш список рассылки, Вы можете в любое время отказаться от рассылки путём информирования нас по указанным контактам для обратной связи.",
"p6":"СБОР И ИСПОЛЬЗОВАНИЕ ИНФОРМАЦИИ, НЕ ЯВЛЯЮЩЕЙСЯ ПЕРСОНАЛЬНОЙ",
"p7":"Мы также собираем персональные данные, не являющиеся персональными − данные, не позволяющие прямо ассоциировать их с каким-либо определённым лицом. Мы можем собирать, использовать, передавать и раскрывать информацию, не являющуюся персональной, для любых целей. Ниже приведены примеры информации, не являющейся персональной, которую мы собираем, и как мы можем её использовать. Мы можем собирать персональные данные, такие как: источник перехода на Сайт, особенности поведения пользователя на Сайте, количество просмотренных на Сайте страниц. Такие данные обезличены и не являются персональными. Эти данные собираются для того, чтобы лучше понимать, какая информация интересна посетителям Сайта и улучшать качество контента.",
"p8":"Персональные данные Пользователей не передаются каким-либо третьим лицам. Пользователи, которые передали нам свои персональные данные до вступления в силу настоящих Правил автоматически соглашаются на использование своих персональных данных. В тоже время, они всегда могут отозвать свое согласие на использование персональных данных через форму обратной связи. Оператор осуществляет блокирование персональных данных, относящихся к соответствующему Пользователю, с момента обращения или запроса Пользователя или его законного представителя, в случае выявления недостоверных персональных данных или неправомерных действий.",
"p9":"Персональные данные пользователя уничтожаются при отзыве субъектом персональных данных согласия на обработку персональных данных.",
"p10":"ИДЕНТИФИКАЦИОННЫЕ ФАЙЛЫ (СOOKIES) И ИНЫЕ ТЕХНОЛОГИИ",
"p11":"Веб-сайт, интерактивные услуги и приложения, сообщения электронной почты и любые иные коммуникации от нашего лица могут использовать идентификационные файлы cookies и иные технологии, такие как пиксельные ярлыки (pixel tags), веб-маяки (web beacons). Соответственно при пользовании веб-сайтом и иными услугами на Вашем устройстве может возникнуть всплывающее окно с уведомлением об использовании cookies. Указанные технологии помогают нам лучше понимать поведение пользователей, сообщают нам, какие разделы нашего сайта были посещены пользователями, и измеряют эффективность рекламы и сетевых поисков. Мы рассматриваем информацию, собираемую файлами cookies и иными технологиями как информацию, не являющуюся персональной. Вы можете контролировать cookies следуя инструкциям Вашего браузера или устройства. Как правило, Вы можете изменить настройки cookies в разделе «Настройки» или «Системные настройки». Как и в случае большинства веб-сайтов, мы собираем некоторую информацию автоматически и храним её в файлах статистики. Такая информация включает в себя адрес Интернет-протокола (IP-адрес), тип и язык браузера, информацию о поставщике Интернет-услуг, страницы отсылки и выхода, сведения об операционной системе, отметку даты и времени, а также сведения о посещениях. Мы используем такую информацию для понимания и анализа тенденций, администрирования сайта, изучения поведения пользователей на сайте и сбора демографической информации о нашем основном контингенте пользователей в целом. Организация может использовать такую информацию в своих маркетинговых целях. В некоторых наших сообщениях электронной почты мы используем интерактивные ссылки на информацию, размещённую на Сайте. Когда пользователи проходят по таким ссылкам, прежде чем они попадают на страницу назначения на нашем веб-сайте, их запросы проходят отдельную регистрацию. Мы отслеживаем такие «проходные» данные, для того чтобы помочь нам определить интерес к отдельным темам и измерить эффективность наших коммуникаций с пользователями. Если Вы предпочитаете, чтобы Ваши обращения не отслеживались подобным образом, Вы не должны проходить по текстовым или графическим ссылкам в сообщениях электронной почты. Пиксельные ярлыки позволяют нам направлять сообщения электронной почты в формате, читаемом потребителями, и сообщают нам, были ли такие сообщения прочитаны. Мы можем использовать такую информацию для ограничения количества направляемых потребителям сообщений или прекращения их направления.",
"p12":"Мы предпринимает меры предосторожности для обеспечения защиты Ваших персональных данных в целях обеспечения защиты персональных данных Пользователя от неправомерного или случайного доступа к ним, уничтожения, изменения, блокирования, копирования, распространения, а также от иных неправомерных действий третьих лиц.",
"p13":"ЦЕЛОСТНОСТЬ И СОХРАНЕНИЕ ПЕРСОНАЛЬНОЙ ИНФОРМАЦИИ",
"p14":"Мы будем хранить Ваши персональные данные и информацию в течение срока, необходимого для выполнения целей, описываемых в настоящей Политике Конфиденциальности. Мы не собираем персональные данные о несовершеннолетних. Если нам станет известно о том, что мы получили персональные данные о несовершеннолетнем, мы предпримем меры для удаления такой информации в максимально короткий срок. Мы настоятельно рекомендуем родителям и иным лицам, под чьим присмотром находятся несовершеннолетние (законные представители — родители, усыновители или попечители), контролировать использование несовершеннолетними веб-сайтов. Оператор не несет ответственности за действия третьих лиц, получивших в результате использования Интернета или Услуг Сайта доступ к информации о Пользователе и за последствия использования данных и информации, которые, в силу природы Сайта, доступны любому пользователю сети Интернет.",
"p15":"ВОПРОСЫ ОТНОСИТЕЛЬНО КОНФИДЕНЦИАЛЬНОСТИ",
"p16":"Если у вас возникнут вопросы в отношении настоящей Политики Конфиденциальности или обработки данных, Вы можете связаться с нами по контактам обратной связи kyrgyzstan@transparency.org.",
"p17":"Пользователи вправе направлять Оператору свои запросы, в том числе запросы относительно использования их персональных данных, направления отзыва согласия на обработку персональных данных в письменной форме по адресу, указанному в разделе Общие положения настоящего положения, или в форме электронного документа, отправленного посредством формы обратной связи. Оператор обязуется рассмотреть и направить ответ на поступивший запрос Пользователя в течение 30 дней с момента поступления обращения.",
"p18":"ДРУГОЕ",
"p19": "Посетитель сайта Организации, предоставляющий свои персональные данные и информацию, тем самым соглашается с положениями данной Политики Конфиденциальности. Мы оставляем за собой право вносить любые изменения в Политику в любое время по своему усмотрению с целью дальнейшего совершенствования системы защиты от несанкционированного доступа к сообщаемым Пользователями персональным данным без согласия Пользователя. Когда мы вносим существенные изменения в Политику Конфиденциальности, на нашем сайте размещается соответствующее уведомление вместе с обновлённой версией Политики Конфиденциальности. Действие настоящей Политики не распространяется на действия и интернет-ресурсы третьих лиц."
}
}
+8 -1
View File
@@ -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 -2
View File
@@ -5,15 +5,18 @@
"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",
"html-react-parser": "^5.1.15",
"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",
+3589
View File
File diff suppressed because it is too large. Load diff
-9
View File
@@ -1,9 +0,0 @@
.app {
padding-top: 78px;
}
@media screen and (max-width: 768px) {
.app {
padding-top: 72px;
}
}
+9
View File
@@ -0,0 +1,9 @@
// .app {
// padding-top: 78px;
// }
// @media screen and (max-width: 768px) {
// .app {
// padding-top: 72px;
// }
// }
File renamed without changes.
@@ -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;
} }
} }
+102
View File
@@ -0,0 +1,102 @@
import Typography from "@/shared/ui/components/Typography/Typography";
import "./AboutUs.scss";
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>
<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 />
+30
View File
@@ -0,0 +1,30 @@
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;
}
} }
} }
+9
View File
@@ -0,0 +1,9 @@
"use client";
import NotFound from "@/widgets/NotFound/NotFound";
const error = () => {
return <NotFound />;
};
export default error;
@@ -6,16 +6,46 @@ 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";
import parse from "html-react-parser";
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,
},
};
}
function convertLinksToHtml(text: string) {
const urlPattern =
/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|$!:,.;]*[-A-Z0-9+&@#\/%=~_|$])[\s.,;]?/gi;
return text.replace(urlPattern, (url) => {
const href = url.trim();
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${href}</a>`;
});
}
const formatDescription = (text: string) => {
return text.replace(/(\r\n|\n|\r)/g, "<br />");
};
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>(`/news/${params.id}/`);
`/news/${params.id}/`
);
return response.data; return response.data;
}; };
@@ -36,9 +66,11 @@ const NewsDetails = async ({
"11": "Ноябрь", "11": "Ноябрь",
"12": "Декабрь", "12": "Декабрь",
}; };
const htmlContentWithLinks = convertLinksToHtml(data.description);
const formattedHtmlContent = formatDescription(htmlContentWithLinks);
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">
@@ -48,9 +80,9 @@ const NewsDetails = async ({
<Image src={calendar} alt="Calendar Icon" /> <Image src={calendar} alt="Calendar Icon" />
<p> <p>
{months[data.created_at.slice(5, 7)]}{" "} {months[data.created_at.slice(5, 7)]}{" "}
{data.created_at.slice(5, 7).slice(0, 1) === "0" {data.created_at.slice(8, 10) === "0"
? data.created_at.slice(6, 7) ? data.created_at.slice(9, 10)
: data.created_at.slice(5, 7)} : data.created_at.slice(8, 10)}
, {data.created_at.slice(0, 4)} , {data.created_at.slice(0, 4)}
</p> </p>
</div> </div>
@@ -64,7 +96,9 @@ const NewsDetails = async ({
<div className="news-details__text"> <div className="news-details__text">
<h3>{data.title}</h3> <h3>{data.title}</h3>
<p>{data.description}</p> <div className="news-details__description">
{parse(formattedHtmlContent)}
</div>
</div> </div>
<ReviewSection endpoint="news" id={data.id} /> <ReviewSection endpoint="news" id={data.id} />
+59
View File
@@ -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;
+9
View File
@@ -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 />;
}
+78
View File
@@ -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;
+102
View File
@@ -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,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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

+99
View File
@@ -0,0 +1,99 @@
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";
import { useTranslations } from "next-intl";
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 = () => {
const tPrivacyPolicy = useTranslations("PrivacyPolicy");
return (
<div className="privacy page-padding">
<BreadCrumbs homeRequired />
<Typography element="h2">{tPrivacyPolicy("titel0")}</Typography>
<div className="privacy__descriptions">
<ol>
<h3><span>{tPrivacyPolicy("title1")}</span></h3>
</ol>
<p><span>
{tPrivacyPolicy("p1")}
<a href='https://askjournal.kg/'>https://askjournal.kg/</a>
{tPrivacyPolicy("p1o")}
</span></p>
<h3><span>{tPrivacyPolicy("title2")}</span></h3>
<p><span>{tPrivacyPolicy("p2")}</span></p>
<h3><span>{tPrivacyPolicy("title3")}</span></h3>
<p><span>{tPrivacyPolicy("p3")}</span></p>
<p><span>{tPrivacyPolicy("p4")}</span></p>
<p><span>{tPrivacyPolicy("p5")}</span></p>
<p><span>{tPrivacyPolicy("p6")}</span></p>
<p><span>{tPrivacyPolicy("p7")}</span></p>
<h3><span>{tPrivacyPolicy("title4")}</span></h3>
<p><span>{tPrivacyPolicy("p8")}</span></p>
<h3><span>{tPrivacyPolicy("title5")}</span></h3>
<p><span>{tPrivacyPolicy("p9")}</span></p>
<p><span>{tPrivacyPolicy("p10")}</span></p>
<p><span>{tPrivacyPolicy("p11")}</span></p>
<h3><span>{tPrivacyPolicy("title6")}</span></h3>
<p><span>{tPrivacyPolicy("p12")}</span></p>
<p><span>{tPrivacyPolicy("p13")}</span></p>
<p><span>{tPrivacyPolicy("p14")}</span></p>
<p><span>{tPrivacyPolicy("p15")}</span></p>
<p><span>{tPrivacyPolicy("p16")}</span></p>
<h3><span>{tPrivacyPolicy("title7")}</span></h3>
<p><span>{tPrivacyPolicy("p17")}</span></p>
<p><span>{tPrivacyPolicy("p18")}</span></p>
<p><span>{tPrivacyPolicy("p19")}</span></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: "/",
});
}
}
} }
}; };
File renamed without changes.
@@ -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 ({
+13
View File
@@ -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;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
"use client";
import NotFound from "@/widgets/NotFound/NotFound";
const error = () => {
return <NotFound />;
};
export default error;
+85
View File
@@ -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;
File renamed without changes.
@@ -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;
}
} }
} }
+56
View File
@@ -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;
+83
View File
@@ -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

+126
View File
@@ -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;
}
} }
} }
+54
View File
@@ -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;
-70
View File
@@ -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;
+1 -4
View File
@@ -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,
+14 -7
View File
@@ -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
} }
-30
View File
@@ -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;
+15
View File
@@ -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>
);
}
-35
View File
@@ -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;
-7
View File
@@ -1,7 +0,0 @@
import React from "react";
const page = () => {
return <div>page</div>;
};
export default page;
-138
View File
@@ -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;
-12
View File
@@ -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;
-29
View File
@@ -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;
-28
View File
@@ -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;
-21
View File
@@ -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;
+11 -6
View File
@@ -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)}...`;
@@ -27,10 +29,7 @@ const NewsCard: React.FC<INewsCard> = ({
return title; return title;
}; };
const sliceDate = (date: string) => { const sliceDate = (date: string) => {
return `${date.slice(8, 10)}/${date.slice(5, 7)}/${date.slice( return `${date.slice(8, 10)}/${date.slice(5, 7)}/${date.slice(0, 4)}`;
0,
4
)}`;
}; };
const sliceDescription = (description: string) => { const sliceDescription = (description: string) => {
@@ -56,8 +55,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>
); );
+29
View File
@@ -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);
}
}
+67
View File
@@ -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

+17 -1
View File
@@ -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>
+123 -5
View File
@@ -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;
}
}
} }
+220 -28
View File
@@ -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

+7 -2
View File
@@ -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

+1
View File
@@ -25,6 +25,7 @@
img { img {
width: 50px; width: 50px;
height: auto;
} }
input { input {
+5 -8
View File
@@ -3,9 +3,9 @@
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> {
handleSubmit: (e: React.MouseEvent<HTMLFormElement>) => void; handleSubmit: (e: React.MouseEvent<HTMLFormElement>) => void;
} }
@@ -17,12 +17,9 @@ const SearchForm: React.FC<ISearchFormProps> = ({
onChange, onChange,
style, style,
}: ISearchFormProps) => { }: ISearchFormProps) => {
const t = useTranslations("general");
return ( return (
<form <form style={style} className="search-form" onSubmit={handleSubmit}>
style={style}
className="search-form"
onSubmit={handleSubmit}
>
<div className="search-form__input"> <div className="search-form__input">
<Image src={search} alt="Search Icon" /> <Image src={search} alt="Search Icon" />
<input <input
@@ -33,7 +30,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;
+11
View File
@@ -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,
};
});
+48 -6
View File
@@ -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",
],
}; };
+10
View File
@@ -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: "",
});
};
+88 -27
View File
@@ -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;
}, },
+7
View File
@@ -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 });
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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;
+8
View File
@@ -0,0 +1,8 @@
export interface IMetatag {
id: number;
title: string;
description: string;
keywords: string;
og_image?: string;
page: string;
}
+14
View File
@@ -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;
}
+5
View File
@@ -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;
}
+2
View File
@@ -1,6 +1,8 @@
export interface IUserRatings { export interface IUserRatings {
user_id: number; user_id: number;
username: string; username: string;
first_name: string;
last_name: string;
report_count: number; report_count: number;
likes_given_count: number; likes_given_count: number;
likes_received_count: number; likes_received_count: number;
+2 -3
View File
@@ -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 {
+11 -2
View File
@@ -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;
Loaded 100 of 224 files, more files were not shown because too many files have changed in this diff. Show more