← All Projects

NEO Wineries

A searchable directory and visit journal for wineries across Northeast Ohio.

PHP Laravel MariaDB Auth0 Filament Google Places API
NEO Wineries screenshot

Wineries in Northeast Ohio are spread across many counties, and there was no single place to browse them all. NEO Wineries lists more than 130 of them, with hours, locations, and a map. Visitors can filter by county and amenities such as gluten-free options, and signed-in users can keep a journal of the wineries they have visited. It started as an older PHP site and was rebuilt on Laravel, with the existing production data (wineries, users, and visit history) carried over through a one-time import command.

A Laravel application backed by MariaDB in production and SQLite for local development. The frontend is Vite and Sass over a Bootstrap foundation with a custom palette, plus a little vanilla JavaScript and a Swiper hero carousel. There is no SPA framework. Filters are real URLs rather than JavaScript-only state, for example /wineries?county=Geauga&gluten_free=1, rendered on the server through a model scope, so every filtered view is crawlable and linkable. Winery pages live at bare slugs to preserve links from the old site, and the sitemap is generated dynamically from the database. Middleware forces HTTPS in production and adds security headers on top of the server's own. Content is managed through a Filament admin panel, restricted to admin users. Hours and map coordinates are not maintained by hand: each winery is linked to a Google Place ID, and a daily scheduled Artisan command syncs both from the Google Places API. A separate, referrer-restricted key powers the public map page.

The hardest bug was login that succeeded and then did not stick. The Auth0 package's own session guard wrote credentials correctly during the callback, but they did not reliably survive to the next request, so a technically successful login showed as logged out on the next page load. Rather than guess, temporary diagnostic event listeners confirmed exactly where the state was lost. The fix was to use Auth0 only for the OAuth handshake, with custom login, callback, and logout controllers that hand off to Laravel's own session guard for everything after. A related snag came from the admin panel. Auth0 does not always supply a display name, and Filament's default name resolution expects a non-null string, so an admin without a name got a 500. Implementing Filament's HasName contract with an email fallback fixed it. Migrating the old site also had to be safe to rehearse. The import command has a dry-run mode that reports counts only, so the production data could be checked before anything was written, and winery URLs were kept as bare slugs behind a catch-all route that must stay last, so existing links kept working.

Visit Site