forked from UKSOURCE/hailearning.edu.vn
Initial commit
This commit is contained in:
188
app/countries/countries.json
Normal file
188
app/countries/countries.json
Normal file
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"title": "Danh Sách Quốc Gia",
|
||||
"subtitle": "Chúng tôi hỗ trợ visa cho hơn 50 quốc gia trên thế giới",
|
||||
"regions": [
|
||||
{
|
||||
"name": "Châu Âu",
|
||||
"countries": [
|
||||
{
|
||||
"id": "schengen",
|
||||
"name": "Schengen",
|
||||
"flag": "🇪🇺",
|
||||
"visaTypes": ["Du lịch", "Công tác", "Thăm thân"],
|
||||
"processingTime": "15-20 ngày",
|
||||
"validityPeriod": "90 ngày",
|
||||
"fee": "2,200,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Ảnh 3.5x4.5cm (2 tấm)",
|
||||
"Bảo hiểm du lịch",
|
||||
"Chứng minh tài chính",
|
||||
"Lịch trình du lịch"
|
||||
],
|
||||
"description": "Visa Schengen cho phép du lịch tự do trong 26 quốc gia châu Âu"
|
||||
},
|
||||
{
|
||||
"id": "uk",
|
||||
"name": "Anh",
|
||||
"flag": "🇬🇧",
|
||||
"visaTypes": ["Du lịch", "Công tác", "Học tập"],
|
||||
"processingTime": "15-25 ngày",
|
||||
"validityPeriod": "6 tháng",
|
||||
"fee": "3,800,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Ảnh passport",
|
||||
"Chứng minh tài chính",
|
||||
"Thư mời (nếu có)",
|
||||
"Bảo hiểm du lịch"
|
||||
],
|
||||
"description": "Visa Anh cho phép khám phá vương quốc Anh và Bắc Ireland"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Bắc Mỹ",
|
||||
"countries": [
|
||||
{
|
||||
"id": "usa",
|
||||
"name": "Mỹ",
|
||||
"flag": "🇺🇸",
|
||||
"visaTypes": ["B1/B2", "F1", "J1"],
|
||||
"processingTime": "3-5 tuần",
|
||||
"validityPeriod": "10 năm",
|
||||
"fee": "4,600,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Form DS-160",
|
||||
"Ảnh visa Mỹ",
|
||||
"Chứng minh tài chính",
|
||||
"Phỏng vấn tại lãnh sự quán"
|
||||
],
|
||||
"description": "Visa Mỹ B1/B2 cho phép du lịch và công tác tại Hoa Kỳ"
|
||||
},
|
||||
{
|
||||
"id": "canada",
|
||||
"name": "Canada",
|
||||
"flag": "🇨🇦",
|
||||
"visaTypes": ["TRV", "Study Permit", "Work Permit"],
|
||||
"processingTime": "4-6 tuần",
|
||||
"validityPeriod": "10 năm",
|
||||
"fee": "2,800,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Ảnh passport",
|
||||
"Chứng minh tài chính",
|
||||
"Thư mời (nếu có)",
|
||||
"Khám sức khỏe (nếu cần)"
|
||||
],
|
||||
"description": "Visa Canada cho phép du lịch và làm việc tại Canada"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Châu Á",
|
||||
"countries": [
|
||||
{
|
||||
"id": "japan",
|
||||
"name": "Nhật Bản",
|
||||
"flag": "🇯🇵",
|
||||
"visaTypes": ["Du lịch", "Công tác", "Thăm thân"],
|
||||
"processingTime": "5-7 ngày",
|
||||
"validityPeriod": "90 ngày",
|
||||
"fee": "1,200,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Ảnh 4.5x4.5cm",
|
||||
"Chứng minh tài chính",
|
||||
"Lịch trình du lịch",
|
||||
"Đặt chỗ khách sạn"
|
||||
],
|
||||
"description": "Visa Nhật Bản cho phép khám phá đất nước mặt trời mọc"
|
||||
},
|
||||
{
|
||||
"id": "korea",
|
||||
"name": "Hàn Quốc",
|
||||
"flag": "🇰🇷",
|
||||
"visaTypes": ["C-3", "C-4", "D-2"],
|
||||
"processingTime": "5-10 ngày",
|
||||
"validityPeriod": "90 ngày",
|
||||
"fee": "1,500,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Ảnh 3.5x4.5cm",
|
||||
"Chứng minh tài chính",
|
||||
"Lịch trình du lịch",
|
||||
"Bảo hiểm du lịch"
|
||||
],
|
||||
"description": "Visa Hàn Quốc cho phép du lịch và khám phá văn hóa K-pop"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Châu Đại Dương",
|
||||
"countries": [
|
||||
{
|
||||
"id": "australia",
|
||||
"name": "Úc",
|
||||
"flag": "🇦🇺",
|
||||
"visaTypes": ["Visitor", "Student", "Work"],
|
||||
"processingTime": "15-30 ngày",
|
||||
"validityPeriod": "1 năm",
|
||||
"fee": "4,200,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 6 tháng",
|
||||
"Ảnh passport",
|
||||
"Chứng minh tài chính",
|
||||
"Bảo hiểm y tế",
|
||||
"Khám sức khỏe"
|
||||
],
|
||||
"description": "Visa Úc cho phép du lịch và khám phá lục địa kangaroo"
|
||||
},
|
||||
{
|
||||
"id": "newzealand",
|
||||
"name": "New Zealand",
|
||||
"flag": "🇳🇿",
|
||||
"visaTypes": ["Visitor", "Student", "Work"],
|
||||
"processingTime": "20-25 ngày",
|
||||
"validityPeriod": "9 tháng",
|
||||
"fee": "3,500,000 VNĐ",
|
||||
"requirements": [
|
||||
"Hộ chiếu còn hạn trên 3 tháng",
|
||||
"Ảnh passport",
|
||||
"Chứng minh tài chính",
|
||||
"Bảo hiểm du lịch",
|
||||
"Lịch trình chi tiết"
|
||||
],
|
||||
"description": "Visa New Zealand cho phép khám phá xứ sở cừu và phong cảnh tuyệt đẹp"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"popularCountries": [
|
||||
{
|
||||
"id": "japan",
|
||||
"name": "Nhật Bản",
|
||||
"flag": "🇯🇵",
|
||||
"reason": "Thủ tục đơn giản, tỷ lệ thành công cao"
|
||||
},
|
||||
{
|
||||
"id": "korea",
|
||||
"name": "Hàn Quốc",
|
||||
"flag": "🇰🇷",
|
||||
"reason": "Văn hóa K-pop, ẩm thực hấp dẫn"
|
||||
},
|
||||
{
|
||||
"id": "schengen",
|
||||
"name": "Schengen",
|
||||
"flag": "🇪🇺",
|
||||
"reason": "Một visa đi được 26 quốc gia"
|
||||
},
|
||||
{
|
||||
"id": "usa",
|
||||
"name": "Mỹ",
|
||||
"flag": "🇺🇸",
|
||||
"reason": "Visa 10 năm, nhiều cơ hội"
|
||||
}
|
||||
]
|
||||
}
|
||||
231
app/countries/page.tsx
Normal file
231
app/countries/page.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import countriesData from "./countries.json";
|
||||
|
||||
export default function CountriesPage() {
|
||||
const [selectedRegion, setSelectedRegion] = useState("all");
|
||||
const [selectedCountry, setSelectedCountry] = useState<string | null>(null);
|
||||
|
||||
const filteredRegions =
|
||||
selectedRegion === "all"
|
||||
? countriesData.regions
|
||||
: countriesData.regions.filter(
|
||||
(region) => region.name === selectedRegion,
|
||||
);
|
||||
|
||||
const handleCountryClick = (countryId: string) => {
|
||||
setSelectedCountry(selectedCountry === countryId ? null : countryId);
|
||||
};
|
||||
|
||||
const getCountryById = (id: string) => {
|
||||
for (const region of countriesData.regions) {
|
||||
const country = region.countries.find((c) => c.id === id);
|
||||
if (country) return country;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
{countriesData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600">{countriesData.subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* Popular Countries */}
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6 text-center">
|
||||
Quốc Gia Phổ Biến
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-4 gap-6">
|
||||
{countriesData.popularCountries.map((country) => (
|
||||
<div
|
||||
key={country.id}
|
||||
className="bg-white rounded-lg shadow-md p-6 text-center hover:shadow-lg transition-shadow cursor-pointer"
|
||||
onClick={() => handleCountryClick(country.id)}
|
||||
>
|
||||
<div className="text-4xl mb-3">{country.flag}</div>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">
|
||||
{country.name}
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm">{country.reason}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Region Filter */}
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-wrap gap-4 justify-center">
|
||||
<button
|
||||
onClick={() => setSelectedRegion("all")}
|
||||
className={`px-6 py-2 rounded-full transition-colors ${
|
||||
selectedRegion === "all"
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
Tất cả
|
||||
</button>
|
||||
{countriesData.regions.map((region) => (
|
||||
<button
|
||||
key={region.name}
|
||||
onClick={() => setSelectedRegion(region.name)}
|
||||
className={`px-6 py-2 rounded-full transition-colors ${
|
||||
selectedRegion === region.name
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
{region.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Countries by Region */}
|
||||
<div className="space-y-12">
|
||||
{filteredRegions.map((region) => (
|
||||
<div key={region.name}>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
{region.name}
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{region.countries.map((country) => (
|
||||
<div
|
||||
key={country.id}
|
||||
className="bg-white rounded-lg shadow-md overflow-hidden"
|
||||
>
|
||||
{/* Country Header */}
|
||||
<div
|
||||
className="p-6 cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
onClick={() => handleCountryClick(country.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className="text-3xl mr-4">{country.flag}</span>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
{country.name}
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
{country.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-gray-400">
|
||||
{selectedCountry === country.id ? "▼" : "▶"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Quick Info */}
|
||||
<div className="grid grid-cols-2 gap-4 mt-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500">
|
||||
Thời gian xử lý:
|
||||
</span>
|
||||
<div className="font-medium">
|
||||
{country.processingTime}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500">Phí visa:</span>
|
||||
<div className="font-medium text-blue-600">
|
||||
{country.fee}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Detailed Info */}
|
||||
{selectedCountry === country.id && (
|
||||
<div className="border-t bg-gray-50 p-6">
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Visa Types */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">
|
||||
Loại visa:
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{country.visaTypes.map((type, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm"
|
||||
>
|
||||
{type}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validity */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">
|
||||
Thời hạn visa:
|
||||
</h4>
|
||||
<p className="text-gray-700">
|
||||
{country.validityPeriod}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Requirements */}
|
||||
<div className="mt-6">
|
||||
<h4 className="font-semibold text-gray-900 mb-3">
|
||||
Yêu cầu hồ sơ:
|
||||
</h4>
|
||||
<ul className="space-y-2">
|
||||
{country.requirements.map((req, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-start text-gray-700"
|
||||
>
|
||||
<span className="text-green-500 mr-2 mt-1">
|
||||
✓
|
||||
</span>
|
||||
{req}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-6 flex gap-4">
|
||||
<button className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors">
|
||||
Tư vấn ngay
|
||||
</button>
|
||||
<button className="bg-gray-100 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-200 transition-colors">
|
||||
Xem chi tiết
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contact CTA */}
|
||||
<div className="text-center mt-12 bg-blue-50 rounded-lg p-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
||||
Không Tìm Thấy Quốc Gia Bạn Cần?
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
Chúng tôi hỗ trợ visa cho hơn 50 quốc gia. Liên hệ để được tư vấn
|
||||
chi tiết
|
||||
</p>
|
||||
<button className="bg-blue-600 text-white px-8 py-3 rounded-lg hover:bg-blue-700 transition-colors">
|
||||
Liên hệ tư vấn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user