mirror of
https://github.com/myronblair/epic-download
synced 2026-06-30 17:51:00 -05:00
auto-commit for 8a62051e-b038-4363-a62d-7047e3d6e102
This commit is contained in:
@@ -13,12 +13,13 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../components/ui/dialog';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
|
||||
import { toast } from 'sonner';
|
||||
import { destinations as initialDestinations, specials as initialSpecials } from '../mockData';
|
||||
import { destinationsAPI, specialsAPI, uploadAPI } from '../services/api';
|
||||
|
||||
const AdminDashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
const [destinations, setDestinations] = useState(initialDestinations);
|
||||
const [specials, setSpecials] = useState(initialSpecials);
|
||||
const [destinations, setDestinations] = useState([]);
|
||||
const [specials, setSpecials] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [editingDestination, setEditingDestination] = useState(null);
|
||||
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||
@@ -37,35 +38,59 @@ const AdminDashboard = () => {
|
||||
const isAuthenticated = localStorage.getItem('isAdminAuthenticated');
|
||||
if (!isAuthenticated) {
|
||||
navigate('/admin');
|
||||
} else {
|
||||
fetchData();
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [destinationsData, specialsData] = await Promise.all([
|
||||
destinationsAPI.getAll(),
|
||||
specialsAPI.getAll()
|
||||
]);
|
||||
setDestinations(destinationsData);
|
||||
setSpecials(specialsData);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
toast.error('Failed to load data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('isAdminAuthenticated');
|
||||
localStorage.removeItem('auth_token');
|
||||
toast.success('Logged out successfully');
|
||||
navigate('/admin');
|
||||
};
|
||||
|
||||
const handleAddDestination = () => {
|
||||
const newDest = {
|
||||
...newDestination,
|
||||
id: String(destinations.length + 1),
|
||||
rating: parseFloat(newDestination.rating),
|
||||
price: parseFloat(newDestination.price)
|
||||
};
|
||||
setDestinations([...destinations, newDest]);
|
||||
setIsAddDialogOpen(false);
|
||||
setNewDestination({
|
||||
name: '',
|
||||
location: '',
|
||||
description: '',
|
||||
image: '',
|
||||
category: 'City',
|
||||
rating: 4.5,
|
||||
price: 999,
|
||||
currency: 'USD'
|
||||
});
|
||||
toast.success('Destination added successfully!');
|
||||
const handleAddDestination = async () => {
|
||||
try {
|
||||
const newDest = await destinationsAPI.create({
|
||||
...newDestination,
|
||||
rating: parseFloat(newDestination.rating),
|
||||
price: parseFloat(newDestination.price)
|
||||
});
|
||||
setDestinations([...destinations, newDest]);
|
||||
setIsAddDialogOpen(false);
|
||||
setNewDestination({
|
||||
name: '',
|
||||
location: '',
|
||||
description: '',
|
||||
image: '',
|
||||
category: 'City',
|
||||
rating: 4.5,
|
||||
price: 999,
|
||||
currency: 'USD'
|
||||
});
|
||||
toast.success('Destination added successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error adding destination:', error);
|
||||
toast.error('Failed to add destination');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditDestination = (destination) => {
|
||||
@@ -73,45 +98,71 @@ const AdminDashboard = () => {
|
||||
setIsEditMode(true);
|
||||
};
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
setDestinations(destinations.map(dest =>
|
||||
dest.id === editingDestination.id ? editingDestination : dest
|
||||
));
|
||||
setIsEditMode(false);
|
||||
setEditingDestination(null);
|
||||
toast.success('Destination updated successfully!');
|
||||
};
|
||||
|
||||
const handleDeleteDestination = (id) => {
|
||||
setDestinations(destinations.filter(dest => dest.id !== id));
|
||||
// Also remove from specials if exists
|
||||
setSpecials(specials.filter(special => special.destinationId !== id));
|
||||
toast.success('Destination deleted successfully!');
|
||||
};
|
||||
|
||||
const handleToggleSpecial = (destinationId) => {
|
||||
const existingSpecial = specials.find(s => s.destinationId === destinationId);
|
||||
if (existingSpecial) {
|
||||
setSpecials(specials.filter(s => s.destinationId !== destinationId));
|
||||
toast.success('Removed from specials');
|
||||
} else {
|
||||
const newSpecial = {
|
||||
destinationId,
|
||||
discount: 20,
|
||||
endDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||||
highlights: ['Special offer', 'Limited time', 'Book now']
|
||||
};
|
||||
setSpecials([...specials, newSpecial]);
|
||||
toast.success('Added to specials!');
|
||||
const handleSaveEdit = async () => {
|
||||
try {
|
||||
const updated = await destinationsAPI.update(editingDestination.id, editingDestination);
|
||||
setDestinations(destinations.map(dest =>
|
||||
dest.id === updated.id ? updated : dest
|
||||
));
|
||||
setIsEditMode(false);
|
||||
setEditingDestination(null);
|
||||
toast.success('Destination updated successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error updating destination:', error);
|
||||
toast.error('Failed to update destination');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateSpecial = (destinationId, field, value) => {
|
||||
setSpecials(specials.map(special =>
|
||||
special.destinationId === destinationId
|
||||
? { ...special, [field]: field === 'discount' ? parseFloat(value) : value }
|
||||
: special
|
||||
));
|
||||
const handleDeleteDestination = async (id) => {
|
||||
try {
|
||||
await destinationsAPI.delete(id);
|
||||
setDestinations(destinations.filter(dest => dest.id !== id));
|
||||
setSpecials(specials.filter(special => special.destination_id !== id));
|
||||
toast.success('Destination deleted successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error deleting destination:', error);
|
||||
toast.error('Failed to delete destination');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleSpecial = async (destinationId) => {
|
||||
const existingSpecial = specials.find(s => s.destination_id === destinationId);
|
||||
try {
|
||||
if (existingSpecial) {
|
||||
await specialsAPI.deleteByDestination(destinationId);
|
||||
setSpecials(specials.filter(s => s.destination_id !== destinationId));
|
||||
toast.success('Removed from specials');
|
||||
} else {
|
||||
const newSpecial = await specialsAPI.create({
|
||||
destination_id: destinationId,
|
||||
discount: 20,
|
||||
end_date: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||||
highlights: ['Special offer', 'Limited time', 'Book now']
|
||||
});
|
||||
setSpecials([...specials, newSpecial]);
|
||||
toast.success('Added to specials!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error toggling special:', error);
|
||||
toast.error('Failed to update special');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateSpecial = async (specialId, field, value) => {
|
||||
try {
|
||||
const updatedSpecial = specials.find(s => s.id === specialId);
|
||||
if (!updatedSpecial) return;
|
||||
|
||||
const updateData = { [field]: field === 'discount' ? parseFloat(value) : value };
|
||||
const updated = await specialsAPI.update(specialId, updateData);
|
||||
|
||||
setSpecials(specials.map(special =>
|
||||
special.id === specialId ? updated : special
|
||||
));
|
||||
} catch (error) {
|
||||
console.error('Error updating special:', error);
|
||||
toast.error('Failed to update special');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -247,8 +298,13 @@ const AdminDashboard = () => {
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{destinations.map((destination) => (
|
||||
{loading ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-600">Loading destinations...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{destinations.map((destination) => (
|
||||
<Card key={destination.id} className="overflow-hidden">
|
||||
<div className="relative">
|
||||
<img
|
||||
@@ -279,7 +335,7 @@ const AdminDashboard = () => {
|
||||
<p className="text-sm text-gray-600 mb-4 line-clamp-2">{destination.description}</p>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-xl font-bold text-cyan-600">${destination.price}</span>
|
||||
{specials.some(s => s.destinationId === destination.id) && (
|
||||
{specials.some(s => s.destination_id === destination.id) && (
|
||||
<Badge variant="outline" className="border-red-500 text-red-500">Special</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -307,6 +363,7 @@ const AdminDashboard = () => {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Specials Management */}
|
||||
@@ -318,7 +375,7 @@ const AdminDashboard = () => {
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{destinations.map((destination) => {
|
||||
const special = specials.find(s => s.destinationId === destination.id);
|
||||
const special = specials.find(s => s.destination_id === destination.id);
|
||||
return (
|
||||
<Card key={destination.id}>
|
||||
<CardContent className="pt-6">
|
||||
@@ -342,7 +399,7 @@ const AdminDashboard = () => {
|
||||
<Input
|
||||
type="number"
|
||||
value={special.discount}
|
||||
onChange={(e) => handleUpdateSpecial(destination.id, 'discount', e.target.value)}
|
||||
onChange={(e) => handleUpdateSpecial(special.id, 'discount', e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
@@ -350,8 +407,8 @@ const AdminDashboard = () => {
|
||||
<label className="text-xs text-gray-600">End Date</label>
|
||||
<Input
|
||||
type="date"
|
||||
value={special.endDate}
|
||||
onChange={(e) => handleUpdateSpecial(destination.id, 'endDate', e.target.value)}
|
||||
value={special.end_date}
|
||||
onChange={(e) => handleUpdateSpecial(special.id, 'end_date', e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,22 +5,29 @@ import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
|
||||
import { toast } from 'sonner';
|
||||
import { authAPI } from '../services/api';
|
||||
|
||||
const AdminLogin = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = (e) => {
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
// Mock authentication - will be replaced with real backend
|
||||
if (email === 'admin@epictravel.com' && password === 'admin123') {
|
||||
try {
|
||||
const response = await authAPI.login(email, password);
|
||||
localStorage.setItem('auth_token', response.access_token);
|
||||
localStorage.setItem('isAdminAuthenticated', 'true');
|
||||
toast.success('Login successful!');
|
||||
navigate('/admin/dashboard');
|
||||
} else {
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
toast.error('Invalid credentials. Try: admin@epictravel.com / admin123');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,8 +75,8 @@ const AdminLogin = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" className="w-full bg-cyan-600 hover:bg-cyan-700" size="lg">
|
||||
Sign In
|
||||
<Button type="submit" className="w-full bg-cyan-600 hover:bg-cyan-700" size="lg" disabled={loading}>
|
||||
{loading ? 'Signing in...' : 'Sign In'}
|
||||
</Button>
|
||||
</form>
|
||||
<div className="mt-6 p-4 bg-cyan-50 rounded-lg border border-cyan-200">
|
||||
|
||||
+122
-63
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { MapPin, Star, Calendar, Tag, Search, Send, Mail, Phone, MessageSquare } from 'lucide-react';
|
||||
import { destinations, specials, testimonials, categories } from '../mockData';
|
||||
import { testimonials, categories } from '../mockData';
|
||||
import { destinationsAPI, specialsAPI, contactAPI, newsletterAPI } from '../services/api';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { Textarea } from '../components/ui/textarea';
|
||||
@@ -13,12 +14,44 @@ const Home = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [contactForm, setContactForm] = useState({ name: '', email: '', message: '' });
|
||||
const [newsletterEmail, setNewsletterEmail] = useState('');
|
||||
const [destinations, setDestinations] = useState([]);
|
||||
const [specials, setSpecials] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Fetch destinations and specials on mount
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [destinationsData, specialsData] = await Promise.all([
|
||||
destinationsAPI.getAll(),
|
||||
specialsAPI.getAll()
|
||||
]);
|
||||
setDestinations(destinationsData);
|
||||
setSpecials(specialsData);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
toast.error('Failed to load destinations');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Get special destinations
|
||||
const specialDestinations = specials.map(special => {
|
||||
const dest = destinations.find(d => d.id === special.destinationId);
|
||||
return { ...dest, ...special };
|
||||
});
|
||||
const dest = destinations.find(d => d.id === special.destination_id);
|
||||
if (!dest) return null;
|
||||
return {
|
||||
...dest,
|
||||
discount: special.discount,
|
||||
endDate: special.end_date,
|
||||
highlights: special.highlights,
|
||||
specialId: special.id
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
// Filter destinations
|
||||
const filteredDestinations = destinations.filter(dest => {
|
||||
@@ -28,16 +61,26 @@ const Home = () => {
|
||||
return matchesCategory && matchesSearch;
|
||||
});
|
||||
|
||||
const handleContactSubmit = (e) => {
|
||||
const handleContactSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
toast.success('Message sent! We\'ll get back to you soon.');
|
||||
setContactForm({ name: '', email: '', message: '' });
|
||||
try {
|
||||
await contactAPI.submit(contactForm);
|
||||
toast.success('Message sent! We\'ll get back to you soon.');
|
||||
setContactForm({ name: '', email: '', message: '' });
|
||||
} catch (error) {
|
||||
toast.error('Failed to send message. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleNewsletterSubmit = (e) => {
|
||||
const handleNewsletterSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
toast.success('Successfully subscribed to our newsletter!');
|
||||
setNewsletterEmail('');
|
||||
try {
|
||||
await newsletterAPI.subscribe(newsletterEmail);
|
||||
toast.success('Successfully subscribed to our newsletter!');
|
||||
setNewsletterEmail('');
|
||||
} catch (error) {
|
||||
toast.error('Failed to subscribe. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -95,59 +138,69 @@ const Home = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{specialDestinations.map((special) => (
|
||||
<Card key={special.id} className="overflow-hidden hover:shadow-2xl transition-shadow duration-300 group">
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
src={special.image}
|
||||
alt={special.name}
|
||||
className="w-full h-64 object-cover group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute top-4 right-4 bg-red-500 text-white px-4 py-2 rounded-full font-bold text-lg shadow-lg">
|
||||
{special.discount}% OFF
|
||||
{loading ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-600">Loading specials...</p>
|
||||
</div>
|
||||
) : specialDestinations.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-600">No special offers available at the moment.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{specialDestinations.map((special) => (
|
||||
<Card key={special.id} className="overflow-hidden hover:shadow-2xl transition-shadow duration-300 group">
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
src={special.image}
|
||||
alt={special.name}
|
||||
className="w-full h-64 object-cover group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute top-4 right-4 bg-red-500 text-white px-4 py-2 rounded-full font-bold text-lg shadow-lg">
|
||||
{special.discount}% OFF
|
||||
</div>
|
||||
<div className="absolute bottom-4 left-4 bg-white/95 backdrop-blur-sm px-3 py-1 rounded-full flex items-center space-x-1">
|
||||
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
||||
<span className="font-semibold">{special.rating}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-4 left-4 bg-white/95 backdrop-blur-sm px-3 py-1 rounded-full flex items-center space-x-1">
|
||||
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
|
||||
<span className="font-semibold">{special.rating}</span>
|
||||
</div>
|
||||
</div>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{special.name}</CardTitle>
|
||||
<CardDescription className="flex items-center text-base">
|
||||
<MapPin className="w-4 h-4 mr-1" />
|
||||
{special.location}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 mb-4">{special.description}</p>
|
||||
<div className="space-y-2 mb-4">
|
||||
{special.highlights.map((highlight, idx) => (
|
||||
<div key={idx} className="flex items-start space-x-2 text-sm text-gray-700">
|
||||
<Tag className="w-4 h-4 text-cyan-600 mt-0.5 flex-shrink-0" />
|
||||
<span>{highlight}</span>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">{special.name}</CardTitle>
|
||||
<CardDescription className="flex items-center text-base">
|
||||
<MapPin className="w-4 h-4 mr-1" />
|
||||
{special.location}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 mb-4">{special.description}</p>
|
||||
<div className="space-y-2 mb-4">
|
||||
{special.highlights.map((highlight, idx) => (
|
||||
<div key={idx} className="flex items-start space-x-2 text-sm text-gray-700">
|
||||
<Tag className="w-4 h-4 text-cyan-600 mt-0.5 flex-shrink-0" />
|
||||
<span>{highlight}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<span className="text-gray-500 line-through text-lg">${special.price}</span>
|
||||
<span className="text-3xl font-bold text-cyan-600 ml-2">
|
||||
${Math.round(special.price * (1 - special.discount / 100))}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
Until {new Date(special.endDate).toLocaleDateString()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<span className="text-gray-500 line-through text-lg">${special.price}</span>
|
||||
<span className="text-3xl font-bold text-cyan-600 ml-2">
|
||||
${Math.round(special.price * (1 - special.discount / 100))}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
Until {new Date(special.endDate).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full bg-cyan-600 hover:bg-cyan-700">
|
||||
Book Now
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Button className="w-full bg-cyan-600 hover:bg-cyan-700">
|
||||
Book Now
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -190,8 +243,13 @@ const Home = () => {
|
||||
</div>
|
||||
|
||||
{/* Destinations Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredDestinations.map((destination) => (
|
||||
{loading ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-600">Loading destinations...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredDestinations.map((destination) => (
|
||||
<Card key={destination.id} className="overflow-hidden hover:shadow-xl transition-shadow duration-300 group cursor-pointer">
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
@@ -229,6 +287,7 @@ const Home = () => {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const BACKEND_URL = process.env.REACT_APP_BACKEND_URL;
|
||||
const API = `${BACKEND_URL}/api`;
|
||||
|
||||
// Create axios instance
|
||||
const apiClient = axios.create({
|
||||
baseURL: API,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Add auth token to requests if available
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Auth API
|
||||
export const authAPI = {
|
||||
login: async (email, password) => {
|
||||
const response = await apiClient.post('/auth/login', { email, password });
|
||||
return response.data;
|
||||
},
|
||||
verify: async () => {
|
||||
const response = await apiClient.post('/auth/verify');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Destinations API
|
||||
export const destinationsAPI = {
|
||||
getAll: async (category, search) => {
|
||||
const params = {};
|
||||
if (category) params.category = category;
|
||||
if (search) params.search = search;
|
||||
const response = await apiClient.get('/destinations', { params });
|
||||
return response.data;
|
||||
},
|
||||
getById: async (id) => {
|
||||
const response = await apiClient.get(`/destinations/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
create: async (destination) => {
|
||||
const response = await apiClient.post('/destinations', destination);
|
||||
return response.data;
|
||||
},
|
||||
update: async (id, destination) => {
|
||||
const response = await apiClient.put(`/destinations/${id}`, destination);
|
||||
return response.data;
|
||||
},
|
||||
delete: async (id) => {
|
||||
const response = await apiClient.delete(`/destinations/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Specials API
|
||||
export const specialsAPI = {
|
||||
getAll: async () => {
|
||||
const response = await apiClient.get('/specials');
|
||||
return response.data;
|
||||
},
|
||||
create: async (special) => {
|
||||
const response = await apiClient.post('/specials', special);
|
||||
return response.data;
|
||||
},
|
||||
update: async (id, special) => {
|
||||
const response = await apiClient.put(`/specials/${id}`, special);
|
||||
return response.data;
|
||||
},
|
||||
deleteByDestination: async (destinationId) => {
|
||||
const response = await apiClient.delete(`/specials/destination/${destinationId}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Contact API
|
||||
export const contactAPI = {
|
||||
submit: async (contact) => {
|
||||
const response = await apiClient.post('/contact', contact);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Newsletter API
|
||||
export const newsletterAPI = {
|
||||
subscribe: async (email) => {
|
||||
const response = await apiClient.post('/newsletter/subscribe', { email });
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Image Upload API
|
||||
export const uploadAPI = {
|
||||
uploadImage: async (file) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const response = await apiClient.post('/upload/image', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
export default apiClient;
|
||||
Reference in New Issue
Block a user