Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
89c291f
feat: Add Docker support with multi-stage build
smingolelli Feb 21, 2026
f509cb7
feat: Upgrade all dependencies to latest versions
smingolelli Feb 21, 2026
5aaebd4
chore: Update Dockerfile for React 19 compatibility
smingolelli Feb 21, 2026
029eece
chore: Update Dockerfile for React 19 compatibility
smingolelli Feb 21, 2026
65fb9db
Merge pull request #3 from slmingol/feature/dockerfile-react19-compat
slmingol Feb 21, 2026
b745d3a
fix: Address critical security and configuration issues
smingolelli Feb 21, 2026
0d6de6f
Merge pull request #4 from slmingol/feature/fix-critical-issues
slmingol Feb 21, 2026
5bf0a29
feat: Add developer experience improvements
smingolelli Feb 21, 2026
046d99e
Merge pull request #5 from slmingol/feature/dx-improvements
slmingol Feb 21, 2026
392b6e0
docs: Update README with React 19, Docker, testing, and CI/CD info
smingolelli Feb 21, 2026
554415d
Merge pull request #6 from slmingol/feature/update-readme
slmingol Feb 21, 2026
626996d
feat: Add Storybook for component documentation
smingolelli Feb 21, 2026
60e2fb3
refactor: Rename React component files from .js to .jsx
smingolelli Feb 21, 2026
86cc186
docs: Update README to reflect Storybook is now fully functional
smingolelli Feb 21, 2026
e3ee6f5
Merge pull request #8 from slmingol/refactor/jsx-file-extensions
slmingol Feb 21, 2026
860818d
feat: Add performance monitoring and analytics support
smingolelli Feb 21, 2026
1f80f48
fix: Add type module to package.json to eliminate Node.js warning
smingolelli Feb 21, 2026
553f283
fix: Add package.json metadata fields
smingolelli Feb 21, 2026
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
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
npm-debug.log
.git
.gitignore
.parcel-cache
dist
*.md
.DS_Store
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI/CD

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install dependencies
run: npm install --legacy-peer-deps

- name: Check formatting
run: npm run format:check

- name: Run tests
run: npm run test:run

- name: Build application
run: npm run build

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7

docker-build:
runs-on: ubuntu-latest
needs: build-and-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ yarn-error.log*
.npm/

*.swp

*storybook.log
storybook-static
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
18 changes: 18 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@


/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
"stories": [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-onboarding"
],
"framework": "@storybook/react-vite"
};
export default config;
20 changes: 20 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** @type { import('@storybook/react-vite').Preview } */
const preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: "todo"
}
},
};

export default preview;
7 changes: 7 additions & 0 deletions .storybook/vitest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { setProjectAnnotations } from '@storybook/react-vite';
import * as projectAnnotations from './preview';

// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Build stage
FROM node:20-slim as builder

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm install --legacy-peer-deps

# Copy source code
COPY . .

# Build the application
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy built files from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html

# Copy nginx configuration (optional - uses default if not provided)
# COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
143 changes: 138 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,51 @@ Anyways..

## To Run Locally:

```
### Prerequisites
- Node.js 20 or higher (`.nvmrc` file included for `nvm` users)

### Development
```bash
cd react-connections-game
npm install
npm install --legacy-peer-deps
npm run dev
```

### Using Docker
```bash
# Build and run locally
docker-compose up

# Or use pre-built image from GitHub Container Registry
docker-compose -f docker-compose.simple.yml up
```

The app will be available at `http://localhost:3000`

### Available Scripts
- `npm start` - Start development server (alias for `npm run dev`)
- `npm run dev` - Start development server with hot reload
- `npm run build` - Build for production
- `npm test` - Run tests in watch mode
- `npm run test:run` - Run tests once (CI mode)
- `npm run test:ui` - Open Vitest UI for visual test exploration
- `npm run storybook` - Start Storybook for component development (requires .jsx refactoring)
- `npm run build-storybook` - Build static Storybook site
- `npm run format` - Format code with Prettier
- `npm run format:check` - Check code formatting
- `npm run clean` - Remove all build artifacts and dependencies

### Technology

- [React 18](https://react.dev/)
- [React 19](https://react.dev/)
- [Tailwind CSS](https://tailwindcss.com/)
- [React Spring](https://www.react-spring.dev/) for a few animations
- [React Spring](https://www.react-spring.dev/) for animations
- [Shadcn/ui](https://ui.shadcn.com/) for primitive components
- [Vitest](https://vitest.dev/) + [React Testing Library](https://testing-library.com/react) for testing
- [Storybook](https://storybook.js.org/) for component documentation
- Copied a number of utility functions from a [React Wordle Clone - cwackerfuss/react-wordle](https://github.com/cwackerfuss/react-wordle)
- Built with [Parcel](https://parceljs.org/)
- Dockerized with multi-stage builds (Node 20 + nginx)

### Code Organization

Expand All @@ -36,14 +67,116 @@ npm run dev
- Custom hooks are in `src/hooks`
- Both of these are code snippets taken from [Josh Comeau's Blog](https://www.joshwcomeau.com/snippets/)

### CI/CD

This project includes a GitHub Actions workflow that automatically:
- Runs format checks with Prettier
- Executes the test suite with Vitest
- Builds the application
- Builds and publishes Docker images to GitHub Container Registry (on main branch)

All checks run on every pull request to ensure code quality.

### Testing

Tests are written using Vitest and React Testing Library:
```bash
npm test # Watch mode for development
npm run test:run # Run once (used in CI)
npm run test:ui # Visual test interface
```

### Component Documentation (Storybook)

Storybook is configured for component documentation and interactive development:
```bash
npm run storybook # Start Storybook dev server (port 6006)
npm run build-storybook # Build static Storybook site
```

Component stories available:
- `WordButton` - Interactive word selection button with multiple scenarios
- `Button` - All button variants and sizes from the design system
- `Badge` - Badge variants with game status examples

Storybook includes:
- Visual component library for isolated development
- Accessibility checks via @storybook/addon-a11y
- Automatic documentation generation
- Integration with Vitest for component testing

### Performance Monitoring & Analytics

This app includes built-in support for performance monitoring and analytics:

#### Core Web Vitals

The app automatically tracks [Core Web Vitals](https://web.dev/vitals/) metrics:
- **LCP** (Largest Contentful Paint) - Loading performance
- **INP** (Interaction to Next Paint) - Responsiveness
- **CLS** (Cumulative Layout Shift) - Visual stability
- **FCP** (First Contentful Paint) - Initial render
- **TTFB** (Time to First Byte) - Server response time

In development mode, these metrics are logged to the console. In production, they can be sent to your analytics provider.

#### Game Analytics

The following game events are automatically tracked:
- Game lifecycle: `game_won`, `game_lost`
- Player actions: `guess_submitted`, `guess_correct`, `guess_incorrect`
- UI interactions: `shuffle_clicked`, `deselect_clicked`, `view_results_clicked`, `share_score_clicked`, `info_modal_opened`

#### Configuring Analytics

The analytics system is provider-agnostic. To enable analytics in production, add one of the following to your HTML:

**Google Analytics 4:**
```html
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
</script>
```

**Plausible Analytics:**
```html
<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>
```

**Custom Analytics:**
```javascript
// Set your analytics instance on window object
window.analytics = {
track: (event, properties) => {
// Your custom tracking implementation
},
page: (path) => {
// Your custom page view tracking
}
};
```

All analytics code is located in `src/lib/analytics.js` and `src/lib/performance.js` for easy customization.

#### Similar Projects

- [PuzzGrid](https://puzzgrid.com/about) which allows you to create your own games/puzzles, no code required.
- [Connections Generator by swellgarfo](https://www.reddit.com/r/NYTSpellingBee/comments/152i5cx/for_those_playing_nyt_connections_i_created_a/) which also allows you to create your own games/puzzles, no code required.

### Contributing

- Please fork and submit a PR if you'd like!
Please fork and submit a PR if you'd like!

**Development Requirements:**
- Node.js 20+ (use `nvm use` if you have nvm installed)
- Run `npm install --legacy-peer-deps` to install dependencies
- Run `npm test` while developing to ensure tests pass
- Run `npm run format` before committing to maintain code style
- All PRs must pass CI checks (formatting, tests, build)

### Projects Built Using This Repo:

Expand Down
7 changes: 7 additions & 0 deletions docker-compose.simple.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
connections-game:
image: ghcr.io/slmingol/react-connections-game:latest
container_name: react-connections-game
ports:
- "3000:80"
restart: unless-stopped
26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
services:
connections-game:
build:
context: .
dockerfile: Dockerfile
container_name: react-connections-game
ports:
- "3000:80"
restart: unless-stopped
environment:
- NODE_ENV=production

# Development service (optional - uncomment to use)
# connections-game-dev:
# image: node:20-slim
# container_name: react-connections-game-dev
# working_dir: /app
# volumes:
# - .:/app
# - /app/node_modules
# ports:
# - "1234:1234"
# command: sh -c "npm install && npm run dev"
# environment:
# - NODE_ENV=development
# restart: unless-stopped
Loading