diff --git a/README.md b/README.md index 5c91169..3aef1b4 100755 --- a/README.md +++ b/README.md @@ -1,38 +1,142 @@ -# create-svelte +# NewEnglandBio Frontend -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). +SvelteKit web application for heating oil and biofuel price comparison across New England's 6 states (MA, CT, RI, VT, NH, ME). -## Creating a project +## Tech Stack -If you're seeing this, you've probably already done this step. Congrats! +- **Framework:** SvelteKit with TypeScript +- **Styling:** Tailwind CSS + DaisyUI +- **Icons:** lucide-svelte +- **Build:** Vite, adapter-node +- **Maps:** Pure SVG (no map library) — path data in `src/lib/states/*.ts` -```bash -# create a new project in the current directory -npm create svelte@latest +## Project Structure -# create a new project in my-app -npm create svelte@latest my-app +``` +src/ +├── lib/ +│ ├── api/ +│ │ ├── client.ts # Fetch wrapper with auth, error handling +│ │ ├── types.ts # API response types +│ │ └── index.ts +│ ├── types/ +│ │ └── types.ts # App-wide TypeScript interfaces +│ ├── states/ # SVG county path data per state +│ │ ├── connecticut.ts +│ │ ├── maine.ts +│ │ ├── massachusetts.ts +│ │ ├── newhampshire.ts +│ │ ├── rhodeisland.ts +│ │ └── vermont.ts +│ └── states.ts # State/county stores and data +└── routes/ + ├── +error.svelte + └── (app)/ + ├── +layout.svelte # Navbar, footer, dark mode + ├── +page.svelte # Home — interactive NE state map + ├── login/+page.svelte + ├── register/+page.svelte + ├── [stateSlug]/ + │ ├── +page.svelte # County map for a state + │ └── [countySlug]/ + │ └── +page.svelte # Listings & oil prices for a county + └── vendor/ + ├── +layout.svelte + ├── +page.svelte # Vendor dashboard + ├── profile/+page.svelte # Edit company profile + └── listing/ + ├── +page.svelte # Create listing + └── [id]/+page.svelte # Edit listing ``` -## Developing +## Routes -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +| Path | Access | Description | +|------|--------|-------------| +| `/` | Public | Interactive New England map | +| `/login` | Public | Login form | +| `/register` | Public | Registration form | +| `/:stateSlug` | Public | County map for a state (e.g. `/MA`) | +| `/:stateSlug/:countySlug` | Public | Listings and oil prices for a county | +| `/vendor` | Auth | Vendor dashboard | +| `/vendor/profile` | Auth | Edit company profile | +| `/vendor/listing` | Auth | Create new listing | +| `/vendor/listing/:id` | Auth | Edit a listing | + +## Setup + +```bash +npm install +``` + +### Environment + +Create `.env`: + +``` +PUBLIC_API_URL=http://localhost:9552 +``` + +### Development ```bash npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open ``` -## Building +Dev server starts on **port 5169** with `--host` for network access. Vite proxies `/api` requests to the Rust API during development. -To create a production version of your app: +### Production Build ```bash npm run build +node build ``` -You can preview the production build with `npm run preview`. +Runs on **port 3000** using adapter-node. -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. +## Docker + +**Development:** +```bash +docker build -f Dockerfile -t frontend-dev . +``` + +**Production (multi-stage):** +```bash +docker build -f Dockerfile.prod -t frontend-prod . +docker run -p 3000:3000 frontend-prod +``` + +## API Client + +The API client (`src/lib/api/client.ts`) provides typed methods for all backend interactions: + +- **Auth:** `login()`, `register()`, `logout()`, `isAuthenticated()`, `getStoredUser()` +- **Company:** `getCompany()`, `createCompany()`, `updateCompany()`, `deleteCompany()` +- **Listings:** `getListings()`, `getListingById()`, `createListing()`, `updateListing()`, `deleteListing()` +- **Public:** `getListingsByCounty()`, `getOilPricesByCounty()`, `getCountiesByState()`, `getCategories()` + +Auth uses JWT stored as an httpOnly cookie. User info cached in localStorage. + +## Styling + +Tailwind + DaisyUI with two themes (light/dark). Custom utility classes: + +- `.btn-blue-oil` — primary CTA buttons +- `.bg-orange-oil` — accent backgrounds +- `.btn-state` — map state buttons + +## Scripts + +| Command | Description | +|---------|-------------| +| `npm run dev` | Dev server (port 5169) | +| `npm run build` | Production build | +| `npm run preview` | Preview production build | +| `npm run check` | Svelte type checking | +| `npm run check:watch` | Type checking (watch mode) | + +## Known Issues + +- Maine SVG map paths are inaccurate (CRIT-001) +- API URL `http://localhost:9552` is hardcoded in some components diff --git a/src/app.postcss b/src/app.postcss index d95aeb8..3943f4c 100755 --- a/src/app.postcss +++ b/src/app.postcss @@ -26,3 +26,156 @@ .btn-state { @apply btn btn-outline btn-secondary btn-xs px-1 py-1 text-xs leading-none; } + +/* Home page hero gradient - warm, inviting feel */ +.hero-gradient { + background: linear-gradient(135deg, #0256bf 0%, #1d4ed8 40%, #7c2d12 100%); +} + +/* Subtle warm glow behind the map */ +.map-glow { + box-shadow: 0 0 60px rgba(255, 102, 0, 0.08), 0 0 120px rgba(2, 86, 191, 0.06); +} + +[data-theme="dark"] .map-glow { + box-shadow: 0 0 60px rgba(255, 102, 0, 0.12), 0 0 120px rgba(2, 86, 191, 0.1); +} + +/* State card hover lift effect */ +.state-card { + @apply transition-all duration-200 ease-out; +} +.state-card:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); +} +[data-theme="dark"] .state-card:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); +} + +/* Staggered animation delays for scroll-reveal elements */ +.stagger-1 { animation-delay: 0.1s; } +.stagger-2 { animation-delay: 0.2s; } +.stagger-3 { animation-delay: 0.3s; } +.stagger-4 { animation-delay: 0.4s; } +.stagger-5 { animation-delay: 0.5s; } +.stagger-6 { animation-delay: 0.6s; } + +/* Value prop icon container */ +.value-icon { + @apply flex items-center justify-center w-14 h-14 rounded-full; + background: linear-gradient(135deg, #ff6600 0%, #fb923c 100%); + color: white; +} + +[data-theme="dark"] .value-icon { + background: linear-gradient(135deg, #e55a00 0%, #c2410c 100%); +} + +/* Reusable map container — wraps any interactive SVG map with glow + framing */ +.map-container { + @apply relative map-glow rounded-2xl bg-base-100 p-4 sm:p-6 border border-base-300; +} + +/* Map container label — "Interactive Map" / "Interactive County Map" */ +.map-label { + @apply text-xs font-semibold uppercase tracking-wider text-base-content/40; +} + +/* Map hover indicator — shows current hovered element name */ +.map-hover-indicator { + @apply text-center mb-2 h-8 flex items-center justify-center; +} + +/* Accent pill badge — used for section accents (flame badge, state badge, etc.) */ +.accent-badge { + @apply inline-flex items-center gap-2 px-4 py-2 rounded-full + bg-oil-orange-50 border border-oil-orange-200 + text-sm font-medium text-oil-orange-700; +} +[data-theme="dark"] .accent-badge { + @apply bg-oil-orange-900/20 border-oil-orange-700/40 text-oil-orange-300; +} + +/* County card — interactive grid item with cross-highlight support */ +.county-card { + @apply state-card rounded-xl border-2 p-3 sm:p-4 + bg-base-100 border-base-300 + cursor-pointer select-none + focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2; +} +.county-card:hover, +.county-card.is-highlighted { + @apply border-primary bg-primary/5; +} +[data-theme="dark"] .county-card:hover, +[data-theme="dark"] .county-card.is-highlighted { + @apply border-primary bg-primary/10; +} + +/* Skeleton loading placeholder */ +.skeleton-box { + @apply animate-pulse rounded-xl bg-base-300/50; +} + +/* ============================================================ + PRICE TABLE SYSTEM + Reusable table, row, and card styles for pricing pages + ============================================================ */ + +/* Table wrapper — rounded container with border, no zebra */ +.price-table-wrap { + @apply w-full rounded-xl border border-base-300 bg-base-100 overflow-hidden; +} +[data-theme="dark"] .price-table-wrap { + @apply border-base-300/60; +} + +/* Sortable column header — clickable with hover + active state */ +.sort-header { + @apply cursor-pointer select-none whitespace-nowrap + text-base-content/60 font-semibold text-xs uppercase tracking-wider + transition-colors duration-150; +} +.sort-header:hover { + @apply text-base-content; +} +.sort-header.is-active { + @apply text-primary; +} + +/* Price display — the hero number on every listing */ +.price-hero { + @apply text-2xl sm:text-3xl font-extrabold leading-none; + color: theme('colors.oil-orange.600'); +} +[data-theme="dark"] .price-hero { + color: theme('colors.oil-orange.400'); +} + +/* Secondary price label (cash price, etc.) */ +.price-secondary { + @apply text-sm font-medium text-base-content/50; +} + +/* Listing card — mobile card for a single dealer/price row */ +.listing-card { + @apply state-card rounded-xl border border-base-300 + bg-base-100 p-4 sm:p-5; +} +[data-theme="dark"] .listing-card { + @apply border-base-300/60; +} + +/* Section container — wraps Premium Dealers / Market Prices */ +.price-section { + @apply rounded-2xl bg-base-200/30 border border-base-300/50 p-4 sm:p-6; +} +[data-theme="dark"] .price-section { + @apply bg-base-200/20 border-base-300/30; +} + +/* Info note — subtle helper text below section headers */ +.info-note { + @apply text-sm text-base-content/50 flex items-center gap-1.5; +} diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 8435111..7d2fe00 100755 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -1,49 +1,37 @@ - + + + Biz Hero - Compare Heating Oil Prices in New England + + + + + +
+ {#if heroVisible} +
+ +
+
+ + New England's Heating Oil Price Guide +
+
-
-

Welcome to TradeWar, your go-to source for fuel prices across New England. Compare prices by state and county to find the best deals.

-

Click your state to find prices.

-
+ +

+ Find the Best Heating Oil Prices + + in New England +

-
- {#if browser} - - Interactive Map of New England States - {#each newEnglandStates as state} - handleStateClick(state.id)} - on:mouseenter={() => handleMouseEnter(state.id)} - on:mouseleave={handleMouseLeave} - aria-label={state.id} - tabindex="0" - role="link" - on:keydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleStateClick(state.id)}} - > - {state.name} - - {/each} - - {:else} -
-

Loading map...

+ +

+ Compare prices from local dealers across all six states. + +

+ + +
+ + Click your state on the map to get started + +
{/if} -
+
-
-

States:

- -
+ + + +
+ {#if mapVisible} +
+ +
+ Interactive Map +
+ +
+ {#if hoveredState} + + {newEnglandStates.find(s => s.id === hoveredState)?.name ?? ''} + + {:else} + Hover or tap a state + {/if} +
+ + + {#if browser} + + Interactive Map of New England States + {#each newEnglandStates as state} + handleStateClick(state.id)} + on:mouseenter={() => handleMouseEnter(state.id)} + on:mouseleave={handleMouseLeave} + aria-label={state.id} + tabindex="0" + role="link" + on:keydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleStateClick(state.id)}} + > + {state.name} + + {/each} + + {:else} +
+

Loading map...

+
+ {/if} +
+ {/if} +
+ + + + +
+ {#if statesVisible} +
+

+ Browse by State +

+

+ Select your state to see prices in every county +

+ +
+ {#each newEnglandStates as state, i} + {@const colors = stateColors[state.id] ?? { bg: 'bg-base-200', border: 'border-base-300', text: 'text-base-content', hover: 'hover:bg-base-300' }} + {@const darkColors = stateColorsDark[state.id] ?? { bg: 'dark:bg-base-200', border: 'dark:border-base-300', text: 'dark:text-base-content', hover: 'dark:hover:bg-base-300' }} + handleMouseEnter(state.id)} + on:mouseleave={handleMouseLeave} + > + +
+ {state.name} outline +
+ + + + {state.name} + + + + +
+ {/each} +
+
+ {/if} +
+ + + + +
+ {#if valueVisible} +
+

+ Why Use Biz Hero? +

+

+ Helping New England homeowners make smarter heating decisions +

+ +
+ {#each valueProps as prop, i} +
+
+ +
+

+ {prop.title} +

+

+ {prop.description} +

+
+ {/each} +
+
+ {/if} +
+ + + + {#if !$user} -
- Dealer Login -
+
+
+

+ Are you a heating oil dealer? +

+ + Dealer Login + + +
+
{/if} diff --git a/src/routes/(app)/[stateSlug]/+page.svelte b/src/routes/(app)/[stateSlug]/+page.svelte index 79c2688..1e28d0f 100755 --- a/src/routes/(app)/[stateSlug]/+page.svelte +++ b/src/routes/(app)/[stateSlug]/+page.svelte @@ -1,4 +1,4 @@ - + + + + {#if stateData} + Heating Oil Prices in {stateData.name} | Biz Hero + + {:else} + State Not Found | Biz Hero + {/if} + + + {#if stateData} - -