Skip to content
Open
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
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;"]
84 changes: 79 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 (requires refactoring to use .jsx extensions)
- 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,57 @@ 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 has been configured for component documentation and development:
```bash
npm run storybook # Start Storybook dev server (port 6006)
npm run build-storybook # Build static Storybook site
```

**Note**: Storybook currently requires refactoring to work properly. This codebase uses `.js` file extensions for React components containing JSX, which is non-standard for modern build tools like Vite (used by Storybook). To make Storybook fully functional:
- Rename React component files from `.js` to `.jsx` (e.g., `WordButton.js` → `WordButton.jsx`)
- Example stories have been created in `src/components/*/` for reference

Component stories currently available (once files are refactored):
- `WordButton` - Interactive word selection button
- `Button` - All button variants from the design system
- `Badge` - Badge component with different styles

#### 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