Sarah Vandy | Predator Fishing
A full-stack brand platform for Sarah Vandy, a competitive angler and outdoor personality — a richly animated Next.js 16 public site covering her journey, tournament achievements, media, sponsors, promotions, and a blog, backed by an ASP.NET Core 8 + PostgreSQL API with a JWT-secured admin dashboard for managing every piece of content, deployed on AWS with S3 and CloudFront for media.
Overview
Sarah Vandy is a full-stack platform built for a competitive fishing and outdoor brand, pairing a Next.js 16 public site with an ASP.NET Core 8, Entity Framework Core, and PostgreSQL backend. The public site covers a hero, an about page with a personal journey timeline, a tournament achievements timeline, a filterable media gallery with a full-screen lightbox, a sponsor showcase with a featured-partners marquee, a promotions library with an auto-advancing homepage popup ad and in-page YouTube playback, and a blog engine with rich content — headings, quotes, images, tables, checklists, and embedded video — authored in Tiptap and stored as structured JSON. Every page ends in the same footer with contact details, newsletter sign-up, and social links, and the shared call-to-action scrolls straight to it. The experience runs on a dual light/dark theme with no flash of the wrong theme on load, a bespoke animated cursor, a splash loader, smooth scrolling, and a back-to-top control.
Behind the scenes sits a JWT-secured admin dashboard, built inside the same Next.js codebase under `/admin`, where the athlete manages her Journey, Achievements, Sponsors, Gallery, Promotions, Blogs, newsletter subscribers, Site Settings (contact details and social profile links), homepage section order and visibility, and staff accounts. The dashboard opens with live content counts and recent subscribers. The blog editor is deliberately restricted to the exact node and mark set the backend's content validator allow-lists, with every submitted document re-checked server-side against that same list and scanned for unsafe link and image URL schemes, so a compromised session or an editor bug can never persist something that turns into stored XSS on the public blog. Admin tooling also includes a dependency-free canvas image cropper for fixed-aspect assets, drag-to-reorder lists, per-list search, and a confirmation step before destructive actions.
Auth uses short-lived access tokens kept in memory plus a hashed refresh token delivered as an httpOnly cookie and rotated on every use, with an optional "remember me" that extends the refresh token's lifetime, role-based permissions (Admin/SuperAdmin) that guard against locking out the last remaining SuperAdmin, rate-limited sign-in endpoints, and automatic account lockout with an email alert after repeated failed logins. Every uploaded image is verified by its actual file signature rather than its filename or declared content type, and storage abstracts cleanly between local disk and Amazon S3 by config. Transactional email — credential delivery, password resets, lockout alerts — runs through a swappable email service abstraction so the provider behind it can change without touching a single controller. In production the Next.js app and the .NET API run on an AWS Lightsail Ubuntu server behind Nginx with Let's Encrypt TLS, while images are stored in a private S3 bucket and served through CloudFront on the brand's own images subdomain. The brand's extra domains redirect to the main site, and the database is backed up to S3 with retention rules.
Public Brand Site
- Hero, about page with a personal journey timeline, and a tournament achievements timeline showcasing championships, Angler of the Year titles, and feature publications.
- Filterable media gallery (Fishing, Hunting, Adventures, Behind the Scenes) with a full-screen lightbox.
- Sponsor showcase linking each partner to its real site, with a featured-partners marquee that spotlights pinned sponsors on the homepage.
- A promotions library with an auto-advancing homepage popup ad (carousel controls, crossfade transitions) and an in-page YouTube video modal for sponsor films.
- A blog with rich, block-based posts — headings, quotes, images, tables, checklists, and embedded video — rendered from the same Tiptap JSON the admin editor produces.
- A footer on every page with contact details, newsletter sign-up, and social links, plus a call-to-action that scrolls straight to it from any page.
- Social links shared by the hero, footer, and mobile menu: the profile URLs set in the admin panel are used, with built-in defaults as a fallback.
- Dual light/dark theme with no flash of the wrong theme on load, a bespoke animated cursor on desktop, a branded splash loader, smooth scrolling, and a back-to-top control.
- Server-rendered pages that fall back to built-in content if the API is unreachable, plus a generated sitemap, robots file, and Open Graph metadata.
Admin Content Management Panel
- Dashboard with live content counts and recent newsletter subscribers, plus CRUD across Journey, Achievements, Sponsors, Gallery, Promotions, Blogs, subscribers, Site Settings, and Users.
- Homepage section manager to reorder and show/hide entire page sections without touching code, gated behind an explicit confirm step.
- Tiptap blog editor deliberately restricted to the exact node/mark set the backend's content validator allow-lists, with every save re-verified server-side to block stored XSS before it ever reaches the public blog.
- Dependency-free canvas image cropper for fixed-aspect hero/portrait/sponsor assets, free-size uploads for the gallery, and drag-to-reorder for anything with a display order.
- Site Settings for contact details and social profile links, so staff can update them without a developer.
- Per-list search, staff and SuperAdmin roles, a profile page with password change, and a confirmation step before every delete.
Backend, Security & Infrastructure
- ASP.NET Core 8 Web API with Entity Framework Core over PostgreSQL, structured around a shared base model and one reusable content-management pattern per entity, with versioned database migrations.
- JWT auth with in-memory access tokens and a hashed, httpOnly-cookie refresh token rotated on every refresh, an optional "remember me" for extended sessions, and role-based permissions that guard the last remaining SuperAdmin.
- Rate-limited sign-in endpoints and automatic account lockout with an email alert after repeated failed logins, plus self-service forgot-password, reset-password, and change-password flows.
- Every uploaded image is verified against its real file signature rather than its filename or declared content type, with a fixed list of allowed upload folders and a file size cap.
- Storage abstraction swaps between local disk and Amazon S3 by config, with transactional email routed through a swappable service so the provider can change without touching a single controller.
- Deployed on an AWS Lightsail Ubuntu server behind Nginx with Let's Encrypt TLS, a systemd-managed API, and a pm2-managed frontend.
- Images live in a private S3 bucket reachable only through CloudFront, using least-privilege IAM users and a bucket policy scoped to the image folders; they are served from the brand's own images subdomain.
- Domain setup with the main site on its .com.au address and the brand's other domains redirecting to it over HTTPS; database backups run to S3 with retention rules and a budget alert watches cost.