import { useState, useMemo, useEffect } from "react";
const ADMIN_PASSWORD = "RocketEnt93";
const STORAGE_KEY = "vendor-directory-custom";
const baseVendors = [
{ name: "Very Demure Events", category: "Coordinator", url: "https://www.instagram.com/verydemureevents_/" },
{ name: "Stoic Events", category: "Coordinator", url: "https://www.instagram.com/stoic_events_coordinator/" },
{ name: "Copper & Cove Events", category: "Coordinator", url: "https://www.instagram.com/copperandcoveevents/" },
{ name: "Shelley Ann Events", category: "Planner", url: "https://www.instagram.com/shelleyannevents/" },
{ name: "LOM Productions", category: "Planner", url: "https://www.instagram.com/lomproductions_weddings_events/" },
{ name: "MK Designs", category: "Planner", url: "https://www.instagram.com/mk_designs__/" },
{ name: "Your Party Besties", category: "Staffing", url: "https://www.instagram.com/yourpartybesties_llc" },
{ name: "Cambia Luxe Events", category: "Guestbook", url: "https://www.instagram.com/cambrialuxeevents/" },
{ name: "Absolute Comfort Limousine", category: "Transportation", url: "https://www.instagram.com/absolutecomfortlimousine/" },
{ name: "Classic Charter", category: "Transportation", url: "https://www.instagram.com/classiccharter/" },
{ name: "C&L Ranch", category: "Venue", url: "https://www.instagram.com/cl_ranch/" },
{ name: "The Venue at Sombras", category: "Venue", url: "https://www.instagram.com/venueatsombras/" },
{ name: "The Woodlands", category: "Venue", url: "https://www.instagram.com/thewoodlandsvisalia/" },
{ name: "Springville Ranch", category: "Venue", url: "https://www.instagram.com/springvilleranch/" },
{ name: "Grace Barn", category: "Venue", url: "https://www.instagram.com/gracebarn_krw/" },
{ name: "Fresno Fields", category: "Venue", url: "https://www.instagram.com/wedgewood.fresnofields/" },
{ name: "El Nido De Amor", category: "Venue", url: "https://www.instagram.com/el_nido_de_amor.venue/" },
{ name: "Tuscan Garden Venue", category: "Venue", url: "https://www.instagram.com/tuscan.gardens.venue/" },
{ name: "The Grand 1401", category: "Venue", url: "https://www.instagram.com/thegrand1401/" },
{ name: "Weddings at The Grove", category: "Venue", url: "https://www.instagram.com/thegroveweddings/" },
{ name: "Starlight Grove Events", category: "Venue", url: "https://www.instagram.com/starlightgroveevents/" },
{ name: "Evergreen Island", category: "Venue", url: "https://www.instagram.com/evergreenislandtulare/" },
{ name: "Chateau du Sureau", category: "Venue", url: "https://www.instagram.com/elderberryhouseevents/" },
{ name: "Kingsburg Ranch", category: "Venue", url: "https://www.instagram.com/kingsburgranch/" },
{ name: "Grangeville Oaks", category: "Venue", url: "https://www.instagram.com/grangevilleoaks/" },
{ name: "Sunset Acres", category: "Venue", url: "https://www.instagram.com/thesunsetacrescentralvalley/" },
{ name: "Sequoia Riverlands Trust", category: "Venue", url: "https://www.instagram.com/sequoiariverlands/" },
{ name: "Riverfront Manor", category: "Venue", url: "https://www.instagram.com/riverfrontmanor/" },
{ name: "The Nest Events & Rentals", category: "Decor & Rentals", url: "https://www.instagram.com/thenest_rentals/" },
{ name: "Ari's Arches", category: "Decor & Rentals", url: "https://www.instagram.com/ari_arches/" },
{ name: "Álvarez Balloons", category: "Decor & Rentals", url: "https://www.instagram.com/alvarezballoons" },
{ name: "Creations by Regina", category: "Decor & Rentals", url: "https://www.instagram.com/creations.by.regina" },
{ name: "CT Event Rental", category: "Decor & Rentals", url: "https://www.instagram.com/cteventrental/" },
{ name: "Expo Event Productions", category: "Decor & Rentals", url: "https://www.instagram.com/expoeventproductions/" },
{ name: "Brilliant Lounge", category: "Decor & Rentals", url: "https://www.instagram.com/brilliantlounge" },
{ name: "K O Customs", category: "Decor & Rentals", url: "https://www.instagram.com/k_o_customs" },
{ name: "Lux Balloons", category: "Decor & Rentals", url: "https://www.instagram.com/luxballoonss/" },
{ name: "B & B Decorations", category: "Decor & Rentals", url: "https://www.instagram.com/bbdecorationss__" },
{ name: "Blooming Event Decorator", category: "Decor & Rentals", url: "https://www.instagram.com/blooming_eventdecorator" },
{ name: "Elegancia Decor", category: "Decor & Rentals", url: "https://www.instagram.com/elegancia_decor/" },
{ name: "Modern Luxury Party Rentals", category: "Decor & Rentals", url: "https://www.instagram.com/mlprfresno" },
{ name: "Lavish Decor", category: "Decor & Rentals", url: "https://www.instagram.com/lavishdecor559" },
{ name: "Valley Decors", category: "Decor & Rentals", url: "https://www.instagram.com/valleydecors/" },
{ name: "Protege Events", category: "Decor & Rentals", url: "https://www.instagram.com/protegeevents/" },
{ name: "Prince J's Creations", category: "Decor & Rentals", url: "https://www.instagram.com/princejscreations11/" },
{ name: "Light Up The Walls", category: "Decor & Rentals", url: "https://www.instagram.com/lightupthewalls/" },
{ name: "Magaly's Decorations", category: "Decor & Rentals", url: "https://www.instagram.com/magalysdecorations/" },
{ name: "A Rustic Affair", category: "Decor & Rentals", url: "https://www.instagram.com/arusticaffairrentals/" },
{ name: "The Bubbles Photobooth", category: "Photo Booth", url: "https://www.instagram.com/thebubblesphotobooth" },
{ name: "A Royal Photobooth", category: "Photo Booth", url: "https://www.instagram.com/aroyalphotoboothfresno" },
{ name: "Two Little Doves", category: "Photo Booth", url: "https://www.instagram.com/twolittledoves/" },
{ name: "PhoEver Photo Booth", category: "Photo Booth", url: "https://www.instagram.com/phoeverphotobooth/" },
{ name: "Strike a Pose 360", category: "Photo Booth", url: "https://www.instagram.com/strike_a_pose360photobooths/" },
{ name: "Luis Avalos Barber", category: "Beauty", url: "https://www.instagram.com/itsluis_thebarber/" },
{ name: "makeupbyiza", category: "Beauty", url: "https://www.instagram.com/makeupbyiza/" },
{ name: "MUA Mayhem", category: "Beauty", url: "https://www.instagram.com/mua.mayhem/" },
{ name: "Jessie Arias Makeup", category: "Beauty", url: "https://www.instagram.com/jayarias/" },
{ name: "Aubriana Diane", category: "Beauty", url: "https://www.instagram.com/beautynthebrush_" },
{ name: "Paolas Bridal Beauty", category: "Beauty", url: "https://www.instagram.com/paolasbridalbeauty" },
{ name: "Magda Alsamiri", category: "Beauty", url: "https://www.instagram.com/magdaalsamiri/" },
{ name: "Glammed by Sophie", category: "Beauty", url: "https://www.instagram.com/glammedbysophie" },
{ name: "Hair by Diana", category: "Beauty", url: "https://www.instagram.com/hairbyydiana_" },
{ name: "Dañar Rodriguez", category: "Beauty", url: "https://www.instagram.com/styled_byynaee" },
{ name: "Miki The Stylist", category: "Beauty", url: "https://www.instagram.com/mikithestylist/" },
{ name: "Maricela Alcantar", category: "Beauty", url: "https://www.instagram.com/maricela_alcantar/" },
{ name: "Artistry by Erendira", category: "Beauty", url: "https://www.instagram.com/artistrybyerendira/" },
{ name: "beautybykirstenhaupt", category: "Beauty", url: "https://www.instagram.com/beautybykirstenhaupt/" },
{ name: "Silvia Gonzalez", category: "Beauty", url: "https://www.instagram.com/slys_beauty_secret/" },
{ name: "KYG Luxery Beauty", category: "Beauty", url: "https://www.instagram.com/kyg_luxerybeauty/" },
{ name: "Fredo Hair & Makeup", category: "Beauty", url: "https://www.instagram.com/fredoo_" },
{ name: "Foliage Fresno", category: "Beauty", url: "https://www.instagram.com/foliage_hairsalon/" },
{ name: "Lux Nails by Ingrid", category: "Beauty", url: "https://www.instagram.com/luxnailsbyingrid_/" },
{ name: "Annabelle's Bridal Boutique", category: "Attire", url: "https://www.instagram.com/annabellesbridal/" },
{ name: "Ivory Bridal Boutique", category: "Attire", url: "https://www.instagram.com/ivory_bridalboutique/" },
{ name: "What's Up Europe", category: "Attire", url: "https://www.instagram.com/whatsupeurope/" },
{ name: "Love in Bloom Boutique", category: "Attire", url: "https://www.instagram.com/loveinbloombridal/" },
{ name: "Madeleine's Bridal", category: "Attire", url: "https://www.instagram.com/madeleinesbridal/" },
{ name: "Tux N Tails", category: "Attire", url: "https://www.instagram.com/tux_n_tails_visalia/" },
{ name: "Tux N Ties", category: "Attire", url: "https://www.instagram.com/tux_n_ties_visalia/" },
{ name: "Encore DJs", category: "DJ", url: "https://www.instagram.com/_encoredjs/" },
{ name: "Mariachi Sol y Luna", category: "Mariachi & Banda", url: "https://www.instagram.com/mariachisolylunaoficial/" },
{ name: "Mariachi La Purisima", category: "Mariachi & Banda", url: "https://www.instagram.com/mariachilapurisima" },
{ name: "Mariachi Monarca", category: "Mariachi & Banda", url: "https://www.instagram.com/mariachi_monarca_/" },
{ name: "Mariachi Alas De Jalisco", category: "Mariachi & Banda", url: "https://www.instagram.com/mariachi_alas_de_jalisco/" },
{ name: "Mariachi Andaluz", category: "Mariachi & Banda", url: "https://www.instagram.com/mariachiandaluz/" },
{ name: "Mariachi Show 559", category: "Mariachi & Banda", url: "https://www.instagram.com/mariachishow559" },
{ name: "Banda REVO de Fresno", category: "Mariachi & Banda", url: "https://www.instagram.com/bandarevodefresno" },
{ name: "Event Bartenders 559", category: "Bar & Drinks", url: "https://www.instagram.com/eventbartenders559/" },
{ name: "Barra El Viejon", category: "Bar & Drinks", url: "https://www.instagram.com/barraelviejon" },
{ name: "Solo Una Más Bartending", category: "Bar & Drinks", url: "https://www.instagram.com/solounamas_bartending" },
{ name: "Destiny's Mobile Bar", category: "Bar & Drinks", url: "https://www.instagram.com/destinys.mobilebar" },
{ name: "Tipsy Toes Club", category: "Bar & Drinks", url: "https://www.instagram.com/tipsytoesclub" },
{ name: "Loly's Bar", category: "Bar & Drinks", url: "https://www.instagram.com/lolys_bar/" },
{ name: "Mix & Sips", category: "Bar & Drinks", url: "https://www.instagram.com/mix_n_sip00/" },
{ name: "El Mezcalero", category: "Bar & Drinks", url: "https://www.instagram.com/elmezcalero_/" },
{ name: "Shaker Mami", category: "Bar & Drinks", url: "https://www.instagram.com/shaker_mamis_bartending/" },
{ name: "Garnished Glass", category: "Bar & Drinks", url: "https://www.instagram.com/the.garnishedglass/" },
{ name: "Booze & Brews Mobile Bar", category: "Bar & Drinks", url: "https://www.instagram.com/boozebrews.mobile/" },
{ name: "Nightowl Mobile Bartending 559", category: "Bar & Drinks", url: "https://www.instagram.com/nightowlmobilebartending559/" },
{ name: "Road Shakers Bar Rentals", category: "Bar & Drinks", url: "https://www.instagram.com/road_shakers_bar_rentals_/" },
{ name: "Pour Me Mobile Bar", category: "Bar & Drinks", url: "https://www.instagram.com/pourme_mobilebar/" },
{ name: "La Cantina Mobile Bar", category: "Bar & Drinks", url: "https://www.instagram.com/lacantinamobilebarllc" },
{ name: "The Roaming Bar", category: "Bar & Drinks", url: "https://www.instagram.com/theroamingbar559" },
{ name: "Sip Social Fresno", category: "Bar & Drinks", url: "https://www.instagram.com/sipsocialfresno/" },
{ name: "Tap Truck CenCal", category: "Bar & Drinks", url: "https://www.instagram.com/taptruckcencal/" },
{ name: "The Happy Camper", category: "Bar & Drinks", url: "https://www.instagram.com/thehappycampertrailer/" },
{ name: "Boozy Belles", category: "Bar & Drinks", url: "https://www.instagram.com/theboozybelles/" },
{ name: "The Bar Babee", category: "Bar & Drinks", url: "https://www.instagram.com/thebarbabee" },
{ name: "Saddle Sips Saloon", category: "Bar & Drinks", url: "https://www.instagram.com/saddlesips/" },
{ name: "Dirty Sodas Fresno", category: "Bar & Drinks", url: "https://www.instagram.com/sippinspecialtysodas/" },
{ name: "yakimphotography", category: "Photography", url: "https://www.instagram.com/yakimphotography/" },
{ name: "libtresphoto", category: "Photography", url: "https://www.instagram.com/libtresphoto/" },
{ name: "Keith Hartman Photography", category: "Photography", url: "https://www.instagram.com/hartmanphoto/" },
{ name: "Mykal Estrada Photography", category: "Photography", url: "https://www.instagram.com/mjephotography15" },
{ name: "Erin Danae Photo", category: "Photography", url: "https://www.instagram.com/erindanaephoto/" },
{ name: "Classy Rose Photography", category: "Photography", url: "https://www.instagram.com/classyrosephotography/" },
{ name: "sagearaizaphoto", category: "Photography", url: "https://www.instagram.com/sagearaizaphoto/" },
{ name: "cats.photography", category: "Photography", url: "https://www.instagram.com/cats.photography__/" },
{ name: "Lomeli Images", category: "Photography", url: "https://www.instagram.com/lomeliimages/" },
{ name: "Isaac's Eye Photography", category: "Photography", url: "https://www.instagram.com/isaacseye/" },
{ name: "Graceful Glance Photography", category: "Photography", url: "https://www.instagram.com/gracefulglance/" },
{ name: "Stills by Steven", category: "Photography", url: "https://www.instagram.com/stillsbysteven" },
{ name: "Memories by Brianna", category: "Photography", url: "https://www.instagram.com/memoriesbybrianna" },
{ name: "Víctor Photography", category: "Photography", url: "https://www.instagram.com/victor.photography" },
{ name: "Rodríguez Media", category: "Photography", url: "https://www.instagram.com/rodriguez_media_" },
{ name: "SB Photo Film", category: "Photography", url: "https://www.instagram.com/sbphotofilm/" },
{ name: "Kapptured Photography", category: "Photography", url: "https://www.instagram.com/kappturedphotography/" },
{ name: "Atro Photography", category: "Photography", url: "https://www.instagram.com/atrophotography_" },
{ name: "Focal Point Studios", category: "Photography", url: "https://www.instagram.com/fps.photofilms/" },
{ name: "Kerys Ross Photography", category: "Photography", url: "https://www.instagram.com/kerysrossphotography/" },
{ name: "cinthiag.photo", category: "Photography", url: "https://www.instagram.com/cinthiag.photo/" },
{ name: "Michelle Gunn Photography", category: "Photography", url: "https://www.instagram.com/michellegunnphoto/" },
{ name: "Heather Nave Photography", category: "Photography", url: "https://www.instagram.com/heathernave.photography/" },
{ name: "The Good Life Photography", category: "Photography", url: "https://www.instagram.com/the_good_life_photography/" },
{ name: "Daniel Cang Photography", category: "Photography", url: "https://www.instagram.com/danielcangphotography/" },
{ name: "sjyangproduction", category: "Videography", url: "https://www.instagram.com/sjyangproduction/" },
{ name: "Vow and Tell", category: "Content Creator", url: "https://www.instagram.com/vowandtell/" },
{ name: "Content by Liz", category: "Content Creator", url: "https://www.instagram.com/contentbyliz" },
{ name: "Day of Diaries", category: "Content Creator", url: "https://www.instagram.com/dayofdiaries/" },
{ name: "The Collective Catering", category: "Catering", url: "https://www.instagram.com/reveleventscatering/" },
{ name: "Madres Tacos", category: "Catering", url: "https://www.instagram.com/madrestacos" },
{ name: "Jack's Catering", category: "Catering", url: "https://www.instagram.com/jacks.catering/" },
{ name: "Classic Catering 625", category: "Catering", url: "https://www.instagram.com/classiccatering625/" },
{ name: "Carnitas La Fondita", category: "Catering", url: "https://www.instagram.com/carnitas_la_fondita/" },
{ name: "Guisados Greicy", category: "Catering", url: "https://www.instagram.com/guisados.greicy/" },
{ name: "La Huastequita Catering", category: "Catering", url: "https://www.instagram.com/la_huastequita_catering93" },
{ name: "Los Amigos Fresno", category: "Catering", url: "https://www.instagram.com/losamigos559fresno" },
{ name: "Mama J's Catering", category: "Catering", url: "https://www.instagram.com/mamajs_catering" },
{ name: "Taqueria Don Orozco", category: "Catering", url: "https://www.instagram.com/taqueria_don_orozco_" },
{ name: "Entre Cazuelas Porterville", category: "Catering", url: "https://www.instagram.com/entrecazuelas.porterville" },
{ name: "Don Chuy's Taquiza", category: "Catering", url: "https://www.instagram.com/don_chuys_taquiza_autentica" },
{ name: "Birria y Guisados Jamay", category: "Catering", url: "https://www.instagram.com/birriayguisadosjamay/" },
{ name: "Tío Milios Tacos", category: "Catering", url: "https://www.instagram.com/tiomiliostacos" },
{ name: "Tacos Bussin Mendota", category: "Catering", url: "https://www.instagram.com/tacosbussinmendota" },
{ name: "La Cocinita de Cynthia", category: "Catering", url: "https://www.instagram.com/lacocinitadecynthia" },
{ name: "La Victoria Catering", category: "Catering", url: "https://www.instagram.com/lavictoriacatering" },
{ name: "Street Corner Tacos", category: "Catering", url: "https://www.instagram.com/street_corner_tacos_andmore/" },
{ name: "Kahlo Salsa y Mas", category: "Catering", url: "https://www.instagram.com/kahlo_salsasymas" },
{ name: "Penguin Tacos", category: "Catering", url: "https://www.instagram.com/penguin.tacos/" },
{ name: "Los Amigos Restaurant", category: "Catering", url: "https://www.instagram.com/losamigosrestaurantellc/" },
{ name: "Mi Ranchito", category: "Catering", url: "https://www.instagram.com/mi_ranchito_559/" },
{ name: "Mi Sabor 559", category: "Catering", url: "https://www.instagram.com/mi_sabor559" },
{ name: "La Cocina De Lichita", category: "Catering", url: "https://www.instagram.com/cocinadelichita/" },
{ name: "Chef Guerito", category: "Catering", url: "https://www.instagram.com/chefguerito/" },
{ name: "Yanez Catering", category: "Catering", url: "https://www.instagram.com/yanezcatering/" },
{ name: "Big Bo's Smokehouse", category: "Catering", url: "https://www.instagram.com/big.bosbbq1/" },
{ name: "Our Little Pizza Place", category: "Catering", url: "https://www.instagram.com/ourlittlepizzaplace/" },
{ name: "Cocina Y Taqueria Esparza", category: "Catering", url: "https://www.instagram.com/cocina.esparza/" },
{ name: "House of Floressence", category: "Florals", url: "https://www.instagram.com/houseoffloressence/" },
{ name: "Costco Florals", category: "Florals", url: "https://www.costco.com/bulk-flowers.html" },
{ name: "Rosie's Flower Shop", category: "Florals", url: "https://www.instagram.com/rosiesflowershopfresno" },
{ name: "Ecstasy Flower Co", category: "Florals", url: "https://www.instagram.com/ecstasyflowerco" },
{ name: "The Floral Fix", category: "Florals", url: "https://www.instagram.com/the_floral_fix" },
{ name: "Melis Ramos Eternos", category: "Florals", url: "https://www.instagram.com/melisramoseternos" },
{ name: "FleurElise Floral Studio", category: "Florals", url: "https://www.instagram.com/fleurelisestudio/" },
{ name: "Kiku Floral", category: "Florals", url: "https://www.instagram.com/kikufloral/" },
{ name: "Tropics by Design", category: "Florals", url: "https://www.instagram.com/tropicsbydesign/" },
{ name: "Blooming Moments", category: "Florals", url: "https://www.instagram.com/bloomingmomentsllc/" },
{ name: "Lily and Liv", category: "Florals", url: "https://www.instagram.com/lilyandliv.co/" },
{ name: "Ampersand Ice Cream", category: "Desserts", url: "https://www.instagram.com/ampersandicecream/" },
{ name: "Goodies Cookies", category: "Desserts", url: "https://www.instagram.com/goodiescookiesvisalia/" },
{ name: "Con Besos", category: "Desserts", url: "https://www.instagram.com/conbesos/" },
{ name: "The Treat Bar", category: "Desserts", url: "https://www.instagram.com/the.treat.bar_/" },
{ name: "Mini Pancakes by Chilla", category: "Desserts", url: "https://www.instagram.com/minipancakes_bychilla" },
{ name: "Sweets by Mony", category: "Desserts", url: "https://www.instagram.com/sweetsbymonyy/" },
{ name: "Bellas Mini Pancakes", category: "Desserts", url: "https://www.instagram.com/bellasminipancakes" },
{ name: "Angelina's Sugar Cookies", category: "Desserts", url: "https://www.instagram.com/angelinas_sugarcookies" },
{ name: "Hecho Con Amor Bakery", category: "Desserts", url: "https://www.instagram.com/h.c.a.bakery/" },
{ name: "Bake It Mine", category: "Desserts", url: "https://www.instagram.com/bakeitmine__/" },
{ name: "susan.g_creations", category: "Desserts", url: "https://www.instagram.com/susan.g_creations/" },
{ name: "Aayanah's Bakehouse", category: "Desserts", url: "https://www.instagram.com/aayanahs_bakehouse" },
{ name: "Bedwell's Bakes", category: "Desserts", url: "https://www.instagram.com/bedwells_bakes/" },
{ name: "Bebe the Baker", category: "Desserts", url: "https://www.instagram.com/bebe_the_baker/" },
{ name: "Yazzys Mini Bar", category: "Desserts", url: "https://www.instagram.com/yazzysminibar/" },
{ name: "Leon's Snack Bar", category: "Desserts", url: "https://www.instagram.com/leons_snackbar/" },
{ name: "Crazy Daizy", category: "Charcuterie", url: "https://www.instagram.com/grazydaizy" },
{ name: "Graze Craze Visalia", category: "Charcuterie", url: "https://www.instagram.com/visaliagrazecraze/" },
{ name: "Hosanna's Coffee & Matcha", category: "Coffee", url: "https://www.instagram.com/hosannasofficial/" },
{ name: "The Brew Coffee & Bakery", category: "Coffee", url: "https://www.instagram.com/thebrewcoffeeandbakery/" },
{ name: "D Rose Art Flower Preservation", category: "Flower Preservation", url: "https://www.instagram.com/mrs_d_rose/" },
];
const categoryEmojis = {
"Coordinator": "□", "Planner": "□️", "Staffing": "□", "Guestbook": "□",
"Transportation": "□", "Venue": "□", "Decor & Rentals": "✨", "Photo Booth": "□",
"Beauty": "□", "Attire": "□", "DJ": "□", "Mariachi & Banda": "□",
"Bar & Drinks": "□", "Photography": "□", "Videography": "□", "Content Creator": "□",
"Catering": "□️", "Florals": "□", "Desserts": "□", "Charcuterie": "□",
"Coffee": "☕", "Flower Preservation": "□",
};
const categoryColors = {
"Coordinator": "#c8a97e", "Planner": "#c8a97e", "Staffing": "#b8956a",
"Guestbook": "#d4b896", "Transportation": "#8a7560", "Venue": "#7d5a3c",
"Decor & Rentals": "#c17f5b", "Photo Booth": "#a0522d", "Beauty": "#c9876b",
"Attire": "#8b6b4a", "DJ": "#5c4033", "Mariachi & Banda": "#9e6b4a",
"Bar & Drinks": "#7b3f20", "Photography": "#6b4226", "Videography": "#5a3520",
"Content Creator": "#8b5e3c", "Catering": "#a0522d", "Florals": "#b87333",
"Desserts": "#d4956a", "Charcuterie": "#c4a882", "Coffee": "#6f4e37",
"Flower Preservation": "#c47c5a",
};
export default function VendorDirectory() {
const [activeCategory, setActiveCategory] = useState("All");
const [search, setSearch] = useState("");
const [customVendors, setCustomVendors] = useState([]);
const [adminOpen, setAdminOpen] = useState(false);
const [adminUnlocked, setAdminUnlocked] = useState(false);
const [pwInput, setPwInput] = useState("");
const [pwError, setPwError] = useState(false);
const [form, setForm] = useState({ name: "", category: "", customCategory: "", url: "" });
const [saveMsg, setSaveMsg] = useState("");
const [deleteConfirm, setDeleteConfirm] = useState(null);
// Load saved vendors on mount
useEffect(() => {
const load = async () => {
try {
const result = await window.storage.get(STORAGE_KEY);
if (result && result.value) {
setCustomVendors(JSON.parse(result.value));
}
} catch (e) {
// No saved vendors yet
}
};
load();
}, []);
const allVendors = useMemo(() => [...baseVendors, ...customVendors], [customVendors]);
const categories = useMemo(() => {
const cats = [...new Set(allVendors.map(v => v.category))];
return ["All", ...cats];
}, [allVendors]);
const filtered = useMemo(() => {
return allVendors.filter(v => {
const matchCat = activeCategory === "All" || v.category === activeCategory;
const matchSearch = v.name.toLowerCase().includes(search.toLowerCase()) ||
v.category.toLowerCase().includes(search.toLowerCase());
return matchCat && matchSearch;
});
}, [allVendors, activeCategory, search]);
const grouped = useMemo(() => {
if (activeCategory !== "All" || search) return null;
const groups = {};
filtered.forEach(v => {
if (!groups[v.category]) groups[v.category] = [];
groups[v.category].push(v);
});
return groups;
}, [filtered, activeCategory, search]);
const handleLogin = () => {
if (pwInput === ADMIN_PASSWORD) {
setAdminUnlocked(true);
setPwError(false);
setPwInput("");
} else {
setPwError(true);
setPwInput("");
}
};
const handleAddVendor = async () => {
const finalCategory = form.category === "__new__" ? form.customCategory.trim() : form.category;
if (!form.name.trim() || !finalCategory) return;
const newVendor = {
name: form.name.trim(),
category: finalCategory,
url: form.url.trim() || null,
custom: true,
};
const updated = [...customVendors, newVendor];
setCustomVendors(updated);
try {
await window.storage.set(STORAGE_KEY, JSON.stringify(updated));
setSaveMsg("✓ Vendor saved!");
setTimeout(() => setSaveMsg(""), 3000);
} catch (e) {
setSaveMsg("Error saving -- try again.");
}
setForm({ name: "", category: "", customCategory: "", url: "" });
};
const handleDelete = async (vendorName) => {
const updated = customVendors.filter(v => v.name !== vendorName);
setCustomVendors(updated);
try {
await window.storage.set(STORAGE_KEY, JSON.stringify(updated));
} catch (e) {}
setDeleteConfirm(null);
};
const existingCategories = [...new Set(allVendors.map(v => v.category))].sort();
return (
);
{/* Header */}
);
}
function VendorCard({ vendor, color, isAdmin, onDelete }) {
return (
559 · Central Valley
Trusted Vendor Directory
Vendors we know, love, and stand behind — hand-curated for Central Valley celebrations.
{/* Search */}
{/* Count */}
{/* Admin Modal */}
{adminOpen && (
setSearch(e.target.value)} />
{/* Category filters */}
{categories.map(cat => (
))}
{filtered.length} vendors
{/* Grouped view */}
{grouped ? (
{Object.entries(grouped).map(([cat, items]) => (
))}
) : (
{categoryEmojis[cat] || "□"}
{cat}
{items.length}
{items.map(v => (
setDeleteConfirm(v.name)} />
))}
{filtered.length === 0 ? (
setDeleteConfirm(v.name)} />
))}
)}
No vendors found for "{search}"
) : filtered.map(v => (
{ if (e.target === e.currentTarget) { setAdminOpen(false); setAdminUnlocked(false); setPwError(false); }}}>
)}
{/* Delete confirm */}
{deleteConfirm && (
{!adminUnlocked ? (
<>
{form.category === "__new__" && (
{saveMsg && (
)}
>
)}
Admin Access
Enter your password to manage vendors.
{ setPwInput(e.target.value); setPwError(false); }}
onKeyDown={e => e.key === "Enter" && handleLogin()}
autoFocus
/>
{pwError &&
>
) : (
<>
Incorrect password. Try again.
}Add a Vendor
New vendors save automatically.
setForm(f => ({ ...f, name: e.target.value }))} />
setForm(f => ({ ...f, customCategory: e.target.value }))} />
)}
setForm(f => ({ ...f, url: e.target.value }))} />
{saveMsg}
)} {/* Custom vendor list */} {customVendors.length > 0 && (Your Added Vendors ({customVendors.length})
{customVendors.map(v => (
))}
{v.name}
{v.category}
{ if (e.target === e.currentTarget) setDeleteConfirm(null); }}>
)}
□️
Remove Vendor?
This will remove "{deleteConfirm}" from the directory.
{vendor.name} {vendor.custom && ✦ new}
{vendor.category}
{vendor.url && (
Visit ↗
)}
{isAdmin && vendor.custom && (
)}
559 · Central Valley
Trusted Vendor Directory
Vendors we know, love, and stand behind — hand-curated for Central Valley celebrations.
0 vendors