improved all pages and changed content to match syllabus

This commit is contained in:
Harivansh Rathi 2024-11-29 13:05:39 -05:00
parent 43bd803759
commit b9eb7cf738
32 changed files with 1290 additions and 300 deletions

View file

@ -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>
);
};