Full-stack calculator: React frontend + Go REST API, styled after a Casio SL-200TE (Sezzle technical assessment)
Translation pending
Developer toolsJavaScript · GoGitHub @matiasbarceloOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
A calculator with a React frontend and a Go REST API backend. The UI is modeled on a Casio SL-200TE pocket calculator; every calculation is performed by the backend and returned as JSON.
Operations: addition, subtraction, multiplication, division, exponentiation, square root, percentage, remainder.
Live demo: https://calculator.matiasbarcelo.com (frontend on GitHub Pages) · API: https://sezzle-calculator-api.fly.dev/api/v1/health (Fly.io). The API scales to zero when idle, so the first calculation after a pause can take a couple of seconds.Read the full README ↗A calculator with a React frontend and a Go REST API backend. The UI is modeled on a Casio SL-200TE pocket calculator; every calculation is performed by the backend and returned as JSON.
Operations: addition, subtraction, multiplication, division, exponentiation, square root, percentage, remainder.
Live demo: https://calculator.matiasbarcelo.com (frontend on GitHub Pages) · API: https://sezzle-calculator-api.fly.dev/api/v1/health (Fly.io). The API scales to zero when idle, so the first calculation after a pause can take a couple of seconds.
.
├── backend/ Go REST API (standard library only)
│ ├── cmd/server/ entry point: config, graceful shutdown
│ ├── internal/calculator/ arithmetic + error rules (no HTTP)
│ ├── internal/api/ routing, JSON validation, status codes, CORS
│ └── Dockerfile
├── frontend/ React (Vite) app
│ └── src/
│ ├── components/ Calculator, Display, Keypad, SevenSegmentDigit
│ ├── calculatorLogic.js key-press state machine
│ ├── api.js API client
│ ├── entry.js / format.js typing rules, 8-digit display formatting
│ └── useCalculator.js React hook wiring state + API
├── Dockerfile builds the frontend and serves it with nginx
├── nginx.conf serves the app, proxies /api/ to the backend
├── docker-compose.yml runs frontend + backend together
├── .github/workflows/ CI: tests both layers, deploys the frontend to GitHub Pages
└── docs/
├── CONVERSATION.md AI-assisted development transcript (prompts used)
└── CALCULATOR_BEHAVIOR.md notes on the original Casio SL-200TE
| To run with | You need |
|---|---|
| Docker (recommended) | Docker Desktop or Docker Engine with Compose v2 |
| Without Docker | Go 1.23+ and Node.js 22+ |
Clone the repository and run all commands from its root.
docker compose up --build -d
Open http://localhost:8080.
frontend — nginx serving the built React app on port 8080, forwarding /api/* to the backendbackend — the Go API, reachable only inside the Compose networkStop it:
docker compose down
After changing code, run docker compose up --build -d again to rebuild.
The app has been tested locally with Docker Compose and at phone widths in the browser, and is deployed under my personal domain at https://calculator.matiasbarcelo.com so I can confirm it works on real mobile devices.
Frontend → GitHub Pages at https://calculator.matiasbarcelo.com, via .github/workflows/deploy-pages.yml. Every push to technical-assessment runs the backend and frontend tests, builds the React app, and publishes it.
GitHub Pages only serves static files, so the Go API is hosted separately.
Backend → Fly.io at https://sezzle-calculator-api.fly.dev, configured in backend/fly.toml (builds backend/Dockerfile, scales to zero when idle, health check on /api/v1/health, CORS limited to the Pages domain):
cd backend
fly deploy
The frontend finds the API through the repository Actions variable API_URL (https://sezzle-calculator-api.fly.dev/api/v1), which is passed to the build as VITE_API_URL. After changing it, re-run the workflow.
DNS: calculator.matiasbarcelo.com needs a CNAME record pointing to matiasbarcelo.github.io.
cd backend
go run ./cmd/server
Listens on http://localhost:8080 by default.
| Environment variable | Default | Purpose |
|---|---|---|
PORT | 8080 | Port to listen on |
ALLOWED_ORIGIN | * | CORS Access-Control-Allow-Origin value |
To run the backend alone in Docker:
docker build -t calculator-api ./backend
docker run --rm -p 8081:8080 calculator-api
The dev server proxies /api to http://localhost:8081, so start the backend on port 8081:
cd backend
PORT=8081 go run ./cmd/server
(PowerShell: $env:PORT="8081"; go run ./cmd/server)
Then, in another terminal:
cd frontend
npm install
npm run dev
Open http://localhost:5173. To point the proxy elsewhere, set API_PROXY_TARGET (e.g. API_PROXY_TARGET=http://localhost:9000 npm run dev).
Production build: npm run build (output in frontend/dist).
Backend — unit tests for the arithmetic and HTTP handler tests for every endpoint and error case:
cd backend
go test ./... -cover
HTML coverage report:
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
Frontend — unit tests for the key-press logic (against a fake API), display formatting, and the API client:
cd frontend
npm test
npm run coverage
npm run coverage prints a table and writes an HTML report to frontend/coverage/index.html.
Current coverage:
| Layer | Package / file | Statements |
|---|---|---|
| Backend | internal/api | 93.8% |
| Backend | internal/calculator | 88.9% |
| Frontend | api.js, format.js, entry.js | 100% |
| Frontend | calculatorLogic.js | 98.6% |
| Frontend | all logic files | 90.6% |
Base path: /api/v1. All requests and responses are JSON.
| Method | Endpoint | Body | Result |
|---|---|---|---|
POST | /add | {"a": 2, "b": 3} | a + b |
POST | /subtract | {"a": 2, "b": 3} | a − b |
POST | /multiply | {"a": 2, "b": 3} | a × b |
POST | /divide | {"a": 2, "b": 3} | a ÷ b |
POST | /power | {"a": 2, "b": 3} | ab |
POST | /sqrt | {"a": 16} | √a (b is ignored) |
POST | /percentage | {"a": 200, "b": 5} | b percent of a = a × b ÷ 100 |
POST | /remainder | {"a": 17, "b": 5} | a mod b (sign follows a) |
GET | /operations | — | Lists operations and their operands |
GET | /health | — | {"status": "ok"} |
Success — 200 OK:
{ "operation": "divide", "a": 10, "b": 4, "result": 2.5 }
Error — always {"error": "<message>"}:
| Status | When |
|---|---|
400 Bad Request | Empty or malformed JSON, unknown fields, non-numeric operands, missing a or b |
404 Not Found | Unknown operation |
405 Method Not Allowed | Wrong HTTP method |
422 Unprocessable Entity | Valid input that can't be computed: division or remainder by zero, square root of a negative number, result that is not finite (e.g. 10^400) |
With docker compose up running, the API is available through the frontend at http://localhost:8080/api/v1. (If you run the backend directly, use its port instead.)
Addition
curl -X POST http://localhost:8080/api/v1/add \
-H "Content-Type: application/json" \
-d '{"a": 12, "b": 30}'
{"operation":"add","a":12,"b":30,"result":42}
Division
curl -X POST http://localhost:8080/api/v1/divide \
-H "Content-Type: application/json" \
-d '{"a": 1, "b": 3}'
{"operation":"divide","a":1,"b":3,"result":0.3333333333333333}
Square root (single operand)
curl -X POST http://localhost:8080/api/v1/sqrt \
-H "Content-Type: application/json" \
-d '{"a": 81}'
{"operation":"sqrt","a":81,"result":9}
Percentage — 5% of 100
curl -X POST http://localhost:8080/api/v1/percentage \
-H "Content-Type: application/json" \
-d '{"a": 100, "b": 5}'
{"operation":"percentage","a":100,"b":5,"result":5}
Division by zero → 422
curl -X POST http://localhost:8080/api/v1/divide \
-H "Content-Type: application/json" \
-d '{"a": 1, "b": 0}'
{"error":"division by zero"}
Square root of a negative number → 422
curl -X POST http://localhost:8080/api/v1/sqrt \
-H "Content-Type: application/json" \
-d '{"a": -9}'
{"error":"square root of a negative number"}
Invalid input → 400
curl -X POST http://localhost:8080/api/v1/add \
-H "Content-Type: application/json" \
-d '{"a": "two", "b": 3}'
{"error":"invalid JSON body: json: cannot unmarshal string into Go struct field CalculateRequest.a of type float64"}
Missing operand → 400
curl -X POST http://localhost:8080/api/v1/multiply \
-H "Content-Type: application/json" \
-d '{"a": 4}'
{"error":"missing operand \"b\""}
| Key | Behavior |
|---|---|
0–9 | Appends a digit on the right. A lone leading 0 is replaced. Max 8 digits. |
• | Decimal point (only one per number). |
+ − × ÷ | Saves the number on screen and waits for the second number. The matching box on the display lights up. |
^ | Exponent: 2 ^ 10 = → 1024. The ^ indicator lights while waiting. |
R | Remainder: 17 R 5 = → 2. |
% | Percentage: 100 % 5 = → 5 (5% of 100). The % indicator lights while waiting. |
√ | Pressed before the number: √ 81 = → 9. The √ indicator lights while typing. |
= | Sends the pending operation to the API and shows the result. |
C | Clears the number being typed (keeps the pending operation). Clears an error. |
AC | Resets everything. |
POST is sent when a calculation is completed.2 + 3 × 4 = → (2 + 3) × 4 = 20.9 + − 4 = → 5).2 + 2 = × 3 = → 12. Typing a digit after = starts a new number.9 + √ 16 = → 13. It is completed when = or another operation key is pressed (√ 9 × 2 = → 6).− pressed right after √ (before any digits) makes the number negative, e.g. √ − 9 =.1 ÷ 3 → 0.3333333); a minus sign uses one digit. Full precision from the API is kept for the next calculation.E with 0 on the display: any API error (division/remainder by zero, square root of a negative, invalid result), a result too large for 8 digits, or the API being unreachable. All keys except C and AC are ignored until cleared.The design is based on an old accounting calculator of my father's from the 2000s, a Casio SL-200TE "Tax & Exchange" model. I chose it because it's symbolic for me: it represents my connection to Argentina and my coming to the United States. It also mirrors this role, a remote position paid in USD that draws on accounting experience from Argentina. On a practical level, it gave me a concrete starting point for the interface: the dual-leaf case, the seven-segment display, and the keypad layout, which I then adapted to the operations this assessment requires.