From 96ca543d00d0e1b96caaeefd843e35e1b06f0136 Mon Sep 17 00:00:00 2001 From: "Muhammad A. Ghorbani" Date: Tue, 13 Oct 2026 20:45:33 +0330 Subject: [PATCH] feat: add CustomTrip destination detail page with multi-language support and management tools --- src/components/shared/CountryDialSelect.tsx | 281 ++ src/components/ui/popup.tsx | 6 + src/data/countries.json | 2262 +++++++++++++++++ src/data/countries.ts | 63 + .../components/SupportServiceView.tsx | 524 ++-- src/features/services/services/support-api.ts | 51 + src/index.css | 4 +- 7 files changed, 2935 insertions(+), 256 deletions(-) create mode 100644 src/components/shared/CountryDialSelect.tsx create mode 100644 src/components/ui/popup.tsx create mode 100644 src/data/countries.json create mode 100644 src/data/countries.ts create mode 100644 src/features/services/services/support-api.ts diff --git a/src/components/shared/CountryDialSelect.tsx b/src/components/shared/CountryDialSelect.tsx new file mode 100644 index 0000000..3b9a640 --- /dev/null +++ b/src/components/shared/CountryDialSelect.tsx @@ -0,0 +1,281 @@ +import React, { useState, useMemo, useRef, useEffect } from 'react' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Input } from '@/components/ui/input' +import { Ic } from '@/icons' +import { cn } from '@/lib/utils' +import { + ALL_COUNTRIES, + POPULAR_COUNTRIES, + normalizeDialDigits, + findCountryByDialCode, + type CountryDialInfo, +} from '@/data/countries' + +export interface CountryDialSelectProps { + value: string + onChange: (dialCode: string, country?: CountryDialInfo) => void + disabled?: boolean + className?: string + buttonClassName?: string +} + +export function CountryDialSelect({ + value, + onChange, + disabled = false, + className, + buttonClassName, +}: CountryDialSelectProps) { + const [open, setOpen] = useState(false) + const [query, setQuery] = useState('') + const searchInputRef = useRef(null) + + // Find currently selected country + const selectedCountry = useMemo(() => { + return findCountryByDialCode(value) || ALL_COUNTRIES[0] + }, [value]) + + // Auto-focus search input on popover open + useEffect(() => { + if (open) { + const timer = setTimeout(() => { + searchInputRef.current?.focus() + }, 60) + return () => clearTimeout(timer) + } else { + setQuery('') + } + }, [open]) + + // Filter countries based on search query + const filteredCountries = useMemo(() => { + const raw = query.trim() + if (!raw) return ALL_COUNTRIES + + const normalized = normalizeDialDigits(raw).toLowerCase() + const cleanDigits = normalized.replace(/[^\d]/g, '') + const cleanDigitsNoZero = cleanDigits.replace(/^0+/, '') + + return ALL_COUNTRIES.filter((c) => { + // 1. Dial code match (e.g. 98, +98, 44, 964) + const cClean = c.dialCode.replace(/[^\d]/g, '') + const dialMatch = + cleanDigits.length > 0 && + (cClean.includes(cleanDigits) || (cleanDigitsNoZero.length > 0 && cClean.includes(cleanDigitsNoZero))) + + // 2. Name matches (Persian, English, Arabic) + const nameFaMatch = c.nameFa.toLowerCase().includes(normalized) + const nameEnMatch = c.nameEn.toLowerCase().includes(normalized) + const nameArMatch = c.nameAr ? c.nameAr.toLowerCase().includes(normalized) : false + + // 3. ISO code match (e.g. IR, IQ, SA, GB, US) + const codeMatch = c.code.toLowerCase() === normalized + + return dialMatch || nameFaMatch || nameEnMatch || nameArMatch || codeMatch + }) + }, [query]) + + // Split into popular & rest if query is empty for quick access + const isSearching = query.trim().length > 0 + const popularList = useMemo(() => POPULAR_COUNTRIES, []) + const otherList = useMemo( + () => ALL_COUNTRIES.filter((c) => !c.isPopular), + [] + ) + + const handleSelect = (c: CountryDialInfo) => { + onChange(c.dialCode, c) + setOpen(false) + setQuery('') + } + + return ( + + + + + + + {/* نوار جستجوی زنده کشور */} +
+ + setQuery(e.target.value)} + placeholder="جستجوی نام یا پیش‌شماره کشور..." + className="h-9 ps-8 pe-8 text-xs bg-surface-base rounded-xl border-border-soft focus-visible:ring-1 focus-visible:ring-primary shadow-none" + /> + {query.trim().length > 0 && ( + + )} +
+ + {/* فهرست کشورها */} +
+ {isSearching ? ( + /* نتایج جستجو */ + filteredCountries.length === 0 ? ( +
+

کشوری با این مشخصات یافت نشد.

+

+ می‌توانید نام فارسی، انگلیسی یا کد کشور را وارد کنید. +

+
+ ) : ( +
+
+ نتایج جستجو + {filteredCountries.length} کشور +
+ {filteredCountries.map((c) => { + const isSelected = c.dialCode === value + return ( + handleSelect(c)} + /> + ) + })} +
+ ) + ) : ( + /* حالت پیش‌فرض: بخش‌بندی پرکاربردها و سایر کشورها */ +
+ {/* کشورهای پرکاربرد */} +
+
+ + کشورهای پرکاربرد زیارتی و منطقه‌ای +
+ {popularList.map((c) => { + const isSelected = c.dialCode === value + return ( + handleSelect(c)} + /> + ) + })} +
+ + {/* خط جداکننده */} +
+ + {/* سایر کشورها */} +
+
+ همه کشورها (به ترتیب الفبا) +
+ {otherList.map((c) => { + const isSelected = c.dialCode === value + return ( + handleSelect(c)} + /> + ) + })} +
+
+ )} +
+ + + ) +} + +function CountryItemRow({ + country, + isSelected, + onSelect, +}: { + country: CountryDialInfo + isSelected: boolean + onSelect: () => void +}) { + return ( + + ) +} diff --git a/src/components/ui/popup.tsx b/src/components/ui/popup.tsx new file mode 100644 index 0000000..613ce45 --- /dev/null +++ b/src/components/ui/popup.tsx @@ -0,0 +1,6 @@ +/** + * Popup component alias (re-exports standard Dialog from ./dialog) + * Created to make finding popup/modal components faster and easier in the project. + */ +export * from './dialog' +export { Dialog as Popup } from './dialog' diff --git a/src/data/countries.json b/src/data/countries.json new file mode 100644 index 0000000..59a3009 --- /dev/null +++ b/src/data/countries.json @@ -0,0 +1,2262 @@ +[ + { + "code": "IR", + "name": "ایران", + "nameFa": "ایران", + "nameEn": "Iran", + "nameAr": "إيران", + "flag": "🇮🇷", + "dialCode": "+98", + "isPopular": true + }, + { + "code": "IQ", + "name": "عراق", + "nameFa": "عراق", + "nameEn": "Iraq", + "nameAr": "العراق", + "flag": "🇮🇶", + "dialCode": "+964", + "isPopular": true + }, + { + "code": "SA", + "name": "عربستان سعودی", + "nameFa": "عربستان سعودی", + "nameEn": "Saudi Arabia", + "nameAr": "المملكة العربية السعودية", + "flag": "🇸🇦", + "dialCode": "+966", + "isPopular": true + }, + { + "code": "AE", + "name": "امارات متحده عربی", + "nameFa": "امارات متحده عربی", + "nameEn": "United Arab Emirates", + "nameAr": "الإمارات العربية المتحدة", + "flag": "🇦🇪", + "dialCode": "+971", + "isPopular": true + }, + { + "code": "OM", + "name": "عُمان", + "nameFa": "عُمان", + "nameEn": "Oman", + "nameAr": "عُمان", + "flag": "🇴🇲", + "dialCode": "+968", + "isPopular": true + }, + { + "code": "KW", + "name": "کویت", + "nameFa": "کویت", + "nameEn": "Kuwait", + "nameAr": "الكويت", + "flag": "🇰🇼", + "dialCode": "+965", + "isPopular": true + }, + { + "code": "BH", + "name": "بحرین", + "nameFa": "بحرین", + "nameEn": "Bahrain", + "nameAr": "البحرين", + "flag": "🇧🇭", + "dialCode": "+973", + "isPopular": true + }, + { + "code": "QA", + "name": "قطر", + "nameFa": "قطر", + "nameEn": "Qatar", + "nameAr": "قطر", + "flag": "🇶🇦", + "dialCode": "+974", + "isPopular": true + }, + { + "code": "TR", + "name": "ترکیه", + "nameFa": "ترکیه", + "nameEn": "Turkey", + "nameAr": "تركيا", + "flag": "🇹🇷", + "dialCode": "+90", + "isPopular": true + }, + { + "code": "SY", + "name": "سوریه", + "nameFa": "سوریه", + "nameEn": "Syria", + "nameAr": "سوريا", + "flag": "🇸🇾", + "dialCode": "+963", + "isPopular": true + }, + { + "code": "LB", + "name": "لبنان", + "nameFa": "لبنان", + "nameEn": "Lebanon", + "nameAr": "لبنان", + "flag": "🇱🇧", + "dialCode": "+961", + "isPopular": true + }, + { + "code": "RU", + "name": "روسیه", + "nameFa": "روسیه", + "nameEn": "Russia", + "nameAr": "روسيا", + "flag": "🇷🇺", + "dialCode": "+7", + "isPopular": true + }, + { + "code": "ID", + "name": "اندونزی", + "nameFa": "اندونزی", + "nameEn": "Indonesia", + "nameAr": "إندونيسيا", + "flag": "🇮🇩", + "dialCode": "+62", + "isPopular": true + }, + { + "code": "PK", + "name": "پاکستان", + "nameFa": "پاکستان", + "nameEn": "Pakistan", + "nameAr": "باكستان", + "flag": "🇵🇰", + "dialCode": "+92", + "isPopular": true + }, + { + "code": "IN", + "name": "هند", + "nameFa": "هند", + "nameEn": "India", + "nameAr": "الهند", + "flag": "🇮🇳", + "dialCode": "+91", + "isPopular": true + }, + { + "code": "AF", + "name": "افغانستان", + "nameFa": "افغانستان", + "nameEn": "Afghanistan", + "nameAr": "أفغانستان", + "flag": "🇦🇫", + "dialCode": "+93", + "isPopular": true + }, + { + "code": "EG", + "name": "مصر", + "nameFa": "مصر", + "nameEn": "Egypt", + "nameAr": "مصر", + "flag": "🇪🇬", + "dialCode": "+20", + "isPopular": true + }, + { + "code": "GB", + "name": "انگلستان", + "nameFa": "انگلستان", + "nameEn": "United Kingdom", + "nameAr": "المملكة المتحدة", + "flag": "🇬🇧", + "dialCode": "+44", + "isPopular": true + }, + { + "code": "US", + "name": "ایالات متحده آمریکا", + "nameFa": "ایالات متحده آمریکا", + "nameEn": "United States", + "nameAr": "الولايات المتحدة", + "flag": "🇺🇸", + "dialCode": "+1", + "isPopular": true + }, + { + "code": "DE", + "name": "آلمان", + "nameFa": "آلمان", + "nameEn": "Germany", + "nameAr": "ألمانيا", + "flag": "🇩🇪", + "dialCode": "+49", + "isPopular": true + }, + { + "code": "FR", + "name": "فرانسه", + "nameFa": "فرانسه", + "nameEn": "France", + "nameAr": "فرنسا", + "flag": "🇫🇷", + "dialCode": "+33", + "isPopular": true + }, + { + "code": "CA", + "name": "کانادا", + "nameFa": "کانادا", + "nameEn": "Canada", + "nameAr": "كندا", + "flag": "🇨🇦", + "dialCode": "+1", + "isPopular": true + }, + { + "code": "AZ", + "name": "آذربایجان", + "nameFa": "آذربایجان", + "nameEn": "Azerbaijan", + "nameAr": "أذربيجان", + "flag": "🇦🇿", + "dialCode": "+994", + "isPopular": true + }, + { + "code": "AM", + "name": "ارمنستان", + "nameFa": "ارمنستان", + "nameEn": "Armenia", + "nameAr": "أرمينيا", + "flag": "🇦🇲", + "dialCode": "+374", + "isPopular": true + }, + { + "code": "GE", + "name": "گرجستان", + "nameFa": "گرجستان", + "nameEn": "Georgia", + "nameAr": "جورجيا", + "flag": "🇬🇪", + "dialCode": "+995", + "isPopular": true + }, + { + "code": "TJ", + "name": "تاجیکستان", + "nameFa": "تاجیکستان", + "nameEn": "Tajikistan", + "nameAr": "طاجيكستان", + "flag": "🇹🇯", + "dialCode": "+992", + "isPopular": true + }, + { + "code": "UZ", + "name": "ازبکستان", + "nameFa": "ازبکستان", + "nameEn": "Uzbekistan", + "nameAr": "أوزبكستان", + "flag": "🇺🇿", + "dialCode": "+998", + "isPopular": true + }, + { + "code": "TM", + "name": "ترکمنستان", + "nameFa": "ترکمنستان", + "nameEn": "Turkmenistan", + "nameAr": "تركمانستان", + "flag": "🇹🇲", + "dialCode": "+993", + "isPopular": true + }, + { + "code": "JO", + "name": "اردن", + "nameFa": "اردن", + "nameEn": "Jordan", + "nameAr": "الأردن", + "flag": "🇯🇴", + "dialCode": "+962", + "isPopular": true + }, + { + "code": "AU", + "name": "استرالیا", + "nameFa": "استرالیا", + "nameEn": "Australia", + "nameAr": "أستراليا", + "flag": "🇦🇺", + "dialCode": "+61", + "isPopular": true + }, + { + "code": "AR", + "name": "آرژانتین", + "nameFa": "آرژانتین", + "nameEn": "Argentina", + "nameAr": "الأرجنتين", + "flag": "🇦🇷", + "dialCode": "+54", + "isPopular": false + }, + { + "code": "AW", + "name": "آروبا", + "nameFa": "آروبا", + "nameEn": "Aruba", + "nameAr": "أروبا", + "flag": "🇦🇼", + "dialCode": "+297", + "isPopular": false + }, + { + "code": "ZA", + "name": "آفریقای جنوبی", + "nameFa": "آفریقای جنوبی", + "nameEn": "South Africa", + "nameAr": "جنوب أفريقيا", + "flag": "🇿🇦", + "dialCode": "+27", + "isPopular": false + }, + { + "code": "AL", + "name": "آلبانی", + "nameFa": "آلبانی", + "nameEn": "Albania", + "nameAr": "ألبانيا", + "flag": "🇦🇱", + "dialCode": "+355", + "isPopular": false + }, + { + "code": "AG", + "name": "آنتیگوا و باربودا", + "nameFa": "آنتیگوا و باربودا", + "nameEn": "Antigua & Barbuda", + "nameAr": "أنتيغوا وبربودا", + "flag": "🇦🇬", + "dialCode": "+1268", + "isPopular": false + }, + { + "code": "AD", + "name": "آندورا", + "nameFa": "آندورا", + "nameEn": "Andorra", + "nameAr": "أندورا", + "flag": "🇦🇩", + "dialCode": "+376", + "isPopular": false + }, + { + "code": "AO", + "name": "آنگولا", + "nameFa": "آنگولا", + "nameEn": "Angola", + "nameAr": "أنغولا", + "flag": "🇦🇴", + "dialCode": "+244", + "isPopular": false + }, + { + "code": "AI", + "name": "آنگویلا", + "nameFa": "آنگویلا", + "nameEn": "Anguilla", + "nameAr": "أنغويلا", + "flag": "🇦🇮", + "dialCode": "+1264", + "isPopular": false + }, + { + "code": "AT", + "name": "اتریش", + "nameFa": "اتریش", + "nameEn": "Austria", + "nameAr": "النمسا", + "flag": "🇦🇹", + "dialCode": "+43", + "isPopular": false + }, + { + "code": "ET", + "name": "اتیوپی", + "nameFa": "اتیوپی", + "nameEn": "Ethiopia", + "nameAr": "إثيوبيا", + "flag": "🇪🇹", + "dialCode": "+251", + "isPopular": false + }, + { + "code": "UY", + "name": "اروگوئه", + "nameFa": "اروگوئه", + "nameEn": "Uruguay", + "nameAr": "أوروغواي", + "flag": "🇺🇾", + "dialCode": "+598", + "isPopular": false + }, + { + "code": "ER", + "name": "اریتره", + "nameFa": "اریتره", + "nameEn": "Eritrea", + "nameAr": "إريتريا", + "flag": "🇪🇷", + "dialCode": "+291", + "isPopular": false + }, + { + "code": "ES", + "name": "اسپانیا", + "nameFa": "اسپانیا", + "nameEn": "Spain", + "nameAr": "إسبانيا", + "flag": "🇪🇸", + "dialCode": "+34", + "isPopular": false + }, + { + "code": "EE", + "name": "استونی", + "nameFa": "استونی", + "nameEn": "Estonia", + "nameAr": "إستونيا", + "flag": "🇪🇪", + "dialCode": "+372", + "isPopular": false + }, + { + "code": "SK", + "name": "اسلواکی", + "nameFa": "اسلواکی", + "nameEn": "Slovakia", + "nameAr": "سلوفاكيا", + "flag": "🇸🇰", + "dialCode": "+421", + "isPopular": false + }, + { + "code": "SI", + "name": "اسلوونی", + "nameFa": "اسلوونی", + "nameEn": "Slovenia", + "nameAr": "سلوفينيا", + "flag": "🇸🇮", + "dialCode": "+386", + "isPopular": false + }, + { + "code": "SZ", + "name": "اسواتینی", + "nameFa": "اسواتینی", + "nameEn": "Eswatini", + "nameAr": "إسواتيني", + "flag": "🇸🇿", + "dialCode": "+268", + "isPopular": false + }, + { + "code": "EC", + "name": "اکوادور", + "nameFa": "اکوادور", + "nameEn": "Ecuador", + "nameAr": "الإكوادور", + "flag": "🇪🇨", + "dialCode": "+593", + "isPopular": false + }, + { + "code": "SV", + "name": "السالوادور", + "nameFa": "السالوادور", + "nameEn": "El Salvador", + "nameAr": "السلفادور", + "flag": "🇸🇻", + "dialCode": "+503", + "isPopular": false + }, + { + "code": "UA", + "name": "اوکراین", + "nameFa": "اوکراین", + "nameEn": "Ukraine", + "nameAr": "أوكرانيا", + "flag": "🇺🇦", + "dialCode": "+380", + "isPopular": false + }, + { + "code": "UG", + "name": "اوگاندا", + "nameFa": "اوگاندا", + "nameEn": "Uganda", + "nameAr": "أوغندا", + "flag": "🇺🇬", + "dialCode": "+256", + "isPopular": false + }, + { + "code": "IT", + "name": "ایتالیا", + "nameFa": "ایتالیا", + "nameEn": "Italy", + "nameAr": "إيطاليا", + "flag": "🇮🇹", + "dialCode": "+39", + "isPopular": false + }, + { + "code": "IE", + "name": "ایرلند", + "nameFa": "ایرلند", + "nameEn": "Ireland", + "nameAr": "أيرلندا", + "flag": "🇮🇪", + "dialCode": "+353", + "isPopular": false + }, + { + "code": "IS", + "name": "ایسلند", + "nameFa": "ایسلند", + "nameEn": "Iceland", + "nameAr": "آيسلندا", + "flag": "🇮🇸", + "dialCode": "+354", + "isPopular": false + }, + { + "code": "BB", + "name": "باربادوس", + "nameFa": "باربادوس", + "nameEn": "Barbados", + "nameAr": "باربادوس", + "flag": "🇧🇧", + "dialCode": "+1246", + "isPopular": false + }, + { + "code": "BS", + "name": "باهاما", + "nameFa": "باهاما", + "nameEn": "Bahamas", + "nameAr": "البهاما", + "flag": "🇧🇸", + "dialCode": "+1242", + "isPopular": false + }, + { + "code": "BR", + "name": "برزیل", + "nameFa": "برزیل", + "nameEn": "Brazil", + "nameAr": "البرازيل", + "flag": "🇧🇷", + "dialCode": "+55", + "isPopular": false + }, + { + "code": "BM", + "name": "برمودا", + "nameFa": "برمودا", + "nameEn": "Bermuda", + "nameAr": "برمودا", + "flag": "🇧🇲", + "dialCode": "+1441", + "isPopular": false + }, + { + "code": "BN", + "name": "برونئی", + "nameFa": "برونئی", + "nameEn": "Brunei", + "nameAr": "بروناي", + "flag": "🇧🇳", + "dialCode": "+673", + "isPopular": false + }, + { + "code": "BY", + "name": "بلاروس", + "nameFa": "بلاروس", + "nameEn": "Belarus", + "nameAr": "بيلاروسيا", + "flag": "🇧🇾", + "dialCode": "+375", + "isPopular": false + }, + { + "code": "BE", + "name": "بلژیک", + "nameFa": "بلژیک", + "nameEn": "Belgium", + "nameAr": "بلجيكا", + "flag": "🇧🇪", + "dialCode": "+32", + "isPopular": false + }, + { + "code": "BG", + "name": "بلغارستان", + "nameFa": "بلغارستان", + "nameEn": "Bulgaria", + "nameAr": "بلغاريا", + "flag": "🇧🇬", + "dialCode": "+359", + "isPopular": false + }, + { + "code": "BZ", + "name": "بلیز", + "nameFa": "بلیز", + "nameEn": "Belize", + "nameAr": "بليز", + "flag": "🇧🇿", + "dialCode": "+501", + "isPopular": false + }, + { + "code": "BD", + "name": "بنگلادش", + "nameFa": "بنگلادش", + "nameEn": "Bangladesh", + "nameAr": "بنغلاديش", + "flag": "🇧🇩", + "dialCode": "+880", + "isPopular": false + }, + { + "code": "BJ", + "name": "بنین", + "nameFa": "بنین", + "nameEn": "Benin", + "nameAr": "بنين", + "flag": "🇧🇯", + "dialCode": "+229", + "isPopular": false + }, + { + "code": "BT", + "name": "بوتان", + "nameFa": "بوتان", + "nameEn": "Bhutan", + "nameAr": "بوتان", + "flag": "🇧🇹", + "dialCode": "+975", + "isPopular": false + }, + { + "code": "BW", + "name": "بوتسوانا", + "nameFa": "بوتسوانا", + "nameEn": "Botswana", + "nameAr": "بوتسوانا", + "flag": "🇧🇼", + "dialCode": "+267", + "isPopular": false + }, + { + "code": "BF", + "name": "بورکینافاسو", + "nameFa": "بورکینافاسو", + "nameEn": "Burkina Faso", + "nameAr": "بوركينا فاسو", + "flag": "🇧🇫", + "dialCode": "+226", + "isPopular": false + }, + { + "code": "BI", + "name": "بوروندی", + "nameFa": "بوروندی", + "nameEn": "Burundi", + "nameAr": "بوروندي", + "flag": "🇧🇮", + "dialCode": "+257", + "isPopular": false + }, + { + "code": "BA", + "name": "بوسنی و هرزگوین", + "nameFa": "بوسنی و هرزگوین", + "nameEn": "Bosnia & Herzegovina", + "nameAr": "البوسنة والهرسك", + "flag": "🇧🇦", + "dialCode": "+387", + "isPopular": false + }, + { + "code": "BO", + "name": "بولیوی", + "nameFa": "بولیوی", + "nameEn": "Bolivia", + "nameAr": "بوليفيا", + "flag": "🇧🇴", + "dialCode": "+591", + "isPopular": false + }, + { + "code": "PG", + "name": "پاپوآ گینه نو", + "nameFa": "پاپوآ گینه نو", + "nameEn": "Papua New Guinea", + "nameAr": "بابوا غينيا الجديدة", + "flag": "🇵🇬", + "dialCode": "+675", + "isPopular": false + }, + { + "code": "PY", + "name": "پاراگوئه", + "nameFa": "پاراگوئه", + "nameEn": "Paraguay", + "nameAr": "باراغواي", + "flag": "🇵🇾", + "dialCode": "+595", + "isPopular": false + }, + { + "code": "PW", + "name": "پالائو", + "nameFa": "پالائو", + "nameEn": "Palau", + "nameAr": "بالاو", + "flag": "🇵🇼", + "dialCode": "+680", + "isPopular": false + }, + { + "code": "PA", + "name": "پاناما", + "nameFa": "پاناما", + "nameEn": "Panama", + "nameAr": "بنما", + "flag": "🇵🇦", + "dialCode": "+507", + "isPopular": false + }, + { + "code": "PT", + "name": "پرتغال", + "nameFa": "پرتغال", + "nameEn": "Portugal", + "nameAr": "البرتغال", + "flag": "🇵🇹", + "dialCode": "+351", + "isPopular": false + }, + { + "code": "PE", + "name": "پرو", + "nameFa": "پرو", + "nameEn": "Peru", + "nameAr": "بيرو", + "flag": "🇵🇪", + "dialCode": "+51", + "isPopular": false + }, + { + "code": "PF", + "name": "پلی‌نزی فرانسه", + "nameFa": "پلی‌نزی فرانسه", + "nameEn": "French Polynesia", + "nameAr": "بولينيزيا الفرنسية", + "flag": "🇵🇫", + "dialCode": "+689", + "isPopular": false + }, + { + "code": "PR", + "name": "پورتوریکو", + "nameFa": "پورتوریکو", + "nameEn": "Puerto Rico", + "nameAr": "بورتوريكو", + "flag": "🇵🇷", + "dialCode": "+1 787", + "isPopular": false + }, + { + "code": "TZ", + "name": "تانزانیا", + "nameFa": "تانزانیا", + "nameEn": "Tanzania", + "nameAr": "تنزانيا", + "flag": "🇹🇿", + "dialCode": "+255", + "isPopular": false + }, + { + "code": "TH", + "name": "تایلند", + "nameFa": "تایلند", + "nameEn": "Thailand", + "nameAr": "تايلاند", + "flag": "🇹🇭", + "dialCode": "+66", + "isPopular": false + }, + { + "code": "TW", + "name": "تایوان", + "nameFa": "تایوان", + "nameEn": "Taiwan", + "nameAr": "تايوان", + "flag": "🇹🇼", + "dialCode": "+886", + "isPopular": false + }, + { + "code": "TT", + "name": "ترینیداد و توباگو", + "nameFa": "ترینیداد و توباگو", + "nameEn": "Trinidad & Tobago", + "nameAr": "ترينيداد وتوباغو", + "flag": "🇹🇹", + "dialCode": "+1 868", + "isPopular": false + }, + { + "code": "TK", + "name": "توکلائو", + "nameFa": "توکلائو", + "nameEn": "Tokelau", + "nameAr": "توكيلاو", + "flag": "🇹🇰", + "dialCode": "+690", + "isPopular": false + }, + { + "code": "TG", + "name": "توگو", + "nameFa": "توگو", + "nameEn": "Togo", + "nameAr": "توغو", + "flag": "🇹🇬", + "dialCode": "+228", + "isPopular": false + }, + { + "code": "TN", + "name": "تونس", + "nameFa": "تونس", + "nameEn": "Tunisia", + "nameAr": "تونس", + "flag": "🇹🇳", + "dialCode": "+216", + "isPopular": false + }, + { + "code": "TO", + "name": "تونگا", + "nameFa": "تونگا", + "nameEn": "Tonga", + "nameAr": "تونغا", + "flag": "🇹🇴", + "dialCode": "+676", + "isPopular": false + }, + { + "code": "TV", + "name": "تووالو", + "nameFa": "تووالو", + "nameEn": "Tuvalu", + "nameAr": "توفالو", + "flag": "🇹🇻", + "dialCode": "+688", + "isPopular": false + }, + { + "code": "TL", + "name": "تیمور شرقی", + "nameFa": "تیمور شرقی", + "nameEn": "Timor-Leste", + "nameAr": "تيمور الشرقية", + "flag": "🇹🇱", + "dialCode": "+670", + "isPopular": false + }, + { + "code": "JM", + "name": "جامائیکا", + "nameFa": "جامائیکا", + "nameEn": "Jamaica", + "nameAr": "جامايكا", + "flag": "🇯🇲", + "dialCode": "+1 876", + "isPopular": false + }, + { + "code": "GI", + "name": "جبل طارق", + "nameFa": "جبل طارق", + "nameEn": "Gibraltar", + "nameAr": "جبل طارق", + "flag": "🇬🇮", + "dialCode": "+350", + "isPopular": false + }, + { + "code": "JE", + "name": "جرزی", + "nameFa": "جرزی", + "nameEn": "Jersey", + "nameAr": "جيرزي", + "flag": "🇯🇪", + "dialCode": "+44", + "isPopular": false + }, + { + "code": "PN", + "name": "جزایر پیت‌کرن", + "nameFa": "جزایر پیت‌کرن", + "nameEn": "Pitcairn Islands", + "nameAr": "جزر بيتكيرن", + "flag": "🇵🇳", + "dialCode": "+64", + "isPopular": false + }, + { + "code": "TC", + "name": "جزایر تورکس و کایکوس", + "nameFa": "جزایر تورکس و کایکوس", + "nameEn": "Turks & Caicos Islands", + "nameAr": "جزر توركس وكايكوس", + "flag": "🇹🇨", + "dialCode": "+1 649", + "isPopular": false + }, + { + "code": "SB", + "name": "جزایر سلیمان", + "nameFa": "جزایر سلیمان", + "nameEn": "Solomon Islands", + "nameAr": "جزر سليمان", + "flag": "🇸🇧", + "dialCode": "+677", + "isPopular": false + }, + { + "code": "FO", + "name": "جزایر فارو", + "nameFa": "جزایر فارو", + "nameEn": "Faroe Islands", + "nameAr": "جزر فارو", + "flag": "🇫🇴", + "dialCode": "+298", + "isPopular": false + }, + { + "code": "FK", + "name": "جزایر فالکلند", + "nameFa": "جزایر فالکلند", + "nameEn": "Falkland Islands", + "nameAr": "جزر فوكلاند", + "flag": "🇫🇰", + "dialCode": "+500", + "isPopular": false + }, + { + "code": "CK", + "name": "جزایر کوک", + "nameFa": "جزایر کوک", + "nameEn": "Cook Islands", + "nameAr": "جزر كوك", + "flag": "🇨🇰", + "dialCode": "+682", + "isPopular": false + }, + { + "code": "KY", + "name": "جزایر کیمن", + "nameFa": "جزایر کیمن", + "nameEn": "Cayman Islands", + "nameAr": "جزر كايمان", + "flag": "🇰🇾", + "dialCode": "+1345", + "isPopular": false + }, + { + "code": "MH", + "name": "جزایر مارشال", + "nameFa": "جزایر مارشال", + "nameEn": "Marshall Islands", + "nameAr": "جزر مارشال", + "flag": "🇲🇭", + "dialCode": "+692", + "isPopular": false + }, + { + "code": "MP", + "name": "جزایر ماریانای شمالی", + "nameFa": "جزایر ماریانای شمالی", + "nameEn": "Northern Mariana Islands", + "nameAr": "جزر ماريانا الشمالية", + "flag": "🇲🇵", + "dialCode": "+1 670", + "isPopular": false + }, + { + "code": "IM", + "name": "جزیره من", + "nameFa": "جزیره من", + "nameEn": "Isle of Man", + "nameAr": "جزيرة مان", + "flag": "🇮🇲", + "dialCode": "+44", + "isPopular": false + }, + { + "code": "NF", + "name": "جزیره نورفک", + "nameFa": "جزیره نورفک", + "nameEn": "Norfolk Island", + "nameAr": "جزيرة نورفولك", + "flag": "🇳🇫", + "dialCode": "+672", + "isPopular": false + }, + { + "code": "CF", + "name": "جمهوری آفریقای مرکزی", + "nameFa": "جمهوری آفریقای مرکزی", + "nameEn": "Central African Republic", + "nameAr": "جمهورية أفريقيا الوسطى", + "flag": "🇨🇫", + "dialCode": "+236", + "isPopular": false + }, + { + "code": "CZ", + "name": "جمهوری چک", + "nameFa": "جمهوری چک", + "nameEn": "Czechia", + "nameAr": "التشيك", + "flag": "🇨🇿", + "dialCode": "+420", + "isPopular": false + }, + { + "code": "CD", + "name": "جمهوری دموکراتیک کنگو", + "nameFa": "جمهوری دموکراتیک کنگو", + "nameEn": "Congo - Kinshasa", + "nameAr": "جمهورية الكونغو الديمقراطية", + "flag": "🇨🇩", + "dialCode": "+243", + "isPopular": false + }, + { + "code": "DO", + "name": "جمهوری دومینیکن", + "nameFa": "جمهوری دومینیکن", + "nameEn": "Dominican Republic", + "nameAr": "جمهورية الدومينيكان", + "flag": "🇩🇴", + "dialCode": "+1 809, +1 829, +1 849", + "isPopular": false + }, + { + "code": "AQ", + "name": "جنوبگان", + "nameFa": "جنوبگان", + "nameEn": "Antarctica", + "nameAr": "القارة القطبية الجنوبية", + "flag": "🇦🇶", + "dialCode": "+672", + "isPopular": false + }, + { + "code": "DJ", + "name": "جیبوتی", + "nameFa": "جیبوتی", + "nameEn": "Djibouti", + "nameAr": "جيبوتي", + "flag": "🇩🇯", + "dialCode": "+253", + "isPopular": false + }, + { + "code": "TD", + "name": "چاد", + "nameFa": "چاد", + "nameEn": "Chad", + "nameAr": "تشاد", + "flag": "🇹🇩", + "dialCode": "+235", + "isPopular": false + }, + { + "code": "CN", + "name": "چین", + "nameFa": "چین", + "nameEn": "China", + "nameAr": "الصين", + "flag": "🇨🇳", + "dialCode": "+86", + "isPopular": false + }, + { + "code": "DK", + "name": "دانمارک", + "nameFa": "دانمارک", + "nameEn": "Denmark", + "nameAr": "الدنمارك", + "flag": "🇩🇰", + "dialCode": "+45", + "isPopular": false + }, + { + "code": "DM", + "name": "دومینیکا", + "nameFa": "دومینیکا", + "nameEn": "Dominica", + "nameAr": "دومينيكا", + "flag": "🇩🇲", + "dialCode": "+1767", + "isPopular": false + }, + { + "code": "RE", + "name": "رئونیون", + "nameFa": "رئونیون", + "nameEn": "Réunion", + "nameAr": "ريونيون", + "flag": "🇷🇪", + "dialCode": "+262", + "isPopular": false + }, + { + "code": "RW", + "name": "رواندا", + "nameFa": "رواندا", + "nameEn": "Rwanda", + "nameAr": "رواندا", + "flag": "🇷🇼", + "dialCode": "+250", + "isPopular": false + }, + { + "code": "RO", + "name": "رومانی", + "nameFa": "رومانی", + "nameEn": "Romania", + "nameAr": "رومانيا", + "flag": "🇷🇴", + "dialCode": "+40", + "isPopular": false + }, + { + "code": "JP", + "name": "ژاپن", + "nameFa": "ژاپن", + "nameEn": "Japan", + "nameAr": "اليابان", + "flag": "🇯🇵", + "dialCode": "+81", + "isPopular": false + }, + { + "code": "ST", + "name": "سائوتومه و پرنسیپ", + "nameFa": "سائوتومه و پرنسیپ", + "nameEn": "Sao Tome & Principe", + "nameAr": "ساو تومي وبرينسيبي", + "flag": "🇸🇹", + "dialCode": "+239", + "isPopular": false + }, + { + "code": "CI", + "name": "ساحل عاج", + "nameFa": "ساحل عاج", + "nameEn": "Côte d’Ivoire", + "nameAr": "ساحل العاج", + "flag": "🇨🇮", + "dialCode": "+225", + "isPopular": false + }, + { + "code": "WS", + "name": "ساموآ", + "nameFa": "ساموآ", + "nameEn": "Samoa", + "nameAr": "ساموا", + "flag": "🇼🇸", + "dialCode": "+685", + "isPopular": false + }, + { + "code": "PS", + "name": "سرزمین‌های فلسطینی", + "nameFa": "سرزمین‌های فلسطینی", + "nameEn": "Palestinian Territories", + "nameAr": "الأراضي الفلسطينية", + "flag": "🇵🇸", + "dialCode": "+970", + "isPopular": false + }, + { + "code": "LK", + "name": "سریلانکا", + "nameFa": "سریلانکا", + "nameEn": "Sri Lanka", + "nameAr": "سريلانكا", + "flag": "🇱🇰", + "dialCode": "+94", + "isPopular": false + }, + { + "code": "PM", + "name": "سن پیر و میکلن", + "nameFa": "سن پیر و میکلن", + "nameEn": "Saint Pierre & Miquelon", + "nameAr": "سان بيير وميكلون", + "flag": "🇵🇲", + "dialCode": "+508", + "isPopular": false + }, + { + "code": "SM", + "name": "سن مارینو", + "nameFa": "سن مارینو", + "nameEn": "San Marino", + "nameAr": "سان مارينو", + "flag": "🇸🇲", + "dialCode": "+378", + "isPopular": false + }, + { + "code": "KN", + "name": "سنت کیتس و نویس", + "nameFa": "سنت کیتس و نویس", + "nameEn": "Saint Kitts & Nevis", + "nameAr": "سانت كيتس ونيفيس", + "flag": "🇰🇳", + "dialCode": "+1 869", + "isPopular": false + }, + { + "code": "LC", + "name": "سنت لوسیا", + "nameFa": "سنت لوسیا", + "nameEn": "Saint Lucia", + "nameAr": "سانت لوسيا", + "flag": "🇱🇨", + "dialCode": "+1 758", + "isPopular": false + }, + { + "code": "SX", + "name": "سنت مارتن", + "nameFa": "سنت مارتن", + "nameEn": "Sint Maarten", + "nameAr": "سينت مارتن", + "flag": "🇸🇽", + "dialCode": "+1 721", + "isPopular": false + }, + { + "code": "SH", + "name": "سنت هلنا", + "nameFa": "سنت هلنا", + "nameEn": "Saint Helena", + "nameAr": "سانت هيلانة", + "flag": "🇸🇭", + "dialCode": "+290", + "isPopular": false + }, + { + "code": "SG", + "name": "سنگاپور", + "nameFa": "سنگاپور", + "nameEn": "Singapore", + "nameAr": "سنغافورة", + "flag": "🇸🇬", + "dialCode": "+65", + "isPopular": false + }, + { + "code": "SN", + "name": "سنگال", + "nameFa": "سنگال", + "nameEn": "Senegal", + "nameAr": "السنغال", + "flag": "🇸🇳", + "dialCode": "+221", + "isPopular": false + }, + { + "code": "SE", + "name": "سوئد", + "nameFa": "سوئد", + "nameEn": "Sweden", + "nameAr": "السويد", + "flag": "🇸🇪", + "dialCode": "+46", + "isPopular": false + }, + { + "code": "CH", + "name": "سوئیس", + "nameFa": "سوئیس", + "nameEn": "Switzerland", + "nameAr": "سويسرا", + "flag": "🇨🇭", + "dialCode": "+41", + "isPopular": false + }, + { + "code": "SD", + "name": "سودان", + "nameFa": "سودان", + "nameEn": "Sudan", + "nameAr": "السودان", + "flag": "🇸🇩", + "dialCode": "+249", + "isPopular": false + }, + { + "code": "SS", + "name": "سودان جنوبی", + "nameFa": "سودان جنوبی", + "nameEn": "South Sudan", + "nameAr": "جنوب السودان", + "flag": "🇸🇸", + "dialCode": "+211", + "isPopular": false + }, + { + "code": "SR", + "name": "سورینام", + "nameFa": "سورینام", + "nameEn": "Suriname", + "nameAr": "سورينام", + "flag": "🇸🇷", + "dialCode": "+597", + "isPopular": false + }, + { + "code": "SO", + "name": "سومالی", + "nameFa": "سومالی", + "nameEn": "Somalia", + "nameAr": "الصومال", + "flag": "🇸🇴", + "dialCode": "+252", + "isPopular": false + }, + { + "code": "SL", + "name": "سیرالئون", + "nameFa": "سیرالئون", + "nameEn": "Sierra Leone", + "nameAr": "سيراليون", + "flag": "🇸🇱", + "dialCode": "+232", + "isPopular": false + }, + { + "code": "SC", + "name": "سیشل", + "nameFa": "سیشل", + "nameEn": "Seychelles", + "nameAr": "سيشل", + "flag": "🇸🇨", + "dialCode": "+248", + "isPopular": false + }, + { + "code": "CL", + "name": "شیلی", + "nameFa": "شیلی", + "nameEn": "Chile", + "nameAr": "تشيلي", + "flag": "🇨🇱", + "dialCode": "+56", + "isPopular": false + }, + { + "code": "RS", + "name": "صربستان", + "nameFa": "صربستان", + "nameEn": "Serbia", + "nameAr": "صربيا", + "flag": "🇷🇸", + "dialCode": "+381", + "isPopular": false + }, + { + "code": "GH", + "name": "غنا", + "nameFa": "غنا", + "nameEn": "Ghana", + "nameAr": "غانا", + "flag": "🇬🇭", + "dialCode": "+233", + "isPopular": false + }, + { + "code": "FI", + "name": "فنلاند", + "nameFa": "فنلاند", + "nameEn": "Finland", + "nameAr": "فنلندا", + "flag": "🇫🇮", + "dialCode": "+358", + "isPopular": false + }, + { + "code": "FJ", + "name": "فیجی", + "nameFa": "فیجی", + "nameEn": "Fiji", + "nameAr": "فيجي", + "flag": "🇫🇯", + "dialCode": "+679", + "isPopular": false + }, + { + "code": "PH", + "name": "فیلیپین", + "nameFa": "فیلیپین", + "nameEn": "Philippines", + "nameAr": "الفلبين", + "flag": "🇵🇭", + "dialCode": "+63", + "isPopular": false + }, + { + "code": "CY", + "name": "قبرس", + "nameFa": "قبرس", + "nameEn": "Cyprus", + "nameAr": "قبرص", + "flag": "🇨🇾", + "dialCode": "+357", + "isPopular": false + }, + { + "code": "KG", + "name": "قرقیزستان", + "nameFa": "قرقیزستان", + "nameEn": "Kyrgyzstan", + "nameAr": "قيرغيزستان", + "flag": "🇰🇬", + "dialCode": "+996", + "isPopular": false + }, + { + "code": "KZ", + "name": "قزاقستان", + "nameFa": "قزاقستان", + "nameEn": "Kazakhstan", + "nameAr": "كازاخستان", + "flag": "🇰🇿", + "dialCode": "+7", + "isPopular": false + }, + { + "code": "CR", + "name": "کاستاریکا", + "nameFa": "کاستاریکا", + "nameEn": "Costa Rica", + "nameAr": "كوستاريكا", + "flag": "🇨🇷", + "dialCode": "+506", + "isPopular": false + }, + { + "code": "NC", + "name": "کالدونیای جدید", + "nameFa": "کالدونیای جدید", + "nameEn": "New Caledonia", + "nameAr": "كاليدونيا الجديدة", + "flag": "🇳🇨", + "dialCode": "+687", + "isPopular": false + }, + { + "code": "KH", + "name": "کامبوج", + "nameFa": "کامبوج", + "nameEn": "Cambodia", + "nameAr": "كمبوديا", + "flag": "🇰🇭", + "dialCode": "+855", + "isPopular": false + }, + { + "code": "CM", + "name": "کامرون", + "nameFa": "کامرون", + "nameEn": "Cameroon", + "nameAr": "الكاميرون", + "flag": "🇨🇲", + "dialCode": "+237", + "isPopular": false + }, + { + "code": "HR", + "name": "کرواسی", + "nameFa": "کرواسی", + "nameEn": "Croatia", + "nameAr": "كرواتيا", + "flag": "🇭🇷", + "dialCode": "+385", + "isPopular": false + }, + { + "code": "KR", + "name": "کره جنوبی", + "nameFa": "کره جنوبی", + "nameEn": "South Korea", + "nameAr": "كوريا الجنوبية", + "flag": "🇰🇷", + "dialCode": "+82", + "isPopular": false + }, + { + "code": "KP", + "name": "کره شمالی", + "nameFa": "کره شمالی", + "nameEn": "North Korea", + "nameAr": "كوريا الشمالية", + "flag": "🇰🇵", + "dialCode": "+850", + "isPopular": false + }, + { + "code": "CO", + "name": "کلمبیا", + "nameFa": "کلمبیا", + "nameEn": "Colombia", + "nameAr": "كولومبيا", + "flag": "🇨🇴", + "dialCode": "+57", + "isPopular": false + }, + { + "code": "CG", + "name": "کنگو برازاویل", + "nameFa": "کنگو برازاویل", + "nameEn": "Congo - Brazzaville", + "nameAr": "الكونغو - برازافيل", + "flag": "🇨🇬", + "dialCode": "+242", + "isPopular": false + }, + { + "code": "KE", + "name": "کنیا", + "nameFa": "کنیا", + "nameEn": "Kenya", + "nameAr": "كينيا", + "flag": "🇰🇪", + "dialCode": "+254", + "isPopular": false + }, + { + "code": "CU", + "name": "کوبا", + "nameFa": "کوبا", + "nameEn": "Cuba", + "nameAr": "كوبا", + "flag": "🇨🇺", + "dialCode": "+53", + "isPopular": false + }, + { + "code": "CW", + "name": "کوراسائو", + "nameFa": "کوراسائو", + "nameEn": "Curaçao", + "nameAr": "كوراساو", + "flag": "🇨🇼", + "dialCode": "+599", + "isPopular": false + }, + { + "code": "XK", + "name": "کوزوو", + "nameFa": "کوزوو", + "nameEn": "Kosovo", + "nameAr": "كوسوفو", + "flag": "🇽🇰", + "dialCode": "+383", + "isPopular": false + }, + { + "code": "KM", + "name": "کومور", + "nameFa": "کومور", + "nameEn": "Comoros", + "nameAr": "جزر القمر", + "flag": "🇰🇲", + "dialCode": "+269", + "isPopular": false + }, + { + "code": "CV", + "name": "کیپ ورد", + "nameFa": "کیپ ورد", + "nameEn": "Cabo Verde", + "nameAr": "الرأس الأخضر", + "flag": "🇨🇻", + "dialCode": "+238", + "isPopular": false + }, + { + "code": "KI", + "name": "کیریباتی", + "nameFa": "کیریباتی", + "nameEn": "Kiribati", + "nameAr": "كيريباتي", + "flag": "🇰🇮", + "dialCode": "+686", + "isPopular": false + }, + { + "code": "GA", + "name": "گابن", + "nameFa": "گابن", + "nameEn": "Gabon", + "nameAr": "الغابون", + "flag": "🇬🇦", + "dialCode": "+241", + "isPopular": false + }, + { + "code": "GM", + "name": "گامبیا", + "nameFa": "گامبیا", + "nameEn": "Gambia", + "nameAr": "غامبيا", + "flag": "🇬🇲", + "dialCode": "+220", + "isPopular": false + }, + { + "code": "GD", + "name": "گرنادا", + "nameFa": "گرنادا", + "nameEn": "Grenada", + "nameAr": "غرينادا", + "flag": "🇬🇩", + "dialCode": "+1473", + "isPopular": false + }, + { + "code": "GG", + "name": "گرنزی", + "nameFa": "گرنزی", + "nameEn": "Guernsey", + "nameAr": "غيرنزي", + "flag": "🇬🇬", + "dialCode": "+44", + "isPopular": false + }, + { + "code": "GL", + "name": "گرینلند", + "nameFa": "گرینلند", + "nameEn": "Greenland", + "nameAr": "جرينلاند", + "flag": "🇬🇱", + "dialCode": "+299", + "isPopular": false + }, + { + "code": "GT", + "name": "گواتمالا", + "nameFa": "گواتمالا", + "nameEn": "Guatemala", + "nameAr": "غواتيمالا", + "flag": "🇬🇹", + "dialCode": "+502", + "isPopular": false + }, + { + "code": "GP", + "name": "گوادلوپ", + "nameFa": "گوادلوپ", + "nameEn": "Guadeloupe", + "nameAr": "غوادلوب", + "flag": "🇬🇵", + "dialCode": "+590", + "isPopular": false + }, + { + "code": "GU", + "name": "گوام", + "nameFa": "گوام", + "nameEn": "Guam", + "nameAr": "غوام", + "flag": "🇬🇺", + "dialCode": "+1671", + "isPopular": false + }, + { + "code": "GY", + "name": "گویان", + "nameFa": "گویان", + "nameEn": "Guyana", + "nameAr": "غيانا", + "flag": "🇬🇾", + "dialCode": "+592", + "isPopular": false + }, + { + "code": "GF", + "name": "گویان فرانسه", + "nameFa": "گویان فرانسه", + "nameEn": "French Guiana", + "nameAr": "غويانا الفرنسية", + "flag": "🇬🇫", + "dialCode": "+594", + "isPopular": false + }, + { + "code": "GN", + "name": "گینه", + "nameFa": "گینه", + "nameEn": "Guinea", + "nameAr": "غينيا", + "flag": "🇬🇳", + "dialCode": "+224", + "isPopular": false + }, + { + "code": "GQ", + "name": "گینه استوایی", + "nameFa": "گینه استوایی", + "nameEn": "Equatorial Guinea", + "nameAr": "غينيا الاستوائية", + "flag": "🇬🇶", + "dialCode": "+240", + "isPopular": false + }, + { + "code": "GW", + "name": "گینه بیسائو", + "nameFa": "گینه بیسائو", + "nameEn": "Guinea-Bissau", + "nameAr": "غينيا بيساو", + "flag": "🇬🇼", + "dialCode": "+245", + "isPopular": false + }, + { + "code": "LA", + "name": "لائوس", + "nameFa": "لائوس", + "nameEn": "Laos", + "nameAr": "لاوس", + "flag": "🇱🇦", + "dialCode": "+856", + "isPopular": false + }, + { + "code": "LV", + "name": "لتونی", + "nameFa": "لتونی", + "nameEn": "Latvia", + "nameAr": "لاتفيا", + "flag": "🇱🇻", + "dialCode": "+371", + "isPopular": false + }, + { + "code": "LS", + "name": "لسوتو", + "nameFa": "لسوتو", + "nameEn": "Lesotho", + "nameAr": "ليسوتو", + "flag": "🇱🇸", + "dialCode": "+266", + "isPopular": false + }, + { + "code": "LU", + "name": "لوکزامبورگ", + "nameFa": "لوکزامبورگ", + "nameEn": "Luxembourg", + "nameAr": "لوكسمبورغ", + "flag": "🇱🇺", + "dialCode": "+352", + "isPopular": false + }, + { + "code": "PL", + "name": "لهستان", + "nameFa": "لهستان", + "nameEn": "Poland", + "nameAr": "بولندا", + "flag": "🇵🇱", + "dialCode": "+48", + "isPopular": false + }, + { + "code": "LR", + "name": "لیبریا", + "nameFa": "لیبریا", + "nameEn": "Liberia", + "nameAr": "ليبيريا", + "flag": "🇱🇷", + "dialCode": "+231", + "isPopular": false + }, + { + "code": "LY", + "name": "لیبی", + "nameFa": "لیبی", + "nameEn": "Libya", + "nameAr": "ليبيا", + "flag": "🇱🇾", + "dialCode": "+218", + "isPopular": false + }, + { + "code": "LT", + "name": "لیتوانی", + "nameFa": "لیتوانی", + "nameEn": "Lithuania", + "nameAr": "ليتوانيا", + "flag": "🇱🇹", + "dialCode": "+370", + "isPopular": false + }, + { + "code": "LI", + "name": "لیختن‌اشتاین", + "nameFa": "لیختن‌اشتاین", + "nameEn": "Liechtenstein", + "nameAr": "ليختنشتاين", + "flag": "🇱🇮", + "dialCode": "+423", + "isPopular": false + }, + { + "code": "MG", + "name": "ماداگاسکار", + "nameFa": "ماداگاسکار", + "nameEn": "Madagascar", + "nameAr": "مدغشقر", + "flag": "🇲🇬", + "dialCode": "+261", + "isPopular": false + }, + { + "code": "MQ", + "name": "مارتینیک", + "nameFa": "مارتینیک", + "nameEn": "Martinique", + "nameAr": "مارتينيك", + "flag": "🇲🇶", + "dialCode": "+596", + "isPopular": false + }, + { + "code": "MO", + "name": "ماکائو", + "nameFa": "ماکائو", + "nameEn": "Macao SAR China", + "nameAr": "ماكاو", + "flag": "🇲🇴", + "dialCode": "+853", + "isPopular": false + }, + { + "code": "MW", + "name": "مالاوی", + "nameFa": "مالاوی", + "nameEn": "Malawi", + "nameAr": "مالاوي", + "flag": "🇲🇼", + "dialCode": "+265", + "isPopular": false + }, + { + "code": "MT", + "name": "مالت", + "nameFa": "مالت", + "nameEn": "Malta", + "nameAr": "مالطا", + "flag": "🇲🇹", + "dialCode": "+356", + "isPopular": false + }, + { + "code": "MV", + "name": "مالدیو", + "nameFa": "مالدیو", + "nameEn": "Maldives", + "nameAr": "جزر المالديف", + "flag": "🇲🇻", + "dialCode": "+960", + "isPopular": false + }, + { + "code": "MY", + "name": "مالزی", + "nameFa": "مالزی", + "nameEn": "Malaysia", + "nameAr": "ماليزيا", + "flag": "🇲🇾", + "dialCode": "+60", + "isPopular": false + }, + { + "code": "ML", + "name": "مالی", + "nameFa": "مالی", + "nameEn": "Mali", + "nameAr": "مالي", + "flag": "🇲🇱", + "dialCode": "+223", + "isPopular": false + }, + { + "code": "YT", + "name": "مایوت", + "nameFa": "مایوت", + "nameEn": "Mayotte", + "nameAr": "مايوت", + "flag": "🇾🇹", + "dialCode": "+262", + "isPopular": false + }, + { + "code": "HU", + "name": "مجارستان", + "nameFa": "مجارستان", + "nameEn": "Hungary", + "nameAr": "المجر", + "flag": "🇭🇺", + "dialCode": "+36", + "isPopular": false + }, + { + "code": "MA", + "name": "مراکش", + "nameFa": "مراکش", + "nameEn": "Morocco", + "nameAr": "المغرب", + "flag": "🇲🇦", + "dialCode": "+212", + "isPopular": false + }, + { + "code": "MN", + "name": "مغولستان", + "nameFa": "مغولستان", + "nameEn": "Mongolia", + "nameAr": "منغوليا", + "flag": "🇲🇳", + "dialCode": "+976", + "isPopular": false + }, + { + "code": "MX", + "name": "مکزیک", + "nameFa": "مکزیک", + "nameEn": "Mexico", + "nameAr": "المكسيك", + "flag": "🇲🇽", + "dialCode": "+52", + "isPopular": false + }, + { + "code": "MR", + "name": "موریتانی", + "nameFa": "موریتانی", + "nameEn": "Mauritania", + "nameAr": "موريتانيا", + "flag": "🇲🇷", + "dialCode": "+222", + "isPopular": false + }, + { + "code": "MU", + "name": "موریس", + "nameFa": "موریس", + "nameEn": "Mauritius", + "nameAr": "موريشيوس", + "flag": "🇲🇺", + "dialCode": "+230", + "isPopular": false + }, + { + "code": "MZ", + "name": "موزامبیک", + "nameFa": "موزامبیک", + "nameEn": "Mozambique", + "nameAr": "موزمبيق", + "flag": "🇲🇿", + "dialCode": "+258", + "isPopular": false + }, + { + "code": "MD", + "name": "مولداوی", + "nameFa": "مولداوی", + "nameEn": "Moldova", + "nameAr": "مولدوفا", + "flag": "🇲🇩", + "dialCode": "+373", + "isPopular": false + }, + { + "code": "MC", + "name": "موناکو", + "nameFa": "موناکو", + "nameEn": "Monaco", + "nameAr": "موناكو", + "flag": "🇲🇨", + "dialCode": "+377", + "isPopular": false + }, + { + "code": "MS", + "name": "مونتسرات", + "nameFa": "مونتسرات", + "nameEn": "Montserrat", + "nameAr": "مونتسرات", + "flag": "🇲🇸", + "dialCode": "+1 664", + "isPopular": false + }, + { + "code": "ME", + "name": "مونته‌نگرو", + "nameFa": "مونته‌نگرو", + "nameEn": "Montenegro", + "nameAr": "الجبل الأسود", + "flag": "🇲🇪", + "dialCode": "+382", + "isPopular": false + }, + { + "code": "MM", + "name": "میانمار (برمه)", + "nameFa": "میانمار (برمه)", + "nameEn": "Myanmar (Burma)", + "nameAr": "ميانمار (بورما)", + "flag": "🇲🇲", + "dialCode": "+95", + "isPopular": false + }, + { + "code": "FM", + "name": "میکرونزی", + "nameFa": "میکرونزی", + "nameEn": "Micronesia", + "nameAr": "ميكرونيزيا", + "flag": "🇫🇲", + "dialCode": "+691", + "isPopular": false + }, + { + "code": "NR", + "name": "نائورو", + "nameFa": "نائورو", + "nameEn": "Nauru", + "nameAr": "ناورو", + "flag": "🇳🇷", + "dialCode": "+674", + "isPopular": false + }, + { + "code": "NA", + "name": "نامیبیا", + "nameFa": "نامیبیا", + "nameEn": "Namibia", + "nameAr": "ناميبيا", + "flag": "🇳🇦", + "dialCode": "+264", + "isPopular": false + }, + { + "code": "NP", + "name": "نپال", + "nameFa": "نپال", + "nameEn": "Nepal", + "nameAr": "نيبال", + "flag": "🇳🇵", + "dialCode": "+977", + "isPopular": false + }, + { + "code": "NO", + "name": "نروژ", + "nameFa": "نروژ", + "nameEn": "Norway", + "nameAr": "النرويج", + "flag": "🇳🇴", + "dialCode": "+47", + "isPopular": false + }, + { + "code": "NE", + "name": "نیجر", + "nameFa": "نیجر", + "nameEn": "Niger", + "nameAr": "النيجر", + "flag": "🇳🇪", + "dialCode": "+227", + "isPopular": false + }, + { + "code": "NG", + "name": "نیجریه", + "nameFa": "نیجریه", + "nameEn": "Nigeria", + "nameAr": "نيجيريا", + "flag": "🇳🇬", + "dialCode": "+234", + "isPopular": false + }, + { + "code": "NI", + "name": "نیکاراگوئه", + "nameFa": "نیکاراگوئه", + "nameEn": "Nicaragua", + "nameAr": "نيكاراغوا", + "flag": "🇳🇮", + "dialCode": "+505", + "isPopular": false + }, + { + "code": "NZ", + "name": "نیوزیلند", + "nameFa": "نیوزیلند", + "nameEn": "New Zealand", + "nameAr": "نيوزيلندا", + "flag": "🇳🇿", + "dialCode": "+64", + "isPopular": false + }, + { + "code": "NU", + "name": "نیووی", + "nameFa": "نیووی", + "nameEn": "Niue", + "nameAr": "نيوي", + "flag": "🇳🇺", + "dialCode": "+683", + "isPopular": false + }, + { + "code": "VA", + "name": "واتیکان", + "nameFa": "واتیکان", + "nameEn": "Vatican City", + "nameAr": "الفاتيكان", + "flag": "🇻🇦", + "dialCode": "+379", + "isPopular": false + }, + { + "code": "WF", + "name": "والیس و فوتونا", + "nameFa": "والیس و فوتونا", + "nameEn": "Wallis & Futuna", + "nameAr": "واليس وفوتونا", + "flag": "🇼🇫", + "dialCode": "+681", + "isPopular": false + }, + { + "code": "VU", + "name": "وانواتو", + "nameFa": "وانواتو", + "nameEn": "Vanuatu", + "nameAr": "فانواتو", + "flag": "🇻🇺", + "dialCode": "+678", + "isPopular": false + }, + { + "code": "VE", + "name": "ونزوئلا", + "nameFa": "ونزوئلا", + "nameEn": "Venezuela", + "nameAr": "فنزويلا", + "flag": "🇻🇪", + "dialCode": "+58", + "isPopular": false + }, + { + "code": "VN", + "name": "ویتنام", + "nameFa": "ویتنام", + "nameEn": "Vietnam", + "nameAr": "فيتنام", + "flag": "🇻🇳", + "dialCode": "+84", + "isPopular": false + }, + { + "code": "HT", + "name": "هائیتی", + "nameFa": "هائیتی", + "nameEn": "Haiti", + "nameAr": "هايتي", + "flag": "🇭🇹", + "dialCode": "+509", + "isPopular": false + }, + { + "code": "NL", + "name": "هلند", + "nameFa": "هلند", + "nameEn": "Netherlands", + "nameAr": "هولندا", + "flag": "🇳🇱", + "dialCode": "+31", + "isPopular": false + }, + { + "code": "HN", + "name": "هندوراس", + "nameFa": "هندوراس", + "nameEn": "Honduras", + "nameAr": "هندوراس", + "flag": "🇭🇳", + "dialCode": "+504", + "isPopular": false + }, + { + "code": "HK", + "name": "هنگ کنگ", + "nameFa": "هنگ کنگ", + "nameEn": "Hong Kong SAR China", + "nameAr": "هونغ كونغ", + "flag": "🇭🇰", + "dialCode": "+852", + "isPopular": false + }, + { + "code": "GR", + "name": "یونان", + "nameFa": "یونان", + "nameEn": "Greece", + "nameAr": "اليونان", + "flag": "🇬🇷", + "dialCode": "+30", + "isPopular": false + } +] \ No newline at end of file diff --git a/src/data/countries.ts b/src/data/countries.ts new file mode 100644 index 0000000..cbd9fe3 --- /dev/null +++ b/src/data/countries.ts @@ -0,0 +1,63 @@ +import countriesData from './countries.json' + +export interface CountryDialInfo { + code: string + name: string + nameFa: string + nameEn: string + nameAr?: string + flag: string + dialCode: string + isPopular?: boolean +} + +export const ALL_COUNTRIES: CountryDialInfo[] = countriesData as CountryDialInfo[] + +export const POPULAR_COUNTRIES: CountryDialInfo[] = ALL_COUNTRIES.filter((c) => c.isPopular) + +// For backwards compatibility +export const COUNTRY_DIAL_CODES: CountryDialInfo[] = ALL_COUNTRIES + +/** + * Normalizes Persian and Arabic digits to standard English digits + */ +export function normalizeDialDigits(input: string): string { + if (!input) return '' + const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] + const arabicDigits = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'] + let res = input + for (let i = 0; i < 10; i++) { + res = res.replace(new RegExp(persianDigits[i], 'g'), String(i)) + res = res.replace(new RegExp(arabicDigits[i], 'g'), String(i)) + } + return res +} + +/** + * Splits a full international phone number into dial code and national body + */ +export function splitPhoneNumber(fullPhone: string, defaultCode = '+44'): { dialCode: string; body: string } { + const clean = (fullPhone || '').trim().replace(/^00/, '+') + if (!clean) return { dialCode: defaultCode, body: '' } + + // Sort by dialCode length descending to match longest code first (+1264 before +1) + const sorted = [...ALL_COUNTRIES].sort((a, b) => b.dialCode.length - a.dialCode.length) + for (const c of sorted) { + if (clean.startsWith(c.dialCode)) { + return { + dialCode: c.dialCode, + body: clean.slice(c.dialCode.length).replace(/[^\d]/g, ''), + } + } + } + return { dialCode: defaultCode, body: clean.replace(/[^\d]/g, '') } +} + +/** + * Finds country by dial code + */ +export function findCountryByDialCode(dialCode: string): CountryDialInfo | undefined { + if (!dialCode) return undefined + const clean = dialCode.startsWith('+') ? dialCode : '+' + dialCode + return ALL_COUNTRIES.find((c) => c.dialCode === clean) +} diff --git a/src/features/services/components/SupportServiceView.tsx b/src/features/services/components/SupportServiceView.tsx index 29a8371..13927a3 100644 --- a/src/features/services/components/SupportServiceView.tsx +++ b/src/features/services/components/SupportServiceView.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react' +import React, { useState, useEffect, useMemo } from 'react' import { Ic, type IconName } from '@/icons' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' @@ -12,6 +12,8 @@ import { DialogContent, DialogHeader, DialogTitle, + DialogDescription, + DialogBody, DialogFooter, } from '@/components/ui/dialog' import { @@ -19,9 +21,21 @@ import { type ServiceLanguageCode, SERVICE_LANGUAGES, } from '@/components/shared/ServiceTranslation' +import { useTranslation } from '@/i18n' +import { CountryDialSelect } from '@/components/shared/CountryDialSelect' +import { COUNTRY_DIAL_CODES, splitPhoneNumber } from '@/data/countries' +import { + fetchAdminSupportChannels, + createSupportChannel, + updateSupportChannel, + toggleSupportChannelActive, + deleteSupportChannel, +} from '../services/support-api' + +export { COUNTRY_DIAL_CODES } export interface SupportChannel { - id: string + id: string | number icon: string phoneNumber: string whatsappNumber: string @@ -29,6 +43,7 @@ export interface SupportChannel { isActive: boolean titles: Record descriptions: Record + title?: string } const INITIAL_SUPPORT_CHANNELS: SupportChannel[] = [ @@ -154,41 +169,6 @@ const AVAILABLE_ICONS: { id: string; label: string; icon: IconName }[] = [ { id: 'phone', label: 'تماس', icon: 'phone' }, ] -export const COUNTRY_DIAL_CODES = [ - { code: 'GB', name: 'انگلستان', flag: '🇬🇧', dialCode: '+44' }, - { code: 'IQ', name: 'عراق', flag: '🇮🇶', dialCode: '+964' }, - { code: 'IR', name: 'ایران', flag: '🇮🇷', dialCode: '+98' }, - { code: 'SA', name: 'عربستان', flag: '🇸🇦', dialCode: '+966' }, - { code: 'AE', name: 'امارات', flag: '🇦🇪', dialCode: '+971' }, - { code: 'KW', name: 'کویت', flag: '🇰🇼', dialCode: '+965' }, - { code: 'BH', name: 'بحرین', flag: '🇧🇭', dialCode: '+973' }, - { code: 'OM', name: 'عمان', flag: '🇴🇲', dialCode: '+968' }, - { code: 'QA', name: 'قطر', flag: '🇶🇦', dialCode: '+974' }, - { code: 'TR', name: 'ترکیه', flag: '🇹🇷', dialCode: '+90' }, - { code: 'LB', name: 'لبنان', flag: '🇱🇧', dialCode: '+961' }, - { code: 'SY', name: 'سوریه', flag: '🇸🇾', dialCode: '+963' }, - { code: 'RU', name: 'روسیه', flag: '🇷🇺', dialCode: '+7' }, - { code: 'ID', name: 'اندونزی', flag: '🇮🇩', dialCode: '+62' }, - { code: 'PK', name: 'پاکستان', flag: '🇵🇰', dialCode: '+92' }, - { code: 'IN', name: 'هند', flag: '🇮🇳', dialCode: '+91' }, - { code: 'AF', name: 'افغانستان', flag: '🇦🇫', dialCode: '+93' }, - { code: 'EG', name: 'مصر', flag: '🇪🇬', dialCode: '+20' }, - { code: 'DE', name: 'آلمان', flag: '🇩🇪', dialCode: '+49' }, - { code: 'FR', name: 'فرانسه', flag: '🇫🇷', dialCode: '+33' }, - { code: 'US', name: 'آمریکا / کانادا', flag: '🇺🇸', dialCode: '+1' }, -] - -function splitPhoneNumber(fullPhone: string): { dialCode: string; body: string } { - const clean = (fullPhone || '').trim().replace(/^00/, '+') - if (!clean) return { dialCode: '+44', body: '' } - const sorted = [...COUNTRY_DIAL_CODES].sort((a, b) => b.dialCode.length - a.dialCode.length) - for (const c of sorted) { - if (clean.startsWith(c.dialCode)) { - return { dialCode: c.dialCode, body: clean.slice(c.dialCode.length).replace(/[^\d]/g, '') } - } - } - return { dialCode: '+44', body: clean.replace(/[^\d]/g, '') } -} export function SupportServiceView() { const [channels, setChannels] = useState(() => { @@ -201,6 +181,26 @@ export function SupportServiceView() { return INITIAL_SUPPORT_CHANNELS }) + // بارگذاری کانال‌ها از سرور به هنگام بارگذاری صفحه + useEffect(() => { + let isMounted = true + fetchAdminSupportChannels().then((data) => { + if (isMounted && Array.isArray(data) && data.length > 0) { + setChannels(data) + try { + localStorage.setItem('aqila_support_channels', JSON.stringify(data)) + } catch { + // ignore + } + } + }).catch((err) => { + console.error('Failed to load support channels from backend', err) + }) + return () => { + isMounted = false + } + }, []) + const persistChannels = (updated: SupportChannel[]) => { setChannels(updated) try { @@ -210,8 +210,8 @@ export function SupportServiceView() { } } - // زبان پیش‌نمایش کارت‌ها (پیش‌فرض عربی مطابق اپلیکیشن) - const [previewLang, setPreviewLang] = useState('ar') + // دریافت زبان فعال پنل + const { t, language } = useTranslation() // فیلترها const [statusFilter, setStatusFilter] = useState<'ALL' | 'ACTIVE' | 'INACTIVE'>('ALL') @@ -220,7 +220,7 @@ export function SupportServiceView() { // دیالوگ افزودن / ویرایش const [isDialogOpen, setIsDialogOpen] = useState(false) const [editingChannel, setEditingChannel] = useState(null) - const [dialogLangTab, setDialogLangTab] = useState('ar') + const [dialogLangTab, setDialogLangTab] = useState((language as ServiceLanguageCode) || 'fa') const [formTitles, setFormTitles] = useState>({ ar: '', fa: '', en: '', ru: '', id: '', @@ -236,7 +236,7 @@ export function SupportServiceView() { const handleOpenCreate = () => { setEditingChannel(null) - setDialogLangTab('ar') + setDialogLangTab((language as ServiceLanguageCode) || 'fa') setFormTitles({ ar: '', fa: '', en: '', ru: '', id: '' }) setFormDescriptions({ ar: '', fa: '', en: '', ru: '', id: '' }) setFormCountryDialCode('+44') @@ -249,7 +249,7 @@ export function SupportServiceView() { const handleOpenEdit = (channel: SupportChannel) => { setEditingChannel(channel) - setDialogLangTab('ar') + setDialogLangTab((language as ServiceLanguageCode) || 'fa') setFormTitles({ ...channel.titles }) setFormDescriptions({ ...channel.descriptions }) const parsed = splitPhoneNumber(channel.phoneNumber) @@ -272,7 +272,7 @@ export function SupportServiceView() { toast.success(`چت واتس‌اپ برای شماره ${formCountryDialCode} ${cleanBody} باز شد.`) } - const handleSaveChannel = (e: React.FormEvent) => { + const handleSaveChannel = async (e: React.FormEvent) => { e.preventDefault() if (!formTitles.ar.trim() && !formTitles.fa.trim() && !formTitles.en.trim()) { toast.error('لطفاً عنوان کانال را وارد کنید.') @@ -285,58 +285,81 @@ export function SupportServiceView() { } const fullPhone = `${formCountryDialCode}${cleanBody}` + const channelPayload = { + icon: formIcon, + phoneNumber: fullPhone, + contact: fullPhone, + whatsappNumber: fullPhone, + order: formOrder, + isActive: formIsActive, + titles: { ...formTitles }, + descriptions: { ...formDescriptions }, + } + if (editingChannel) { const updated = channels.map((c) => - c.id === editingChannel.id + String(c.id) === String(editingChannel.id) ? { ...c, - icon: formIcon, - phoneNumber: fullPhone, - whatsappNumber: fullPhone, - order: formOrder, - isActive: formIsActive, - titles: { ...formTitles }, - descriptions: { ...formDescriptions }, + ...channelPayload, + id: editingChannel.id, } : c ) persistChannels(updated) toast.success('کانال پشتیبانی به‌روزرسانی شد.') + try { + await updateSupportChannel(editingChannel.id, channelPayload) + } catch (err) { + toast.error('خطا در ذخیره‌سازی تغییرات در سرور.') + } } else { - const newChannel: SupportChannel = { - id: `ch-${Date.now()}`, - icon: formIcon, - phoneNumber: fullPhone, - whatsappNumber: fullPhone, - order: formOrder, - isActive: formIsActive, - titles: { ...formTitles }, - descriptions: { ...formDescriptions }, + try { + const created = await createSupportChannel(channelPayload) + persistChannels([...channels, created]) + toast.success('کانال پشتیبانی با موفقیت اضافه شد.') + } catch (err) { + const newChannel: SupportChannel = { + id: `ch-${Date.now()}`, + ...channelPayload, + } + persistChannels([...channels, newChannel]) + toast.success('کانال پشتیبانی با موفقیت اضافه شد.') } - persistChannels([...channels, newChannel]) - toast.success('کانال پشتیبانی با موفقیت اضافه شد.') } setIsDialogOpen(false) } - const handleToggleActive = (id: string, e?: React.MouseEvent) => { + const handleToggleActive = async (id: string | number, e?: React.MouseEvent) => { e?.stopPropagation() - const updated = channels.map((c) => { - if (c.id === id) { - const nextState = !c.isActive - toast.success(nextState ? 'کانال فعال شد.' : 'کانال غیرفعال شد.') - return { ...c, isActive: nextState } - } - return c - }) + const target = channels.find((c) => String(c.id) === String(id)) + if (!target) return + const nextState = !target.isActive + + const updated = channels.map((c) => + String(c.id) === String(id) ? { ...c, isActive: nextState } : c + ) persistChannels(updated) + toast.success(nextState ? 'کانال فعال شد.' : 'کانال غیرفعال شد.') + + try { + await toggleSupportChannelActive(id, nextState) + } catch (err) { + console.error('Failed to toggle active on backend', err) + toast.error('خطا در ذخیره‌سازی وضعیت در سرور.') + } } - const handleDeleteChannel = (channel: SupportChannel) => { + const handleDeleteChannel = async (channel: SupportChannel) => { if (confirm(`آیا از حذف این کانال اطمینان دارید؟`)) { - const updated = channels.filter((c) => c.id !== channel.id) + const updated = channels.filter((c) => String(c.id) !== String(channel.id)) persistChannels(updated) toast.success('کانال پشتیبانی حذف شد.') + try { + await deleteSupportChannel(channel.id) + } catch (err) { + console.error('Failed to delete on server', err) + } } } @@ -383,7 +406,7 @@ export function SupportServiceView() { : 'text-grey-3 hover:text-foreground' )} > - همه ({toFa(channels.length)}) + {t('all')} ({toFa(channels.length)})
- -
- - {/* انتخابگر زبان پیش‌نمایش ۵ زبانه */} -
- {SERVICE_LANGUAGES.map((l) => ( - - ))} -
{/* جستجو و دکمه افزودن */} @@ -440,7 +441,7 @@ export function SupportServiceView() { setSearchQuery(e.target.value)} - placeholder="جستجو..." + placeholder={`${t('search')}...`} className="h-8 ps-8 text-xs bg-surface-base rounded-xl border-border-soft" /> @@ -451,7 +452,7 @@ export function SupportServiceView() { className="rounded-xl text-xs gap-1.5 h-8 font-bold bg-primary text-primary-foreground hover:bg-primary/90 shrink-0" > - افزودن + {t('add')} @@ -459,8 +460,20 @@ export function SupportServiceView() { {/* کارت‌های ساده، خلوت و مدرن کانال‌ها */}
{filteredChannels.map((channel) => { - const title = channel.titles[previewLang] || channel.titles.ar || channel.titles.fa - const desc = channel.descriptions[previewLang] || channel.descriptions.ar + const langKey = (language as ServiceLanguageCode) || 'fa' + const title = + channel.titles?.[langKey] || + channel.titles?.fa || + channel.titles?.ar || + channel.titles?.en || + channel.title || + '' + const desc = + channel.descriptions?.[langKey] || + channel.descriptions?.fa || + channel.descriptions?.ar || + channel.descriptions?.en || + '' return ( - {channel.isActive ? 'فعال' : 'غیرفعال'} + {channel.isActive ? t('active') : t('inactive')}
@@ -516,12 +529,14 @@ export function SupportServiceView() { {/* ردیف شماره تماس و اکشن‌ها */}
@@ -551,191 +566,190 @@ export function SupportServiceView() { })}
- {/* دیالوگ تمیز افزودن و ویرایش */} + {/* دیالوگ استاندارد افزودن و ویرایش مطابق معماری dialog.tsx */} - - - + + {/* هدر استاندارد با تفکیک و دکمه بستن داخلی */} + + {editingChannel ? 'ویرایش کانال پشتیبانی' : 'افزودن کانال پشتیبانی'} + + تنظیم اطلاعات تماس، زبان‌های محتوا و وضعیت نمایش در اپلیکیشن + -
- {/* تب‌های ۵ زبان با کامپوننت مشترک ServiceTranslation */} - setDialogLangTab(lang)} - completedLangs={Object.fromEntries( - Object.entries(formTitles).map(([k, v]) => [k, Boolean(v?.trim())]) - )} - primaryLang="ar" - title="زبان محتوا" - variant="grid" - /> + {/* فرم با اسکرول بدنه و فوتر پین‌شده */} + + + {/* تب‌های ۵ زبان با حالت قرصی ریسپانسیو بدون کراپ متن */} + setDialogLangTab(lang)} + completedLangs={Object.fromEntries( + Object.entries(formTitles).map(([k, v]) => [k, Boolean(v?.trim())]) + )} + primaryLang="ar" + title="زبان محتوا" + variant="pills" + /> + + {/* عنوان به زبان فعال */} + {(() => { + const currentMeta = SERVICE_LANGUAGES.find((l) => l.code === dialogLangTab) || SERVICE_LANGUAGES[0] + return ( +
+
+ + + setFormTitles({ ...formTitles, [dialogLangTab]: e.target.value }) + } + dir={currentMeta.dir} + placeholder={`عنوان به ${currentMeta.name}...`} + className="h-9 text-xs bg-surface-base rounded-xl border-border-soft" + /> +
- {/* عنوان به زبان فعال */} - {(() => { - const currentMeta = SERVICE_LANGUAGES.find((l) => l.code === dialogLangTab) || SERVICE_LANGUAGES[0] - return ( -
-
-
+ ) + })()} + + {/* شماره تماس همراه با انتخابگر کشور و پرچم + دکمه تست مستقیم واتس‌اپ */} +
+
+
+ - - setFormTitles({ ...formTitles, [dialogLangTab]: e.target.value }) - } - dir={currentMeta.dir} - placeholder={`عنوان به ${currentMeta.name}...`} - className="h-9 text-xs bg-surface-base rounded-xl border-border-soft" - /> + + {/* دکمه اختصاصی باز کردن و تست مستقیم واتس‌اپ */} +
-
- + {/* کامپوننت ترکیبی انتخاب کشور با پرچم + اینپوت شماره */} +
+ {/* سلکتور پیش‌شماره کشور همراه با پرچم و قابلیت سرچ */} +
+ setFormCountryDialCode(dialCode)} + /> +
+ + {/* فیلد وارد کردن شماره تماس */} - setFormDescriptions({ ...formDescriptions, [dialogLangTab]: e.target.value }) - } - dir={currentMeta.dir} - placeholder="توضیح کوتاه اختیاری..." - className="h-9 text-xs bg-surface-base rounded-xl border-border-soft" + value={formPhoneBody} + onChange={(e) => setFormPhoneBody(e.target.value)} + dir="ltr" + placeholder="2071234577" + className="h-9 border-0 bg-transparent text-xs font-mono focus-visible:ring-0 shadow-none px-3" + required />
-
- ) - })()} - - {/* شماره تماس همراه با انتخابگر کشور و پرچم + دکمه تست مستقیم واتس‌اپ */} -
-
-
- - - {/* دکمه اختصاصی باز کردن و تست مستقیم واتس‌اپ */} - -
- {/* کامپوننت ترکیبی انتخاب کشور با پرچم + اینپوت شماره */} -
- {/* سلکتور پیش‌شماره کشور همراه با پرچم */} -
- - + {/* پیش‌نمایش زنده شماره */} +
+ شماره نهایی: + + {formCountryDialCode} {formPhoneBody || '---'} +
+
- {/* فیلد وارد کردن شماره تماس */} + {/* ترتیب نمایش */} +
+ setFormPhoneBody(e.target.value)} - dir="ltr" - placeholder="2071234577" - className="h-9 border-0 bg-transparent text-xs font-mono focus-visible:ring-0 shadow-none px-3" - required + type="number" + min="1" + value={formOrder} + onChange={(e) => setFormOrder(parseInt(e.target.value) || 1)} + className="h-9 text-xs bg-surface-base rounded-xl border-border-soft font-mono" />
- - {/* پیش‌نمایش زنده شماره */} -
- شماره نهایی: - - {formCountryDialCode} {formPhoneBody || '---'} - -
- {/* ترتیب نمایش */} -
+ {/* آیکون و وضعیت فعال */} +
- setFormOrder(parseInt(e.target.value) || 1)} - className="h-9 text-xs bg-surface-base rounded-xl border-border-soft font-mono" - /> +
+ {AVAILABLE_ICONS.map((item) => ( + + ))} +
-
- - {/* آیکون و وضعیت فعال */} -
- -
- {AVAILABLE_ICONS.map((item) => ( - - ))} + +
+ + وضعیت فعال در اپلیکیشن + +
-
- -
- - وضعیت فعال در اپلیکیشن - - -
+ - + {/* فوتر استاندارد ثابت با استایل سیستم طراحی پنل */} + diff --git a/src/features/services/services/support-api.ts b/src/features/services/services/support-api.ts new file mode 100644 index 0000000..41f9018 --- /dev/null +++ b/src/features/services/services/support-api.ts @@ -0,0 +1,51 @@ +import { apiFetch } from '@/services/http' +import type { SupportChannel } from '../components/SupportServiceView' + +/** + * دریافت لیست کانال‌های پشتیبانی از سرور برای پنل ادمین + */ +export async function fetchAdminSupportChannels(params?: { search?: string }): Promise { + const q = new URLSearchParams() + if (params?.search) q.set('search', params.search) + const url = `contact/admin/consultants/${q.toString() ? `?${q.toString()}` : ''}` + const res = await apiFetch(url) + if (Array.isArray(res)) return res + if (res && Array.isArray(res.results)) return res.results + return [] +} + +/** + * ایجاد کانال پشتیبانی جدید + */ +export async function createSupportChannel(data: Partial): Promise { + return apiFetch('contact/admin/consultants/', { + method: 'POST', + body: data, + }) +} + +/** + * ویرایش کانال پشتیبانی + */ +export async function updateSupportChannel(id: string | number, data: Partial): Promise { + return apiFetch(`contact/admin/consultants/${id}/`, { + method: 'PATCH', + body: data, + }) +} + +/** + * تغییر وضعیت فعال/غیرفعال کانال پشتیبانی + */ +export async function toggleSupportChannelActive(id: string | number, isActive: boolean): Promise { + return updateSupportChannel(id, { isActive, is_active: isActive } as any) +} + +/** + * حذف کانال پشتیبانی + */ +export async function deleteSupportChannel(id: string | number): Promise { + return apiFetch(`contact/admin/consultants/${id}/`, { + method: 'DELETE', + }) +} diff --git a/src/index.css b/src/index.css index b074d4c..f9b45b9 100644 --- a/src/index.css +++ b/src/index.css @@ -320,4 +320,6 @@ html.light .shimmer-wave, animation: shimmerWave 1.5s infinite linear; } - +.dir-ltr { + direction: ltr !important; +}