Skip to content

Instantly share code, notes, and snippets.

@collegeman
Created February 28, 2026 20:41
Show Gist options
  • Select an option

  • Save collegeman/a85f9488c4d815d9e54ea6533dd1ccdd to your computer and use it in GitHub Desktop.

Select an option

Save collegeman/a85f9488c4d815d9e54ea6533dd1ccdd to your computer and use it in GitHub Desktop.
brianhelton.com: Laravel 12 + React site conversion plan

Plan: Convert brianhelton.com Figma Export to Laravel 12 + React Site

Context

Brian Helton's portfolio site exists as a Figma-exported React SPA in brianHeltonDotCom/. It has 3 pages (Home, Bio, Portfolio) with the Portfolio gated by a password — but the protection is entirely client-side (hardcoded password in JS, images publicly accessible). We need to turn this into a production site backed by Laravel 12 so the portfolio content is truly protected server-side. Then commit and push to git@github.com:collegeman/brianhelton.com.git.

Architecture

Laravel serves the React SPA + provides API routes for portfolio auth:

  • POST /api/portfolio/verify — checks password, sets server session
  • GET /api/portfolio/items — returns portfolio data (only if authenticated)
  • GET /api/portfolio/images/{filename} — serves protected images (only if authenticated)
  • GET /{any} — catch-all serves the React SPA via Blade template

Portfolio images move to storage/app/portfolio/ (private). Headshot stays in public/images/ (public). Password moves to .env. Portfolio data (titles, descriptions) moves to the controller — removed from the JS bundle entirely.

Key Fix

images_old.png needs renaming to imagery_old.png to match portfolio data references.

Dead Code Removal

The Figma export includes ~50 unused shadcn/ui components in src/app/components/ui/ and src/app/components/figma/ — none are imported. These will not be migrated. Similarly, ~25 unused npm dependencies (Radix UI, recharts, react-dnd, etc.) will be dropped.

Steps

1. Git init + commit original source

  • git init, add remote, commit brianHeltonDotCom/ as-is for history

2. Install Laravel 12

  • composer create-project laravel/laravel _laravel_tmp in a temp dir
  • rsync -a _laravel_tmp/ . --exclude='.git' to overlay onto repo root
  • Remove temp dir

3. Organize images

  • Copy headshot.jpegpublic/images/
  • Copy portfolio images → storage/app/portfolio/
  • Rename images_old.pngimagery_old.png during copy

4. Migrate React source to resources/js/

  • Copy only used files: main.tsx, App.tsx, routes.tsx, theme.ts, styles.css
  • Components: Layout.tsx, ImageCompare.tsx, PortfolioCarousel.tsx, PortfolioGate.tsx
  • Pages: Home.tsx, BioDetail.tsx, Portfolio.tsx
  • Styles: fonts.css, index.css, tailwind.css, theme.css
  • Skip: data/portfolio.ts (moves to server), components/ui/, components/figma/

5. Create package.json with merged deps

Only actually-used packages:

  • Runtime: @emotion/react, @emotion/styled, @mui/material, @mui/icons-material, motion, react, react-dom, react-router, tw-animate-css
  • Dev: @tailwindcss/vite, @types/react, @types/react-dom, @vitejs/plugin-react, laravel-vite-plugin, tailwindcss, typescript, vite

6. Configure vite.config.ts

Laravel Vite plugin + React plugin + Tailwind CSS plugin, entry: resources/js/main.tsx

7. Create Blade SPA shell

resources/views/app.blade.php — minimal HTML with @viteReactRefresh + @vite('resources/js/main.tsx') + <div id="root">

8. Create PortfolioController

  • verify(): Validates password against config('services.portfolio.password'), sets session
  • items(): Returns full portfolio data as JSON (all 5 items with titles, descriptions, image URLs pointing to /api/portfolio/images/...)
  • image(): Streams file from storage/app/portfolio/ with cache headers

9. Create PortfolioAuthenticated middleware

Checks session('portfolio_authenticated'), returns 401 if not set. Register alias in bootstrap/app.php.

10. Configure routes in routes/web.php

All portfolio API routes defined in web.php (not api.php) to share session middleware. SPA catch-all route must come last with negative lookahead for /api.

11. Environment config

  • Add PORTFOLIO_PASSWORD=ECiY9mMmdGZ6cwFZrxW8 to .env
  • Add portfolio.password to config/services.php

12. Modify PortfolioGate.tsx

  • Remove hardcoded password
  • handleSubmit calls POST /api/portfolio/verify with CSRF token from XSRF-TOKEN cookie
  • On success, calls onUnlock() (no more sessionStorage)

13. Modify Portfolio.tsx

  • Remove import of portfolioItems from ../data/portfolio
  • On mount, try GET /api/portfolio/items to check existing session
  • After unlock, fetch items from API
  • Pass fetched items to PortfolioCarousel

14. Update type imports

  • Move PortfolioItem interface to a shared types file or define locally in Portfolio.tsx and PortfolioCarousel.tsx

15. npm install

16. Verify

  • php artisan serve + npm run dev
  • Test: Home page loads, Bio page shows headshot, Portfolio shows gate
  • Test: Wrong password → error, correct password → carousel with images
  • Test: Direct image URL /api/portfolio/images/shell_new.png → 401 without auth

17. Delete brianHeltonDotCom/ directory

18. Final commit + git push -u origin main

Critical Files to Modify/Create

File Action
app/Http/Controllers/PortfolioController.php Create
app/Http/Middleware/PortfolioAuthenticated.php Create
bootstrap/app.php Modify (register middleware alias)
config/services.php Modify (add portfolio password)
resources/views/app.blade.php Create
resources/js/app/components/PortfolioGate.tsx Modify (API calls)
resources/js/app/pages/Portfolio.tsx Modify (fetch from API)
resources/js/app/components/PortfolioCarousel.tsx Modify (type import)
routes/web.php Modify (API + catch-all)
vite.config.ts Replace
package.json Replace
.env Modify
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment