141 lines
6.7 KiB
TypeScript
141 lines
6.7 KiB
TypeScript
"use client";
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useAuth } from "../../hooks/useAuth";
|
|
import { useCountries } from "../../hooks/useCountries";
|
|
import Sidebar from "../../components/Sidebar";
|
|
import Header from "../../components/Header";
|
|
import CountryCard from "../../components/CountryCard";
|
|
import { countryAPI } from "../../lib/api";
|
|
import { toast } from "react-toastify";
|
|
import { Plus, X, Globe } from "lucide-react";
|
|
|
|
export default function CountriesPage() {
|
|
const router = useRouter();
|
|
const { user, loading, isAdmin } = useAuth();
|
|
const { countries, refetch } = useCountries();
|
|
const [showModal, setShowModal] = useState(false);
|
|
const [form, setForm] = useState({ name: "", code: "", flag_emoji: "", vfs_url: "", processing_time: "", visa_types: "", requirements: "" });
|
|
|
|
useEffect(() => { if (!loading && !user) router.push("/login"); }, [user, loading, router]);
|
|
|
|
const handleCreate = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
const visa_types = form.visa_types.split(",").map((s) => s.trim()).filter(Boolean);
|
|
const requirements = form.requirements.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
await countryAPI.create({ ...form, visa_types, requirements });
|
|
toast.success("Country created");
|
|
setShowModal(false);
|
|
setForm({ name: "", code: "", flag_emoji: "", vfs_url: "", processing_time: "", visa_types: "", requirements: "" });
|
|
refetch();
|
|
} catch (error: any) {
|
|
toast.error(error.response?.data?.message || "Failed to create country");
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: string) => {
|
|
if (!confirm("Delete this country?")) return;
|
|
try {
|
|
await countryAPI.delete(id);
|
|
toast.success("Country deleted");
|
|
refetch();
|
|
} catch (error: any) {
|
|
toast.error(error.response?.data?.message || "Failed to delete");
|
|
}
|
|
};
|
|
|
|
if (loading || !user) return null;
|
|
|
|
return (
|
|
<div className="flex min-h-screen bg-dark-900">
|
|
<Sidebar />
|
|
<div className="flex-1 flex flex-col">
|
|
<Header />
|
|
<main className="flex-1 p-6 overflow-y-auto">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">Countries</h1>
|
|
<p className="text-gray-500">Manage visa destinations</p>
|
|
</div>
|
|
{isAdmin && (
|
|
<button onClick={() => setShowModal(true)} className="btn-primary flex items-center gap-2">
|
|
<Plus className="w-4 h-4" />
|
|
Add Country
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
{countries.map((country) => (
|
|
<div key={country.id} className="relative group">
|
|
<CountryCard country={country} />
|
|
{isAdmin && (
|
|
<button
|
|
onClick={() => handleDelete(country.id)}
|
|
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 p-1.5 bg-dark-800 rounded-lg text-gray-400 hover:text-accent-red transition-all"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{showModal && (
|
|
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4">
|
|
<div className="card w-full max-w-lg">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
|
<Globe className="w-5 h-5 text-accent-blue" />
|
|
Add Country
|
|
</h2>
|
|
<button onClick={() => setShowModal(false)} className="text-gray-400 hover:text-white"><X className="w-5 h-5" /></button>
|
|
</div>
|
|
<form onSubmit={handleCreate} className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="label">Name *</label>
|
|
<input required value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} className="input w-full" placeholder="France" />
|
|
</div>
|
|
<div>
|
|
<label className="label">Code *</label>
|
|
<input required value={form.code} onChange={(e) => setForm({ ...form, code: e.target.value.toUpperCase() })} className="input w-full" placeholder="FR" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="label">Flag Emoji</label>
|
|
<input value={form.flag_emoji} onChange={(e) => setForm({ ...form, flag_emoji: e.target.value })} className="input w-full" placeholder="🇫🇷" />
|
|
</div>
|
|
<div>
|
|
<label className="label">Processing Time</label>
|
|
<input value={form.processing_time} onChange={(e) => setForm({ ...form, processing_time: e.target.value })} className="input w-full" placeholder="15-20 days" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="label">VFS URL</label>
|
|
<input type="url" value={form.vfs_url} onChange={(e) => setForm({ ...form, vfs_url: e.target.value })} className="input w-full" placeholder="https://..." />
|
|
</div>
|
|
<div>
|
|
<label className="label">Visa Types (comma separated)</label>
|
|
<input value={form.visa_types} onChange={(e) => setForm({ ...form, visa_types: e.target.value })} className="input w-full" placeholder="Tourist, Business, Student" />
|
|
</div>
|
|
<div>
|
|
<label className="label">Requirements (one per line)</label>
|
|
<textarea value={form.requirements} onChange={(e) => setForm({ ...form, requirements: e.target.value })} className="input w-full h-24 resize-none" placeholder="Passport valid 6 months 2 photos Bank statement" />
|
|
</div>
|
|
<div className="flex justify-end gap-3 pt-4">
|
|
<button type="button" onClick={() => setShowModal(false)} className="px-4 py-2 text-gray-400 hover:text-white">Cancel</button>
|
|
<button type="submit" className="btn-primary">Create Country</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|