forked from UKSOURCE/hailearning.edu.vn
Initial commit
This commit is contained in:
33
app/about/about.json
Normal file
33
app/about/about.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"title": "Về Chúng Tôi",
|
||||
"subtitle": "Đối tác tin cậy cho dịch vụ visa và du lịch",
|
||||
"description": "Chúng tôi là công ty hàng đầu trong lĩnh vực tư vấn visa và dịch vụ du lịch, với hơn 10 năm kinh nghiệm phục vụ khách hàng.",
|
||||
"sections": [
|
||||
{
|
||||
"heading": "Sứ Mệnh",
|
||||
"content": "Mang đến cho khách hàng những trải nghiệm du lịch tuyệt vời và hỗ trợ thủ tục visa một cách nhanh chóng, chính xác."
|
||||
},
|
||||
{
|
||||
"heading": "Tầm Nhìn",
|
||||
"content": "Trở thành công ty dẫn đầu trong lĩnh vực dịch vụ visa và du lịch tại Việt Nam."
|
||||
},
|
||||
{
|
||||
"heading": "Giá Trị Cốt Lõi",
|
||||
"content": "Uy tín - Chất lượng - Tận tâm - Chuyên nghiệp"
|
||||
}
|
||||
],
|
||||
"stats": [
|
||||
{
|
||||
"number": "10000+",
|
||||
"label": "Khách hàng hài lòng"
|
||||
},
|
||||
{
|
||||
"number": "50+",
|
||||
"label": "Quốc gia hỗ trợ visa"
|
||||
},
|
||||
{
|
||||
"number": "99%",
|
||||
"label": "Tỷ lệ thành công"
|
||||
}
|
||||
]
|
||||
}
|
||||
49
app/about/page.tsx
Normal file
49
app/about/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import aboutData from "./about.json";
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
{aboutData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-6">{aboutData.subtitle}</p>
|
||||
<p className="text-lg text-gray-700 leading-relaxed">
|
||||
{aboutData.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Sections */}
|
||||
<div className="grid md:grid-cols-3 gap-8 mb-12">
|
||||
{aboutData.sections.map((section, index) => (
|
||||
<div key={index} className="bg-white p-6 rounded-lg shadow-md">
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-4">
|
||||
{section.heading}
|
||||
</h3>
|
||||
<p className="text-gray-700 leading-relaxed">{section.content}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="bg-blue-50 rounded-lg p-8">
|
||||
<h2 className="text-2xl font-bold text-center text-gray-900 mb-8">
|
||||
Thành Tích Của Chúng Tôi
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{aboutData.stats.map((stat, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<div className="text-3xl font-bold text-blue-600 mb-2">
|
||||
{stat.number}
|
||||
</div>
|
||||
<div className="text-gray-700">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
255
app/appointment/appointment.json
Normal file
255
app/appointment/appointment.json
Normal file
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"title": "Đặt Lịch Tư Vấn",
|
||||
"subtitle": "Nhận tư vấn miễn phí từ chuyên gia visa của chúng tôi",
|
||||
"hero": {
|
||||
"title": "Tư Vấn Visa Miễn Phí",
|
||||
"description": "Đặt lịch hẹn với chuyên gia để được tư vấn chi tiết về visa và dịch vụ phù hợp nhất cho bạn.",
|
||||
"benefits": [
|
||||
"Tư vấn 1-1 với chuyên gia",
|
||||
"Phân tích hồ sơ miễn phí",
|
||||
"Báo giá chi tiết",
|
||||
"Lộ trình xin visa rõ ràng"
|
||||
]
|
||||
},
|
||||
"appointmentTypes": [
|
||||
{
|
||||
"id": "consultation",
|
||||
"name": "Tư Vấn Tổng Quát",
|
||||
"duration": "30 phút",
|
||||
"price": "Miễn phí",
|
||||
"description": "Tư vấn về loại visa phù hợp, yêu cầu cơ bản và quy trình xin visa",
|
||||
"icon": "💬",
|
||||
"popular": true
|
||||
},
|
||||
{
|
||||
"id": "document-review",
|
||||
"name": "Kiểm Tra Hồ Sơ",
|
||||
"duration": "45 phút",
|
||||
"price": "500,000 VNĐ",
|
||||
"description": "Kiểm tra chi tiết hồ sơ hiện tại và đưa ra lời khuyên cải thiện",
|
||||
"icon": "📋",
|
||||
"popular": false
|
||||
},
|
||||
{
|
||||
"id": "interview-prep",
|
||||
"name": "Luyện Phỏng Vấn",
|
||||
"duration": "60 phút",
|
||||
"price": "800,000 VNĐ",
|
||||
"description": "Luyện tập kỹ năng phỏng vấn visa với chuyên gia có kinh nghiệm",
|
||||
"icon": "🎯",
|
||||
"popular": false
|
||||
},
|
||||
{
|
||||
"id": "premium",
|
||||
"name": "Tư Vấn Cao Cấp",
|
||||
"duration": "90 phút",
|
||||
"price": "1,200,000 VNĐ",
|
||||
"description": "Tư vấn toàn diện bao gồm chiến lược, hồ sơ và luyện phỏng vấn",
|
||||
"icon": "⭐",
|
||||
"popular": false
|
||||
}
|
||||
],
|
||||
"timeSlots": [
|
||||
{
|
||||
"time": "08:00",
|
||||
"label": "8:00 AM",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"time": "09:00",
|
||||
"label": "9:00 AM",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"time": "10:00",
|
||||
"label": "10:00 AM",
|
||||
"available": false
|
||||
},
|
||||
{
|
||||
"time": "11:00",
|
||||
"label": "11:00 AM",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"time": "13:00",
|
||||
"label": "1:00 PM",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"time": "14:00",
|
||||
"label": "2:00 PM",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"time": "15:00",
|
||||
"label": "3:00 PM",
|
||||
"available": false
|
||||
},
|
||||
{
|
||||
"time": "16:00",
|
||||
"label": "4:00 PM",
|
||||
"available": true
|
||||
},
|
||||
{
|
||||
"time": "17:00",
|
||||
"label": "5:00 PM",
|
||||
"available": true
|
||||
}
|
||||
],
|
||||
"consultants": [
|
||||
{
|
||||
"id": "nguyen-van-a",
|
||||
"name": "Nguyễn Văn A",
|
||||
"title": "Chuyên gia Visa Mỹ & Canada",
|
||||
"experience": "8 năm kinh nghiệm",
|
||||
"specialties": ["Visa Mỹ", "Visa Canada", "Visa du học"],
|
||||
"languages": ["Tiếng Việt", "English"],
|
||||
"rating": 4.9,
|
||||
"reviews": 156,
|
||||
"avatar": "/images/consultant-1.jpg",
|
||||
"description": "Chuyên gia hàng đầu về visa Bắc Mỹ với tỷ lệ thành công 98%"
|
||||
},
|
||||
{
|
||||
"id": "tran-thi-b",
|
||||
"name": "Trần Thị B",
|
||||
"title": "Chuyên gia Visa Châu Âu",
|
||||
"experience": "6 năm kinh nghiệm",
|
||||
"specialties": ["Visa Schengen", "Visa Anh", "Visa du lịch"],
|
||||
"languages": ["Tiếng Việt", "English", "Français"],
|
||||
"rating": 4.8,
|
||||
"reviews": 203,
|
||||
"avatar": "/images/consultant-2.jpg",
|
||||
"description": "Chuyên gia visa châu Âu với kinh nghiệm sống và làm việc tại Pháp"
|
||||
},
|
||||
{
|
||||
"id": "le-van-c",
|
||||
"name": "Lê Văn C",
|
||||
"title": "Chuyên gia Visa Châu Á",
|
||||
"experience": "5 năm kinh nghiệm",
|
||||
"specialties": ["Visa Nhật Bản", "Visa Hàn Quốc", "Visa Singapore"],
|
||||
"languages": ["Tiếng Việt", "日本語", "한국어"],
|
||||
"rating": 4.9,
|
||||
"reviews": 134,
|
||||
"avatar": "/images/consultant-3.jpg",
|
||||
"description": "Chuyên gia visa châu Á với khả năng giao tiếp đa ngôn ngữ"
|
||||
}
|
||||
],
|
||||
"formFields": [
|
||||
{
|
||||
"name": "fullName",
|
||||
"label": "Họ và tên",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "Nhập họ và tên đầy đủ"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"label": "Email",
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"placeholder": "example@email.com"
|
||||
},
|
||||
{
|
||||
"name": "phone",
|
||||
"label": "Số điện thoại",
|
||||
"type": "tel",
|
||||
"required": true,
|
||||
"placeholder": "+84 xxx xxx xxx"
|
||||
},
|
||||
{
|
||||
"name": "visaType",
|
||||
"label": "Loại visa quan tâm",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
"Visa du lịch",
|
||||
"Visa công tác",
|
||||
"Visa du học",
|
||||
"Visa thăm thân",
|
||||
"Visa lao động",
|
||||
"Chưa xác định"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "country",
|
||||
"label": "Quốc gia đích",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
"Mỹ",
|
||||
"Canada",
|
||||
"Schengen",
|
||||
"Anh",
|
||||
"Úc",
|
||||
"Nhật Bản",
|
||||
"Hàn Quốc",
|
||||
"Singapore",
|
||||
"Khác"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "travelDate",
|
||||
"label": "Dự kiến ngày đi",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"placeholder": ""
|
||||
},
|
||||
{
|
||||
"name": "previousVisa",
|
||||
"label": "Đã từng xin visa nước này?",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"options": [
|
||||
"Chưa bao giờ",
|
||||
"Đã xin và được duyệt",
|
||||
"Đã xin nhưng bị từ chối",
|
||||
"Đang trong quá trình xin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "notes",
|
||||
"label": "Ghi chú thêm",
|
||||
"type": "textarea",
|
||||
"required": false,
|
||||
"placeholder": "Mô tả thêm về tình huống của bạn..."
|
||||
}
|
||||
],
|
||||
"faqs": [
|
||||
{
|
||||
"question": "Tư vấn có thực sự miễn phí không?",
|
||||
"answer": "Có, buổi tư vấn tổng quát 30 phút đầu tiên hoàn toàn miễn phí. Bạn chỉ cần đặt lịch và tham gia."
|
||||
},
|
||||
{
|
||||
"question": "Tôi có thể hủy hoặc đổi lịch hẹn không?",
|
||||
"answer": "Có, bạn có thể hủy hoặc đổi lịch hẹn trước 24 giờ mà không mất phí. Liên hệ với chúng tôi để thay đổi."
|
||||
},
|
||||
{
|
||||
"question": "Buổi tư vấn diễn ra như thế nào?",
|
||||
"answer": "Buổi tư vấn có thể diễn ra trực tiếp tại văn phòng hoặc online qua video call tùy theo lựa chọn của bạn."
|
||||
},
|
||||
{
|
||||
"question": "Tôi cần chuẩn bị gì cho buổi tư vấn?",
|
||||
"answer": "Bạn nên chuẩn bị các câu hỏi cụ thể và nếu có, mang theo hộ chiếu và các giấy tờ liên quan để được tư vấn chính xác hơn."
|
||||
}
|
||||
],
|
||||
"offices": [
|
||||
{
|
||||
"name": "TP. Hồ Chí Minh",
|
||||
"address": "123 Nguyễn Huệ, Quận 1",
|
||||
"phone": "+84 28 1234 5678",
|
||||
"hours": "8:00 - 18:00 (T2-T6), 8:00 - 12:00 (T7)"
|
||||
},
|
||||
{
|
||||
"name": "Hà Nội",
|
||||
"address": "456 Hoàn Kiếm, Hà Nội",
|
||||
"phone": "+84 24 1234 5678",
|
||||
"hours": "8:00 - 18:00 (T2-T6), 8:00 - 12:00 (T7)"
|
||||
},
|
||||
{
|
||||
"name": "Đà Nẵng",
|
||||
"address": "789 Hải Châu, Đà Nẵng",
|
||||
"phone": "+84 236 1234 567",
|
||||
"hours": "8:00 - 18:00 (T2-T6), 8:00 - 12:00 (T7)"
|
||||
}
|
||||
]
|
||||
}
|
||||
501
app/appointment/page.tsx
Normal file
501
app/appointment/page.tsx
Normal file
@@ -0,0 +1,501 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import appointmentData from "./appointment.json";
|
||||
|
||||
export default function AppointmentPage() {
|
||||
const [selectedType, setSelectedType] = useState("consultation");
|
||||
const [selectedConsultant, setSelectedConsultant] = useState("");
|
||||
const [selectedDate, setSelectedDate] = useState("");
|
||||
const [selectedTime, setSelectedTime] = useState("");
|
||||
const [selectedOffice, setSelectedOffice] = useState("online");
|
||||
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
fullName: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
visaType: "",
|
||||
country: "",
|
||||
travelDate: "",
|
||||
previousVisa: "",
|
||||
notes: "",
|
||||
});
|
||||
|
||||
const handleInputChange = (
|
||||
e: React.ChangeEvent<
|
||||
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||
>,
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
console.log("Appointment booked:", {
|
||||
type: selectedType,
|
||||
consultant: selectedConsultant,
|
||||
date: selectedDate,
|
||||
time: selectedTime,
|
||||
office: selectedOffice,
|
||||
...formData,
|
||||
});
|
||||
alert(
|
||||
"Đặt lịch thành công! Chúng tôi sẽ liên hệ xác nhận trong thời gian sớm nhất.",
|
||||
);
|
||||
};
|
||||
|
||||
const toggleFaq = (index: number) => {
|
||||
setOpenFaq(openFaq === index ? null : index);
|
||||
};
|
||||
|
||||
const selectedAppointmentType = appointmentData.appointmentTypes.find(
|
||||
(type) => type.id === selectedType,
|
||||
);
|
||||
const selectedConsultantData = appointmentData.consultants.find(
|
||||
(c) => c.id === selectedConsultant,
|
||||
);
|
||||
|
||||
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">
|
||||
{appointmentData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8">
|
||||
{appointmentData.subtitle}
|
||||
</p>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="grid md:grid-cols-4 gap-6 max-w-4xl mx-auto">
|
||||
{appointmentData.hero.benefits.map((benefit, index) => (
|
||||
<div key={index} className="bg-blue-50 p-4 rounded-lg">
|
||||
<p className="text-blue-800 font-medium">{benefit}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-8">
|
||||
{/* Booking Form */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Đặt Lịch Hẹn
|
||||
</h2>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Appointment Type */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
Loại tư vấn <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{appointmentData.appointmentTypes.map((type) => (
|
||||
<div
|
||||
key={type.id}
|
||||
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
||||
selectedType === type.id
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
onClick={() => setSelectedType(type.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center">
|
||||
<span className="text-2xl mr-3">{type.icon}</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">{type.name}</h3>
|
||||
{type.popular && (
|
||||
<span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
|
||||
Phổ biến
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-bold text-blue-600">
|
||||
{type.price}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{type.duration}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-600 text-sm">
|
||||
{type.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Consultant Selection */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
Chọn chuyên gia
|
||||
</label>
|
||||
<div className="space-y-3">
|
||||
<div
|
||||
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
||||
selectedConsultant === ""
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
onClick={() => setSelectedConsultant("")}
|
||||
>
|
||||
<div className="font-medium">
|
||||
Để chúng tôi chọn chuyên gia phù hợp
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
Dựa trên loại visa và quốc gia bạn quan tâm
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{appointmentData.consultants.map((consultant) => (
|
||||
<div
|
||||
key={consultant.id}
|
||||
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
||||
selectedConsultant === consultant.id
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
onClick={() => setSelectedConsultant(consultant.id)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center mr-4">
|
||||
<span className="text-gray-600">👤</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-semibold">
|
||||
{consultant.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{consultant.title}
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<span className="mr-2">
|
||||
⭐ {consultant.rating}
|
||||
</span>
|
||||
<span className="mr-2">
|
||||
({consultant.reviews} đánh giá)
|
||||
</span>
|
||||
<span>{consultant.experience}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date & Time */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Ngày hẹn <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={selectedDate}
|
||||
onChange={(e) => setSelectedDate(e.target.value)}
|
||||
min={new Date().toISOString().split("T")[0]}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Giờ hẹn <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={selectedTime}
|
||||
onChange={(e) => setSelectedTime(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Chọn giờ</option>
|
||||
{appointmentData.timeSlots.map((slot) => (
|
||||
<option
|
||||
key={slot.time}
|
||||
value={slot.time}
|
||||
disabled={!slot.available}
|
||||
>
|
||||
{slot.label} {!slot.available && "(Đã đặt)"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Office Selection */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||
Hình thức tư vấn <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="space-y-3">
|
||||
<div
|
||||
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
||||
selectedOffice === "online"
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
onClick={() => setSelectedOffice("online")}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="text-2xl mr-3">💻</span>
|
||||
<div>
|
||||
<div className="font-medium">Tư vấn online</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
Video call qua Zoom/Google Meet
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{appointmentData.offices.map((office, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
||||
selectedOffice === office.name
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-gray-300"
|
||||
}`}
|
||||
onClick={() => setSelectedOffice(office.name)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="text-2xl mr-3">🏢</span>
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
Văn phòng {office.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{office.address}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{office.hours}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Personal Information */}
|
||||
<div className="border-t pt-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Thông Tin Cá Nhân
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{appointmentData.formFields.map((field) => (
|
||||
<div
|
||||
key={field.name}
|
||||
className={
|
||||
field.type === "textarea" ? "md:col-span-2" : ""
|
||||
}
|
||||
>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{field.label}
|
||||
{field.required && (
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
)}
|
||||
</label>
|
||||
|
||||
{field.type === "textarea" ? (
|
||||
<textarea
|
||||
name={field.name}
|
||||
value={
|
||||
formData[field.name as keyof typeof formData]
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
placeholder={field.placeholder}
|
||||
required={field.required}
|
||||
rows={4}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
) : field.type === "select" ? (
|
||||
<select
|
||||
name={field.name}
|
||||
value={
|
||||
formData[field.name as keyof typeof formData]
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
required={field.required}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">
|
||||
Chọn {field.label.toLowerCase()}
|
||||
</option>
|
||||
{field.options?.map((option, index) => (
|
||||
<option key={index} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type={field.type}
|
||||
name={field.name}
|
||||
value={
|
||||
formData[field.name as keyof typeof formData]
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
placeholder={field.placeholder}
|
||||
required={field.required}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg hover:bg-blue-700 transition-colors font-medium text-lg"
|
||||
>
|
||||
Đặt Lịch Hẹn
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
{/* Booking Summary */}
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Tóm Tắt Đặt Lịch
|
||||
</h3>
|
||||
|
||||
{selectedAppointmentType && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Loại tư vấn:</span>
|
||||
<span className="font-medium">
|
||||
{selectedAppointmentType.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Thời gian:</span>
|
||||
<span className="font-medium">
|
||||
{selectedAppointmentType.duration}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Phí:</span>
|
||||
<span className="font-medium text-blue-600">
|
||||
{selectedAppointmentType.price}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{selectedConsultantData && (
|
||||
<div className="border-t pt-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Chuyên gia:</span>
|
||||
<span className="font-medium">
|
||||
{selectedConsultantData.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedDate && selectedTime && (
|
||||
<div className="border-t pt-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Ngày giờ:</span>
|
||||
<span className="font-medium">
|
||||
{selectedDate} lúc{" "}
|
||||
{
|
||||
appointmentData.timeSlots.find(
|
||||
(s) => s.time === selectedTime,
|
||||
)?.label
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedOffice && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Hình thức:</span>
|
||||
<span className="font-medium">
|
||||
{selectedOffice === "online"
|
||||
? "Online"
|
||||
: `Văn phòng ${selectedOffice}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="bg-blue-50 rounded-lg p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Cần Hỗ Trợ?
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center">
|
||||
<span className="text-blue-600 mr-2">📞</span>
|
||||
<span>1900 1234</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-blue-600 mr-2">✉️</span>
|
||||
<span>info@visaservice.com</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-blue-600 mr-2">💬</span>
|
||||
<span>Chat trực tuyến 24/7</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FAQs */}
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8 text-center">
|
||||
Câu Hỏi Thường Gặp
|
||||
</h2>
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{appointmentData.faqs.map((faq, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white rounded-lg shadow-md overflow-hidden"
|
||||
>
|
||||
<button
|
||||
className="w-full p-6 text-left hover:bg-gray-50 transition-colors"
|
||||
onClick={() => toggleFaq(index)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-gray-900">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<span className="text-gray-400">
|
||||
{openFaq === index ? "−" : "+"}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{openFaq === index && (
|
||||
<div className="px-6 pb-6">
|
||||
<p className="text-gray-700">{faq.answer}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
93
app/blog/blog.json
Normal file
93
app/blog/blog.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"title": "Blog & Tin Tức",
|
||||
"subtitle": "Cập nhật thông tin mới nhất về visa và du lịch",
|
||||
"featured": {
|
||||
"id": "visa-schengen-2024",
|
||||
"title": "Hướng Dẫn Xin Visa Schengen 2024 - Cập Nhật Mới Nhất",
|
||||
"excerpt": "Thủ tục xin visa Schengen đã có những thay đổi quan trọng trong năm 2024. Cùng tìm hiểu chi tiết...",
|
||||
"image": "/images/schengen-visa.jpg",
|
||||
"date": "2024-01-15",
|
||||
"author": "Nguyễn Văn A",
|
||||
"category": "Visa",
|
||||
"readTime": "5 phút đọc"
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"name": "Tất cả",
|
||||
"slug": "all",
|
||||
"count": 24
|
||||
},
|
||||
{
|
||||
"name": "Visa",
|
||||
"slug": "visa",
|
||||
"count": 12
|
||||
},
|
||||
{
|
||||
"name": "Du lịch",
|
||||
"slug": "travel",
|
||||
"count": 8
|
||||
},
|
||||
{
|
||||
"name": "Thủ tục",
|
||||
"slug": "procedures",
|
||||
"count": 4
|
||||
}
|
||||
],
|
||||
"posts": [
|
||||
{
|
||||
"id": "visa-my-2024",
|
||||
"title": "Thay Đổi Mới Trong Thủ Tục Xin Visa Mỹ 2024",
|
||||
"excerpt": "Lãnh sự quán Mỹ đã công bố những thay đổi quan trọng trong quy trình xin visa...",
|
||||
"image": "/images/us-visa.jpg",
|
||||
"date": "2024-01-10",
|
||||
"author": "Trần Thị B",
|
||||
"category": "Visa",
|
||||
"readTime": "7 phút đọc",
|
||||
"tags": ["Visa Mỹ", "Thủ tục", "2024"]
|
||||
},
|
||||
{
|
||||
"id": "du-lich-nhat-ban",
|
||||
"title": "Top 10 Địa Điểm Du Lịch Nhật Bản Không Thể Bỏ Qua",
|
||||
"excerpt": "Khám phá những điểm đến tuyệt vời nhất tại đất nước mặt trời mọc...",
|
||||
"image": "/images/japan-travel.jpg",
|
||||
"date": "2024-01-08",
|
||||
"author": "Lê Văn C",
|
||||
"category": "Du lịch",
|
||||
"readTime": "6 phút đọc",
|
||||
"tags": ["Nhật Bản", "Du lịch", "Điểm đến"]
|
||||
},
|
||||
{
|
||||
"id": "visa-han-quoc-tips",
|
||||
"title": "Bí Quyết Xin Visa Hàn Quốc Thành Công 100%",
|
||||
"excerpt": "Những kinh nghiệm quý báu từ chuyên gia để tăng tỷ lệ thành công...",
|
||||
"image": "/images/korea-visa.jpg",
|
||||
"date": "2024-01-05",
|
||||
"author": "Phạm Thị D",
|
||||
"category": "Visa",
|
||||
"readTime": "8 phút đọc",
|
||||
"tags": ["Visa Hàn Quốc", "Kinh nghiệm", "Thành công"]
|
||||
},
|
||||
{
|
||||
"id": "du-lich-chau-au",
|
||||
"title": "Lịch Trình Du Lịch Châu Âu 14 Ngày Hoàn Hảo",
|
||||
"excerpt": "Khám phá 7 quốc gia châu Âu với lịch trình được thiết kế tối ưu...",
|
||||
"image": "/images/europe-travel.jpg",
|
||||
"date": "2024-01-03",
|
||||
"author": "Hoàng Văn E",
|
||||
"category": "Du lịch",
|
||||
"readTime": "10 phút đọc",
|
||||
"tags": ["Châu Âu", "Lịch trình", "14 ngày"]
|
||||
},
|
||||
{
|
||||
"id": "thu-tuc-ho-so",
|
||||
"title": "Checklist Hồ Sơ Xin Visa - Không Bỏ Sót Gì",
|
||||
"excerpt": "Danh sách chi tiết các giấy tờ cần thiết cho từng loại visa...",
|
||||
"image": "/images/documents.jpg",
|
||||
"date": "2024-01-01",
|
||||
"author": "Vũ Thị F",
|
||||
"category": "Thủ tục",
|
||||
"readTime": "4 phút đọc",
|
||||
"tags": ["Hồ sơ", "Checklist", "Visa"]
|
||||
}
|
||||
]
|
||||
}
|
||||
144
app/blog/page.tsx
Normal file
144
app/blog/page.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import blogData from "./blog.json";
|
||||
|
||||
export default function BlogPage() {
|
||||
const [selectedCategory, setSelectedCategory] = useState("all");
|
||||
|
||||
const filteredPosts =
|
||||
selectedCategory === "all"
|
||||
? blogData.posts
|
||||
: blogData.posts.filter(
|
||||
(post) => post.category.toLowerCase() === selectedCategory,
|
||||
);
|
||||
|
||||
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">
|
||||
{blogData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600">{blogData.subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* Featured Post */}
|
||||
<div className="mb-12">
|
||||
<div className="bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg p-8 text-white">
|
||||
<div className="flex items-center mb-4">
|
||||
<span className="bg-yellow-400 text-black px-3 py-1 rounded-full text-sm font-medium mr-4">
|
||||
Nổi bật
|
||||
</span>
|
||||
<span className="text-blue-100">
|
||||
{blogData.featured.category}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold mb-4">
|
||||
{blogData.featured.title}
|
||||
</h2>
|
||||
<p className="text-blue-100 mb-6 text-lg">
|
||||
{blogData.featured.excerpt}
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center text-blue-100">
|
||||
<span className="mr-4">👤 {blogData.featured.author}</span>
|
||||
<span className="mr-4">📅 {blogData.featured.date}</span>
|
||||
<span>⏱️ {blogData.featured.readTime}</span>
|
||||
</div>
|
||||
<button className="bg-white text-blue-600 px-6 py-2 rounded-lg hover:bg-blue-50 transition-colors">
|
||||
Đọc thêm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-wrap gap-4 justify-center">
|
||||
{blogData.categories.map((category) => (
|
||||
<button
|
||||
key={category.slug}
|
||||
onClick={() => setSelectedCategory(category.slug)}
|
||||
className={`px-6 py-2 rounded-full transition-colors ${
|
||||
selectedCategory === category.slug
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
{category.name} ({category.count})
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Blog Posts Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredPosts.map((post) => (
|
||||
<article
|
||||
key={post.id}
|
||||
className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"
|
||||
>
|
||||
{/* Post Image */}
|
||||
<div className="h-48 bg-gray-200 flex items-center justify-center">
|
||||
<span className="text-gray-400">📷 {post.category}</span>
|
||||
</div>
|
||||
|
||||
{/* Post Content */}
|
||||
<div className="p-6">
|
||||
{/* Category & Date */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm">
|
||||
{post.category}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">{post.date}</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3 line-clamp-2">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
{/* Excerpt */}
|
||||
<p className="text-gray-700 mb-4 line-clamp-3">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{post.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="bg-gray-100 text-gray-600 px-2 py-1 rounded text-xs"
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Meta Info */}
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<span>👤 {post.author}</span>
|
||||
<span>⏱️ {post.readTime}</span>
|
||||
</div>
|
||||
|
||||
{/* Read More Button */}
|
||||
<button className="w-full mt-4 bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors">
|
||||
Đọc thêm
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Load More */}
|
||||
<div className="text-center mt-12">
|
||||
<button className="bg-gray-100 text-gray-700 px-8 py-3 rounded-lg hover:bg-gray-200 transition-colors">
|
||||
Xem thêm bài viết
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
191
app/components/Footer.tsx
Normal file
191
app/components/Footer.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const quickLinks = [
|
||||
{ name: "Về chúng tôi", href: "/about" },
|
||||
{ name: "Dịch vụ", href: "/services" },
|
||||
{ name: "Bảng giá", href: "/pricing" },
|
||||
{ name: "Blog", href: "/blog" },
|
||||
{ name: "Liên hệ", href: "/contact" },
|
||||
];
|
||||
|
||||
const visaServices = [
|
||||
{ name: "Visa Mỹ", href: "/visa" },
|
||||
{ name: "Visa Schengen", href: "/visa" },
|
||||
{ name: "Visa Nhật Bản", href: "/visa" },
|
||||
{ name: "Visa Hàn Quốc", href: "/visa" },
|
||||
{ name: "Visa Canada", href: "/visa" },
|
||||
];
|
||||
|
||||
const countries = [
|
||||
{ name: "Châu Âu", href: "/countries" },
|
||||
{ name: "Bắc Mỹ", href: "/countries" },
|
||||
{ name: "Châu Á", href: "/countries" },
|
||||
{ name: "Châu Đại Dương", href: "/countries" },
|
||||
];
|
||||
|
||||
return (
|
||||
<footer className="bg-gray-900 text-white">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{/* Company Info */}
|
||||
<div>
|
||||
<div className="flex items-center space-x-2 mb-6">
|
||||
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-xl">V</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-xl">VisaService</div>
|
||||
<div className="text-sm text-gray-400">Dịch vụ visa uy tín</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Đối tác tin cậy cho dịch vụ visa và du lịch với hơn 10 năm kinh
|
||||
nghiệm. Cam kết mang đến dịch vụ chất lượng cao với tỷ lệ thành
|
||||
công 99%.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<a
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="sr-only">Facebook</span>
|
||||
📘
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="sr-only">Instagram</span>
|
||||
📷
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="sr-only">YouTube</span>
|
||||
📺
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="sr-only">LinkedIn</span>
|
||||
💼
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg mb-6">Liên Kết Nhanh</h3>
|
||||
<ul className="space-y-3">
|
||||
{quickLinks.map((link) => (
|
||||
<li key={link.name}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Visa Services */}
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg mb-6">Dịch Vụ Visa</h3>
|
||||
<ul className="space-y-3">
|
||||
{visaServices.map((service) => (
|
||||
<li key={service.name}>
|
||||
<Link
|
||||
href={service.href}
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
{service.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg mb-6">Thông Tin Liên Hệ</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start">
|
||||
<span className="text-blue-400 mr-3 mt-1">📍</span>
|
||||
<div>
|
||||
<p className="text-gray-400">
|
||||
123 Nguyễn Huệ, Quận 1<br />
|
||||
TP. Hồ Chí Minh
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-blue-400 mr-3">📞</span>
|
||||
<p className="text-gray-400">+84 28 1234 5678</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-blue-400 mr-3">✉️</span>
|
||||
<p className="text-gray-400">info@visaservice.com</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="text-blue-400 mr-3 mt-1">🕒</span>
|
||||
<div>
|
||||
<p className="text-gray-400">
|
||||
Thứ 2 - Thứ 6: 8:00 - 18:00
|
||||
<br />
|
||||
Thứ 7: 8:00 - 12:00
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Section */}
|
||||
<div className="border-t border-gray-800 mt-12 pt-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="text-gray-400 text-sm mb-4 md:mb-0">
|
||||
© {currentYear} VisaService. Tất cả quyền được bảo lưu.
|
||||
</div>
|
||||
<div className="flex space-x-6 text-sm">
|
||||
<Link
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
Chính sách bảo mật
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
Điều khoản sử dụng
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
Sitemap
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating Contact Button */}
|
||||
<div className="fixed bottom-6 right-6 z-50">
|
||||
<Link
|
||||
href="/appointment"
|
||||
className="bg-green-500 text-white p-4 rounded-full shadow-lg hover:bg-green-600 transition-colors flex items-center justify-center"
|
||||
>
|
||||
<span className="text-2xl">💬</span>
|
||||
</Link>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
114
app/components/Header.tsx
Normal file
114
app/components/Header.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Header() {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const navigation = [
|
||||
{ name: "Trang chủ", href: "/" },
|
||||
{ name: "Về chúng tôi", href: "/about" },
|
||||
{ name: "Dịch vụ", href: "/services" },
|
||||
{ name: "Visa", href: "/visa" },
|
||||
{ name: "Quốc gia", href: "/countries" },
|
||||
{ name: "Bảng giá", href: "/pricing" },
|
||||
{ name: "Blog", href: "/blog" },
|
||||
{ name: "Liên hệ", href: "/contact" },
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="bg-white shadow-lg sticky top-0 z-50">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-xl">V</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-xl text-gray-900">VisaService</div>
|
||||
<div className="text-xs text-gray-500">Dịch vụ visa uy tín</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden lg:flex items-center space-x-8">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="text-gray-700 hover:text-blue-600 transition-colors font-medium"
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="hidden lg:flex items-center space-x-4">
|
||||
<Link
|
||||
href="/appointment"
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors font-medium"
|
||||
>
|
||||
Đặt lịch tư vấn
|
||||
</Link>
|
||||
<div className="flex items-center text-gray-600">
|
||||
<span className="text-sm">📞</span>
|
||||
<span className="ml-1 font-medium">1900 1234</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
className="lg:hidden p-2"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
>
|
||||
<div className="w-6 h-6 flex flex-col justify-center items-center">
|
||||
<span
|
||||
className={`bg-gray-600 block transition-all duration-300 ease-out h-0.5 w-6 rounded-sm ${isMenuOpen ? "rotate-45 translate-y-1" : "-translate-y-0.5"}`}
|
||||
></span>
|
||||
<span
|
||||
className={`bg-gray-600 block transition-all duration-300 ease-out h-0.5 w-6 rounded-sm my-0.5 ${isMenuOpen ? "opacity-0" : "opacity-100"}`}
|
||||
></span>
|
||||
<span
|
||||
className={`bg-gray-600 block transition-all duration-300 ease-out h-0.5 w-6 rounded-sm ${isMenuOpen ? "-rotate-45 -translate-y-1" : "translate-y-0.5"}`}
|
||||
></span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{isMenuOpen && (
|
||||
<div className="lg:hidden border-t border-gray-200">
|
||||
<div className="py-4 space-y-4">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="block text-gray-700 hover:text-blue-600 transition-colors font-medium"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<Link
|
||||
href="/appointment"
|
||||
className="block bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors font-medium text-center mb-4"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
Đặt lịch tư vấn
|
||||
</Link>
|
||||
<div className="flex items-center justify-center text-gray-600">
|
||||
<span className="text-sm">📞</span>
|
||||
<span className="ml-1 font-medium">1900 1234</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
89
app/contact/contact.json
Normal file
89
app/contact/contact.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"title": "Liên Hệ Với Chúng Tôi",
|
||||
"subtitle": "Chúng tôi luôn sẵn sàng hỗ trợ bạn 24/7",
|
||||
"contactInfo": {
|
||||
"address": {
|
||||
"label": "Địa chỉ",
|
||||
"value": "123 Nguyễn Huệ, Quận 1, TP.HCM",
|
||||
"icon": "📍"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Điện thoại",
|
||||
"value": "+84 28 1234 5678",
|
||||
"icon": "📞"
|
||||
},
|
||||
"email": {
|
||||
"label": "Email",
|
||||
"value": "info@visaservice.com",
|
||||
"icon": "✉️"
|
||||
},
|
||||
"hours": {
|
||||
"label": "Giờ làm việc",
|
||||
"value": "Thứ 2 - Thứ 6: 8:00 - 18:00\nThứ 7: 8:00 - 12:00",
|
||||
"icon": "🕒"
|
||||
}
|
||||
},
|
||||
"offices": [
|
||||
{
|
||||
"name": "Văn Phòng TP.HCM",
|
||||
"address": "123 Nguyễn Huệ, Quận 1, TP.HCM",
|
||||
"phone": "+84 28 1234 5678",
|
||||
"email": "hcm@visaservice.com"
|
||||
},
|
||||
{
|
||||
"name": "Văn Phòng Hà Nội",
|
||||
"address": "456 Hoàn Kiếm, Hà Nội",
|
||||
"phone": "+84 24 1234 5678",
|
||||
"email": "hanoi@visaservice.com"
|
||||
},
|
||||
{
|
||||
"name": "Văn Phòng Đà Nẵng",
|
||||
"address": "789 Hải Châu, Đà Nẵng",
|
||||
"phone": "+84 236 1234 567",
|
||||
"email": "danang@visaservice.com"
|
||||
}
|
||||
],
|
||||
"formFields": [
|
||||
{
|
||||
"name": "fullName",
|
||||
"label": "Họ và tên",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "Nhập họ và tên của bạn"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"label": "Email",
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"placeholder": "example@email.com"
|
||||
},
|
||||
{
|
||||
"name": "phone",
|
||||
"label": "Số điện thoại",
|
||||
"type": "tel",
|
||||
"required": true,
|
||||
"placeholder": "+84 xxx xxx xxx"
|
||||
},
|
||||
{
|
||||
"name": "service",
|
||||
"label": "Dịch vụ quan tâm",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
"Tư vấn visa",
|
||||
"Chuẩn bị hồ sơ",
|
||||
"Đặt vé máy bay",
|
||||
"Tour trọn gói",
|
||||
"Khác"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"label": "Tin nhắn",
|
||||
"type": "textarea",
|
||||
"required": false,
|
||||
"placeholder": "Mô tả chi tiết nhu cầu của bạn..."
|
||||
}
|
||||
]
|
||||
}
|
||||
157
app/contact/page.tsx
Normal file
157
app/contact/page.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import contactData from "./contact.json";
|
||||
|
||||
export default function ContactPage() {
|
||||
const [formData, setFormData] = useState({
|
||||
fullName: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
service: "",
|
||||
message: "",
|
||||
});
|
||||
|
||||
const handleInputChange = (
|
||||
e: React.ChangeEvent<
|
||||
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||
>,
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
console.log("Form submitted:", formData);
|
||||
// Handle form submission here
|
||||
alert(
|
||||
"Cảm ơn bạn đã liên hệ! Chúng tôi sẽ phản hồi trong thời gian sớm nhất.",
|
||||
);
|
||||
};
|
||||
|
||||
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">
|
||||
{contactData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600">{contactData.subtitle}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-12">
|
||||
{/* Contact Information */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Thông Tin Liên Hệ
|
||||
</h2>
|
||||
|
||||
{/* Contact Details */}
|
||||
<div className="space-y-6 mb-8">
|
||||
{Object.entries(contactData.contactInfo).map(([key, info]) => (
|
||||
<div key={key} className="flex items-start">
|
||||
<span className="text-2xl mr-4">{info.icon}</span>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">
|
||||
{info.label}
|
||||
</h3>
|
||||
<p className="text-gray-700 whitespace-pre-line">
|
||||
{info.value}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Offices */}
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">Văn Phòng</h3>
|
||||
<div className="space-y-4">
|
||||
{contactData.offices.map((office, index) => (
|
||||
<div key={index} className="bg-gray-50 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">
|
||||
{office.name}
|
||||
</h4>
|
||||
<p className="text-gray-700 text-sm mb-1">
|
||||
📍 {office.address}
|
||||
</p>
|
||||
<p className="text-gray-700 text-sm mb-1">
|
||||
📞 {office.phone}
|
||||
</p>
|
||||
<p className="text-gray-700 text-sm">✉️ {office.email}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Gửi Tin Nhắn
|
||||
</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{contactData.formFields.map((field) => (
|
||||
<div key={field.name}>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{field.label}
|
||||
{field.required && (
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
)}
|
||||
</label>
|
||||
|
||||
{field.type === "textarea" ? (
|
||||
<textarea
|
||||
name={field.name}
|
||||
value={formData[field.name as keyof typeof formData]}
|
||||
onChange={handleInputChange}
|
||||
placeholder={field.placeholder}
|
||||
required={field.required}
|
||||
rows={4}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
) : field.type === "select" ? (
|
||||
<select
|
||||
name={field.name}
|
||||
value={formData[field.name as keyof typeof formData]}
|
||||
onChange={handleInputChange}
|
||||
required={field.required}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Chọn dịch vụ</option>
|
||||
{field.options?.map((option, index) => (
|
||||
<option key={index} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type={field.type}
|
||||
name={field.name}
|
||||
value={formData[field.name as keyof typeof formData]}
|
||||
onChange={handleInputChange}
|
||||
placeholder={field.placeholder}
|
||||
required={field.required}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg hover:bg-blue-700 transition-colors font-medium"
|
||||
>
|
||||
Gửi Tin Nhắn
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
11
app/globals.css
Normal file
11
app/globals.css
Normal file
@@ -0,0 +1,11 @@
|
||||
@charset "UTF-8";
|
||||
@import "tailwindcss";
|
||||
:root {
|
||||
--body: #fff;
|
||||
--font-primary: var(--font-inter);
|
||||
--font-heading: var(--font-space-grotesk);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-primary);
|
||||
}
|
||||
145
app/layout.tsx
Normal file
145
app/layout.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import type { Metadata } from "next";
|
||||
import Script from "next/script";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Header from "./components/Header";
|
||||
import Footer from "./components/Footer";
|
||||
import { Inter, Space_Grotesk } from "next/font/google";
|
||||
const ASSET_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
});
|
||||
|
||||
const spaceGrotesk = Space_Grotesk({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-space-grotesk",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "VisaService - Dịch vụ visa uy tín",
|
||||
description:
|
||||
"Dịch vụ visa chuyên nghiệp với tỷ lệ thành công 99%. Hỗ trợ visa cho hơn 50 quốc gia.",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
//
|
||||
return (
|
||||
<html lang="en" className={`${inter.variable} ${spaceGrotesk.variable}`}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
// <html lang="vi">
|
||||
// <head>
|
||||
// {/* Font Awesome 6.4.0 from CDN - for reliable icon support */}
|
||||
// <link rel="stylesheet" href="../public/assets/css" />
|
||||
// {/* Bootstrap min.css */}
|
||||
// <link
|
||||
// rel="stylesheet"
|
||||
// href={`${ASSET_URL}/assets/css/bootstrap.min.css`}
|
||||
// />
|
||||
// {/* Animate.css */}
|
||||
// <link rel="stylesheet" href={`${ASSET_URL}/assets/css/animate.css`} />
|
||||
|
||||
// {/* Magnific Popup.css */}
|
||||
// <link
|
||||
// rel="stylesheet"
|
||||
// href={`${ASSET_URL}/assets/css/magnific-popup.css`}
|
||||
// />
|
||||
// {/* MeanMenu.css */}
|
||||
// <link rel="stylesheet" href={`${ASSET_URL}/assets/css/meanmenu.css`} />
|
||||
// {/* Odometer.css */}
|
||||
// <link rel="stylesheet" href={`${ASSET_URL}/assets/css/odometer.css`} />
|
||||
// {/* Swiper Bundle.css */}
|
||||
// <link
|
||||
// rel="stylesheet"
|
||||
// href={`${ASSET_URL}/assets/css/swiper-bundle.min.css`}
|
||||
// />
|
||||
// {/* Nice Select.css */}
|
||||
// <link
|
||||
// rel="stylesheet"
|
||||
// href={`${ASSET_URL}/assets/css/nice-select.css`}
|
||||
// />
|
||||
// {/* Main.css */}
|
||||
// <link rel="stylesheet" href={`${ASSET_URL}/assets/css/main.css`} />
|
||||
// </head>
|
||||
// <body
|
||||
// className={`${geistSans.variable} ${geistMono.variable} antialiased bg-gray-50`}
|
||||
// >
|
||||
// <Header />
|
||||
// <main className="min-h-screen">{children}</main>
|
||||
// <Footer />
|
||||
|
||||
// {/* Scripts */}
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/jquery-3.7.1.min.js`}
|
||||
// strategy="beforeInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/viewport.jquery.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/bootstrap.bundle.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/jquery.nice-select.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/jquery.waypoints.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/odometer.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/swiper-bundle.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/jquery.meanmenu.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/jquery.magnific-popup.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/wow.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/circle-progress.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/gsap.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/lenis.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/ScrollTrigger.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/SplitText.min.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// <Script
|
||||
// src={`${ASSET_URL}/assets/js/main.js`}
|
||||
// strategy="afterInteractive"
|
||||
// />
|
||||
// </body>
|
||||
// </html>
|
||||
// );
|
||||
65
app/page.tsx
Normal file
65
app/page.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
188
app/pricing/page.tsx
Normal file
188
app/pricing/page.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import pricingData from "./pricing.json";
|
||||
|
||||
export default function PricingPage() {
|
||||
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">
|
||||
{pricingData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-4">{pricingData.subtitle}</p>
|
||||
<p className="text-sm text-gray-500 bg-yellow-50 p-3 rounded-lg inline-block">
|
||||
💡 {pricingData.note}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Pricing Packages */}
|
||||
<div className="grid md:grid-cols-3 gap-8 mb-16">
|
||||
{pricingData.packages.map((pkg) => (
|
||||
<div
|
||||
key={pkg.id}
|
||||
className={`relative bg-white rounded-lg shadow-lg p-6 ${
|
||||
pkg.popular ? "ring-2 ring-blue-500 transform scale-105" : ""
|
||||
}`}
|
||||
>
|
||||
{/* Popular Badge */}
|
||||
{pkg.popular && (
|
||||
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
|
||||
<span className="bg-blue-500 text-white px-4 py-1 rounded-full text-sm font-medium">
|
||||
Phổ biến nhất
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Package Header */}
|
||||
<div className="text-center mb-6">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{pkg.name}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">{pkg.description}</p>
|
||||
<div className="text-3xl font-bold text-blue-600">
|
||||
{parseInt(pkg.price).toLocaleString()} {pkg.currency}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="mb-6">
|
||||
<h4 className="font-semibold text-gray-900 mb-3">Bao gồm:</h4>
|
||||
<ul className="space-y-2">
|
||||
{pkg.features.map((feature, index) => (
|
||||
<li key={index} className="flex items-center text-gray-700">
|
||||
<span className="text-green-500 mr-2">✓</span>
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Limitations */}
|
||||
{pkg.limitations.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<h4 className="font-semibold text-gray-900 mb-3">Lưu ý:</h4>
|
||||
<ul className="space-y-2">
|
||||
{pkg.limitations.map((limitation, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center text-gray-600 text-sm"
|
||||
>
|
||||
<span className="text-orange-500 mr-2">⚠</span>
|
||||
{limitation}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CTA Button */}
|
||||
<button
|
||||
className={`w-full py-3 px-6 rounded-lg font-medium transition-colors ${
|
||||
pkg.popular
|
||||
? "bg-blue-600 text-white hover:bg-blue-700"
|
||||
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
|
||||
}`}
|
||||
>
|
||||
Chọn gói này
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Additional Services */}
|
||||
<div className="mb-16">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8 text-center">
|
||||
Dịch Vụ Bổ Sung
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{pricingData.additionalServices.map((service, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white rounded-lg shadow-md p-6 text-center"
|
||||
>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">
|
||||
{service.name}
|
||||
</h3>
|
||||
<div className="text-2xl font-bold text-blue-600 mb-2">
|
||||
{parseInt(service.price).toLocaleString()} VNĐ
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-3">
|
||||
/{service.unit}
|
||||
</div>
|
||||
<p className="text-gray-700 text-sm">{service.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Country Pricing */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8 text-center">
|
||||
Phí Visa Theo Quốc Gia
|
||||
</h2>
|
||||
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Quốc gia
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Phí visa
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Thời gian xử lý
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Tỷ lệ thành công
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{pricingData.countries.map((country, index) => (
|
||||
<tr key={index} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{country.name}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{parseInt(country.visaFee).toLocaleString()} VNĐ
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{country.processingTime}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
{country.successRate}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</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">
|
||||
Cần Tư Vấn Chi Tiết?
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
Liên hệ với chúng tôi để được tư vấn miễn phí và nhận báo giá phù
|
||||
hợp nhất
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<button className="bg-blue-600 text-white px-8 py-3 rounded-lg hover:bg-blue-700 transition-colors">
|
||||
Tư vấn miễn phí
|
||||
</button>
|
||||
<button className="bg-white text-blue-600 border border-blue-600 px-8 py-3 rounded-lg hover:bg-blue-50 transition-colors">
|
||||
Gọi ngay: 1900 1234
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
128
app/pricing/pricing.json
Normal file
128
app/pricing/pricing.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"title": "Bảng Giá Dịch Vụ",
|
||||
"subtitle": "Giá cả minh bạch, không phí ẩn",
|
||||
"note": "Giá có thể thay đổi tùy theo từng trường hợp cụ thể. Liên hệ để được tư vấn chi tiết.",
|
||||
"packages": [
|
||||
{
|
||||
"id": "basic",
|
||||
"name": "Gói Cơ Bản",
|
||||
"description": "Phù hợp cho khách hàng lần đầu xin visa",
|
||||
"price": "1,500,000",
|
||||
"currency": "VNĐ",
|
||||
"popular": false,
|
||||
"features": [
|
||||
"Tư vấn miễn phí",
|
||||
"Kiểm tra hồ sơ cơ bản",
|
||||
"Hướng dẫn điền form",
|
||||
"Hỗ trợ qua email",
|
||||
"Bảo hành 30 ngày"
|
||||
],
|
||||
"limitations": [
|
||||
"Không bao gồm phí lãnh sự",
|
||||
"Không dịch thuật",
|
||||
"Hỗ trợ trong giờ hành chính"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "premium",
|
||||
"name": "Gói Cao Cấp",
|
||||
"description": "Dịch vụ toàn diện với nhiều ưu đãi",
|
||||
"price": "3,500,000",
|
||||
"currency": "VNĐ",
|
||||
"popular": true,
|
||||
"features": [
|
||||
"Tất cả tính năng gói Cơ Bản",
|
||||
"Dịch thuật chuyên nghiệp",
|
||||
"Kiểm tra hồ sơ chi tiết",
|
||||
"Hỗ trợ 24/7",
|
||||
"Bảo hành 90 ngày",
|
||||
"Tư vấn trực tiếp",
|
||||
"Hỗ trợ booking phỏng vấn"
|
||||
],
|
||||
"limitations": ["Không bao gồm phí lãnh sự"]
|
||||
},
|
||||
{
|
||||
"id": "vip",
|
||||
"name": "Gói VIP",
|
||||
"description": "Dịch vụ cao cấp nhất với cam kết thành công",
|
||||
"price": "6,500,000",
|
||||
"currency": "VNĐ",
|
||||
"popular": false,
|
||||
"features": [
|
||||
"Tất cả tính năng gói Cao Cấp",
|
||||
"Cam kết thành công 99%",
|
||||
"Hoàn tiền nếu bị từ chối",
|
||||
"Tư vấn 1-1 với chuyên gia",
|
||||
"Ưu tiên xử lý hồ sơ",
|
||||
"Bảo hành 1 năm",
|
||||
"Hỗ trợ sau khi có visa",
|
||||
"Tặng bảo hiểm du lịch"
|
||||
],
|
||||
"limitations": []
|
||||
}
|
||||
],
|
||||
"additionalServices": [
|
||||
{
|
||||
"name": "Dịch thuật công chứng",
|
||||
"price": "200,000",
|
||||
"unit": "trang",
|
||||
"description": "Dịch thuật và công chứng các loại giấy tờ"
|
||||
},
|
||||
{
|
||||
"name": "Chụp ảnh visa",
|
||||
"price": "100,000",
|
||||
"unit": "bộ",
|
||||
"description": "Chụp ảnh theo tiêu chuẩn từng quốc gia"
|
||||
},
|
||||
{
|
||||
"name": "Đặt lịch phỏng vấn",
|
||||
"price": "500,000",
|
||||
"unit": "lần",
|
||||
"description": "Hỗ trợ đặt lịch phỏng vấn tại lãnh sự quán"
|
||||
},
|
||||
{
|
||||
"name": "Tư vấn phỏng vấn",
|
||||
"price": "800,000",
|
||||
"unit": "buổi",
|
||||
"description": "Luyện tập và tư vấn kỹ năng phỏng vấn"
|
||||
}
|
||||
],
|
||||
"countries": [
|
||||
{
|
||||
"name": "Mỹ",
|
||||
"visaFee": "4,600,000",
|
||||
"processingTime": "3-5 tuần",
|
||||
"successRate": "95%"
|
||||
},
|
||||
{
|
||||
"name": "Schengen",
|
||||
"visaFee": "2,200,000",
|
||||
"processingTime": "2-3 tuần",
|
||||
"successRate": "98%"
|
||||
},
|
||||
{
|
||||
"name": "Anh",
|
||||
"visaFee": "3,800,000",
|
||||
"processingTime": "3-4 tuần",
|
||||
"successRate": "96%"
|
||||
},
|
||||
{
|
||||
"name": "Canada",
|
||||
"visaFee": "2,800,000",
|
||||
"processingTime": "4-6 tuần",
|
||||
"successRate": "94%"
|
||||
},
|
||||
{
|
||||
"name": "Úc",
|
||||
"visaFee": "4,200,000",
|
||||
"processingTime": "2-4 tuần",
|
||||
"successRate": "97%"
|
||||
},
|
||||
{
|
||||
"name": "Nhật Bản",
|
||||
"visaFee": "1,200,000",
|
||||
"processingTime": "1-2 tuần",
|
||||
"successRate": "99%"
|
||||
}
|
||||
]
|
||||
}
|
||||
73
app/services/page.tsx
Normal file
73
app/services/page.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import servicesData from "./services.json";
|
||||
|
||||
export default function ServicesPage() {
|
||||
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">
|
||||
{servicesData.title}
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600">{servicesData.subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* Services Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-2 gap-8">
|
||||
{servicesData.services.map((service) => (
|
||||
<div
|
||||
key={service.id}
|
||||
className="bg-white rounded-lg shadow-lg p-6 hover:shadow-xl transition-shadow"
|
||||
>
|
||||
{/* Service Header */}
|
||||
<div className="flex items-center mb-4">
|
||||
<span className="text-4xl mr-4">{service.icon}</span>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
{service.name}
|
||||
</h3>
|
||||
<p className="text-blue-600 font-medium">{service.price}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-700 mb-6">{service.description}</p>
|
||||
|
||||
{/* Features */}
|
||||
<div className="mb-6">
|
||||
<h4 className="font-semibold text-gray-900 mb-3">Tính năng:</h4>
|
||||
<ul className="space-y-2">
|
||||
{service.features.map((feature, index) => (
|
||||
<li key={index} className="flex items-center text-gray-700">
|
||||
<span className="text-green-500 mr-2">✓</span>
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<button className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors">
|
||||
Tìm Hiểu Thêm
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contact CTA */}
|
||||
<div className="text-center mt-12 bg-gray-50 rounded-lg p-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
||||
Cần Tư Vấn Thêm?
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
Liên hệ với chúng tôi để được tư vấn miễn phí và nhận báo giá chi
|
||||
tiết
|
||||
</p>
|
||||
<button className="bg-green-600 text-white px-8 py-3 rounded-lg hover:bg-green-700 transition-colors">
|
||||
Liên Hệ Ngay
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
app/services/services.json
Normal file
58
app/services/services.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"title": "Dịch Vụ Của Chúng Tôi",
|
||||
"subtitle": "Giải pháp toàn diện cho nhu cầu visa và du lịch",
|
||||
"services": [
|
||||
{
|
||||
"id": "visa-consulting",
|
||||
"name": "Tư Vấn Visa",
|
||||
"description": "Tư vấn chuyên sâu về thủ tục visa cho hơn 50 quốc gia",
|
||||
"icon": "🛂",
|
||||
"features": [
|
||||
"Tư vấn miễn phí",
|
||||
"Hỗ trợ 24/7",
|
||||
"Đảm bảo thành công",
|
||||
"Thủ tục nhanh chóng"
|
||||
],
|
||||
"price": "Từ 500,000 VNĐ"
|
||||
},
|
||||
{
|
||||
"id": "document-preparation",
|
||||
"name": "Chuẩn Bị Hồ Sơ",
|
||||
"description": "Hỗ trợ chuẩn bị và kiểm tra hồ sơ visa chính xác",
|
||||
"icon": "📋",
|
||||
"features": [
|
||||
"Kiểm tra hồ sơ kỹ lưỡng",
|
||||
"Hướng dẫn chi tiết",
|
||||
"Dịch thuật chuyên nghiệp",
|
||||
"Bảo mật thông tin"
|
||||
],
|
||||
"price": "Từ 300,000 VNĐ"
|
||||
},
|
||||
{
|
||||
"id": "travel-booking",
|
||||
"name": "Đặt Vé & Khách Sạn",
|
||||
"description": "Dịch vụ đặt vé máy bay và khách sạn với giá ưu đãi",
|
||||
"icon": "✈️",
|
||||
"features": [
|
||||
"Giá cạnh tranh",
|
||||
"Đặt chỗ linh hoạt",
|
||||
"Hỗ trợ thay đổi",
|
||||
"Bảo hiểm du lịch"
|
||||
],
|
||||
"price": "Phí dịch vụ 2%"
|
||||
},
|
||||
{
|
||||
"id": "tour-packages",
|
||||
"name": "Tour Trọn Gói",
|
||||
"description": "Các gói tour du lịch được thiết kế chuyên nghiệp",
|
||||
"icon": "🌍",
|
||||
"features": [
|
||||
"Lịch trình linh hoạt",
|
||||
"Hướng dẫn viên chuyên nghiệp",
|
||||
"Bao gồm visa",
|
||||
"Bảo hiểm toàn diện"
|
||||
],
|
||||
"price": "Từ 15,000,000 VNĐ"
|
||||
}
|
||||
]
|
||||
}
|
||||
416
app/visa/[slug]/Countrydetails.tsx
Normal file
416
app/visa/[slug]/Countrydetails.tsx
Normal file
@@ -0,0 +1,416 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import visaData from "../visa.json";
|
||||
|
||||
const ASSET_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
||||
|
||||
interface CountryDetailsClientProps {
|
||||
country: {
|
||||
id: number;
|
||||
name: string;
|
||||
icon: string;
|
||||
services: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export default function CountryDetailsClient({
|
||||
country,
|
||||
}: CountryDetailsClientProps) {
|
||||
const [showBackToTop, setShowBackToTop] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
// Get detailed data from visa.json
|
||||
const countryData = visaData.visaSystem.detailedView.activeCountry;
|
||||
const relatedCountries =
|
||||
visaData.visaSystem.detailedView.relatedCountries.map((c: any) => ({
|
||||
...c,
|
||||
icon: `${ASSET_URL}/${c.icon}`,
|
||||
}));
|
||||
const contactInfo = visaData.visaSystem.contactInfo;
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setShowBackToTop(window.scrollY > 100);
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const scrollToTop = () => window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Back to Top */}
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className={`fixed bottom-8 right-8 w-12 h-12 rounded-full bg-white shadow-lg flex items-center justify-center cursor-pointer transition-all z-40 ${
|
||||
showBackToTop ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 text-blue-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M7 11l5-5m0 0l5 5m-5-5v12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/* Header Top */}
|
||||
<div className="bg-gray-900 text-white py-3 hidden lg:block">
|
||||
<div className="max-w-7xl mx-auto px-8 flex justify-between items-center text-sm">
|
||||
<ul className="flex gap-8">
|
||||
<li>
|
||||
<a href="tel:+093783575222">+09 378 357 5222</a>
|
||||
</li>
|
||||
<li>69 Street, 5th Avenue LA, United States</li>
|
||||
<li>
|
||||
<a href="mailto:info@example.com">info@example.com</a>
|
||||
</li>
|
||||
</ul>
|
||||
<select className="bg-gray-900 text-white border-0 outline-none cursor-pointer">
|
||||
<option>English</option>
|
||||
<option>Bangla</option>
|
||||
<option>Hindi</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-20 bg-white shadow-md">
|
||||
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-4 flex justify-between items-center">
|
||||
<Link href="/" className="flex-shrink-0">
|
||||
<img
|
||||
src={`${ASSET_URL}/assets/img/logo/black-logo.svg`}
|
||||
alt="logo"
|
||||
className="h-10"
|
||||
/>
|
||||
</Link>
|
||||
<nav className="hidden lg:flex gap-8">
|
||||
<Link href="/" className="text-gray-700 hover:text-blue-600">
|
||||
Home
|
||||
</Link>
|
||||
<Link href="/about" className="text-gray-700 hover:text-blue-600">
|
||||
About Us
|
||||
</Link>
|
||||
<Link
|
||||
href="/country-list"
|
||||
className="text-gray-700 hover:text-blue-600"
|
||||
>
|
||||
VISA
|
||||
</Link>
|
||||
<Link href="/contact" className="text-gray-700 hover:text-blue-600">
|
||||
Contact Us
|
||||
</Link>
|
||||
</nav>
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="lg:hidden text-2xl"
|
||||
>
|
||||
☰
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="fixed inset-0 z-30 lg:hidden">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
></div>
|
||||
<div className="absolute left-0 top-0 w-80 h-full bg-white p-6 overflow-y-auto">
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="float-right text-2xl"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<nav className="space-y-3 mt-8">
|
||||
<Link href="/" className="block py-2">
|
||||
Home
|
||||
</Link>
|
||||
<Link href="/about" className="block py-2">
|
||||
About Us
|
||||
</Link>
|
||||
<Link href="/country-list" className="block py-2">
|
||||
Visa
|
||||
</Link>
|
||||
<Link href="/contact" className="block py-2">
|
||||
Contact Us
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<section
|
||||
className="py-20 px-4 lg:px-8 bg-cover relative"
|
||||
style={{
|
||||
backgroundImage: `url('${ASSET_URL}/${countryData.mainImage}')`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/30"></div>
|
||||
<div className="max-w-7xl mx-auto relative z-10 text-center text-white">
|
||||
<h1 className="text-5xl font-bold mb-6">{countryData.title}</h1>
|
||||
<ul className="flex justify-center gap-4">
|
||||
<li>
|
||||
<Link href="/">Home</Link>
|
||||
</li>
|
||||
<li>›</li>
|
||||
<li>{countryData.name}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{/* Country Details */}
|
||||
<section className="py-20 px-4 lg:px-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Main Content */}
|
||||
<div className="lg:col-span-2">
|
||||
<img
|
||||
src={`${ASSET_URL}/${countryData.mainImage}`}
|
||||
alt={countryData.name}
|
||||
className="w-full rounded-lg mb-8"
|
||||
/>
|
||||
|
||||
<h2 className="text-4xl font-bold mb-4">{countryData.name}</h2>
|
||||
<p className="text-gray-700 mb-4">{countryData.description}</p>
|
||||
<p className="text-gray-700 mb-4">{countryData.additionalInfo}</p>
|
||||
<h5 className="text-xl font-semibold mb-6">
|
||||
{countryData.tagline}
|
||||
</h5>
|
||||
|
||||
{/* Visa Types */}
|
||||
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||
{countryData.visaTypes.map((typeGroup: any, idx: number) => (
|
||||
<React.Fragment key={idx}>
|
||||
{typeGroup.items.map((item: any, itemIdx: number) => (
|
||||
<div
|
||||
key={itemIdx}
|
||||
className="p-6 border-l-4 border-blue-600"
|
||||
>
|
||||
<h5 className="font-bold mb-2">{item.title}</h5>
|
||||
<p className="text-sm text-gray-600">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Visa Process */}
|
||||
<h3 className="text-2xl font-bold mb-6">USA Visa Process</h3>
|
||||
<ul className="space-y-4 mb-8">
|
||||
{countryData.visaProcess.steps.map((process: any) => (
|
||||
<li key={process.number} className="flex gap-4">
|
||||
<span className="font-bold text-blue-600 flex-shrink-0">
|
||||
{process.number}.
|
||||
</span>
|
||||
<span>
|
||||
<strong>{process.title}</strong> – {process.description}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Gallery */}
|
||||
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||
{countryData.gallery.map((image: string, idx: number) => (
|
||||
<img
|
||||
key={idx}
|
||||
src={`${ASSET_URL}/${image}`}
|
||||
alt={`${countryData.name} gallery`}
|
||||
className="rounded-lg w-full"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Visa Types List */}
|
||||
<h3 className="text-2xl font-bold mb-6">Types of Visas</h3>
|
||||
{countryData.visaCategories.steps.map(
|
||||
(subGroup: string[], groupIdx: number) => (
|
||||
<ul className="visa-list-2" key={groupIdx}>
|
||||
{/* Map lần 2 để render từng chuỗi trong mảng con */}
|
||||
{subGroup.map((category: string, idx: number) => (
|
||||
<li key={idx}>
|
||||
<i className="fa-solid fa-chevrons-right"></i>
|
||||
{category}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
),
|
||||
)}
|
||||
|
||||
{/* Service Options */}
|
||||
<h3 className="text-2xl font-bold mb-6">
|
||||
Our {countryData.name} Visa Service Options
|
||||
</h3>
|
||||
<ul className="space-y-4">
|
||||
{countryData.visaProcess.steps.map((process: any) => (
|
||||
<li key={process.number} className="flex gap-4">
|
||||
<span className="font-bold text-blue-600 flex-shrink-0">
|
||||
{process.number}.
|
||||
</span>
|
||||
<span>
|
||||
<strong>{process.title}</strong> – {process.description}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="lg:col-span-1">
|
||||
{/* Related Countries */}
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
||||
{relatedCountries.map((c: any) => (
|
||||
<div
|
||||
key={c.id}
|
||||
className="flex items-center justify-between py-4 border-b last:border-0 cursor-pointer hover:text-blue-600"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<img src={c.icon} alt={c.name} className="w-10 h-10" />
|
||||
<h5 className="font-medium">{c.name}</h5>
|
||||
</div>
|
||||
<span className="text-blue-600">›</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contact Box */}
|
||||
<div
|
||||
className="rounded-lg p-6 text-white relative"
|
||||
style={{
|
||||
backgroundImage: `url('${ASSET_URL}/assets/img/inner-page/country-details/bg.jpg')`,
|
||||
backgroundSize: "cover",
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/60 rounded-lg"></div>
|
||||
<div className="relative z-10">
|
||||
<h3 className="text-2xl font-bold mb-2">
|
||||
Visa & Immigration
|
||||
</h3>
|
||||
<p className="mb-6">Need Help? Book Lab Visit</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
📞
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm">Call Us:</span>
|
||||
<p className="font-bold">
|
||||
<a href={`tel:${contactInfo.phone}`}>
|
||||
{contactInfo.phone}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
✉️
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm">Mail Us:</span>
|
||||
<p className="font-bold">
|
||||
<a href={`mailto:${contactInfo.email}`}>
|
||||
{contactInfo.email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
📍
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm">Location:</span>
|
||||
<p className="font-bold">{contactInfo.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer
|
||||
className="py-20 px-4 lg:px-8 bg-cover text-white relative"
|
||||
style={{
|
||||
backgroundImage: `url('${ASSET_URL}/assets/img/home-1/footer-bg.jpg')`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/60"></div>
|
||||
<div className="max-w-7xl mx-auto relative z-10 text-center">
|
||||
<h2 className="text-3xl font-bold mb-4">
|
||||
<a href="tel:+16336547896">+163 3654 7896</a>
|
||||
</h2>
|
||||
<h2 className="text-xl mb-8">
|
||||
69 Street, 5th Avenue LA, United States
|
||||
</h2>
|
||||
<Link href="/">
|
||||
<img
|
||||
src={`${ASSET_URL}/assets/img/logo/white-logo.svg`}
|
||||
alt="logo"
|
||||
className="h-10 mx-auto mb-8"
|
||||
/>
|
||||
</Link>
|
||||
<ul className="flex flex-wrap justify-center gap-8 mb-8">
|
||||
<li>
|
||||
<a href="/" className="hover:text-blue-300">
|
||||
Home
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about" className="hover:text-blue-300">
|
||||
About Us
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/country-list" className="hover:text-blue-300">
|
||||
Visa
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog" className="hover:text-blue-300">
|
||||
Pages
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/contact" className="hover:text-blue-300">
|
||||
Contact Us
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex justify-center gap-6">
|
||||
<a href="#" className="hover:text-blue-300">
|
||||
𝕏
|
||||
</a>
|
||||
<a href="#" className="hover:text-blue-300">
|
||||
📷
|
||||
</a>
|
||||
<a href="#" className="hover:text-blue-300">
|
||||
in
|
||||
</a>
|
||||
<a href="#" className="hover:text-blue-300">
|
||||
▶
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
283
app/visa/[slug]/page.tsx
Normal file
283
app/visa/[slug]/page.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import React from "react";
|
||||
import visaData from "../visa.json";
|
||||
import Breadcrumb from "../components/Breadcrumb";
|
||||
|
||||
const ASSET_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
||||
|
||||
interface VisaCountryData {
|
||||
id: number;
|
||||
name: string;
|
||||
icon: string;
|
||||
services: string[];
|
||||
}
|
||||
|
||||
interface CountryDetailsProps {
|
||||
params: Promise<{
|
||||
slug: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Helper function to map slugs to country names
|
||||
const getCountryFromSlug = (slug: string): string => {
|
||||
const slugToCountry: { [key: string]: string } = {
|
||||
france: "France",
|
||||
uk: "UK",
|
||||
canada: "Canada",
|
||||
germany: "Germany",
|
||||
spain: "Spain",
|
||||
"south-korea": "South Korea",
|
||||
japan: "Japan",
|
||||
croatia: "Croatia",
|
||||
england: "England",
|
||||
indonesia: "Indonesia",
|
||||
"united-states-of-america": "United States of America",
|
||||
};
|
||||
|
||||
return slugToCountry[slug] || slug;
|
||||
};
|
||||
|
||||
export default function CountryDetailsPage({ params }: CountryDetailsProps) {
|
||||
const [country, setCountry] = useState<VisaCountryData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [slug, setSlug] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
// Unwrap the params Promise
|
||||
Promise.resolve(params).then((resolvedParams) => {
|
||||
const currentSlug = resolvedParams.slug;
|
||||
setSlug(currentSlug);
|
||||
|
||||
const countryName = getCountryFromSlug(currentSlug);
|
||||
const foundCountry = visaData.visaSystem.summaryList.find(
|
||||
(c) => c.name === countryName,
|
||||
);
|
||||
|
||||
if (foundCountry) {
|
||||
setCountry(foundCountry);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
}, [params]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="spinner"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!country) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<h1>Country not found</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const countryData = visaData.visaSystem.detailedView.activeCountry;
|
||||
const relatedCountries = visaData.visaSystem.detailedView.relatedCountries;
|
||||
const contactInfo = visaData.visaSystem.contactInfo;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Breadcrumb-Wrapper Section Start */}
|
||||
<Breadcrumb
|
||||
title={countryData.breadcrumb.title}
|
||||
breadcrumbItems={[
|
||||
{ label: "Home", href: "/" },
|
||||
{ label: countryData.name },
|
||||
]}
|
||||
backgroundImage={`${ASSET_URL}/${countryData.breadcrumb.image}`}
|
||||
/>
|
||||
|
||||
{/* Country-details Section Start */}
|
||||
<section className="country-details-section section-padding fix">
|
||||
<div className="container">
|
||||
<div className="country-details-wrapper">
|
||||
<div className="row g-4">
|
||||
{/* Main Content */}
|
||||
<div className="col-lg-8">
|
||||
<div className="country-details-post">
|
||||
<div className="details-image">
|
||||
<img
|
||||
src={`${ASSET_URL}/${countryData.mainImage}`}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<div className="country-details-content">
|
||||
<h2>{countryData.name}</h2>
|
||||
<p>{countryData.description}</p>
|
||||
<p className="mt-3">{countryData.additionalInfo}</p>
|
||||
<h5>{countryData.tagline}</h5>
|
||||
|
||||
{/* Visa Types */}
|
||||
<div className="tourist-visa-box">
|
||||
{/* Render mảng đầu tiên (index 0) */}
|
||||
{countryData.visaTypes[0] && (
|
||||
<div className="tourist-box style-2">
|
||||
{countryData.visaTypes[0].items.map(
|
||||
(item: any, itemIdx: number) => (
|
||||
<div key={itemIdx} className="tourist-content">
|
||||
<h5>{item.title}</h5>
|
||||
<p>{item.description}</p>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Render mảng thứ hai (index 1) */}
|
||||
{countryData.visaTypes[1] && (
|
||||
<div className="tourist-box">
|
||||
{countryData.visaTypes[1].items.map(
|
||||
(item: any, itemIdx: number) => (
|
||||
<div key={itemIdx} className="tourist-content">
|
||||
<h5>{item.title}</h5>
|
||||
<p>{item.description}</p>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Visa Process */}
|
||||
<h3 className="text">{countryData.visaProcess.title}</h3>
|
||||
<ul className="list-item">
|
||||
{countryData.visaProcess.steps.map(
|
||||
(process: any, idx: number) => (
|
||||
<li key={idx}>
|
||||
{process.number}. {process.title} –
|
||||
<span>{process.description}</span>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{/* Gallery */}
|
||||
<div className="row g-4 mb-4">
|
||||
{countryData.gallery.map((image: string, idx: number) => (
|
||||
<div key={idx} className="col-lg-6">
|
||||
<div className="thumb">
|
||||
<img src={`${ASSET_URL}/${image}`} alt="img" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Visa Categories */}
|
||||
<h3 className="text mb-3">
|
||||
{countryData.visaCategories.title}
|
||||
</h3>
|
||||
|
||||
{countryData.visaCategories.steps.map(
|
||||
(subGroup: string[], groupIdx: number) => (
|
||||
<ul className="visa-list-2" key={groupIdx}>
|
||||
{/* Map lần 2 để render từng chuỗi trong mảng con */}
|
||||
{subGroup.map((category: string, idx: number) => (
|
||||
<li key={idx}>
|
||||
<i className="fa-solid fa-chevrons-right"></i>
|
||||
{category}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
),
|
||||
)}
|
||||
|
||||
{/* Service Options */}
|
||||
<h3 className="text">{countryData.visaService.title}</h3>
|
||||
<ul className="list-item">
|
||||
{countryData.visaService.steps.map(
|
||||
(process: any, idx: number) => (
|
||||
<li key={idx}>
|
||||
{process.number}. {process.title} –
|
||||
<span>{process.description}</span>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="col-lg-4">
|
||||
<div className="country-details-sideber">
|
||||
{relatedCountries.map((relCountry: any, idx: number) => (
|
||||
<div key={idx} className="icon-box-item">
|
||||
<div className="left-item">
|
||||
<div className="icon">
|
||||
<img
|
||||
src={`${ASSET_URL}/${relCountry.icon}`}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<h5>{relCountry.name}</h5>
|
||||
</div>
|
||||
<i className="fa-solid fa-chevrons-right"></i>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Contact Box */}
|
||||
<div
|
||||
className="visa-contact-box bg-cover"
|
||||
style={{
|
||||
backgroundImage: `url(${ASSET_URL}/assets/img/inner-page/country-details/bg.jpg)`,
|
||||
padding: "30px",
|
||||
borderRadius: "8px",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<div className="content">
|
||||
<h3>{contactInfo.sectionTitle}</h3>
|
||||
<p>{contactInfo.helpText}</p>
|
||||
<div className="icon-item">
|
||||
<div className="icon">
|
||||
<i className="fa-solid fa-phone"></i>
|
||||
</div>
|
||||
<div className="cont">
|
||||
<span>{contactInfo.phone.label}: </span>
|
||||
<h6>
|
||||
<a href={`tel:${contactInfo.phone.link}`}>
|
||||
{contactInfo.phone.value}
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className="icon-item">
|
||||
<div className="icon">
|
||||
<i className="fa-regular fa-envelope"></i>
|
||||
</div>
|
||||
<div className="cont">
|
||||
<span>{contactInfo.email.label}: </span>
|
||||
<h6>
|
||||
<a href={`mailto:${contactInfo.email.link}`}>
|
||||
{contactInfo.email.value}
|
||||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className="icon-item">
|
||||
<div className="icon">
|
||||
<i className="fa-regular fa-location-dot"></i>
|
||||
</div>
|
||||
<div className="cont">
|
||||
<span>{contactInfo.location.label}: </span>
|
||||
<h6>{contactInfo.location.address}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
52
app/visa/components/Breadcrumb.tsx
Normal file
52
app/visa/components/Breadcrumb.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
const ASSET_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
||||
|
||||
interface BreadcrumbProps {
|
||||
title: string;
|
||||
breadcrumbItems: Array<{
|
||||
label: string;
|
||||
href?: string;
|
||||
}>;
|
||||
backgroundImage?: string;
|
||||
showShape?: boolean;
|
||||
}
|
||||
|
||||
export default function Breadcrumb({
|
||||
title,
|
||||
breadcrumbItems,
|
||||
backgroundImage = `${ASSET_URL}/assets/img/inner-page/breadcrumb.jpg`,
|
||||
showShape = true,
|
||||
}: BreadcrumbProps) {
|
||||
return (
|
||||
<section
|
||||
className="breadcrumb-wrapper fix bg-cover"
|
||||
style={{
|
||||
backgroundImage: `url(${backgroundImage})`,
|
||||
}}
|
||||
>
|
||||
{showShape && (
|
||||
<div className="shape">
|
||||
<img src={`${ASSET_URL}/assets/img/inner-page/shape.png`} alt="img" />
|
||||
</div>
|
||||
)}
|
||||
<div className="container">
|
||||
<div className="page-heading">
|
||||
<h1 className="breadcrumb-title">{title}</h1>
|
||||
<ul className="breadcrumb-list">
|
||||
{breadcrumbItems.map((item, index) => (
|
||||
<li key={index}>
|
||||
{item.href ? (
|
||||
<a href={item.href}>{item.label}</a>
|
||||
) : (
|
||||
<>
|
||||
{index > 0 && <i className="fa-solid fa-chevron-right"></i>}
|
||||
{item.label}
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
411
app/visa/layout.tsx
Normal file
411
app/visa/layout.tsx
Normal file
@@ -0,0 +1,411 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
const ASSET_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Visaway – Immigration & Visa Consulting HTML Template",
|
||||
description: "Visaway – Immigration & Visa Consulting HTML Template",
|
||||
authors: [{ name: "Gramentheme" }],
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: "http://localhost:3000",
|
||||
},
|
||||
};
|
||||
|
||||
export default function VisaLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* Preloader Start */}
|
||||
<div id="preloader" className="preloader" style={{ display: "none" }}>
|
||||
<div className="animation-preloader">
|
||||
<div className="spinner"></div>
|
||||
<div className="txt-loading">
|
||||
<span data-text-preloader="V" className="letters-loading">
|
||||
{" "}
|
||||
V{" "}
|
||||
</span>
|
||||
<span data-text-preloader="I" className="letters-loading">
|
||||
{" "}
|
||||
I{" "}
|
||||
</span>
|
||||
<span data-text-preloader="S" className="letters-loading">
|
||||
{" "}
|
||||
S{" "}
|
||||
</span>
|
||||
<span data-text-preloader="A" className="letters-loading">
|
||||
{" "}
|
||||
A{" "}
|
||||
</span>
|
||||
<span data-text-preloader="W" className="letters-loading">
|
||||
{" "}
|
||||
W{" "}
|
||||
</span>
|
||||
<span data-text-preloader="A" className="letters-loading">
|
||||
{" "}
|
||||
A{" "}
|
||||
</span>
|
||||
<span data-text-preloader="Y" className="letters-loading">
|
||||
{" "}
|
||||
Y{" "}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-center">Loading</p>
|
||||
</div>
|
||||
<div className="loader">
|
||||
<div className="row">
|
||||
<div className="col-3 loader-section section-left">
|
||||
<div className="bg"></div>
|
||||
</div>
|
||||
<div className="col-3 loader-section section-left">
|
||||
<div className="bg"></div>
|
||||
</div>
|
||||
<div className="col-3 loader-section section-right">
|
||||
<div className="bg"></div>
|
||||
</div>
|
||||
<div className="col-3 loader-section section-right">
|
||||
<div className="bg"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* GT Back To Top Start */}
|
||||
<button id="back-top" className="back-to-top show">
|
||||
<i className="fa-regular fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
{/* GT MouseCursor Start */}
|
||||
<div className="mouseCursor cursor-outer"></div>
|
||||
<div className="mouseCursor cursor-inner"></div>
|
||||
|
||||
{/* Header-Top-Section Start */}
|
||||
<div className="header-top-section">
|
||||
<div className="container-fluid">
|
||||
<div className="header-top-wrapper">
|
||||
<div className="header-left">
|
||||
<ul className="list">
|
||||
<li className="style-2">
|
||||
<span>Help Line</span>
|
||||
<i className="fa-solid fa-phone"></i>
|
||||
<a href="tel:+093783575222">+09 378 357 5222</a>
|
||||
</li>
|
||||
<li>
|
||||
<i className="fa-solid fa-location-dot"></i>
|
||||
69 Street, 5th AvenueLA, United States
|
||||
</li>
|
||||
<li>
|
||||
<i className="fa-solid fa-envelope"></i>
|
||||
<a href="mailto:info@example.com">info@example.com</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
<div className="flag-wrap">
|
||||
<div className="flag">
|
||||
<i className="fa-solid fa-globe"></i>
|
||||
</div>
|
||||
<div className="nice-select" tabIndex={0}>
|
||||
<span className="current"> English </span>
|
||||
<ul className="list">
|
||||
<li data-value="1" className="option selected focus">
|
||||
English
|
||||
</li>
|
||||
<li data-value="1" className="option">
|
||||
Bangla
|
||||
</li>
|
||||
<li data-value="1" className="option">
|
||||
Hindi
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="social-item">
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-linkedin"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-twitter"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-instagram"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-youtube"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Offcanvas Area Start */}
|
||||
<div className="fix-area">
|
||||
<div className="offcanvas__info">
|
||||
<div className="offcanvas__wrapper">
|
||||
<div className="offcanvas__content">
|
||||
<div className="offcanvas__top mb-5 d-flex justify-content-between align-items-center">
|
||||
<div className="offcanvas__logo">
|
||||
<a href="/">
|
||||
<img
|
||||
src={`${ASSET_URL}/assets/img/logo/black-logo.svg`}
|
||||
alt="logo-img"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="offcanvas__close">
|
||||
<button>
|
||||
<i className="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text d-none d-xl-block">
|
||||
Nullam dignissim, ante scelerisque the is euismod fermentum odio
|
||||
sem semper the is erat, a feugiat leo urna eget eros. Duis
|
||||
Aenean a imperdiet risus.
|
||||
</p>
|
||||
<div className="mobile-menu fix mb-3"></div>
|
||||
<div className="offcanvas__contact d-xl-block">
|
||||
<h4 className="d-xl-block">Contact Info</h4>
|
||||
<ul className="d-xl-block">
|
||||
<li className="d-flex align-items-center">
|
||||
<div className="offcanvas__contact-icon">
|
||||
<i className="fal fa-map-marker-alt"></i>
|
||||
</div>
|
||||
<div className="offcanvas__contact-text">
|
||||
<a target="_blank" href="#">
|
||||
Main Street, Melbourne, Australia
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="d-flex align-items-center">
|
||||
<div className="offcanvas__contact-icon mr-15">
|
||||
<i className="fal fa-envelope"></i>
|
||||
</div>
|
||||
<div className="offcanvas__contact-text">
|
||||
<a href="mailto:info@example.com">
|
||||
<span>info@example.com</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="d-flex align-items-center">
|
||||
<div className="offcanvas__contact-icon mr-15">
|
||||
<i className="fal fa-clock"></i>
|
||||
</div>
|
||||
<div className="offcanvas__contact-text">
|
||||
<a target="_blank" href="#">
|
||||
Mod-friday, 09am -05pm
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="d-flex align-items-center">
|
||||
<div className="offcanvas__contact-icon mr-15">
|
||||
<i className="far fa-phone"></i>
|
||||
</div>
|
||||
<div className="offcanvas__contact-text">
|
||||
<a href="tel:+11002345909">+11002345909</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="social-icon d-flex align-items-center">
|
||||
<a href="#">
|
||||
<i className="fab fa-facebook-f"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fab fa-twitter"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fab fa-youtube"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fab fa-linkedin-in"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="offcanvas__overlay"></div>
|
||||
|
||||
{/* Header Section Start */}
|
||||
{/* <header id="header-sticky" className="header-1">
|
||||
<div className="container-fluid">
|
||||
<div className="mega-menu-wrapper">
|
||||
<div className="header-main">
|
||||
<div className="header-left">
|
||||
<div className="logo">
|
||||
<a href="/" className="header-logo-2">
|
||||
<img
|
||||
src={`${ASSET_URL}/assets/img/logo/black-logo.svg`}
|
||||
alt="logo-img"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mean__menu-wrapper">
|
||||
<div className="main-menu">
|
||||
<nav id="mobile-menu">
|
||||
<ul>
|
||||
<li className="has-dropdown active menu-thumb">
|
||||
<a href="/"> Home </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about">About Us</a>
|
||||
</li>
|
||||
<li className="has-dropdown">
|
||||
<a href="#"> Pages </a>
|
||||
<ul className="submenu">
|
||||
<li>
|
||||
<a href="/services">Services</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/visa">Country List</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/pricing">Our Pricing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/appointment">Appointment</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/visa"> VISA </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog"> Blog </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/contact">Contact Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="header-right d-flex align-items-center mt-0">
|
||||
<div className="header-call-item">
|
||||
<a href="#" className="main-header__search search-toggler">
|
||||
<i className="fa-regular fa-magnifying-glass"></i>
|
||||
</a>
|
||||
<a href="/contact" className="theme-btn">
|
||||
Apply now
|
||||
<i className="fa-solid fa-arrow-right"></i>
|
||||
</a>
|
||||
<div className="header__hamburger my-auto">
|
||||
<div className="sidebar__toggle">
|
||||
<i className="fa-solid fa-bars-staggered"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header> */}
|
||||
|
||||
{/* Search Area Start */}
|
||||
<div className="search-popup">
|
||||
<div className="search-popup__overlay search-toggler"></div>
|
||||
<div className="search-popup__content">
|
||||
<form
|
||||
role="search"
|
||||
method="get"
|
||||
className="search-popup__form"
|
||||
action="#"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
name="search"
|
||||
placeholder="Search Here..."
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
aria-label="search submit"
|
||||
className="search-btn"
|
||||
>
|
||||
<span>
|
||||
<i className="fa-regular fa-magnifying-glass"></i>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
{children}
|
||||
|
||||
{/* Footer Section Start */}
|
||||
{/* <footer
|
||||
className="footer-section fix bg-cover"
|
||||
style={{
|
||||
backgroundImage: `url(${ASSET_URL}/assets/img/home-1/footer-bg.jpg)`,
|
||||
}}
|
||||
>
|
||||
<div className="container">
|
||||
<div className="footer-wrapper">
|
||||
<div className="row">
|
||||
<div className="col-xl-12">
|
||||
<div className="footer-item">
|
||||
<h2>
|
||||
<a href="tel:+16336547896">+163 3654 7896</a>
|
||||
</h2>
|
||||
<h2 className="text">
|
||||
69 Street, 5th AvenueLA, United States
|
||||
</h2>
|
||||
<div className="footer-list-item">
|
||||
<a href="/">
|
||||
<img
|
||||
src={`${ASSET_URL}/assets/img/logo/white-logo.svg`}
|
||||
alt="img"
|
||||
/>
|
||||
</a>
|
||||
<ul className="footer-list">
|
||||
<li>
|
||||
<a href="/">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about">About Us</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/visa">Visa</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog">Pages</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog">Article</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/contact">Contact Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="social-icon">
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-twitter"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-instagram"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-linkedin"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i className="fa-brands fa-youtube"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
98
app/visa/page.tsx
Normal file
98
app/visa/page.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import visaData from "./visa.json";
|
||||
import Breadcrumb from "./components/Breadcrumb";
|
||||
|
||||
const ASSET_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
||||
|
||||
interface VisaCountry {
|
||||
id: number;
|
||||
name: string;
|
||||
icon: string;
|
||||
services: string[];
|
||||
}
|
||||
|
||||
const visaCountries: VisaCountry[] = visaData.visaSystem.summaryList.map(
|
||||
(country) => ({
|
||||
...country,
|
||||
icon: `${ASSET_URL}/${country.icon}`,
|
||||
}),
|
||||
);
|
||||
|
||||
export default function VisaListPage() {
|
||||
const getSlug = (countryName: string): string => {
|
||||
return countryName.toLowerCase().replace(/\s+/g, "-");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Breadcrumb-Wrapper Section Start */}
|
||||
<Breadcrumb
|
||||
title={visaData.visaSystem.breadcrumb.list.title}
|
||||
breadcrumbItems={[
|
||||
{ label: "Home", href: "/" },
|
||||
{ label: visaData.visaSystem.breadcrumb.list.title },
|
||||
]}
|
||||
backgroundImage={`${ASSET_URL}/${visaData.visaSystem.breadcrumb.list.image}`}
|
||||
/>
|
||||
|
||||
{/* Service Section Start */}
|
||||
<section className="visa-provide-section section-padding section-bg-1 fix">
|
||||
<div className="container">
|
||||
<div className="row g-4">
|
||||
{visaCountries.map((country) => (
|
||||
<div key={country.id} className="col-lg-6">
|
||||
<div className="visa-provide-box mt-0">
|
||||
<div className="visa-top-item">
|
||||
<div className="visa-left">
|
||||
<div className="icon">
|
||||
<img src={country.icon} alt="img" />
|
||||
</div>
|
||||
<div className="content">
|
||||
<p>Visa Service</p>
|
||||
<h3>
|
||||
<a href={`/visa/${getSlug(country.name)}`}>
|
||||
{country.name}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={`/visa/${getSlug(country.name)}`}
|
||||
className="theme-btn"
|
||||
>
|
||||
Read More
|
||||
<i className="fa-solid fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div className="visa-list-item">
|
||||
<ul className="list">
|
||||
<li>
|
||||
<i className="fa-regular fa-arrow-right"></i>
|
||||
{country.services[0]}
|
||||
</li>
|
||||
<li>
|
||||
<i className="fa-regular fa-arrow-right"></i>
|
||||
{country.services[1]}
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="list">
|
||||
<li>
|
||||
<i className="fa-regular fa-arrow-right"></i>
|
||||
{country.services[2]}
|
||||
</li>
|
||||
<li>
|
||||
<i className="fa-regular fa-arrow-right"></i>
|
||||
{country.services[3]}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
295
app/visa/visa.json
Normal file
295
app/visa/visa.json
Normal file
@@ -0,0 +1,295 @@
|
||||
{
|
||||
"visaSystem": {
|
||||
"breadcrumb": {
|
||||
"list": {
|
||||
"title": "Country List",
|
||||
"image": "assets/img/inner-page/breadcrumb.jpg"
|
||||
}
|
||||
},
|
||||
"summaryList": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "France",
|
||||
"icon": "assets/img/home-2/visa/03.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "UK",
|
||||
"icon": "assets/img/home-2/visa/11.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Canada",
|
||||
"icon": "assets/img/home-2/visa/02.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Germany",
|
||||
"icon": "assets/img/home-2/visa/12.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Spain",
|
||||
"icon": "assets/img/home-2/visa/13.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "South Korea",
|
||||
"icon": "assets/img/home-2/visa/14.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Japan",
|
||||
"icon": "assets/img/home-2/visa/15.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Croatia",
|
||||
"icon": "assets/img/home-2/visa/16.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "England",
|
||||
"icon": "assets/img/home-2/visa/17.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Indonesia",
|
||||
"icon": "assets/img/home-2/visa/18.png",
|
||||
"services": [
|
||||
"Student Visa & Admission",
|
||||
"Work Visa – H1B",
|
||||
"Work permit for Canada",
|
||||
"Student Visa for Canada"
|
||||
]
|
||||
}
|
||||
],
|
||||
"detailedView": {
|
||||
"activeCountry": {
|
||||
"id": 1,
|
||||
"name": "United States of America ",
|
||||
"title": "COUNTRY USA",
|
||||
"breadcrumb": {
|
||||
"title": "COUNTRY USA",
|
||||
"image": "assets/img/inner-page/breadcrumb.jpg"
|
||||
},
|
||||
"mainImage": "assets/img/inner-page/country-details/details-1.jpg",
|
||||
"description": "The United States is one of the most popular destinations for international students and immigrants, offering world-class universities, diverse cultural experiences, and countless career opportunities...",
|
||||
"additionalInfo": "Our consultancy provides complete guidance for study visas, work permits, and permanent residency pathways tailored to your goals.",
|
||||
"tagline": "Over the last 35 Years we made an impact that is strong & we have long way to go.",
|
||||
"visaTypes": [
|
||||
{
|
||||
"category": "Tourist & Work",
|
||||
"items": [
|
||||
{
|
||||
"title": "Tourist Visa",
|
||||
"description": "Broad term that can refer to various aspects of interconnectedness"
|
||||
},
|
||||
{
|
||||
"title": "Work Permit",
|
||||
"description": "Broad term that can refer to various aspects of interconnectedness"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "Student & Family",
|
||||
"items": [
|
||||
{
|
||||
"title": "Student",
|
||||
"description": "Broad term that can refer to various aspects of interconnectedness"
|
||||
},
|
||||
{
|
||||
"title": "Tourist Visa",
|
||||
"description": "Broad term that can refer to various aspects of interconnectedness"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"visaProcess": {
|
||||
"title": "USA Visa Process",
|
||||
"steps": [
|
||||
{
|
||||
"number": "01",
|
||||
"title": "Consultation & Eligibility Check",
|
||||
"description": "Our experts review your profile and visa requirements."
|
||||
},
|
||||
{
|
||||
"number": "02",
|
||||
"title": "Application Preparation",
|
||||
"description": "We help with document collection, form filling, and statement drafting."
|
||||
},
|
||||
{
|
||||
"number": "03",
|
||||
"title": "Submission",
|
||||
"description": "Visa application is submitted online with required fees."
|
||||
},
|
||||
{
|
||||
"number": "04",
|
||||
"title": "Interview Guidance",
|
||||
"description": "Get training and mock sessions for embassy interview."
|
||||
},
|
||||
{
|
||||
"number": "05",
|
||||
"title": "Approval & Travel",
|
||||
"description": "Once approved, we provide travel and pre-departure guidance."
|
||||
}
|
||||
]
|
||||
},
|
||||
"gallery": [
|
||||
"assets/img/inner-page/country-details/details-2.jpg",
|
||||
"assets/img/inner-page/country-details/details-3.png"
|
||||
],
|
||||
"visaCategories": {
|
||||
"title": "Types of USA Visas",
|
||||
"steps": [
|
||||
[
|
||||
"Student Visa (F1, M1, J1)",
|
||||
"Work Visa (H1B, L1)",
|
||||
"Tourist Visa (B1/B2)"
|
||||
],
|
||||
["Family/Spouse Visa (K1, IR1, F2A)", "Green Card / Immigrant Visa"]
|
||||
]
|
||||
},
|
||||
"visaService": {
|
||||
"title": "Our USA Visa Service Options",
|
||||
"steps": [
|
||||
{
|
||||
"number": "01",
|
||||
"title": "Consultation & Eligibility Check",
|
||||
"description": "Our experts review your profile and visa requirements."
|
||||
},
|
||||
{
|
||||
"number": "02",
|
||||
"title": "Application Preparation",
|
||||
"description": "We help with document collection, form filling, and statement drafting."
|
||||
},
|
||||
{
|
||||
"number": "03",
|
||||
"title": "Submission",
|
||||
"description": "Visa application is submitted online with required fees."
|
||||
},
|
||||
{
|
||||
"number": "04",
|
||||
"title": "Interview Guidance",
|
||||
"description": "Get training and mock sessions for embassy interview."
|
||||
},
|
||||
{
|
||||
"number": "05",
|
||||
"title": "Approval & Travel",
|
||||
"description": "Once approved, we provide travel and pre-departure guidance."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"relatedCountries": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Canada",
|
||||
"icon": "assets/img/inner-page/country-details/01.png"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "USA",
|
||||
"icon": "assets/img/inner-page/country-details/02.png"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "USA",
|
||||
"icon": "assets/img/inner-page/country-details/03.png"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Saint Helena",
|
||||
"icon": "assets/img/inner-page/country-details/05.png"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Iran",
|
||||
"icon": "assets/img/inner-page/country-details/06.png"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Spain",
|
||||
"icon": "assets/img/inner-page/country-details/07.png"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Japan",
|
||||
"icon": "assets/img/inner-page/country-details/08.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"contactInfo": {
|
||||
"sectionTitle": "Visa & Immigration",
|
||||
"helpText": "Need Help? Book Lab Visit",
|
||||
"phone": {
|
||||
"label": "Call Us",
|
||||
"value": "+009 438 222 9540",
|
||||
"link": "tel:+0094382229540"
|
||||
},
|
||||
"email": {
|
||||
"label": "Mail Us",
|
||||
"value": "infor@xridergamil.com",
|
||||
"link": "mailto:infor@xridergamil.com"
|
||||
},
|
||||
"location": {
|
||||
"label": "Location",
|
||||
"address": "Toronto, Montreal, City 2026"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user