fix: completion page apis, validation, email states

This commit is contained in:
Sajad Mirjalili
2025-08-20 20:55:24 +03:30
parent 58c044542a
commit b02f655d4d
38 changed files with 935 additions and 1203 deletions

View File

@@ -1,254 +1,10 @@
{
"helloWorld": "hello world",
"country": {
"afghanistan": "Afghanistan",
"aland_islands": "Aland islands",
"albania": "Albania",
"algeria": "Algeria",
"american_samoa": "American samoa",
"andorra": "Andorra",
"angola": "Angola",
"anguilla": "Anguilla",
"antarctica": "Antarctica",
"antigua_and_barbuda": "Antigua and barbuda",
"argentina": "Argentina",
"armenia": "Armenia",
"aruba": "Aruba",
"australia": "Australia",
"austria": "Austria",
"azerbaijan": "Azerbaijan",
"bahamas": "Bahamas",
"bahrain": "Bahrain",
"bangladesh": "Bangladesh",
"barbados": "Barbados",
"belarus": "Belarus",
"belgium": "Belgium",
"belize": "Belize",
"benin": "Benin",
"bermuda": "Bermuda",
"bhutan": "Bhutan",
"bolivia": "Bolivia",
"bosnia_and_herzegovina": "Bosnia and herzegovina",
"botswana": "Botswana",
"brazil": "Brazil",
"british_indian_ocean_territory": "British indian ocean territory",
"british_virgin_islands": "British virgin islands",
"brunei": "Brunei",
"bulgaria": "Bulgaria",
"burkina_faso": "Burkina faso",
"burundi": "Burundi",
"cambodia": "Cambodia",
"cameroon": "Cameroon",
"canada": "Canada",
"cape_verde": "Cape verde",
"cayman_islands": "Cayman islands",
"central_african_republic": "Central african republic",
"chad": "Chad",
"chile": "Chile",
"china": "China",
"christmas_island": "Christmas island",
"cocos_keeling_islands": "Cocos keeling islands",
"colombia": "Colombia",
"comoros": "Comoros",
"congo_brazzaville": "Congo brazzaville",
"congo_kinshasa": "Congo kinshasa",
"cook_islands": "Cook islands",
"costa_rica": "Costa rica",
"cote_divoire": "Cote divoire",
"croatia": "Croatia",
"cuba": "Cuba",
"curacao": "Curacao",
"cyprus": "Cyprus",
"czech_republic": "Czech republic",
"denmark": "Denmark",
"djibouti": "Djibouti",
"dominica": "Dominica",
"dominican_republic": "Dominican republic",
"ecuador": "Ecuador",
"egypt": "Egypt",
"el_salvador": "El salvador",
"equatorial_guinea": "Equatorial guinea",
"eritrea": "Eritrea",
"estonia": "Estonia",
"eswatini": "Eswatini",
"ethiopia": "Ethiopia",
"falkland_islands": "Falkland islands",
"faroe_islands": "Faroe islands",
"fiji": "Fiji",
"finland": "Finland",
"france": "France",
"french_guiana": "French guiana",
"french_polynesia": "French polynesia",
"gabon": "Gabon",
"gambia": "Gambia",
"georgia": "Georgia",
"germany": "Germany",
"ghana": "Ghana",
"gibraltar": "Gibraltar",
"greece": "Greece",
"greenland": "Greenland",
"grenada": "Grenada",
"guadeloupe": "Guadeloupe",
"guam": "Guam",
"guatemala": "Guatemala",
"guernsey": "Guernsey",
"guinea": "Guinea",
"guinea_bissau": "Guinea bissau",
"guyana": "Guyana",
"haiti": "Haiti",
"honduras": "Honduras",
"hong_kong": "Hong kong",
"hungary": "Hungary",
"iceland": "Iceland",
"india": "India",
"indonesia": "Indonesia",
"iran": "Iran",
"iraq": "Iraq",
"ireland": "Ireland",
"isle_of_man": "Isle of man",
"israel": "Israel",
"italy": "Italy",
"jamaica": "Jamaica",
"japan": "Japan",
"jersey": "Jersey",
"jordan": "Jordan",
"kazakhstan": "Kazakhstan",
"kenya": "Kenya",
"kiribati": "Kiribati",
"kosovo": "Kosovo",
"kuwait": "Kuwait",
"kyrgyzstan": "Kyrgyzstan",
"laos": "Laos",
"latvia": "Latvia",
"lebanon": "Lebanon",
"lesotho": "Lesotho",
"liberia": "Liberia",
"libya": "Libya",
"liechtenstein": "Liechtenstein",
"lithuania": "Lithuania",
"luxembourg": "Luxembourg",
"macau": "Macau",
"madagascar": "Madagascar",
"malawi": "Malawi",
"malaysia": "Malaysia",
"maldives": "Maldives",
"mali": "Mali",
"malta": "Malta",
"marshall_islands": "Marshall islands",
"martinique": "Martinique",
"mauritania": "Mauritania",
"mauritius": "Mauritius",
"mayotte": "Mayotte",
"mexico": "Mexico",
"micronesia": "Micronesia",
"moldova": "Moldova",
"monaco": "Monaco",
"mongolia": "Mongolia",
"montenegro": "Montenegro",
"montserrat": "Montserrat",
"morocco": "Morocco",
"mozambique": "Mozambique",
"myanmar": "Myanmar",
"namibia": "Namibia",
"nauru": "Nauru",
"nepal": "Nepal",
"netherlands": "Netherlands",
"new_caledonia": "New caledonia",
"new_zealand": "New zealand",
"nicaragua": "Nicaragua",
"niger": "Niger",
"nigeria": "Nigeria",
"niue": "Niue",
"norfolk_island": "Norfolk island",
"north_korea": "North korea",
"north_macedonia": "North macedonia",
"northern_mariana_islands": "Northern mariana islands",
"norway": "Norway",
"oman": "Oman",
"pakistan": "Pakistan",
"palau": "Palau",
"palestine": "Palestine",
"panama": "Panama",
"papua_new_guinea": "Papua new guinea",
"paraguay": "Paraguay",
"peru": "Peru",
"philippines": "Philippines",
"pitcairn_islands": "Pitcairn islands",
"poland": "Poland",
"portugal": "Portugal",
"puerto_rico": "Puerto rico",
"qatar": "Qatar",
"reunion": "Reunion",
"romania": "Romania",
"russia": "Russia",
"rwanda": "Rwanda",
"saint_barthelemy": "Saint barthelemy",
"saint_helena": "Saint helena",
"saint_kitts_and_nevis": "Saint kitts and nevis",
"saint_lucia": "Saint lucia",
"saint_martin": "Saint martin",
"saint_pierre_and_miquelon": "Saint pierre and miquelon",
"saint_vincent_and_the_grenadines": "Saint vincent and the grenadines",
"samoa": "Samoa",
"san_marino": "San marino",
"sao_tome_and_principe": "Sao tome and principe",
"saudi_arabia": "Saudi arabia",
"senegal": "Senegal",
"serbia": "Serbia",
"seychelles": "Seychelles",
"sierra_leone": "Sierra leone",
"singapore": "Singapore",
"sint_maarten": "Sint maarten",
"slovakia": "Slovakia",
"slovenia": "Slovenia",
"solomon_islands": "Solomon islands",
"somalia": "Somalia",
"south_africa": "South africa",
"south_georgia_and_south_sandwich_islands": "South georgia and south sandwich islands",
"south_korea": "South korea",
"south_sudan": "South sudan",
"spain": "Spain",
"sri_lanka": "Sri lanka",
"sudan": "Sudan",
"suriname": "Suriname",
"svalbard_and_jan_mayen": "Svalbard and jan mayen",
"sweden": "Sweden",
"switzerland": "Switzerland",
"syria": "Syria",
"taiwan": "Taiwan",
"tajikistan": "Tajikistan",
"tanzania": "Tanzania",
"thailand": "Thailand",
"timor_leste": "Timor leste",
"togo": "Togo",
"tokelau": "Tokelau",
"tonga": "Tonga",
"trinidad_and_tobago": "Trinidad and tobago",
"tunisia": "Tunisia",
"turkey": "Turkey",
"turkmenistan": "Turkmenistan",
"turks_and_caicos_islands": "Turks and caicos islands",
"tuvalu": "Tuvalu",
"us_virgin_islands": "Us virgin islands",
"uganda": "Uganda",
"ukraine": "Ukraine",
"united_arab_emirates": "United arab emirates",
"united_kingdom": "United kingdom",
"united_states": "United states",
"uruguay": "Uruguay",
"uzbekistan": "Uzbekistan",
"vanuatu": "Vanuatu",
"vatican_city": "Vatican city",
"venezuela": "Venezuela",
"vietnam": "Vietnam",
"wallis_and_futuna": "Wallis and futuna",
"western_sahara": "Western sahara",
"yemen": "Yemen",
"zambia": "Zambia",
"zimbabwe": "Zimbabwe"
"labels": {
"search": "search"
},
"messages": {
"noResualtFound": "No result found.",
"serverError": "Internal server error"
"serverError": "Internal server error",
"networkError": "Network error. Please check your internet connection and try again."
}
}

View File

@@ -4,7 +4,7 @@
"description": "Enter your business information",
"name": "Name",
"familyName": "Family Name",
"gender": "Gender",
"gender": "Gender(Optional)",
"optionalNationalCode": "National Code(Optional)",
"determinePassword": "Determine Password",
"password": "Password",
@@ -23,8 +23,8 @@
"hasSpecialChar": "includes sign (!@#$%^&*)",
"notCompatibility": "does not match",
"emailCorrectForm": "Enter the correct email form.",
"agreementPart1": "By continuing the registration process, you agree to the",
"agreementLinkText": " Harmony Terms and Conditions",
"agreementPart1": "By continuing the registration process, you agree to the ",
"agreementLinkText": "Harmony Terms and Conditions",
"agreementPart2": ".",
"sent": "sent",
"country": "country",
@@ -34,5 +34,18 @@
"submitting": "Submitting...",
"success": "Success",
"agreement": "1. Confidentiality of Information: Harmony commits under no circumstances to disclose users identity information, such as phone numbers, email addresses, passwords, user IDs, or any related data, to third parties.\n\n2. User information is used solely for providing authentication services and remains confidential even after account deactivation or termination.\n\n3. Harmony is obliged to implement necessary security measures to prevent unauthorized access.\n\n4. Responsibility for Account Security: Users must protect their accounts and choose strong, non-guessable passwords. Periodic password changes and immediate action in case of suspected unauthorized access are required. Any misuse of the account due to user negligence is the responsibility of the user.\n\n5. Security Breaches and Cyber Attacks: Harmony is not responsible for security breaches caused by cyber attacks beyond the system's control. However, Harmony employs up-to-date security standards and encryption to prevent such incidents.\n\n6. User Negligence in Protecting Information: If account information is disclosed due to user negligence or error, Harmony bears no responsibility. Determining such cases, based on system security logs, is the responsibility of Harmony's technical manager.\n\n7. Accurate Logging of Activities: All events related to registering, editing, and deleting information in the system are accurately and immutably logged. Claims regarding deletion or modification of data without logs are invalid unless supported by documentation provided by the user.\n\n8. Service Updates: Harmony services may be updated or changed over time. Continued use of the system after changes implies acceptance of the new terms. If users disagree, they may request account deletion.\n\n9. User Support: Support is provided only via email and phone, free of charge. Harmony is not obligated to provide in-person support or training beyond basic services.\n\n10. Official Communication Channels: Harmony communicates with users only via the phone number and email registered in the user account. Official announcements are sent through these channels.\n\n11. Official Domains for Communication: All emails from Harmony are sent exclusively from the domain harmony.id. Users must verify this to prevent phishing or similar attacks.\n\n12. Compliance with Iranian Laws: Users must comply with all applicable laws of the Islamic Republic of Iran, including the “Electronic Commerce Law,” “Computer Crimes Law,” and related legislation. Responsibility for violations rests with the user.\n\n13. Temporary Data Retention After Account Termination: Upon account termination or deletion, user information is stored securely for 30 days and permanently deleted thereafter.\n\n14. Ownership of User Data: All data submitted by users belongs to them. Harmony has no ownership over this information. Users are responsible for the accuracy, quality, and legality of their data.\n\n15. Purposeful Use of Identity Information: Collected identity information during registration is used only for authentication and basic services. It will not be shared with any third party without explicit user consent, except under a court order or legal authority.\n\n16. Permanent Data Confidentiality: Harmony commits to maintaining confidentiality of collected information even after the end of the user relationship or account closure.\n\n17. Limitation of Liability: Harmony is not liable for direct or indirect damages resulting from use or inability to use the authentication services.\n\n18. Disruptions in Communication Infrastructure: Harmony is not responsible for disruptions caused by the internet, infrastructure services, or other issues beyond its control.\n\n19. Force Majeure and Unforeseen Events: Harmony bears no responsibility for natural disasters, strikes, power outages, cyber attacks, or other events beyond its control that prevent service delivery.\n\n20. Services Dependent on Third Parties: If parts of the authentication services are provided by third parties, the usage terms of those services are the responsibility of those companies, and Harmony bears no liability.\n\n21. Guarantee of Data Access in Case of Service Termination: If Harmony ceases operations permanently, it commits to keeping servers active for two years and allowing users access to their data.\n\n22. Notification of Service Interruptions: If service interruption is necessary, Harmony must notify users at least 12 hours in advance via email or SMS."
},
"validation": {
"firstNameRequired": "First name is required.",
"lastNameRequired": "Last name is required.",
"countryRequired": "Country is required.",
"emailInvalid": "A valid email is required.",
"nationalIdInvalid": "National ID must be 10 digits.",
"passwordRequired": "Password is required.",
"passwordInvalid": "Password does not meet all the requirements.",
"confirmPasswordRequired": "Please confirm your password.",
"passwordsDoNotMatch": "Passwords do not match.",
"verificationCodeRequired": "Verification code is required.",
"mustVerifyCode": "Please click the verify button to confirm the code."
}
}

View File

@@ -1,247 +1,249 @@
{
"country.afghanistan": "Afghanistan",
"country.aland_islands": "Aland islands",
"country.albania": "Albania",
"country.algeria": "Algeria",
"country.american_samoa": "American samoa",
"country.andorra": "Andorra",
"country.angola": "Angola",
"country.anguilla": "Anguilla",
"country.antarctica": "Antarctica",
"country.antigua_and_barbuda": "Antigua and barbuda",
"country.argentina": "Argentina",
"country.armenia": "Armenia",
"country.aruba": "Aruba",
"country.australia": "Australia",
"country.austria": "Austria",
"country.azerbaijan": "Azerbaijan",
"country.bahamas": "Bahamas",
"country.bahrain": "Bahrain",
"country.bangladesh": "Bangladesh",
"country.barbados": "Barbados",
"country.belarus": "Belarus",
"country.belgium": "Belgium",
"country.belize": "Belize",
"country.benin": "Benin",
"country.bermuda": "Bermuda",
"country.bhutan": "Bhutan",
"country.bolivia": "Bolivia",
"country.bosnia_and_herzegovina": "Bosnia and herzegovina",
"country.botswana": "Botswana",
"country.brazil": "Brazil",
"country.british_indian_ocean_territory": "British indian ocean territory",
"country.british_virgin_islands": "British virgin islands",
"country.brunei": "Brunei",
"country.bulgaria": "Bulgaria",
"country.burkina_faso": "Burkina faso",
"country.burundi": "Burundi",
"country.cambodia": "Cambodia",
"country.cameroon": "Cameroon",
"country.canada": "Canada",
"country.cape_verde": "Cape verde",
"country.cayman_islands": "Cayman islands",
"country.central_african_republic": "Central african republic",
"country.chad": "Chad",
"country.chile": "Chile",
"country.china": "China",
"country.christmas_island": "Christmas island",
"country.cocos_keeling_islands": "Cocos keeling islands",
"country.colombia": "Colombia",
"country.comoros": "Comoros",
"country.congo_brazzaville": "Congo brazzaville",
"country.congo_kinshasa": "Congo kinshasa",
"country.cook_islands": "Cook islands",
"country.costa_rica": "Costa rica",
"country.cote_divoire": "Cote divoire",
"country.croatia": "Croatia",
"country.cuba": "Cuba",
"country.curacao": "Curacao",
"country.cyprus": "Cyprus",
"country.czech_republic": "Czech republic",
"country.denmark": "Denmark",
"country.djibouti": "Djibouti",
"country.dominica": "Dominica",
"country.dominican_republic": "Dominican republic",
"country.ecuador": "Ecuador",
"country.egypt": "Egypt",
"country.el_salvador": "El salvador",
"country.equatorial_guinea": "Equatorial guinea",
"country.eritrea": "Eritrea",
"country.estonia": "Estonia",
"country.eswatini": "Eswatini",
"country.ethiopia": "Ethiopia",
"country.falkland_islands": "Falkland islands",
"country.faroe_islands": "Faroe islands",
"country.fiji": "Fiji",
"country.finland": "Finland",
"country.france": "France",
"country.french_guiana": "French guiana",
"country.french_polynesia": "French polynesia",
"country.gabon": "Gabon",
"country.gambia": "Gambia",
"country.georgia": "Georgia",
"country.germany": "Germany",
"country.ghana": "Ghana",
"country.gibraltar": "Gibraltar",
"country.greece": "Greece",
"country.greenland": "Greenland",
"country.grenada": "Grenada",
"country.guadeloupe": "Guadeloupe",
"country.guam": "Guam",
"country.guatemala": "Guatemala",
"country.guernsey": "Guernsey",
"country.guinea": "Guinea",
"country.guinea_bissau": "Guinea bissau",
"country.guyana": "Guyana",
"country.haiti": "Haiti",
"country.honduras": "Honduras",
"country.hong_kong": "Hong kong",
"country.hungary": "Hungary",
"country.iceland": "Iceland",
"country.india": "India",
"country.indonesia": "Indonesia",
"country.iran": "Iran",
"country.iraq": "Iraq",
"country.ireland": "Ireland",
"country.isle_of_man": "Isle of man",
"country.israel": "Israel",
"country.italy": "Italy",
"country.jamaica": "Jamaica",
"country.japan": "Japan",
"country.jersey": "Jersey",
"country.jordan": "Jordan",
"country.kazakhstan": "Kazakhstan",
"country.kenya": "Kenya",
"country.kiribati": "Kiribati",
"country.kosovo": "Kosovo",
"country.kuwait": "Kuwait",
"country.kyrgyzstan": "Kyrgyzstan",
"country.laos": "Laos",
"country.latvia": "Latvia",
"country.lebanon": "Lebanon",
"country.lesotho": "Lesotho",
"country.liberia": "Liberia",
"country.libya": "Libya",
"country.liechtenstein": "Liechtenstein",
"country.lithuania": "Lithuania",
"country.luxembourg": "Luxembourg",
"country.macau": "Macau",
"country.madagascar": "Madagascar",
"country.malawi": "Malawi",
"country.malaysia": "Malaysia",
"country.maldives": "Maldives",
"country.mali": "Mali",
"country.malta": "Malta",
"country.marshall_islands": "Marshall islands",
"country.martinique": "Martinique",
"country.mauritania": "Mauritania",
"country.mauritius": "Mauritius",
"country.mayotte": "Mayotte",
"country.mexico": "Mexico",
"country.micronesia": "Micronesia",
"country.moldova": "Moldova",
"country.monaco": "Monaco",
"country.mongolia": "Mongolia",
"country.montenegro": "Montenegro",
"country.montserrat": "Montserrat",
"country.morocco": "Morocco",
"country.mozambique": "Mozambique",
"country.myanmar": "Myanmar",
"country.namibia": "Namibia",
"country.nauru": "Nauru",
"country.nepal": "Nepal",
"country.netherlands": "Netherlands",
"country.new_caledonia": "New caledonia",
"country.new_zealand": "New zealand",
"country.nicaragua": "Nicaragua",
"country.niger": "Niger",
"country.nigeria": "Nigeria",
"country.niue": "Niue",
"country.norfolk_island": "Norfolk island",
"country.north_korea": "North korea",
"country.north_macedonia": "North macedonia",
"country.northern_mariana_islands": "Northern mariana islands",
"country.norway": "Norway",
"country.oman": "Oman",
"country.pakistan": "Pakistan",
"country.palau": "Palau",
"country.palestine": "Palestine",
"country.panama": "Panama",
"country.papua_new_guinea": "Papua new guinea",
"country.paraguay": "Paraguay",
"country.peru": "Peru",
"country.philippines": "Philippines",
"country.pitcairn_islands": "Pitcairn islands",
"country.poland": "Poland",
"country.portugal": "Portugal",
"country.puerto_rico": "Puerto rico",
"country.qatar": "Qatar",
"country.reunion": "Reunion",
"country.romania": "Romania",
"country.russia": "Russia",
"country.rwanda": "Rwanda",
"country.saint_barthelemy": "Saint barthelemy",
"country.saint_helena": "Saint helena",
"country.saint_kitts_and_nevis": "Saint kitts and nevis",
"country.saint_lucia": "Saint lucia",
"country.saint_martin": "Saint martin",
"country.saint_pierre_and_miquelon": "Saint pierre and miquelon",
"country.saint_vincent_and_the_grenadines": "Saint vincent and the grenadines",
"country.samoa": "Samoa",
"country.san_marino": "San marino",
"country.sao_tome_and_principe": "Sao tome and principe",
"country.saudi_arabia": "Saudi arabia",
"country.senegal": "Senegal",
"country.serbia": "Serbia",
"country.seychelles": "Seychelles",
"country.sierra_leone": "Sierra leone",
"country.singapore": "Singapore",
"country.sint_maarten": "Sint maarten",
"country.slovakia": "Slovakia",
"country.slovenia": "Slovenia",
"country.solomon_islands": "Solomon islands",
"country.somalia": "Somalia",
"country.south_africa": "South africa",
"country.south_georgia_and_south_sandwich_islands": "South georgia and south sandwich islands",
"country.south_korea": "South korea",
"country.south_sudan": "South sudan",
"country.spain": "Spain",
"country.sri_lanka": "Sri lanka",
"country.sudan": "Sudan",
"country.suriname": "Suriname",
"country.svalbard_and_jan_mayen": "Svalbard and jan mayen",
"country.sweden": "Sweden",
"country.switzerland": "Switzerland",
"country.syria": "Syria",
"country.taiwan": "Taiwan",
"country.tajikistan": "Tajikistan",
"country.tanzania": "Tanzania",
"country.thailand": "Thailand",
"country.timor_leste": "Timor leste",
"country.togo": "Togo",
"country.tokelau": "Tokelau",
"country.tonga": "Tonga",
"country.trinidad_and_tobago": "Trinidad and tobago",
"country.tunisia": "Tunisia",
"country.turkey": "Turkey",
"country.turkmenistan": "Turkmenistan",
"country.turks_and_caicos_islands": "Turks and caicos islands",
"country.tuvalu": "Tuvalu",
"country.us_virgin_islands": "Us virgin islands",
"country.uganda": "Uganda",
"country.ukraine": "Ukraine",
"country.united_arab_emirates": "United arab emirates",
"country.united_kingdom": "United kingdom",
"country.united_states": "United states",
"country.uruguay": "Uruguay",
"country.uzbekistan": "Uzbekistan",
"country.vanuatu": "Vanuatu",
"country.vatican_city": "Vatican city",
"country.venezuela": "Venezuela",
"country.vietnam": "Vietnam",
"country.wallis_and_futuna": "Wallis and futuna",
"country.western_sahara": "Western sahara",
"country.yemen": "Yemen",
"country.zambia": "Zambia",
"country.zimbabwe": "Zimbabwe"
"country": {
"afghanistan": "Afghanistan",
"aland_islands": "Aland islands",
"albania": "Albania",
"algeria": "Algeria",
"american_samoa": "American samoa",
"andorra": "Andorra",
"angola": "Angola",
"anguilla": "Anguilla",
"antarctica": "Antarctica",
"antigua_and_barbuda": "Antigua and barbuda",
"argentina": "Argentina",
"armenia": "Armenia",
"aruba": "Aruba",
"australia": "Australia",
"austria": "Austria",
"azerbaijan": "Azerbaijan",
"bahamas": "Bahamas",
"bahrain": "Bahrain",
"bangladesh": "Bangladesh",
"barbados": "Barbados",
"belarus": "Belarus",
"belgium": "Belgium",
"belize": "Belize",
"benin": "Benin",
"bermuda": "Bermuda",
"bhutan": "Bhutan",
"bolivia": "Bolivia",
"bosnia_and_herzegovina": "Bosnia and herzegovina",
"botswana": "Botswana",
"brazil": "Brazil",
"british_indian_ocean_territory": "British indian ocean territory",
"british_virgin_islands": "British virgin islands",
"brunei": "Brunei",
"bulgaria": "Bulgaria",
"burkina_faso": "Burkina faso",
"burundi": "Burundi",
"cambodia": "Cambodia",
"cameroon": "Cameroon",
"canada": "Canada",
"cape_verde": "Cape verde",
"cayman_islands": "Cayman islands",
"central_african_republic": "Central african republic",
"chad": "Chad",
"chile": "Chile",
"china": "China",
"christmas_island": "Christmas island",
"cocos_keeling_islands": "Cocos keeling islands",
"colombia": "Colombia",
"comoros": "Comoros",
"congo_brazzaville": "Congo brazzaville",
"congo_kinshasa": "Congo kinshasa",
"cook_islands": "Cook islands",
"costa_rica": "Costa rica",
"cote_divoire": "Cote divoire",
"croatia": "Croatia",
"cuba": "Cuba",
"curacao": "Curacao",
"cyprus": "Cyprus",
"czech_republic": "Czech republic",
"denmark": "Denmark",
"djibouti": "Djibouti",
"dominica": "Dominica",
"dominican_republic": "Dominican republic",
"ecuador": "Ecuador",
"egypt": "Egypt",
"el_salvador": "El salvador",
"equatorial_guinea": "Equatorial guinea",
"eritrea": "Eritrea",
"estonia": "Estonia",
"eswatini": "Eswatini",
"ethiopia": "Ethiopia",
"falkland_islands": "Falkland islands",
"faroe_islands": "Faroe islands",
"fiji": "Fiji",
"finland": "Finland",
"france": "France",
"french_guiana": "French guiana",
"french_polynesia": "French polynesia",
"gabon": "Gabon",
"gambia": "Gambia",
"georgia": "Georgia",
"germany": "Germany",
"ghana": "Ghana",
"gibraltar": "Gibraltar",
"greece": "Greece",
"greenland": "Greenland",
"grenada": "Grenada",
"guadeloupe": "Guadeloupe",
"guam": "Guam",
"guatemala": "Guatemala",
"guernsey": "Guernsey",
"guinea": "Guinea",
"guinea_bissau": "Guinea bissau",
"guyana": "Guyana",
"haiti": "Haiti",
"honduras": "Honduras",
"hong_kong": "Hong kong",
"hungary": "Hungary",
"iceland": "Iceland",
"india": "India",
"indonesia": "Indonesia",
"iran": "Iran",
"iraq": "Iraq",
"ireland": "Ireland",
"isle_of_man": "Isle of man",
"israel": "Israel",
"italy": "Italy",
"jamaica": "Jamaica",
"japan": "Japan",
"jersey": "Jersey",
"jordan": "Jordan",
"kazakhstan": "Kazakhstan",
"kenya": "Kenya",
"kiribati": "Kiribati",
"kosovo": "Kosovo",
"kuwait": "Kuwait",
"kyrgyzstan": "Kyrgyzstan",
"laos": "Laos",
"latvia": "Latvia",
"lebanon": "Lebanon",
"lesotho": "Lesotho",
"liberia": "Liberia",
"libya": "Libya",
"liechtenstein": "Liechtenstein",
"lithuania": "Lithuania",
"luxembourg": "Luxembourg",
"macau": "Macau",
"madagascar": "Madagascar",
"malawi": "Malawi",
"malaysia": "Malaysia",
"maldives": "Maldives",
"mali": "Mali",
"malta": "Malta",
"marshall_islands": "Marshall islands",
"martinique": "Martinique",
"mauritania": "Mauritania",
"mauritius": "Mauritius",
"mayotte": "Mayotte",
"mexico": "Mexico",
"micronesia": "Micronesia",
"moldova": "Moldova",
"monaco": "Monaco",
"mongolia": "Mongolia",
"montenegro": "Montenegro",
"montserrat": "Montserrat",
"morocco": "Morocco",
"mozambique": "Mozambique",
"myanmar": "Myanmar",
"namibia": "Namibia",
"nauru": "Nauru",
"nepal": "Nepal",
"netherlands": "Netherlands",
"new_caledonia": "New caledonia",
"new_zealand": "New zealand",
"nicaragua": "Nicaragua",
"niger": "Niger",
"nigeria": "Nigeria",
"niue": "Niue",
"norfolk_island": "Norfolk island",
"north_korea": "North korea",
"north_macedonia": "North macedonia",
"northern_mariana_islands": "Northern mariana islands",
"norway": "Norway",
"oman": "Oman",
"pakistan": "Pakistan",
"palau": "Palau",
"palestine": "Palestine",
"panama": "Panama",
"papua_new_guinea": "Papua new guinea",
"paraguay": "Paraguay",
"peru": "Peru",
"philippines": "Philippines",
"pitcairn_islands": "Pitcairn islands",
"poland": "Poland",
"portugal": "Portugal",
"puerto_rico": "Puerto rico",
"qatar": "Qatar",
"reunion": "Reunion",
"romania": "Romania",
"russia": "Russia",
"rwanda": "Rwanda",
"saint_barthelemy": "Saint barthelemy",
"saint_helena": "Saint helena",
"saint_kitts_and_nevis": "Saint kitts and nevis",
"saint_lucia": "Saint lucia",
"saint_martin": "Saint martin",
"saint_pierre_and_miquelon": "Saint pierre and miquelon",
"saint_vincent_and_the_grenadines": "Saint vincent and the grenadines",
"samoa": "Samoa",
"san_marino": "San marino",
"sao_tome_and_principe": "Sao tome and principe",
"saudi_arabia": "Saudi arabia",
"senegal": "Senegal",
"serbia": "Serbia",
"seychelles": "Seychelles",
"sierra_leone": "Sierra leone",
"singapore": "Singapore",
"sint_maarten": "Sint maarten",
"slovakia": "Slovakia",
"slovenia": "Slovenia",
"solomon_islands": "Solomon islands",
"somalia": "Somalia",
"south_africa": "South africa",
"south_georgia_and_south_sandwich_islands": "South georgia and south sandwich islands",
"south_korea": "South korea",
"south_sudan": "South sudan",
"spain": "Spain",
"sri_lanka": "Sri lanka",
"sudan": "Sudan",
"suriname": "Suriname",
"svalbard_and_jan_mayen": "Svalbard and jan mayen",
"sweden": "Sweden",
"switzerland": "Switzerland",
"syria": "Syria",
"taiwan": "Taiwan",
"tajikistan": "Tajikistan",
"tanzania": "Tanzania",
"thailand": "Thailand",
"timor_leste": "Timor leste",
"togo": "Togo",
"tokelau": "Tokelau",
"tonga": "Tonga",
"trinidad_and_tobago": "Trinidad and tobago",
"tunisia": "Tunisia",
"turkey": "Turkey",
"turkmenistan": "Turkmenistan",
"turks_and_caicos_islands": "Turks and caicos islands",
"tuvalu": "Tuvalu",
"us_virgin_islands": "Us virgin islands",
"uganda": "Uganda",
"ukraine": "Ukraine",
"united_arab_emirates": "United arab emirates",
"united_kingdom": "United kingdom",
"united_states": "United states",
"uruguay": "Uruguay",
"uzbekistan": "Uzbekistan",
"vanuatu": "Vanuatu",
"vatican_city": "Vatican city",
"venezuela": "Venezuela",
"vietnam": "Vietnam",
"wallis_and_futuna": "Wallis and futuna",
"western_sahara": "Western sahara",
"yemen": "Yemen",
"zambia": "Zambia",
"zimbabwe": "Zimbabwe"
}
}

View File

@@ -3,11 +3,11 @@
"account": "Account",
"personalInfo": "Personal information",
"phoneNumber": "Phone number",
"contactInfo": "Contact info",
"email": "Email",
"security": "Security",
"password": "Password",
"verifiedAddress": "Verified addresses",
"recentLogins": "Recent logins",
"recentSessions": "Recent Sessions",
"activeDevices": "Active devices",
"settings": "Settings"
}

View File

@@ -2,202 +2,9 @@
"labels": {
"search": "جست و جو"
},
"country": {
"afghanistan": "افغانستان",
"aland_islands": "جزایر آلند",
"albania": "آلبانی",
"algeria": "الجزایر",
"american_samoa": "ساموای آمریکایی",
"andorra": "آندورا",
"angola": "آنگولا",
"anguilla": "آنگویلا",
"antarctica": "جنوبگان",
"antigua_and_barbuda": "آنتیگوا و باربودا",
"argentina": "آرژانتین",
"armenia": "ارمنستان",
"aruba": "آروبا",
"australia": "استرالیا",
"austria": "اتریش",
"azerbaijan": "آذربایجان",
"bahamas": "باهاما",
"bahrain": "بحرین",
"bangladesh": "بنگلادش",
"barbados": "باربادوس",
"belarus": "بلاروس",
"belgium": "بلژیک",
"belize": "بلیز",
"benin": "بنین",
"bermuda": "برمودا",
"bhutan": "بوتان",
"bolivia": "بولیوی",
"bosnia_and_herzegovina": "بوسنی و هرزگوین",
"botswana": "بوتسوانا",
"brazil": "برزیل",
"british_virgin_islands": "جزایر ویرجین بریتانیا",
"brunei": "برونئی",
"bulgaria": "بلغارستان",
"burkina_faso": "بورکینافاسو",
"burundi": "بوروندی",
"cambodia": "کامبوج",
"cameroon": "کامرون",
"canada": "کانادا",
"cape_verde": "کیپ ورد",
"cayman_islands": "جزایر کیمن",
"central_african_republic": "جمهوری آفریقای مرکزی",
"chad": "چاد",
"chile": "شیلی",
"china": "چین",
"colombia": "کلمبیا",
"comoros": "کومور",
"costa_rica": "کاستاریکا",
"cote_divoire": "ساحل عاج",
"croatia": "کرواسی",
"cuba": "کوبا",
"cyprus": "قبرس",
"czech_republic": "جمهوری چک",
"denmark": "دانمارک",
"djibouti": "جیبوتی",
"dominica": "دومینیکا",
"dominican_republic": "جمهوری دومینیکن",
"ecuador": "اکوادور",
"egypt": "مصر",
"el_salvador": "السالوادور",
"equatorial_guinea": "گینه استوایی",
"eritrea": "اریتره",
"estonia": "استونی",
"eswatini": "سوازیلند",
"ethiopia": "اتیوپی",
"fiji": "فیجی",
"finland": "فنلاند",
"france": "فرانسه",
"gabon": "گابن",
"gambia": "گامبیا",
"georgia": "گرجستان",
"germany": "آلمان",
"ghana": "غنا",
"greece": "یونان",
"guatemala": "گواتمالا",
"guinea": "گینه",
"guinea_bissau": "گینه بیسائو",
"guyana": "گویان",
"haiti": "هائیتی",
"honduras": "هندوراس",
"hungary": "مجارستان",
"iceland": "ایسلند",
"india": "هندوستان",
"indonesia": "اندونزی",
"iran": "ایران",
"iraq": "عراق",
"ireland": "ایرلند",
"israel": "اسرائیل",
"italy": "ایتالیا",
"jamaica": "جامائیکا",
"japan": "ژاپن",
"jordan": "اردن",
"kazakhstan": "قزاقستان",
"kenya": "کنیا",
"kuwait": "کویت",
"kyrgyzstan": "قرقیزستان",
"laos": "لائوس",
"latvia": "لتونی",
"lebanon": "لبنان",
"lesotho": "لسوتو",
"liberia": "لیبریا",
"libya": "لیبی",
"luxembourg": "لوکزامبورگ",
"malaysia": "مالزی",
"maldives": "مالدیو",
"mali": "مالی",
"malta": "مالت",
"mauritania": "موریتانی",
"mauritius": "موریس",
"mexico": "مکزیک",
"moldova": "مولداوی",
"monaco": "موناکو",
"mongolia": "مغولستان",
"morocco": "مراکش",
"mozambique": "موزامبیک",
"myanmar": "میانمار",
"namibia": "نامیبیا",
"nepal": "نپال",
"netherlands": "هلند",
"new_zealand": "نیوزیلند",
"nicaragua": "نیکاراگوئه",
"niger": "نیجر",
"nigeria": "نیجریه",
"north_korea": "کره شمالی",
"north_macedonia": "مقدونیه",
"norway": "نروژ",
"oman": "عمان",
"pakistan": "پاکستان",
"palau": "پالائو",
"panama": "پاناما",
"papua_new_guinea": "پاپوآ گینه نو",
"paraguay": "پاراگوئه",
"peru": "پرو",
"philippines": "فیلیپین",
"poland": "لهستان",
"portugal": "پرتغال",
"qatar": "قطر",
"romania": "رومانی",
"russia": "روسیه",
"rwanda": "رواندا",
"saudi_arabia": "عربستان سعودی",
"senegal": "سنگال",
"serbia": "صربستان",
"seychelles": "سیشل",
"sierra_leone": "سیرالئون",
"singapore": "سنگاپور",
"south_africa": "آفریقای جنوبی",
"south_korea": "کره جنوبی",
"south_sudan": "سودان جنوبی",
"spain": "اسپانیا",
"sri_lanka": "سری‌لانکا",
"sudan": "سودان",
"suriname": "سورینام",
"sweden": "سوئد",
"switzerland": "سوئیس",
"syria": "سوریه",
"taiwan": "تایوان",
"tajikistan": "تاجیکستان",
"tanzania": "تانزانیا",
"thailand": "تایلند",
"timor_leste": "تیمور شرقی",
"togo": "توگو",
"tonga": "تونگا",
"trinidad_and_tobago": "ترینیداد و توباگو",
"tunisia": "تونس",
"turkey": "ترکیه",
"turkmenistan": "ترکمنستان",
"tuvalu": "تووالو",
"uganda": "اوگاندا",
"ukraine": "اوکراین",
"united_arab_emirates": "امارات متحده عربی",
"united_kingdom": "انگلستان",
"united_states": "ایالات متحده آمریکا",
"uruguay": "اروگوئه",
"uzbekistan": "ازبکستان",
"vanuatu": "وانواتو",
"venezuela": "ونزوئلا",
"vietnam": "ویتنام",
"yemen": "یمن",
"zambia": "زامبیا",
"zimbabwe": "زیمبابوه"
},
"messages": {
"noResualtFound": "نتیجه ای یافت نشد.",
"serverError": "خطای سمت سرور"
},
"side": {
"account": "حساب کاربری",
"personalInfo": "اطلاعات شخصی",
"contactInfo": "شماره تماس",
"email": "ایمیل",
"security": "امنیت",
"password": "رمز عبور",
"confirmedIps": "آدرس های تایید شده",
"recentSessions": "ورود های اخیر",
"activeSessions": "نشست های فعال",
"setting": "تنظیمات"
"serverError": "خطای سمت سرور",
"networkError": "خطای شبکه. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید."
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,182 +1,184 @@
{
"country.afghanistan": "افغانستان",
"country.aland_islands": "جزایر آلند",
"country.albania": "آلبانی",
"country.algeria": "الجزایر",
"country.american_samoa": "ساموای آمریکایی",
"country.andorra": "آندورا",
"country.angola": "آنگولا",
"country.anguilla": "آنگویلا",
"country.antarctica": "جنوبگان",
"country.antigua_and_barbuda": "آنتیگوا و باربودا",
"country.argentina": "آرژانتین",
"country.armenia": "ارمنستان",
"country.aruba": "آروبا",
"country.australia": "استرالیا",
"country.austria": "اتریش",
"country.azerbaijan": "آذربایجان",
"country.bahamas": "باهاما",
"country.bahrain": حرین",
"country.bangladesh": "بنگلادش",
"country.barbados": اربادوس",
"country.belarus": لاروس",
"country.belgium": "بلژیک",
"country.belize": "بلیز",
"country.benin": نین",
"country.bermuda": رمودا",
"country.bhutan": وتان",
"country.bolivia": "بولیوی",
"country.bosnia_and_herzegovina": "بوسنی و هرزگوین",
"country.botswana": "بوتسوانا",
"country.brazil": رزیل",
"country.british_virgin_islands": "جزایر ویرجین بریتانیا",
"country.brunei": "برونئی",
"country.bulgaria": لغارستان",
"country.burkina_faso": "بورکینافاسو",
"country.burundi": "بوروندی",
"country.cambodia": "کامبوج",
"country.cameroon": "کامرون",
"country.canada": "کانادا",
"country.cape_verde": یپ ورد",
"country.cayman_islands": "جزایر کیمن",
"country.central_african_republic": "جمهوری آفریقای مرکزی",
"country.chad": "چاد",
"country.chile": "شیلی",
"country.china": "چین",
"country.colombia": "کلمبیا",
"country.comoros": ومور",
"country.costa_rica": استاریکا",
"country.cote_divoire": "ساحل عاج",
"country.croatia": "کرواسی",
"country.cuba": وبا",
"country.cyprus": "قبرس",
"country.czech_republic": "جمهوری چک",
"country.denmark": "دانمارک",
"country.djibouti": "جیبوتی",
"country.dominica": "دومینیکا",
"country.dominican_republic": "جمهوری دومینیکن",
"country.ecuador": "اکوادور",
"country.egypt": "مصر",
"country.el_salvador": "السالوادور",
"country.equatorial_guinea": "گینه استوایی",
"country.eritrea": "اریتره",
"country.estonia": "استونی",
"country.eswatini": "سوازیلند",
"country.ethiopia": "اتیوپی",
"country.fiji": "فیجی",
"country.finland": نلاند",
"country.france": رانسه",
"country.gabon": "گابن",
"country.gambia": "گامبیا",
"country.georgia": رجستان",
"country.germany": "آلمان",
"country.ghana": "غنا",
"country.greece": "یونان",
"country.guatemala": "گواتمالا",
"country.guinea": ینه",
"country.guinea_bissau": "گینه بیسائو",
"country.guyana": "گویان",
"country.haiti": "هائیتی",
"country.honduras": "هندوراس",
"country.hungary": "مجارستان",
"country.iceland": "ایسلند",
"country.india": "هندوستان",
"country.indonesia": "اندونزی",
"country.iran": "ایران",
"country.iraq": "عراق",
"country.ireland": "ایرلند",
"country.israel": "اسرائیل",
"country.italy": "ایتالیا",
"country.jamaica": "جامائیکا",
"country.japan": "ژاپن",
"country.jordan": "اردن",
"country.kazakhstan": "قزاقستان",
"country.kenya": "کنیا",
"country.kuwait": ویت",
"country.kyrgyzstan": "قرقیزستان",
"country.laos": "لائوس",
"country.latvia": تونی",
"country.lebanon": بنان",
"country.lesotho": سوتو",
"country.liberia": یبریا",
"country.libya": "لیبی",
"country.luxembourg": "لوکزامبورگ",
"country.malaysia": "مالزی",
"country.maldives": "مالدیو",
"country.mali": "مالی",
"country.malta": "مالت",
"country.mauritania": وریتانی",
"country.mauritius": "موریس",
"country.mexico": کزیک",
"country.moldova": ولداوی",
"country.monaco": "موناکو",
"country.mongolia": غولستان",
"country.morocco": راکش",
"country.mozambique": وزامبیک",
"country.myanmar": یانمار",
"country.namibia": "نامیبیا",
"country.nepal": پال",
"country.netherlands": "هلند",
"country.new_zealand": "نیوزیلند",
"country.nicaragua": "نیکاراگوئه",
"country.niger": "نیجر",
"country.nigeria": "نیجریه",
"country.north_korea": "کره شمالی",
"country.north_macedonia": "مقدونیه",
"country.norway": "نروژ",
"country.oman": "عمان",
"country.pakistan": "پاکستان",
"country.palau": "پالائو",
"country.panama": "پاناما",
"country.papua_new_guinea": "پاپوآ گینه نو",
"country.paraguay": "پاراگوئه",
"country.peru": رو",
"country.philippines": "فیلیپین",
"country.poland": "لهستان",
"country.portugal": "پرتغال",
"country.qatar": "قطر",
"country.romania": ومانی",
"country.russia": "روسیه",
"country.rwanda": "رواندا",
"country.saudi_arabia": "عربستان سعودی",
"country.senegal": "سنگال",
"country.serbia": "صربستان",
"country.seychelles": "سیشل",
"country.sierra_leone": "سیرالئون",
"country.singapore": نگاپور",
"country.south_africa": "آفریقای جنوبی",
"country.south_korea": "کره جنوبی",
"country.south_sudan": "سودان جنوبی",
"country.spain": "اسپانیا",
"country.sri_lanka": "سری‌لانکا",
"country.sudan": ودان",
"country.suriname": "سورینام",
"country.sweden": "سوئد",
"country.switzerland": "سوئیس",
"country.syria": "سوریه",
"country.taiwan": "تایوان",
"country.tajikistan": "تاجیکستان",
"country.tanzania": "تانزانیا",
"country.thailand": "تایلند",
"country.timor_leste": "تیمور شرقی",
"country.togo": "توگو",
"country.tonga": "تونگا",
"country.trinidad_and_tobago": "ترینیداد و توباگو",
"country.tunisia": "تونس",
"country.turkey": رکیه",
"country.turkmenistan": "ترکمنستان",
"country.tuvalu": ووالو",
"country.uganda": "اوگاندا",
"country.ukraine": "اوکراین",
"country.united_arab_emirates": "امارات متحده عربی",
"country.united_kingdom": "انگلستان",
"country.united_states": "ایالات متحده آمریکا",
"country.uruguay": "اروگوئه",
"country.uzbekistan": "ازبکستان",
"country.vanuatu": "وانواتو",
"country.venezuela": نزوئلا",
"country.vietnam": یتنام",
"country.yemen": "یمن",
"country.zambia": "زامبیا",
"country.zimbabwe": یمبابوه"
"country": {
"afghanistan": "افغانستان",
"aland_islands": "جزایر آلند",
"albania": "آلبانی",
"algeria": "الجزایر",
"american_samoa": "ساموای آمریکایی",
"andorra": "آندورا",
"angola": "آنگولا",
"anguilla": "آنگویلا",
"antarctica": "جنوبگان",
"antigua_and_barbuda": "آنتیگوا و باربودا",
"argentina": "آرژانتین",
"armenia": "ارمنستان",
"aruba": "آروبا",
"australia": "استرالیا",
"austria": "اتریش",
"azerbaijan": "آذربایجان",
"bahamas": اهاما",
"bahrain": حرین",
"bangladesh": نگلادش",
"barbados": "باربادوس",
"belarus": "بلاروس",
"belgium": "بلژیک",
"belize": لیز",
"benin": نین",
"bermuda": رمودا",
"bhutan": "بوتان",
"bolivia": "بولیوی",
"bosnia_and_herzegovina": "بوسنی و هرزگوین",
"botswana": وتسوانا",
"brazil": "برزیل",
"british_virgin_islands": "جزایر ویرجین بریتانیا",
"brunei": رونئی",
"bulgaria": "بلغارستان",
"burkina_faso": "بورکینافاسو",
"burundi": "بوروندی",
"cambodia": "کامبوج",
"cameroon": "کامرون",
"canada": انادا",
"cape_verde": "کیپ ورد",
"cayman_islands": "جزایر کیمن",
"central_african_republic": "جمهوری آفریقای مرکزی",
"chad": "چاد",
"chile": "شیلی",
"china": "چین",
"colombia": لمبیا",
"comoros": ومور",
"costa_rica": "کاستاریکا",
"cote_divoire": "ساحل عاج",
"croatia": رواسی",
"cuba": "کوبا",
"cyprus": "قبرس",
"czech_republic": "جمهوری چک",
"denmark": "دانمارک",
"djibouti": "جیبوتی",
"dominica": "دومینیکا",
"dominican_republic": "جمهوری دومینیکن",
"ecuador": "اکوادور",
"egypt": "مصر",
"el_salvador": "السالوادور",
"equatorial_guinea": "گینه استوایی",
"eritrea": "اریتره",
"estonia": "استونی",
"eswatini": "سوازیلند",
"ethiopia": "اتیوپی",
"fiji": یجی",
"finland": نلاند",
"france": "فرانسه",
"gabon": "گابن",
"gambia": امبیا",
"georgia": "گرجستان",
"germany": "آلمان",
"ghana": "غنا",
"greece": "یونان",
"guatemala": واتمالا",
"guinea": "گینه",
"guinea_bissau": "گینه بیسائو",
"guyana": "گویان",
"haiti": "هائیتی",
"honduras": "هندوراس",
"hungary": "مجارستان",
"iceland": "ایسلند",
"india": "هندوستان",
"indonesia": "اندونزی",
"iran": "ایران",
"iraq": "عراق",
"ireland": "ایرلند",
"israel": "اسرائیل",
"italy": "ایتالیا",
"jamaica": "جامائیکا",
"japan": "ژاپن",
"jordan": "اردن",
"kazakhstan": "قزاقستان",
"kenya": نیا",
"kuwait": "کویت",
"kyrgyzstan": "قرقیزستان",
"laos": ائوس",
"latvia": تونی",
"lebanon": بنان",
"lesotho": سوتو",
"liberia": "لیبریا",
"libya": "لیبی",
"luxembourg": "لوکزامبورگ",
"malaysia": "مالزی",
"maldives": "مالدیو",
"mali": "مالی",
"malta": الت",
"mauritania": "موریتانی",
"mauritius": وریس",
"mexico": کزیک",
"moldova": "مولداوی",
"monaco": وناکو",
"mongolia": غولستان",
"morocco": راکش",
"mozambique": وزامبیک",
"myanmar": "میانمار",
"namibia": امیبیا",
"nepal": "نپال",
"netherlands": "هلند",
"new_zealand": "نیوزیلند",
"nicaragua": "نیکاراگوئه",
"niger": "نیجر",
"nigeria": "نیجریه",
"north_korea": "کره شمالی",
"north_macedonia": "مقدونیه",
"norway": روژ",
"oman": "عمان",
"pakistan": "پاکستان",
"palau": "پالائو",
"panama": "پاناما",
"papua_new_guinea": "پاپوآ گینه نو",
"paraguay": اراگوئه",
"peru": "پرو",
"philippines": "فیلیپین",
"poland": "لهستان",
"portugal": "پرتغال",
"qatar": "قطر",
"romania": "رومانی",
"russia": "روسیه",
"rwanda": "رواندا",
"saudi_arabia": "عربستان سعودی",
"senegal": "سنگال",
"serbia": "صربستان",
"seychelles": "سیشل",
"sierra_leone": یرالئون",
"singapore": "سنگاپور",
"south_africa": "آفریقای جنوبی",
"south_korea": "کره جنوبی",
"south_sudan": "سودان جنوبی",
"spain": "اسپانیا",
"sri_lanka": ری‌لانکا",
"sudan": "سودان",
"suriname": "سورینام",
"sweden": "سوئد",
"switzerland": "سوئیس",
"syria": "سوریه",
"taiwan": "تایوان",
"tajikistan": "تاجیکستان",
"tanzania": "تانزانیا",
"thailand": "تایلند",
"timor_leste": "تیمور شرقی",
"togo": "توگو",
"tonga": "تونگا",
"trinidad_and_tobago": "ترینیداد و توباگو",
"tunisia": ونس",
"turkey": "ترکیه",
"turkmenistan": رکمنستان",
"tuvalu": "تووالو",
"uganda": "اوگاندا",
"ukraine": "اوکراین",
"united_arab_emirates": "امارات متحده عربی",
"united_kingdom": "انگلستان",
"united_states": "ایالات متحده آمریکا",
"uruguay": "اروگوئه",
"uzbekistan": "ازبکستان",
"vanuatu": انواتو",
"venezuela": نزوئلا",
"vietnam": "ویتنام",
"yemen": "یمن",
"zambia": امبیا",
"zimbabwe": "زیمبابوه"
}
}

View File

@@ -1,14 +0,0 @@
{
"side": {
"account": "حساب کاربری",
"personalInfo": "اطلاعات شخصی",
"phoneNumber": "شماره تماس",
"email": "ایمیل",
"security": "امنیت",
"password": "رمز عبور",
"verifiedAddress": "آدرس های تایید شده",
"recentLogins": "ورود های اخیر",
"activeDevices": "دستگاه های فعال",
"settings": "تنظیمات"
}
}

View File

@@ -0,0 +1,13 @@
{
"side": {
"account": "حساب کاربری",
"personalInfo": "اطلاعات شخصی",
"contactInfo": "شماره تماس",
"email": "ایمیل",
"security": "امنیت",
"password": "رمز عبور",
"recentSessions": "ورود های اخیر",
"activeSessions": "نشست های فعال",
"setting": "تنظیمات"
}
}

View File

@@ -33,7 +33,7 @@ export function CardContainer({
justifyContent: 'space-between',
alignItems: { xs: 'flex-start', sm: 'center' },
flexDirection: { xs: 'column', sm: 'row' },
bgcolor: highlighted ? 'primary.light' : 'background.default',
bgcolor: highlighted ? 'background.primary' : 'background.default',
p: 2,
borderRadius: 1,
gap: { xs: 1, sm: 0 },

View File

@@ -1,4 +1,4 @@
import { Box, IconButton, Skeleton, Typography } from '@mui/material';
import { Box, IconButton, Typography } from '@mui/material';
import { Icon } from '@rkheftan/harmony-ui';
import { More } from 'iconsax-react';
import type { UserInfo } from '@/contexts/AuthContext';
@@ -7,7 +7,7 @@ interface HeaderProps {
user: UserInfo;
}
export const Header: React.FC<HeaderProps> = ({ user, loading }) => {
export const Header: React.FC<HeaderProps> = ({ user }) => {
return (
<Box
sx={{

View File

@@ -1,14 +1,9 @@
import {
Avatar,
Box,
IconButton,
Toolbar as MuiToolbar,
Skeleton,
} from '@mui/material';
import { Avatar, Box, IconButton, Toolbar as MuiToolbar } from '@mui/material';
import { Icon } from '@rkheftan/harmony-ui';
import { HambergerMenu, Menu } from 'iconsax-react';
import type { Dispatch, SetStateAction } from 'react';
import type { UserInfo } from '@/contexts/AuthContext';
import Logo from '../Logo';
interface ToolbarProps {
sideNavOpen: boolean;
@@ -22,12 +17,12 @@ export const Toolbar: React.FC<ToolbarProps> = ({
setSideNavOpen,
isMobile,
user,
loading,
}) => {
return (
<MuiToolbar
sx={{
height: (t) => t.spacing(isMobile ? 8 : 10.5),
bgcolor: 'background.paper',
px: isMobile ? 3 : 2,
borderBottom: (t) => `1px solid ${t.palette.divider}`,
boxSizing: 'content-box',

View File

@@ -10,8 +10,9 @@ const getIcon = (icon?: Iconsax) => (isSelected: boolean) =>
<Icon Component={icon} variant={isSelected ? 'Bold' : 'Broken'} />
) : undefined;
// TODO: make this a hook
export function buildNavItems(routes: RouteConfig[]): NavItemConfig[] {
const { t } = useTranslation();
const { t } = useTranslation('sideNav');
return routes.flatMap((route) => {
// Check if route itself does not have a navItem but its child has

View File

@@ -1,5 +1,4 @@
import { CircularProgress, Stack } from '@mui/material';
import React from 'react';
export const Loading = () => {
return (
@@ -7,7 +6,7 @@ export const Loading = () => {
sx={{
alignItems: 'center',
justifyContent: 'center',
backgroundColor: (t) => t.palette.background.default,
backgroundColor: 'background.default',
position: 'fixed',
inset: '0',
zIndex: (t) => t.zIndex.tooltip + 1,

View File

@@ -5,6 +5,7 @@ import {
} from '../types/completionFormApiTypes';
import apiClient from '@/lib/apiClient';
// TODO: define generic type
export const sendEmailOtpApi = async (payload: SendEmailOtpPayload) => {
return apiClient.post('/User/SendEmailOtp', payload);
};

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { useMemo } from 'react';
import { AuthenticationCard } from '../AuthenticationCard';
import { Box, CardHeader, Divider, Stack, Typography } from '@mui/material';
import { Box, Divider, Stack, Typography } from '@mui/material';
import AccountCreatedIcon from '@/assets/account-created.svg';
import { useTranslation } from 'react-i18next';
import { Link as RouterLink, useSearchParams } from 'react-router-dom';

View File

@@ -1,7 +1,5 @@
import { Stack, Typography, useTheme } from '@mui/material';
import { Icon } from '@rkheftan/harmony-ui';
import { Box, Profile2User } from 'iconsax-react';
import React from 'react';
import { Profile2User } from 'iconsax-react';
import { useTranslation } from 'react-i18next';
export const AccountCreatedClubBanner = () => {
@@ -10,6 +8,7 @@ export const AccountCreatedClubBanner = () => {
return (
<Stack direction="row" spacing={2} sx={{ pb: 1 }} alignItems="start">
{/* FIXME: Replace with Club Logo (this icon is placeholder) */}
<Profile2User size={80} color={theme.palette.club.main} />
<Stack spacing={0.5}>

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import type { RequestedApplication } from './AccountCreated';
import { Box, Button, useTheme } from '@mui/material';
import { Box, Button } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CountDownTimer } from '@/components/CountDownTimer';
import type { PalleteColor } from '@/theme/palette';

View File

@@ -1,20 +1,22 @@
import { Paper } from '@mui/material';
import { Paper, type SxProps, type Theme } from '@mui/material';
import { type PropsWithChildren } from 'react';
export interface AuthenticationCardProps extends PropsWithChildren {
maxWidth?: string;
sx?: SxProps<Theme>;
}
// Beacuse in the otp verify there is a element outside of the authentication card
export const AuthenticationCard = ({
children,
maxWidth,
sx,
}: AuthenticationCardProps) => {
return (
<Paper
elevation={0}
sx={{
borderRadius: 4,
borderRadius: 2,
p: {
xs: 4,
md: 6,
@@ -22,6 +24,8 @@ export const AuthenticationCard = ({
marginInline: 2,
width: (t) => `calc(100% - ${t.spacing(2)})`,
maxWidth: maxWidth ?? '552px',
...sx,
}}
>
{children}

View File

@@ -6,7 +6,7 @@ import { isNumeric } from '@/utils/regexes/isNumeric';
import { CompleteSignUp } from './CompleteSignUp';
import { EnterPasswordForm } from './EnterPasswordForm';
import { UserStatus } from '../../types/userTypes';
import type { CountryCode, GUID } from '@/types/commonTypes';
import type { CountryCode } from '@/types/commonTypes';
import { VerifyPhoneNumber } from './VerifyPhoneNumber';
import { useNavigate, useSearchParams } from 'react-router-dom';

View File

@@ -40,7 +40,7 @@ export function CountryCodeSelector({
const open = Boolean(anchorEl);
const searchInputRef = useRef<HTMLInputElement>(null);
const menuWidth = menuAnchor ? menuAnchor.clientWidth : 'auto';
const { t, i18n } = useTranslation();
const { t, i18n } = useTranslation(['country', 'common']);
const selectedCountry =
countries.find((c) => c.phone === value) || countries[0];
@@ -176,7 +176,7 @@ export function CountryCodeSelector({
inputRef={searchInputRef}
size="small"
fullWidth
label={t('labels.search')}
label={t('labels.search', { ns: 'common' })}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
@@ -207,7 +207,7 @@ export function CountryCodeSelector({
}}
/>
</ListItemIcon>
<ListItemText primary={t(country.label)} />
<ListItemText primary={t(country.label, { ns: 'country' })} />
<Typography color="text.secondary">
{country.phone}
</Typography>

View File

@@ -61,8 +61,13 @@ export default function DateOfBirth({ value, onChange }: DateOfBirthProps) {
slotProps={{
textField: {
fullWidth: true,
},
popper: {
sx: {
flex: '1 1 260px',
'& .MuiDateCalendar-root': {
// TODO: fix this to use textfield width instead of defining hardcode
width: '309px',
},
},
},
}}

View File

@@ -8,41 +8,45 @@ import {
Typography,
InputAdornment,
IconButton,
CircularProgress,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { TickCircle, Edit } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
import { type EmailSectionProps } from '../../types/settingForm';
import { toLocaleDigits } from '@/utils/persianDigit';
export function EmailSection({
showEmail,
setShowEmail,
email,
setEmail,
correctEmail,
codeSent,
verificationCode,
setVerificationCode,
buttonState,
getButtonLabel,
handleSendCode,
handleVerifyCode,
emailVerified,
loading,
isVerifyingCode,
isSendingCode,
handleEditEmail,
errors,
touched,
handleBlur,
countdown,
}: EmailSectionProps) {
const { t } = useTranslation('completionForm');
const onSendCodeClick = () => {
if (!correctEmail) return;
handleSendCode();
};
const { t, i18n } = useTranslation('completionForm');
const handleToggleEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
setShowEmail(e.target.checked);
};
const formatTimerValue = () => {
const m = String(Math.floor(countdown / 60)).padStart(2, '0');
const s = String(countdown % 60).padStart(2, '0');
return toLocaleDigits(`${m}:${s}`, i18n.language);
};
return (
<>
<FormGroup>
@@ -51,7 +55,6 @@ export function EmailSection({
display: 'flex',
alignItems: 'center',
gap: 1,
px: { xs: 2, sm: 0 },
}}
>
<Switch checked={showEmail} onChange={handleToggleEmail} />
@@ -84,13 +87,16 @@ export function EmailSection({
variant="outlined"
fullWidth
value={email}
autoFocus
onChange={(e) => setEmail(e.target.value)}
error={email.length > 0 && !correctEmail}
onBlur={() => handleBlur('email')}
error={touched.email && !!errors.email}
helperText={touched.email && errors.email}
sx={{ flex: '1 1 260px' }}
slotProps={{
input: {
startAdornment:
!loading && emailVerified ? (
!isVerifyingCode && emailVerified ? (
<InputAdornment position="end">
<Icon
Component={TickCircle}
@@ -118,31 +124,36 @@ export function EmailSection({
},
}}
/>
{!loading && !emailVerified && (
<Button
type="button"
variant="text"
onClick={onSendCodeClick}
sx={{
minWidth: '140px',
width: { xs: '100%', sm: '156px' },
alignSelf: 'center',
textTransform: 'none',
}}
>
{getButtonLabel()}
</Button>
)}
{!isVerifyingCode &&
!emailVerified &&
(buttonState === 'counting' ? (
<Typography
sx={{
minWidth: '140px',
width: { xs: '100%', sm: '156px' },
alignSelf: 'center',
textAlign: 'center',
}}
color="primary"
variant="body1"
>
{formatTimerValue()}
</Typography>
) : (
<Button
variant="text"
onClick={handleSendCode}
loading={isSendingCode}
sx={{
width: { xs: '100%', sm: '156px' },
alignSelf: 'center',
}}
>
{t('completion.vericationCodeButton')}
</Button>
))}
</Box>
{email && (
<Typography
sx={{ color: correctEmail ? 'success.main' : 'error.main' }}
variant="caption"
>
{correctEmail ? '' : t('completion.emailCorrectForm')}
</Typography>
)}
{!emailVerified && codeSent && correctEmail && (
{!emailVerified && codeSent && (
<Box
sx={{
display: 'flex',
@@ -157,23 +168,21 @@ export function EmailSection({
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
sx={{ flex: '1 1 260px' }}
disabled={loading}
disabled={isVerifyingCode}
onBlur={() => handleBlur('verificationCode')}
error={touched.verificationCode && !!errors.verificationCode}
helperText={touched.verificationCode && errors.verificationCode}
/>
<Button
variant="contained"
variant="outlined"
onClick={handleVerifyCode}
disabled={loading}
loading={isVerifyingCode}
sx={{
width: { xs: '100%', sm: '156px' },
border: 0.5,
textTransform: 'none',
alignSelf: 'center',
}}
>
{loading ? (
<CircularProgress size={20} />
) : (
t('completion.checkCodeButton')
)}
{t('completion.checkCodeButton')}
</Button>
</Box>
)}

View File

@@ -28,6 +28,9 @@ export function PasswordSection({
hasSpecialChar,
validPassword,
showValidations,
errors,
touched,
handleBlur,
}: PasswordSectionProps) {
const { t } = useTranslation('completionForm');
const [showPasswordText, setShowPasswordText] = useState(false);
@@ -51,7 +54,6 @@ export function PasswordSection({
sx={{
display: 'flex',
gap: 0.5,
px: { xs: 2, sm: -4 },
alignItems: 'center',
}}
>
@@ -75,7 +77,6 @@ export function PasswordSection({
display: 'flex',
flexDirection: 'column',
gap: 2,
px: { xs: 2, sm: 0 },
}}
>
<Box
@@ -92,12 +93,17 @@ export function PasswordSection({
onChange={(e) => setPassword(e.target.value)}
variant="outlined"
type={showPasswordText ? 'text' : 'password'}
autoFocus
onBlur={() => handleBlur('password')}
error={touched.password && !!errors.password}
helperText={touched.password && errors.password}
sx={{
flex: '1 1 260px',
'& .MuiInputBase-input': {
pr: 8, // Increased padding to accommodate both icons
},
}}
// FIXME: deprecated
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -146,12 +152,9 @@ export function PasswordSection({
variant="outlined"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
error={confirmPassword.length > 0 && !matchPassword}
helperText={
confirmPassword.length > 0 && !matchPassword
? t('completion.notCompatibility')
: ' '
}
onBlur={() => handleBlur('confirmPassword')}
error={touched.confirmPassword && !!errors.confirmPassword}
helperText={touched.confirmPassword && errors.confirmPassword}
type={showPasswordRepetitionText ? 'text' : 'password'}
sx={{
flex: '1 1 260px',
@@ -205,7 +208,7 @@ export function PasswordSection({
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2,
gap: { xs: 0, sm: 2 },
justifyContent: { xs: 'center', sm: 'flex-start' },
}}
>

View File

@@ -30,12 +30,15 @@ export function PersonalInfoFields({
setSex,
country,
setCountry,
errors,
touched,
handleBlur,
}: PersonalInfoFieldsProps) {
const { t } = useTranslation('completionForm');
const { t } = useTranslation(['completionForm', 'country']);
const countryOptions = countries.map((c) => ({
code: c.code,
label: t(c.label, { ns: 'countries' }),
label: t(c.label, { ns: 'country' }),
}));
const currentCountry = countryOptions.find((c) => c.code === country) || null;
@@ -67,7 +70,6 @@ export function PersonalInfoFields({
flexDirection: 'column',
gap: 2,
width: '100%',
px: { xs: 2, sm: 4, md: 1 },
}}
>
<Box
@@ -85,6 +87,9 @@ export function PersonalInfoFields({
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
sx={{ flex: '1 1 260px' }}
onBlur={() => handleBlur('firstName')}
error={touched.firstName && !!errors.firstName}
helperText={touched.firstName && errors.firstName}
/>
<TextField
label={t('completion.familyName')}
@@ -93,6 +98,9 @@ export function PersonalInfoFields({
value={lastName}
onChange={(e) => setLastName(e.target.value)}
sx={{ flex: '1 1 260px' }}
onBlur={() => handleBlur('lastName')}
error={touched.lastName && !!errors.lastName}
helperText={touched.lastName && errors.lastName}
/>
</Box>
@@ -107,7 +115,7 @@ export function PersonalInfoFields({
<FormControl sx={{ flex: '1 1 260px' }}>
<InputLabel>{t('completion.gender')}</InputLabel>
<Select
value={sex}
value={sex || ''}
label={t('completion.gender')}
onChange={handleChangeSex}
>
@@ -129,6 +137,9 @@ export function PersonalInfoFields({
onChange={(e) => setNationalId(e.target.value)}
variant="outlined"
sx={{ flex: '1 1 260px' }}
onBlur={() => handleBlur('nationalId')}
error={touched.nationalId && !!errors.nationalId}
helperText={touched.nationalId && errors.nationalId}
/>
</Box>
@@ -146,6 +157,7 @@ export function PersonalInfoFields({
getOptionLabel={(option) => option.label}
value={currentCountry}
onChange={(_, newValue) => setCountry(newValue?.code || '')}
onBlur={() => handleBlur('country')}
renderOption={(props, option) => (
<Box component="li" {...props} key={option.code}>
<ReactCountryFlag
@@ -157,13 +169,19 @@ export function PersonalInfoFields({
// TODO: Check alignment for better styling definition
marginTop: '-2px',
marginRight: '4px',
marginLeft: '8px',
}}
/>
{option.label}
</Box>
)}
renderInput={(params) => (
<TextField {...params} label={t('completion.country')} />
<TextField
{...params}
label={t('completion.country')}
error={touched.country && !!errors.country}
helperText={touched.country && errors.country}
/>
)}
clearOnEscape
/>

View File

@@ -11,7 +11,7 @@ import {
import { useTranslation } from 'react-i18next';
import { type SubmitProps } from '../../types/settingForm';
export function SubmitSection({ onSubmit, loading, error }: SubmitProps) {
export function SubmitSection({ onSubmit, loading }: SubmitProps) {
const { t, i18n } = useTranslation('completionForm');
const [openDialog, setOpenDialog] = useState(false);
@@ -63,7 +63,6 @@ export function SubmitSection({ onSubmit, loading, error }: SubmitProps) {
? t('completion.submitting')
: t('completion.registerButton')}
</Button>
{error && <Typography color="error">{error}</Typography>}
</Box>
<Dialog

View File

@@ -7,8 +7,6 @@ import { PasswordSection } from '../components/UserCompletionForm/PasswordSectio
import { EmailSection } from '../components/UserCompletionForm/EmailSection';
import { SubmitSection } from '../components/UserCompletionForm/SubmitSection';
import { useToast } from '@rkheftan/harmony-ui';
import { toLocaleDigits } from '../../../utils/persianDigit';
import i18n from '@/config/i18n';
import { Gender } from '../types/settingForm';
import { useApi } from '@/hooks/useApi';
import {
@@ -22,6 +20,9 @@ import { containsNumber } from '@/utils/regexes/containsNumber';
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
import { isEmail } from '@/utils/regexes/isEmail';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { FlexBox } from '@/components/common/FlexBox';
import { AuthenticationCard } from '../components/AuthenticationCard';
import { nationalIdRegex } from '@/utils/regexes/nationalId';
export function UserCompletionPage() {
const [params] = useSearchParams();
@@ -33,7 +34,7 @@ export function UserCompletionPage() {
const [lastName, setLastName] = useState('');
const [nationalId, setNationalId] = useState('');
const [birthDate, setBirthDate] = useState<Date | null>(null);
const [sex, setSex] = useState<Gender>(Gender.Female);
const [sex, setSex] = useState<Gender | null>(null);
const [country, setCountry] = useState('');
const [showPasswordSection, setShowPasswordSection] = useState(false);
const [password, setPassword] = useState('');
@@ -50,6 +51,9 @@ export function UserCompletionPage() {
const [emailVerified, setEmailVerified] = useState(false);
const [showPasswordValidations, setShowPasswordValidations] = useState(false);
const [errors, setErrors] = useState<{ [key: string]: string }>({});
const [touched, setTouched] = useState<{ [key: string]: boolean }>({});
const hasNumber = containsNumber(password);
const hasMinLength = least8Chars(password);
const hasUpperAndLower = hasUpperAndLowerLetter(password);
@@ -60,94 +64,14 @@ export function UserCompletionPage() {
const matchPassword = password === confirmPassword;
const correctEmail = isEmail(email);
const { execute: sendCode, data: sendCodeData } = useApi(sendEmailOtpApi);
const { execute: sendCode, loading: isSendingCode } = useApi(sendEmailOtpApi);
const {
execute: verifyCode,
data: verifyCodeData,
loading: isVerifyingCode,
} = useApi(confirmEmailOtpApi);
const { execute: verifyCode, loading: isVerifyingCode } =
useApi(confirmEmailOtpApi);
const {
execute: submitForm,
data: submitData,
loading: isSubmitting,
error: submitError,
} = useApi(completeUserInformationApi);
const getErrorMessage = (error: unknown): string | null => {
if (!error) return null;
if (error instanceof Error) return error.message;
return String(error);
};
useEffect(() => {
if (sendCodeData) {
if (sendCodeData.data.success) {
showToast({
message:
sendCodeData.data.message || t('completion.successfullCodeSent'),
severity: 'success',
});
setCodeSent(true);
setButtonState('counting');
setCountdown(120);
} else {
showToast({
message: sendCodeData.data.message || t('completion.problem'),
severity: 'error',
});
}
}
}, [sendCodeData, showToast, t]);
useEffect(() => {
if (verifyCodeData) {
if (verifyCodeData.data.success) {
setEmailVerified(true);
showToast({
message: verifyCodeData.data.message || t('completion.codeVerified'),
severity: 'success',
});
} else {
showToast({
message: verifyCodeData.data.message || t('completion.invalidCode'),
severity: 'error',
});
setEmailVerified(false);
}
}
}, [verifyCodeData, showToast, t]);
useEffect(() => {
if (submitData) {
showToast({
message:
submitData.data.message ||
t(
submitData.data.success
? 'completion.submitSuccess'
: 'completion.submitError',
),
severity: submitData.data.success ? 'success' : 'error',
});
if (submitData.data.success) {
const returnUrl = params.get('returnUrl');
navigate(
returnUrl
? `/account-created?returnUrl=${returnUrl}`
: '`/account-created',
);
}
} else if (submitError) {
showToast({
message: getErrorMessage(submitError) || t('completion.problem'),
severity: 'error',
});
}
}, [submitData, submitError, showToast, t, navigate, params]);
const { execute: submitForm, loading: isSubmitting } = useApi(
completeUserInformationApi,
);
useEffect(() => {
setShowPasswordValidations(password ? !validPassword : false);
@@ -170,26 +94,84 @@ export function UserCompletionPage() {
return () => clearInterval(timer);
}, [buttonState, countdown]);
const handleSendCode = () => {
sendCode({ email });
};
const handleVerifyCode = () => {
if (!verificationCode.trim()) {
showToast({
message: 'Please enter the verification code',
severity: 'warning',
});
const handleSendCode = async () => {
if (!isEmail(email)) {
setTouched((prev) => ({ ...prev, email: true }));
setErrors((prev) => ({ ...prev, email: t('validation.emailInvalid') }));
return;
}
verifyCode({ email, otpCode: verificationCode });
const res = await sendCode({ email });
if (res) {
if (res.success) {
showToast({
message: res.message || t('completion.successfulCodeSent'),
severity: 'success',
});
setCodeSent(true);
setButtonState('counting');
setCountdown(120);
} else {
showToast({
message: res.message || t('completion.problem'),
severity: 'error',
});
}
}
};
const handleSubmit = () => {
submitForm({
const handleVerifyCode = async () => {
if (!verificationCode.trim()) {
// Manually trigger the validation error and stop
setTouched((prev) => ({ ...prev, verificationCode: true }));
setErrors((prev) => ({
...prev,
verificationCode: t('validation.verificationCodeRequired'),
}));
return;
}
const res = await verifyCode({ email, otpCode: verificationCode });
if (res) {
if (res.success) {
setEmailVerified(true);
showToast({
message: res.message || t('completion.codeVerified'),
severity: 'success',
});
} else {
showToast({
message: res.message || t('completion.invalidCode'),
severity: 'error',
});
setEmailVerified(false);
}
}
};
const handleSubmit = async () => {
setTouched({
firstName: true,
lastName: true,
country: true,
email: showEmail,
nationalId: true,
verificationCode: showEmail,
password: showPasswordSection,
confirmPassword: showPasswordSection,
});
const isValid = validateForm();
if (!isValid) {
return; // Stop the submission
}
const res = await submitForm({
firstName,
lastName,
gender: sex,
gender: sex || 0,
nationalId,
savePassword: showPasswordSection,
password: showPasswordSection ? password : undefined,
@@ -198,15 +180,26 @@ export function UserCompletionPage() {
birthDate,
country,
});
};
const getButtonLabel = () => {
if (buttonState === 'counting') {
const m = String(Math.floor(countdown / 60)).padStart(2, '0');
const s = String(countdown % 60).padStart(2, '0');
return toLocaleDigits(`${m}:${s}`, i18n.language);
if (res) {
if (res.success) {
showToast({
message: res.message || t('completion.submitSuccess'),
severity: 'success',
});
const returnUrl = params.get('returnUrl');
navigate(
returnUrl ? `/account-created?returnUrl=${returnUrl}` : '/setting',
);
} else {
showToast({
message: res.message || t('completion.submitError'),
severity: 'error',
});
}
}
return t('completion.vericationCodeButton');
};
const handleEditEmail = () => {
@@ -216,99 +209,189 @@ export function UserCompletionPage() {
setVerificationCode('');
};
const validateForm = () => {
// TODO: check if need separate validation or same common validation
const newErrors: { [key: string]: string } = {};
// Rule 1: First Name is required
if (!firstName.trim())
newErrors.firstName = t('validation.firstNameRequired');
// Rule 2: Last Name is required
if (!lastName.trim()) newErrors.lastName = t('validation.lastNameRequired');
// Rule 3: Country is required
if (!country) newErrors.country = t('validation.countryRequired');
// Rule 4: Email is required and must be valid IF the section is shown
if (showEmail && !isEmail(email)) {
newErrors.email = t('validation.emailInvalid');
}
// Rule 5: National ID must be 10 digits IF it's not empty
if (nationalId && !nationalIdRegex.test(nationalId)) {
newErrors.nationalId = t('validation.nationalIdInvalid');
}
// Rule 6: If verification code sent and email section is active
if (showEmail && codeSent && !emailVerified) {
if (!verificationCode.trim()) {
// Case 1: The code is required but the field is empty.
newErrors.verificationCode = t('validation.verificationCodeRequired');
} else {
// Case 2: The user has typed a code but hasn't clicked "Verify" yet.
newErrors.verificationCode = t('validation.mustVerifyCode');
}
}
// Rule 7: Password validation
if (showPasswordSection) {
// Rule 1: Check if the main password is valid
if (!password.trim()) {
newErrors.password = t('validation.passwordRequired');
} else if (!validPassword) {
// 'validPassword' is the boolean you already calculate
newErrors.password = t('validation.passwordInvalid');
}
// Rule 2: Check if the confirmation password matches
if (!confirmPassword.trim()) {
newErrors.confirmPassword = t('validation.confirmPasswordRequired');
} else if (!matchPassword) {
// 'matchPassword' is the boolean you already calculate
newErrors.confirmPassword = t('validation.passwordsDoNotMatch');
}
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0; // Returns true if form is valid
};
const handleBlur = (field: string) => {
setTouched((prev) => ({ ...prev, [field]: true }));
};
// re-validate whenever a field the user has touched changes value
useEffect(() => {
// Only run validation if at least one field has been touched
if (Object.keys(touched).length > 0) {
validateForm();
}
}, [
firstName,
lastName,
country,
email,
nationalId,
verificationCode,
codeSent,
emailVerified,
password,
confirmPassword,
showPasswordSection,
touched,
]);
return (
<Box
<FlexBox
direction="column"
align="center"
justify="center"
sx={{
backgroundColor: 'background.default',
minHeight: '100vh',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
p: { xs: 1, sm: 2, md: 3 },
gap: 3,
// TODO: check if this padding needed for mobile view
py: { sm: 0, xs: 2 },
}}
>
<Box sx={{ mb: 2 }}>
<Logo />
</Box>
<Box
<Logo />
<AuthenticationCard
// TODO: check styles
sx={{
width: '100%',
maxWidth: 730,
backgroundColor: 'background.paper',
border: '1px solid #ccc',
display: 'flex',
flexDirection: 'column',
gap: 2,
px: { xs: 2, sm: 4, md: 6 },
py: { xs: 3, sm: 4 },
maxHeight: { sm: '80vh', xs: 'unset' },
flex: { sm: 'unset', xs: 1 },
overflowY: 'auto',
}}
maxWidth="730px"
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography variant="h5" color="text.primary">
{t('completion.title')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('completion.description')}
</Typography>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography variant="h5" color="text.primary">
{t('completion.title')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('completion.description')}
</Typography>
</Box>
<PersonalInfoFields
firstName={firstName}
setFirstName={setFirstName}
lastName={lastName}
setLastName={setLastName}
nationalId={nationalId}
setNationalId={setNationalId}
birthDate={birthDate}
setBirthDate={setBirthDate}
sex={sex}
setSex={setSex}
country={country}
setCountry={setCountry}
errors={errors}
touched={touched}
handleBlur={handleBlur}
/>
<PasswordSection
showPasswordSection={showPasswordSection}
setShowPasswordSection={setShowPasswordSection}
password={password}
setPassword={setPassword}
confirmPassword={confirmPassword}
setConfirmPassword={setConfirmPassword}
matchPassword={matchPassword}
hasNumber={hasNumber}
hasMinLength={hasMinLength}
hasUpperAndLower={hasUpperAndLower}
hasSpecialChar={hasSpecialChar}
validPassword={validPassword}
showValidations={showPasswordValidations}
errors={errors}
touched={touched}
handleBlur={handleBlur}
/>
<EmailSection
showEmail={showEmail}
setShowEmail={setShowEmail}
email={email}
setEmail={setEmail}
correctEmail={correctEmail}
codeSent={codeSent}
verificationCode={verificationCode}
setVerificationCode={setVerificationCode}
buttonState={buttonState}
countdown={countdown}
handleSendCode={handleSendCode}
handleVerifyCode={handleVerifyCode}
emailVerified={emailVerified}
isVerifyingCode={isVerifyingCode}
isSendingCode={isSendingCode}
handleEditEmail={handleEditEmail}
errors={errors}
touched={touched}
handleBlur={handleBlur}
/>
<SubmitSection onSubmit={handleSubmit} loading={isSubmitting} />
</Box>
<PersonalInfoFields
firstName={firstName}
setFirstName={setFirstName}
lastName={lastName}
setLastName={setLastName}
nationalId={nationalId}
setNationalId={setNationalId}
birthDate={birthDate}
setBirthDate={setBirthDate}
sex={sex}
setSex={setSex}
country={country}
setCountry={setCountry}
/>
<PasswordSection
showPasswordSection={showPasswordSection}
setShowPasswordSection={setShowPasswordSection}
password={password}
setPassword={setPassword}
confirmPassword={confirmPassword}
setConfirmPassword={setConfirmPassword}
matchPassword={matchPassword}
hasNumber={hasNumber}
hasMinLength={hasMinLength}
hasUpperAndLower={hasUpperAndLower}
hasSpecialChar={hasSpecialChar}
validPassword={validPassword}
showValidations={showPasswordValidations}
/>
<EmailSection
showEmail={showEmail}
setShowEmail={setShowEmail}
email={email}
setEmail={setEmail}
correctEmail={correctEmail}
codeSent={codeSent}
verificationCode={verificationCode}
setVerificationCode={setVerificationCode}
buttonState={buttonState}
getButtonLabel={getButtonLabel}
handleSendCode={handleSendCode}
handleVerifyCode={handleVerifyCode}
emailVerified={emailVerified}
loading={isVerifyingCode}
handleEditEmail={handleEditEmail}
/>
<SubmitSection
onSubmit={handleSubmit}
loading={isSubmitting}
error={getErrorMessage(submitError)}
success={!!submitData?.data.success}
/>
</Box>
</Box>
</AuthenticationCard>
</FlexBox>
);
}

View File

@@ -16,6 +16,7 @@ export interface ConfirmEmailOtpPayload {
export interface CompleteUserInfoPayload {
firstName: string;
lastName: string;
// TODO: fix this
gender: 0 | 1 | 2;
nationalId: string;
birthDate: Date | null;

View File

@@ -6,12 +6,18 @@ export enum Gender {
Male = 2,
}
export interface ValidationProps {
errors: { [key: string]: string };
touched: { [key: string]: boolean };
handleBlur: (field: string) => void;
}
export interface DateOfBirthProps {
value: Date | null;
onChange: (date: Date | null) => void;
}
export interface EmailSectionProps {
export interface EmailSectionProps extends ValidationProps {
showEmail: boolean;
setShowEmail: (checked: boolean) => void;
email: string;
@@ -21,15 +27,16 @@ export interface EmailSectionProps {
verificationCode: string;
setVerificationCode: (code: string) => void;
buttonState: 'default' | 'counting' | 'sent';
getButtonLabel: () => string;
countdown: number;
handleSendCode: () => void;
handleVerifyCode: () => void;
emailVerified: boolean;
loading: boolean;
isVerifyingCode: boolean;
isSendingCode: boolean;
handleEditEmail: () => void;
}
export interface PasswordSectionProps {
export interface PasswordSectionProps extends ValidationProps {
showPasswordSection: boolean;
setShowPasswordSection: (checked: boolean) => void;
password: string;
@@ -50,13 +57,13 @@ export interface ValidationItemProps {
label: string;
}
export interface PersonalInfoFieldsProps {
export interface PersonalInfoFieldsProps extends ValidationProps {
firstName: string;
setFirstName: (v: string) => void;
lastName: string;
setLastName: (v: string) => void;
sex: Gender;
setSex: Dispatch<SetStateAction<Gender>>;
sex: Gender | null;
setSex: Dispatch<SetStateAction<Gender | null>>;
country: string;
setCountry: (country: string) => void;
nationalId: string;
@@ -68,6 +75,4 @@ export interface PersonalInfoFieldsProps {
export interface SubmitProps {
onSubmit: () => void;
loading: boolean;
error: string | null;
success: boolean;
}

View File

@@ -54,7 +54,7 @@ export function PersonalInformation() {
setData(fetchedData);
setOriginalData(fetchedData);
const imageBaseUrl = process.env.IMAGE_BASE_URL;
const imageBaseUrl = import.meta.env.IMAGE_BASE_URL;
if (profileData.profileImageUrl) {
setUploadedImageUrl(`${imageBaseUrl}${profileData.profileImageUrl}`);

View File

@@ -59,7 +59,7 @@ export function SocialMedia() {
} = useApi(changeEmail);
const fullScreen = useMediaQuery((theme: Theme) =>
theme.breakpoints.down('sm'),
theme?.breakpoints.down('sm'),
);
const downMd = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
const computedMaxWidth = (fullScreen ? 'xs' : downMd ? 'sm' : 'md') as

View File

@@ -33,19 +33,29 @@ export function useApi<T, P extends any[]>(
setData(response.data);
return response.data;
} catch (err: unknown) {
const axiosError: AxiosError = err as AxiosError;
const axiosError = err as AxiosError;
console.log(axiosError);
if (axiosError.response?.status === 401) {
navigate('/login');
}
if (axiosError.response?.status === 500) {
// Check for network error first
if (axiosError.code === 'ERR_NETWORK') {
toast({
message: t('messages.serverError'),
message: t('messages.networkError'),
severity: 'error',
});
}
// Check for HTTP status codes on the response object
else if (axiosError.response) {
if (axiosError.response.status === 401) {
navigate('/login');
}
if (axiosError.response.status === 500) {
toast({
message: t('messages.serverError'),
severity: 'error',
});
}
}
setError(err);
} finally {

View File

@@ -26,6 +26,9 @@ export const CustomThemeProvider: React.FC<{ children: React.ReactNode }> = ({
colorSchemeSelector: 'class',
},
spacing: 8,
shape: {
borderRadius: 8,
},
typography: typography,
components: {
MuiButton: {

View File

@@ -3,7 +3,6 @@ import { AccountCreatedPage } from '@/features/authorization/routes/AccountCreat
import {
Calendar,
Devices,
LocationTick,
Mobile,
PasswordCheck,
Personalcard,
@@ -89,6 +88,10 @@ export const appRoutes: RouteConfig[] = [
element: <Layout />,
authorize: true,
children: [
{
path: '/setting/',
element: <Navigate to="/setting/profile" replace />,
},
{
path: '/setting/profile',
element: <UserInformationPage />,
@@ -135,13 +138,6 @@ export const appRoutes: RouteConfig[] = [
icon: PasswordCheck,
},
},
{
path: '/setting/security#confirmed-ips',
navConfig: {
title: 'side.confirmedIps',
icon: LocationTick,
},
},
{
path: '/setting/security#recent-sessions',
navConfig: {

View File

@@ -4,7 +4,7 @@ import type {
TypeBackground,
TypeText,
} from '@mui/material';
import type { PaletteColorOptions } from 'node_modules/@mui/material';
import type { PaletteColorOptions } from '@mui/material';
export interface Palette {
primary: {

View File

@@ -117,10 +117,12 @@ export const PALETTE: Palette = {
light: {
default: grey[100],
paper: '#FFFFFF',
primary: blue[50],
},
dark: {
default: '#121212',
paper: grey[900],
primary: grey[800],
},
},
// Text colors

View File

@@ -8,6 +8,10 @@ declare module '@mui/material/styles' {
interface PaletteOptions {
club?: PaletteOptions['primary']; // Extend PaletteOptions for optional 'club'
}
interface TypeBackground {
primary: string;
}
}
declare module '@mui/material/Button' {
@@ -16,6 +20,8 @@ declare module '@mui/material/Button' {
}
}
// TODO: move this to types
// has typo
export type PalleteColor =
| 'primary'
| 'secondary'

View File

@@ -0,0 +1 @@
export const nationalIdRegex = /^\d{10}$/;