mirror of
https://github.com/harivansh-afk/Austens-Wedding-Guide.git
synced 2026-04-17 13:05:06 +00:00
improved all pages and changed content to match syllabus
This commit is contained in:
parent
43bd803759
commit
b9eb7cf738
32 changed files with 1290 additions and 300 deletions
|
|
@ -1,51 +1,50 @@
|
|||
import { useState } from 'react';
|
||||
import type { VendorListing } from '../types';
|
||||
import { Search, Filter } from 'lucide-react';
|
||||
import { VendorListing, VendorCategory } from '@/types/vendors';
|
||||
import { VENDOR_LISTINGS } from '@/data/vendors';
|
||||
import VendorCard from '@/components/vendor/VendorCard';
|
||||
import VendorModal from '@/components/vendor/VendorModal';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const SAMPLE_VENDORS: VendorListing[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Pemberley Estate',
|
||||
description: 'A grand estate offering the perfect setting for your matrimonial celebration. With its extensive grounds and elegant halls, Pemberley provides an atmosphere of refined sophistication that would please even the most discerning of couples.',
|
||||
category: 'venue',
|
||||
location: 'Derbyshire',
|
||||
imageUrl: '/images/pemberley.jpg'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Mrs. Bennet\'s Matchmaking Services',
|
||||
description: 'With five daughters successfully married off, Mrs. Bennet brings her expertise to your search for the perfect match. Specializing in gentlemen of good fortune.',
|
||||
category: 'services',
|
||||
location: 'Longbourn, Hertfordshire',
|
||||
imageUrl: '/images/matchmaking.jpg'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Modiste Madame Delafield',
|
||||
description: 'Exquisite wedding attire that combines Regency elegance with modern sensibilities. Our designs have graced the most fashionable assemblies in Bath.',
|
||||
category: 'attire',
|
||||
location: 'Bath',
|
||||
imageUrl: '/images/modiste.jpg'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Meryton Assembly Catering',
|
||||
description: 'From intimate family dinners to grand balls, we provide the finest refreshments worthy of any social occasion. Known for our delectable white soup.',
|
||||
category: 'catering',
|
||||
location: 'Meryton',
|
||||
imageUrl: '/images/catering.jpg'
|
||||
}
|
||||
const CATEGORIES: VendorCategory[] = [
|
||||
'venue',
|
||||
'services',
|
||||
'attire',
|
||||
'catering',
|
||||
'music',
|
||||
'flowers',
|
||||
'transport',
|
||||
'stationery'
|
||||
];
|
||||
|
||||
const LOCATIONS = Array.from(
|
||||
new Set(VENDOR_LISTINGS.map(vendor => vendor.location))
|
||||
).sort();
|
||||
|
||||
const PRICE_RANGES = [
|
||||
{ value: 'all', label: 'All Price Ranges' },
|
||||
{ value: '£', label: '£ - Budget Friendly' },
|
||||
{ value: '££', label: '££ - Moderate' },
|
||||
{ value: '£££', label: '£££ - Premium' },
|
||||
{ value: '££££', label: '££££ - Luxury' },
|
||||
{ value: '£££££', label: '£££££ - Ultra Luxury' }
|
||||
];
|
||||
|
||||
const Vendors = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<VendorListing['category'] | 'all'>('all');
|
||||
const [selectedCategory, setSelectedCategory] = useState<VendorCategory | 'all'>('all');
|
||||
const [selectedLocation, setSelectedLocation] = useState<string | 'all'>('all');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedVendor, setSelectedVendor] = useState<VendorListing | null>(null);
|
||||
const [priceRange, setPriceRange] = useState<string | 'all'>('all');
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
||||
const filteredVendors = SAMPLE_VENDORS.filter(vendor => {
|
||||
const filteredVendors = VENDOR_LISTINGS.filter(vendor => {
|
||||
const matchesCategory = selectedCategory === 'all' || vendor.category === selectedCategory;
|
||||
const matchesLocation = selectedLocation === 'all' || vendor.location === selectedLocation;
|
||||
const matchesPrice = priceRange === 'all' || vendor.priceRange === priceRange;
|
||||
const matchesSearch = vendor.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
vendor.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
vendor.location.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
return matchesCategory && matchesSearch;
|
||||
vendor.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
return matchesCategory && matchesLocation && matchesPrice && matchesSearch;
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -54,66 +53,159 @@ const Vendors = () => {
|
|||
<header className="text-center space-y-4">
|
||||
<h1 className="font-cormorant text-4xl text-sage-900">Vendor Directory</h1>
|
||||
<p className="text-sage-700 max-w-2xl mx-auto">
|
||||
Discover the finest establishments and services to ensure your special day is nothing short of perfect
|
||||
Discover the finest establishments and services to ensure your special day is nothing short of perfect.
|
||||
From grand estates to skilled artisans, find everything you need for a celebration worthy of a Jane Austen novel.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col sm:flex-row justify-between gap-4">
|
||||
<div className="flex gap-2">
|
||||
{(['all', 'venue', 'services', 'attire', 'catering'] as const).map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={`px-4 py-2 rounded-lg capitalize ${
|
||||
selectedCategory === category
|
||||
? 'bg-sage-500 text-white'
|
||||
: 'bg-sage-100 text-sage-700 hover:bg-sage-200'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
{/* Search and Filters */}
|
||||
<div className="space-y-6">
|
||||
{/* Search Bar and Filter Toggle */}
|
||||
<div className="relative max-w-2xl mx-auto flex gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-sage-500" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search vendors..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-3 rounded-lg border border-sage-200 focus:ring-sage-500 focus:border-sage-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-4 py-3 rounded-lg border border-sage-200 hover:bg-sage-50 transition-colors",
|
||||
showFilters && "bg-sage-50 border-sage-300"
|
||||
)}
|
||||
>
|
||||
<Filter className="h-5 w-5" />
|
||||
<span>Filters</span>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search vendors..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="px-4 py-2 rounded-lg border border-sage-200 focus:ring-sage-500 focus:border-sage-500"
|
||||
/>
|
||||
|
||||
{/* Filter Controls */}
|
||||
{showFilters && (
|
||||
<div className="max-w-2xl mx-auto bg-cream-50 rounded-lg p-6 space-y-6 border border-sage-200 shadow-sm">
|
||||
{/* Categories */}
|
||||
<div className="space-y-3">
|
||||
<label className="block text-sm font-medium text-sage-700">Category</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => setSelectedCategory('all')}
|
||||
className={cn(
|
||||
"px-4 py-2 rounded-full text-sm transition-colors",
|
||||
selectedCategory === 'all'
|
||||
? "bg-sage-500 text-white"
|
||||
: "bg-white border border-sage-200 text-sage-700 hover:bg-sage-50"
|
||||
)}
|
||||
>
|
||||
All Categories
|
||||
</button>
|
||||
{CATEGORIES.map(category => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={cn(
|
||||
"px-4 py-2 rounded-full text-sm capitalize transition-colors",
|
||||
selectedCategory === category
|
||||
? "bg-sage-500 text-white"
|
||||
: "bg-white border border-sage-200 text-sage-700 hover:bg-sage-50"
|
||||
)}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Locations */}
|
||||
<div className="space-y-3">
|
||||
<label className="block text-sm font-medium text-sage-700">Location</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => setSelectedLocation('all')}
|
||||
className={cn(
|
||||
"px-4 py-2 rounded-full text-sm transition-colors",
|
||||
selectedLocation === 'all'
|
||||
? "bg-sage-500 text-white"
|
||||
: "bg-white border border-sage-200 text-sage-700 hover:bg-sage-50"
|
||||
)}
|
||||
>
|
||||
All Locations
|
||||
</button>
|
||||
{LOCATIONS.map(location => (
|
||||
<button
|
||||
key={location}
|
||||
onClick={() => setSelectedLocation(location)}
|
||||
className={cn(
|
||||
"px-4 py-2 rounded-full text-sm transition-colors",
|
||||
selectedLocation === location
|
||||
? "bg-sage-500 text-white"
|
||||
: "bg-white border border-sage-200 text-sage-700 hover:bg-sage-50"
|
||||
)}
|
||||
>
|
||||
{location}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price Ranges */}
|
||||
<div className="space-y-3">
|
||||
<label className="block text-sm font-medium text-sage-700">Price Range</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{PRICE_RANGES.map(({ value, label }) => (
|
||||
<button
|
||||
key={value}
|
||||
onClick={() => setPriceRange(value)}
|
||||
className={cn(
|
||||
"px-4 py-2 rounded-full text-sm transition-colors",
|
||||
priceRange === value
|
||||
? "bg-sage-500 text-white"
|
||||
: "bg-white border border-sage-200 text-sage-700 hover:bg-sage-50"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Results Count */}
|
||||
<div className="text-center text-sage-700">
|
||||
{filteredVendors.length} {filteredVendors.length === 1 ? 'vendor' : 'vendors'} found
|
||||
</div>
|
||||
|
||||
{/* Vendor Grid */}
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredVendors.map((vendor) => (
|
||||
<article key={vendor.id} className="bg-cream-50 rounded-lg overflow-hidden shadow-lg">
|
||||
<div className="aspect-w-16 aspect-h-9 bg-sage-200">
|
||||
{/* In a real app, this would be a proper image */}
|
||||
<div className="w-full h-48 bg-sage-300" />
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<h2 className="font-cormorant text-2xl text-sage-900 mb-2">{vendor.name}</h2>
|
||||
<p className="text-sage-700 text-sm mb-2">{vendor.location}</p>
|
||||
<span className="inline-block px-3 py-1 rounded-full text-xs capitalize bg-sage-100 text-sage-700">
|
||||
{vendor.category}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sage-700">{vendor.description}</p>
|
||||
<button className="w-full bg-sage-500 text-white px-4 py-2 rounded-lg hover:bg-sage-600 transition">
|
||||
Request Information
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
<VendorCard
|
||||
key={vendor.id}
|
||||
vendor={vendor}
|
||||
onClick={() => setSelectedVendor(vendor)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* No Results Message */}
|
||||
{filteredVendors.length === 0 && (
|
||||
<div className="text-center py-12 text-sage-700">
|
||||
No vendors found matching your criteria
|
||||
<div className="text-center py-12">
|
||||
<p className="text-sage-700 text-lg">No vendors found matching your criteria</p>
|
||||
<p className="text-sage-600 mt-2">Try adjusting your filters or search terms</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vendor Modal */}
|
||||
{selectedVendor && (
|
||||
<VendorModal
|
||||
vendor={selectedVendor}
|
||||
onClose={() => setSelectedVendor(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue