Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '24'
cache: npm

- name: Install dependencies
Expand Down
329 changes: 35 additions & 294 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,320 +1,61 @@
# ShadowStack
# Andrew Thompson Portfolio

> A modern developer portfolio built to showcase projects, technical
> skills, experience, and ongoing software development work.
Recruiter-focused portfolio for Andrew Thompson, a backend-focused full-stack developer building database-driven applications and REST APIs.

ShadowStack is my personal developer portfolio. It serves as a central
place to present the applications I build, the technologies I work with,
and my growth as a software developer.
V2 replaces the former ShadowStack public identity while preserving the proven React, Vite, Tailwind CSS, Vercel, Resend, testing, CI, and responsive-layout infrastructure.

The project is built with React and Vite, styled with Tailwind CSS, and
deployed with Vercel. It also includes automated testing, Storybook
component previews, GitHub Actions continuous integration, and a
serverless contact endpoint powered by Resend.
## Positioning

## Features
The homepage is designed to answer the core recruiter questions quickly:

- Responsive single-page developer portfolio
- Project showcase with GitHub and live-demo links
- Skills and technology presentation
- Experience and developer background sections
- Animated UI elements using Anime.js
- Reusable React component architecture
- Contact form backed by a serverless API endpoint
- Email delivery through Resend
- Honeypot bot protection and server-side input validation
- Component previews through Storybook
- Automated tests with Vitest and React Testing Library
- GitHub Actions CI for linting, testing, and production builds
- Vercel deployment configuration with SPA routing and security
headers
- Who is Andrew? A backend-focused full-stack developer.
- What is he seeking? A junior backend or full-stack role.
- What can he build? Database-backed APIs and complete client-server applications.
- What makes him different? More than 15 years of leadership, troubleshooting, communication, operations, coaching, and process improvement.
- Where is the evidence? ReliefRoot, FlowBoard, verified technical capabilities, and his professional history.
- How can he be contacted? Direct email, contact form, résumé, LinkedIn, and GitHub.

## Tech Stack
## Featured Work

### Frontend
- **ReliefRoot** - TypeScript, Node.js, Express, PostgreSQL, Prisma, Docker, and GitHub Actions.
- **FlowBoard** - React, TypeScript, Express, PostgreSQL, JWT authentication, and Tailwind CSS.

- React 19
- Vite 6
- Tailwind CSS
- React Icons
- Anime.js
Older bootcamp projects are intentionally separated into Earlier Work. The portfolio does not feature itself as a homepage project.

### Contact API
## Stack

- Vercel serverless function
- Resend email API
- Environment-based API credentials
- Server-side validation
- Honeypot spam protection
- React 19 and Vite 6
- Tailwind CSS
- Vercel serverless contact endpoint
- Resend email delivery
- Vitest and React Testing Library
- ESLint and GitHub Actions

### Testing & Development
## Local Development

- Vitest
- React Testing Library
- Storybook
- ESLint
- PostCSS
- Autoprefixer
Prerequisites: Node.js 24 and npm.

### Deployment & CI

- Vercel
- GitHub Actions
- Node.js 20

## Project Structure

``` text
shadowStack/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .storybook/
├── api/
│ └── contact.js
├── docs/
├── public/
├── src/
│ ├── assets/
│ ├── components/
│ │ └── __tests__/
│ ├── constants/
│ ├── data/
│ ├── hooks/
│ ├── layout/
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx
├── eslint.config.js
├── index.html
├── package.json
├── postcss.config.js
├── tailwind.config.js
├── vercel.json
└── vite.config.js
```

## Application Structure

The portfolio is organized into reusable sections composed in `App.jsx`:

``` text
Layout
├── Header
├── Hero
├── About
├── Projects
├── Skills
├── Experience
├── Contact
└── Footer
```

Project information is separated from presentation components so
portfolio entries can be rendered from project data rather than being
hard-coded directly into the project UI.

## Contact Form

ShadowStack includes a serverless contact endpoint at:

``` text
POST /api/contact
```

The endpoint accepts:

``` json
{
"name": "Example Name",
"email": "[email protected]",
"message": "Hello!",
"website": ""
}
```

`website` is a honeypot field intended to remain empty for legitimate
submissions.

The API:

1. Parses the submitted request.
2. Silently accepts requests that trigger the honeypot.
3. Validates required fields.
4. Enforces maximum input lengths.
5. Sends the message through Resend.
6. Uses the visitor's email as the reply-to address.
7. Returns a success or error response to the client.

The Resend API key is read from the server environment:

``` text
RESEND_API_KEY
```

Secrets should never be committed to the repository.

## Getting Started

### Prerequisites

- Node.js 20
- npm

### Clone the repository

``` bash
git clone https://github.com/Ajthompson88/shadowStack.git
cd shadowStack
```

### Install dependencies

``` bash
npm install
```

### Start the development server

``` bash
```bash
npm ci
npm run dev
```

The Vite development server runs locally on port `5173`.

## Available Scripts

Command Purpose
--------------------------- --------------------------------------
`npm run dev` Start the Vite development server
`npm run build` Create a production build
`npm run preview` Preview the production build locally
`npm run lint` Run ESLint
`npm run test` Run the Vitest test suite once
`npm run test:watch` Run Vitest in watch mode
`npm run storybook` Start Storybook on port 6006
`npm run build-storybook` Build the static Storybook site

## Testing

Automated tests use Vitest with React Testing Library.

Run the full test suite:

``` bash
npm run test
```

For development with automatic reruns:
The development server runs at `http://127.0.0.1:5173`.

``` bash
npm run test:watch
```

Tests live alongside the application code, including component tests
under:
## Verification

``` text
src/components/__tests__/
```

## Storybook

Storybook provides isolated previews for reusable UI components and
visual states.

Start Storybook:

``` bash
npm run storybook
```

Build the static Storybook output:

``` bash
npm run build-storybook
```

## Continuous Integration

The GitHub Actions CI workflow runs on pushes to `main` or `master` and
on pull requests.

The pipeline:

``` text
Checkout
↓
Setup Node.js 20
↓
npm ci
↓
```bash
npm run lint
↓
npm run test
↓
npm test
npm run build
```

Before pushing significant changes, the same checks can be run locally:
CI runs the same checks on pull requests. Production remains protected on `main` while V2 is developed on `feat/portfolio-v2`.

``` bash
npm run lint
npm run test
npm run build
```

## Deployment

ShadowStack is configured for deployment on Vercel.

The included `vercel.json`:

- Rewrites application routes to `index.html` for SPA behavior.
- Adds `X-Frame-Options: SAMEORIGIN`.
- Adds `X-Content-Type-Options: nosniff`.

The contact endpoint under `api/contact.js` is deployed as a serverless
API route.

The deployment environment must provide:

``` text
RESEND_API_KEY
```

## Design

ShadowStack uses a dark/noir visual system with glowing accents,
reusable section layouts, card-based content, and responsive
presentation.

Project-specific visual conventions are documented in:

``` text
docs/style-guide.md
```

The goal is to keep the portfolio visually distinctive without allowing
presentation code to overwhelm the underlying component structure.

## Development Goals

ShadowStack is both a portfolio and an actively maintained software
project. Its purpose is to demonstrate more than a static résumé by
showing practical frontend development, API integration, testing, CI,
deployment, component design, and continued iteration.

Current development priorities include:

- Keeping project information current as new applications are built
- Improving portfolio accessibility and responsive behavior
- Expanding automated test coverage
- Refining reusable components and visual consistency
- Improving the contact workflow and production email configuration
## Contact Form

## License
`POST /api/contact` validates name, email, and message fields, uses a honeypot for simple bot filtering, and sends mail through Resend. Deployment requires `RESEND_API_KEY`.

This project is licensed under the MIT License.
## Work Items

Copyright © 2025--2026 Andrew Thompson
V2 work is tracked in `TODO.md` with stable `PORT-###` identifiers and machine-readable states.
15 changes: 15 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Andrew Thompson Portfolio V2 Work Items

## Planned

- [PLANNED] PORT-002 Add verified project screenshots and architecture visuals for ReliefRoot and FlowBoard.
- [PLANNED] PORT-003 Add dedicated project detail routes with deeper technical case studies.
- [PLANNED] PORT-004 Review résumé positioning and confirm the final public download.
- [PLANNED] PORT-005 Validate the contact form in a Vercel preview deployment.
- [PLANNED] PORT-006 Run accessibility, responsive, performance, and metadata audits.
- [PLANNED] PORT-007 Gather recruiter and developer feedback before replacing production.

## Completed

- [COMPLETED] PORT-000 Preserve production and create `feat/portfolio-v2` from verified `main`.
- [COMPLETED] PORT-001 Establish recruiter-first V2 structure and Andrew Thompson branding.
4 changes: 2 additions & 2 deletions api/contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function POST(request) {
}

const { data, error } = await resend.emails.send({
from: "ShadowStack Portfolio <[email protected]>",
from: "Andrew Thompson Portfolio <[email protected]>",
to: ["[email protected]"],
replyTo: email,
subject: `Portfolio message from ${name}`,
Expand Down Expand Up @@ -69,4 +69,4 @@ ${message}
{ status: 500 }
);
}
}
}
Loading
Loading