RizTech Academy logo
RizTech Academy
Capstone: a tiffin service order trackerLesson 1 of 420 min

What we are building, and the rules it must follow

Nine modules of pieces. This one builds a single application that uses all of them, and it is the thing you show somebody when they ask what you can do.

Read this lesson before writing any code. Deciding what you are building — and what you are not — is most of the difference between finishing and abandoning.

The problem

Anna runs a tiffin service in Kharadi. Forty-odd customers, three kinds of thali, deliveries every day. She currently tracks it in a notebook, and the notebook has three problems: she cannot search it, she recalculates the day's takings by hand every evening, and if she leaves it at home she is stuck.

You are building the page she uses on her phone, standing in a kitchen, with one hand free.

That last sentence decides more than it looks like it does. It is why the layout must work at 390px, why the tap targets have to be big enough, and why "it works on my laptop" is not the finish line.

What it does

Add an order. Customer name, phone, pincode, number of plates, and which thali. Rejected with a clear message if any of it is wrong.

Show today's orders, each with the meal, the plate count, the pincode and what it comes to.

Mark an order delivered, and undo that.

Remove an order.

Search and filter — by name, phone or pincode, and by pending or delivered.

Show the day's totals — how many orders, how many still pending, and the money.

Survive a reload. Close the tab, come back, the orders are there.

Get the rates from a file rather than hard-coding them, so the prices can change without editing the code.

What it deliberately does not do

Naming this is as important as the list above. Every one of these is a real decision, not an omission.

No accounts and no login. One person, one phone. Adding accounts means a server, and this is a front-end course.

No server. Data lives in localStorage. That has consequences you will meet in module 10's third lesson, and the page is honest about them.

No editing an order. Remove and re-add. This is a defensible product decision for a six-field form, and it is the first exercise at the end.

No dates or history. Today's orders only. Adding "which day" changes the data shape, the filtering and the summary all at once — a good extension, and too much for a first build.

No framework, no build step, no dependencies. Plain HTML, CSS and ES modules. You have spent nine modules learning what a framework does for you; do it by hand once.

Scope creep is the thing that kills projects like this. Build exactly this list, get it working, and then extend it. A finished small thing beats an unfinished ambitious one, and it is much better to show someone.

The rules it must follow

These are the course's arguments, and the build is where they have to hold.

State is the truth; the page is a picture of it. One array of orders. Every change updates it, saves it, and re-renders. Nothing is ever read back out of the DOM to find out what is going on.

Money is counted in paise. 80.10 * 3 is 240.29999999999998. Every amount is a whole number of paise, converted only for display, formatted with Intl so it groups as ₹12,34,567 and not ₹1,234,567.

Convert at the boundary. Everything a form gives you is a string. It becomes a number once, during validation, and is a number everywhere after that.

textContent, never innerHTML. Customer names are typed by a person and the rate card comes over a network. Both are untrusted.

Every failure has a state. The rate card can 404, be the wrong thing entirely, or never arrive. Storage can be corrupt. The page says something useful for each, and the orders still render.

One listener for the list. Delegation, so rows added later work without being wired up.

The shape of the code

Nine files, each with one reason to change:

index.html          the markup
styles.css          the styling
data/rates.json     the rate card, fetched at startup
src/
  app.js            wiring: listeners and startup
  state.js          the store — the array, and everything that changes it
  storage.js        localStorage with a fallback that cannot crash the page
  api.js            fetchJson: timeout, response.ok, three kinds of failure
  errors.js         the error types and their messages
  validate.js       form validation, converting at the boundary
  render.js         building the DOM from state
  format.js         rupees and dates

The imports point one way. app.js imports the others; nothing imports app.js. That is how you avoid circular imports by construction rather than by debugging them later.

If a file starts needing to know about two unrelated things, that is the signal to split it. render.js should change when the design changes; api.js should change when the server does. Those are different days.

How the three build lessons go

Building the interface — markup, rendering from state, the form, and the delegated list. At the end of it the page works, in memory.

The data layer — the store, localStorage, and fetching the rate card with every failure handled. At the end of it the page survives a reload and a broken network.

Polish and deploy — validation messages, keyboard and screen-reader access, the mobile layout, and a live URL.

Type it. Do not paste it. You will make mistakes, and fixing your own mistakes is the entire mechanism by which this becomes yours. Every bug in this course was found by running something, and yours will be too.

What finished looks like

A URL you can send someone. It works on a phone, it does something useful, and it does not break when the network does.

That is a portfolio piece. Not because a tiffin tracker is impressive, but because handling the empty state, the error state and the offline case is exactly what separates somebody who has finished a tutorial from somebody who can be given work.

Check your work

The user is a shop owner on a phone in a kitchen — which is why 390px and tap targets are requirements rather than polish.

Seven features are in scope; accounts, a server, editing, history and frameworks are explicitly out. Naming what you are not building is what stops scope creep.

One array is the truth. The page is rendered from it, never read back into it.

Money in paise, converted only for display, because floating point cannot hold 80.10 exactly.

Everything from a form is a string and is converted once, at validation.

textContent for anything a person typed or a server sent.

Nine files, imports pointing one way, so no import cycle is possible.

Practice

Before writing any code:

  1. Write the data shape down. What exactly is one order? Name every field and its type. Decide where the money lives — on the order, or derived from the meal? Both are defensible; know which you chose and why.
  2. Sketch the page on paper at phone width. Where does the form go relative to the list? What does a single row contain?
  3. Write down the four states of the list: nothing yet, loading the rates, an error, and orders shown. What does each look like? Then add a fifth — a search matching nothing — and decide how it differs from "no orders yet".
  4. List every way the page can fail that is not the user's fault. You should get at least four.
  5. Decide what happens when the rate card cannot be loaded but there are already orders in storage. There is a right answer and it is not "show nothing".
  6. Write one sentence saying what this is, as you would to someone else: "A page for a tiffin service owner to …". If you cannot finish that sentence in one line, the scope is still too large.

Next: building the interface, and rendering it from state.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship