Skip to content

KhourySpecialProjects/FlowLog

Repository files navigation

FlowLog

A bullet journal web app built with React + TypeScript. Used as a teaching project for CS4535 to demonstrate containerizing a real application and deploying it to AWS ECS.

Important Note: This project is intended only to be used as a context for deploying a small app to AWS ECS. It is not intended to be used in production. I wrote it with the assistance of Claude Code!


Quick Start

No local Node.js required — everything runs in Docker.

# Hot-reload dev server → http://localhost:5173
docker compose up dev

# Production build served via nginx → http://localhost:80
docker compose up app

To build the image directly (e.g. before pushing to ECR):

Mac (Apple Silicon / M-series): ECS tasks run on linux/amd64 by default, so you must cross-compile:

docker buildx build --platform linux/amd64 -t flowlog .

Windows or Intel Mac: The default build architecture matches ECS, so no flag is needed:

docker build -t flowlog .

Features

  • Daily Log — rapid logging with three entry types: task (), event (), note ()
  • Task status cycling — click the bullet to cycle open → done → migrated → cancelled
  • Collections — create named pages for lists, goals, reference material, etc.
  • Future Log — monthly calendar showing entry counts per day; click any day to jump to it
  • Tags — type #tag anywhere in entry text; tags are parsed automatically and appear in the filter bar
  • Filtering — filter any view by entry type or tag; filters combine
  • Persistence — all data lives in localStorage; survives page refresh

Tech Stack

Layer Choice
UI framework React 18 + TypeScript
Build tool Vite
Styling Tailwind CSS
Icons Lucide React
Persistence localStorage (swappable — see below)
Production server nginx (alpine)
Container Docker multi-stage build

Project Structure

flowlog/
├── src/
│   ├── types/
│   │   └── index.ts          # Entry, Collection, JournalState types
│   ├── utils/
│   │   └── storage.ts        # localStorage read/write — swap point for a real backend
│   ├── hooks/
│   │   └── useJournal.ts     # All state + actions via useReducer
│   ├── components/
│   │   ├── Sidebar.tsx       # Nav: daily dates, future log, collections
│   │   ├── DailyLog.tsx      # Main daily view with entry list
│   │   ├── EntryItem.tsx     # Single entry: type icon, status toggle, inline edit
│   │   ├── AddEntryForm.tsx  # Quick-add input with type selector
│   │   ├── MonthlySpread.tsx # Calendar grid with entry counts
│   │   ├── CollectionView.tsx# Custom collection page
│   │   └── TagFilter.tsx     # Filter bar for tags and entry types
│   ├── App.tsx               # Layout shell + view router
│   ├── main.tsx              # React entry point
│   └── index.css             # Tailwind directives + base styles
├── public/
│   └── favicon.svg
├── Dockerfile                # Multi-stage: node build → nginx serve
├── docker-compose.yml        # dev (hot reload) + app (production) targets
├── nginx.conf                # SPA routing + gzip + asset caching
├── package.json
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts

Data Model

type EntryType   = 'task' | 'event' | 'note';
type TaskStatus  = 'open' | 'done' | 'migrated' | 'cancelled';

interface Entry {
  id:           string;
  type:         EntryType;
  status?:      TaskStatus;       // tasks only
  content:      string;           // raw text; #tags parsed from here
  tags:         string[];         // derived and stored for fast filtering
  date:         string;           // YYYY-MM-DD
  collectionId: string;           // 'daily' | uuid
}

interface Collection {
  id:        string;
  name:      string;
  type:      'daily' | 'monthly' | 'custom';
  createdAt: string;
}

All state is held in JournalState (entries[], collections[], activeView) and persisted as a single JSON blob in localStorage under the key flowlog_state.


State Management

All state lives in src/hooks/useJournal.ts. It uses React's useReducer pattern — one central reducer handles every mutation:

Action Description
ADD_ENTRY Creates an entry and parses #tags from the content
UPDATE_ENTRY Patches an entry; re-parses tags if content changed
DELETE_ENTRY Removes an entry by id
ADD_COLLECTION Creates a custom collection and navigates to it
DELETE_COLLECTION Removes collection and all its entries
SET_VIEW Changes the active view (daily / monthly / collection)

A useEffect in the hook syncs state to localStorage after every dispatch.


Swapping the Storage Layer

src/utils/storage.ts is the only file that knows about localStorage. It exports two functions:

loadState(): JournalState | null
saveState(state: JournalState): void

To point the app at a real backend, replace those two functions — nothing else needs to change. For example, you could fetch() from a REST API in loadState and POST in saveState, or swap in DynamoDB via an API Gateway endpoint.


Docker Details

Multi-stage Dockerfile

Stage 1 (node:20-alpine)  →  npm install + vite build  →  /app/dist
Stage 2 (nginx:alpine)    →  copy dist + nginx.conf    →  EXPOSE 80

The final image contains only nginx and the compiled static files — no Node.js runtime, no source code.

docker-compose.yml targets

docker compose up dev   # Mounts source as a volume; Vite serves with HMR on :5173
docker compose up app   # Builds the production image; nginx serves on :80

nginx.conf

Configured for SPA routing (try_files $uri $uri/ /index.html) so that refreshing any route works correctly. Static assets get a 1-year Cache-Control: immutable header — safe because Vite fingerprints all asset filenames.


Keyboard Shortcuts

Shortcut Action
Enter Submit new entry
Alt+T Switch new entry type to Task
Alt+E Switch new entry type to Event
Alt+N Switch new entry type to Note
Double-click entry Inline edit
Enter / Escape Confirm / cancel inline edit

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors