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.
Laravel serves the React SPA + provides API routes for portfolio auth:
POST /api/portfolio/verify— checks password, sets server sessionGET /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.
images_old.png needs renaming to imagery_old.png to match portfolio data references.
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.
git init, add remote, commitbrianHeltonDotCom/as-is for history
composer create-project laravel/laravel _laravel_tmpin a temp dirrsync -a _laravel_tmp/ . --exclude='.git'to overlay onto repo root- Remove temp dir
- Copy
headshot.jpeg→public/images/ - Copy portfolio images →
storage/app/portfolio/ - Rename
images_old.png→imagery_old.pngduring copy
- 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/
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
Laravel Vite plugin + React plugin + Tailwind CSS plugin, entry: resources/js/main.tsx
resources/views/app.blade.php — minimal HTML with @viteReactRefresh + @vite('resources/js/main.tsx') + <div id="root">
verify(): Validates password againstconfig('services.portfolio.password'), sets sessionitems(): Returns full portfolio data as JSON (all 5 items with titles, descriptions, image URLs pointing to/api/portfolio/images/...)image(): Streams file fromstorage/app/portfolio/with cache headers
Checks session('portfolio_authenticated'), returns 401 if not set. Register alias in bootstrap/app.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.
- Add
PORTFOLIO_PASSWORD=ECiY9mMmdGZ6cwFZrxW8to.env - Add
portfolio.passwordtoconfig/services.php
- Remove hardcoded password
handleSubmitcallsPOST /api/portfolio/verifywith CSRF token fromXSRF-TOKENcookie- On success, calls
onUnlock()(no moresessionStorage)
- Remove import of
portfolioItemsfrom../data/portfolio - On mount, try
GET /api/portfolio/itemsto check existing session - After unlock, fetch items from API
- Pass fetched items to
PortfolioCarousel
- Move
PortfolioIteminterface to a shared types file or define locally inPortfolio.tsxandPortfolioCarousel.tsx
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
| 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 |