Blog

Building My Daily Hub — Phase 1

User settings, live weather, a curated Bible verse of the day, and the architectural decision that changed how I think about component design.

My Daily Hub started as a capstone project for my Code:You software development learning path — a simple HTML, CSS, and JavaScript app to check the boxes. But it quickly became something more: a personal tool I actually wanted to use, and a portfolio project that lets me build real features with the stack I want to work in professionally. React, TypeScript, Supabase, and vanilla CSS. No shortcuts, no magic frameworks doing the heavy lifting.

Phase 1 covered three tools: User Settings, Weather, and Bible Verses. Each one sounds simple on paper. None of them were.

The Stack Decision

I chose React over Vue or Svelte deliberately — not because it's my favorite, but because it's what employers want to see. TypeScript adds type safety and makes the codebase easier to reason about as it grows. Supabase gives me a real PostgreSQL database with Row Level Security, which lets me demonstrate actual SQL skills rather than hiding behind an ORM abstraction. The whole app runs in two modes: authenticated (cloud-synced via Supabase) and demo (localStorage only), so portfolio visitors can explore without creating an account.

That dual-mode architecture is one of the things I'm most satisfied with. Every service file checks auth state and routes to either Supabase or localStorage transparently. The components don't know or care which data source they're talking to.

User Settings — More Than a Form

User Settings was the first tool built, and intentionally so. It established the patterns everything else follows: the service abstraction layer, the upsert pattern for Supabase, form validation, error handling, and the demo mode indicator. Getting this right meant the subsequent tools had a solid foundation to build on.

One early bug worth mentioning: the Supabase upsert was missing the onConflict option, which caused a 409 error when trying to update existing settings. A small fix — adding { onConflict: 'user_id' } to the upsert call — but it's exactly the kind of thing that only surfaces when you're working with real data against a real database.

Weather — Caching and API Design

The weather tool pulls current conditions plus a six-hour hourly forecast and a four-day outlook from WeatherAPI. The hourly data required fetching four days instead of three to handle late-night rollover correctly — if it's 11 PM, you need tomorrow's hours to fill six slots. A small but satisfying detail.

I added a fifteen-minute localStorage cache so the app doesn't hammer the API on every page visit. The cache key includes the city name, so changing your city in settings automatically invalidates the old cache and fetches fresh data.

The Card Refactor — Where It Got Interesting

This is the decision I want to talk about most, because it's where the architecture got genuinely interesting.

The plan from the start was to build a home dashboard that shows a summary of all the tools — current weather, today's Bible verse, tasks due today, and a habit progress snapshot. When I got to building the dashboard, I realized I was duplicating code. The weather display I'd built for the full Weather page was being copy-pasted into the dashboard. Same with the Bible verse card. That's a maintenance problem — change the weather display in one place and you have to remember to change it in the other.

The fix was to extract each tool's dashboard summary into its own reusable card component — CurrentWeatherCard, VerseOfTheDayCard — stored in a shared src/components/cards/ folder. The full tool pages import and use these cards. The dashboard imports and uses the same cards. One source of truth.

But extracting the cards introduced a new problem: duplicate API calls. CurrentWeatherCard fetched its own weather data. So did the full Weather page. When a user visited the Weather tab, two separate fetch calls went out. The fix was optional props — the card accepts a weather prop. If provided, it uses that data. If not, it fetches its own. The full Weather page passes its already-fetched data down. The dashboard renders the card standalone and lets it fetch. Clean, flexible, no duplication.

This pattern — reusable card components with optional data props — is now the standard for every tool going forward. It's the kind of architectural decision that only becomes obvious once you've hit the problem it solves.

Bible Verses — Static Data Done Right

The Bible Verse tool uses API.Bible to fetch verse text, but the verse selection comes from a curated list of one hundred verses I maintain in a TypeScript file. The day of year modulo one hundred picks the verse, so it rotates predictably without a database table. The list is organized into nine themes — Faith & Salvation, Hope & Future, Strength & Courage, and so on — and the component derives the category cards dynamically from the same data file. One source of truth again.

Caching here is date-based: once fetched for today, the verse is stored in localStorage until midnight. No unnecessary API calls, no flash of loading state on repeat visits.

What's Next

Phase 2 is the To Do List — the tool I'll actually use every day, which means it needs to be right. After that: Goal Definitions and Habit Tracking, Journaling, and eventually Favorite Links and Quotes. Each completed tool gets a dashboard card. The home screen fills in as the tools ship.

The live app is at dailyhub.christopherkaelin.com — demo mode works without an account if you want to explore.