A lightweight gate aerospace paper prep tracker built as a local-first browser app.
This repository contains the prep tracker app used to manage Gate AE study progress.
The app is intentionally simple:
- React-based single-page interface (TanStack Router + TanStack Start)
- no backend services
- browser-only data storage (
localStorage) - YouTube playlist and single-video support via a user-provided API key
- in-app YouTube player with watched-time tracking and resume (for both playlists and single videos)
- light/dark theme toggle that persists across refresh
- floating back-to-top button for long pages
- desktop-only experience (mobile viewports see a friendly block screen)
- integrated feedback system backed by Notion
We welcome contributions! Please see our Contributing Guidelines for detailed information on how to get started. It was written in a way that anyone can easily grasp the flow to contribute, do go through it thouroughly if u wanna contribute.
README.mdβ this developer guide.USER_GUIDE.mdβ the user-facing guide intended for first-time visitors.api/feedback.tsβ Vercel serverless function that submits feedback to Notion.src/routes/index.tsxβ thin route shell. Wires app state, persistence, and the active view.src/types/β shared TypeScript types.src/lib/β pure utilities (storage, youtube, syllabus helpers, formatting, syllabus + books data).src/data/β static seed data (recommended resources).src/components/β reusable UI primitives and layout chrome.src/features/β one folder per tab/view (syllabus,books,resources,revise,log,guide).src/styles.cssβ design tokens and app styling.public/books/β section-based PDF assets.
npm installnpm run devnpm run buildnpm run previewnpm run booksβ build PDF metadata frompublic/books/npm run devβ start the Vite dev servernpm run buildβ build the app for productionnpm run previewβ preview production outputnpm run lintβ lint the codebasenpm run formatβ format code with Prettier
The app is a single-page React application using TanStack Router for routing and GSAP for entrance
animations. It is intentionally local-first β all state lives in localStorage.
The codebase follows separation of concerns: the route file owns only top-level state and view
selection; every tab is its own feature module; pure logic (storage, youtube API, syllabus stats)
lives in src/lib/.
- User lands on
/βHome(insrc/routes/index.tsx) mounts. - Home hydrates state from
localStoragevia helpers insrc/lib/storage.ts. - User clicks a nav tab β
viewstate changes (ViewKey). - Matching feature view renders from
src/features/<view>/. - User input updates state β
useEffectpersists back tolocalStorage.
api/
βββ feedback.ts # feedback submission endpoint (Notion)
src/
βββ routes/
β βββ __root.tsx # root layout
β βββ index.tsx # Home: state, persistence, view switch
βββ types/
β βββ index.ts # Progress, Notes, Resource, Revisions, ViewKey, ...
βββ lib/
β βββ storage.ts # STORAGE_KEYS, loadJSON, watch-state helpers
β βββ youtube.ts # url parsing, Data API fetch, IFrame API loader
β βββ syllabus.ts # syllabus data + Section type
β βββ syllabus-utils.ts # topicKey, sectionStats
β βββ books.ts # auto-generated PDF metadata
β βββ format.ts # fmtSize, etc.
βββ data/
β βββ default-resources.ts # recommended starter playlists
βββ components/
β βββ ConfirmModal.tsx # styled confirm dialog
β βββ EmbeddedPlayer.tsx # in-app yt player + watch tracking
β βββ BackToTop.tsx # floating scroll-to-top button
β βββ MobileBlock.tsx # desktop-only overlay for small viewports
β βββ layout/
β βββ AppHeader.tsx # title, progress, theme toggle, version
β βββ AppFooter.tsx
β βββ ViewNav.tsx
βββ features/
βββ syllabus/SyllabusView.tsx
βββ books/BooksView.tsx
βββ resources/
β βββ ResourcesView.tsx # container: state + handlers
β βββ ResourceItem.tsx # single resource row + playlist drawer
β βββ YtApiKeyBox.tsx # api-key editor
βββ revise/ReviseView.tsx
βββ log/LogView.tsx
βββ guide/GuideView.tsx
routes/index.tsxis a thin shell β easy to read end-to-end.- Each tab is isolated; adding a new tab means a new folder under
features/+ one nav entry. - Pure helpers in
lib/are trivially unit-testable and free of React. - Types live in one place (
src/types) so they can be imported from anywhere without cycles.
All data is stored in browser localStorage. Keys are centralized in STORAGE_KEYS
(src/lib/storage.ts):
gate-ae-progress-v1β topic completion stategate-ae-notes-v1β per-section notesgate-ae-resources-v2β saved videos/playlists/linksgate-ae-revise-v1β per-section revision queuegate-ae-yt-key-v1β YouTube Data API keygate-ae-watch-v1β per-video watched seconds, last position, duration (keyed byresourceId::videoId)gate-ae-theme-v1β user-selected theme (light/dark), falls back to system preference
Use loadJSON() for safe parsing with a fallback. Watch-state read/write
goes through loadWatch() / saveWatch() / clearWatchFor().
No server-side storage or authentication is used.
- Owns top-level state (
progress,notes,resources,revisions,active,view). - Hydrates from
localStorageon mount; persists on every change. - Renders the active feature view.
Sectiontype +syllabusarray. Edit this to change sections/topics.
topicKey(section, topic, point)β stable progress key.sectionStats(section, progress)β{ done, total, pct }.
detectKind(url)β classifies a URL asvideo | playlist | link.extractPlaylistId(url)β pullslist=...out of a YouTube URL.fetchPlaylistVideos(playlistId, apiKey)β paged Data API fetch.loadYouTubeAPI()β singleton loader for the IFrame Player API.
- In-app YouTube player with anti-skip watched-time tracking (deltas β€ 2.5s).
- Auto-saves position every ~3s, on pause, tab switch, and unmount.
- Resumes from saved position; auto-marks done at 90% watched.
- Styled confirmation dialog used wherever a destructive action needs a prompt.
- User provides an API key on the Resources page (
YtApiKeyBox). - Key is stored in
localStorageunderSTORAGE_KEYS.ytKey. - When a YouTube playlist URL is added,
ResourcesView:- extracts the playlist ID via
extractPlaylistId() - fetches videos via
fetchPlaylistVideos() - merges saved
doneflags byvideoId
- extracts the playlist ID via
- Each video can be marked done independently or auto-marked at 90% watched.
The embedded player logic lives in EmbeddedPlayer and relies on saved watch state to resume playback cleanly.
The app includes a built-in feedback page that allows users to submit suggestions, bug reports, questions, and general feedback directly from the application.
- User opens the Feedback tab.
- User enters a nickname, category, and message.
- Frontend sends a POST request to
/api/feedback. - Vercel executes the serverless function in
api/feedback.ts. - The function creates a new entry in the configured Notion database.
- The feedback becomes immediately available for review inside Notion.
The feedback endpoint requires:
NOTION_TOKENenvironment variable- a shared Notion database
- matching database properties:
| Property | Type |
|---|---|
| Nickname | Title |
| Message | Rich Text |
| Category | Select |
| Submitted At | Date |
The feedback feature requires deployment on Vercel because it depends on a serverless function.
Local frontend development (npm run dev) does not execute Vercel Functions.
Edit src/lib/syllabus.ts.
Drop new PDFs into public/books/<section>/, then run npm run books to regenerate
src/lib/books.ts.
Edit src/data/default-resources.ts.
Edit src/styles.css (design tokens live at the top).
- Add the key to
ViewKeyinsrc/types/index.ts. - Add it to the
VIEWSarray insrc/components/layout/ViewNav.tsx. - Create
src/features/<name>/<Name>View.tsx. - Render it from
src/routes/index.tsx.
- Make changes in the relevant feature module or shared lib.
- Test locally with
npm run dev. - Update
USER_GUIDE.mdif user-facing behavior changes. - Commit and push.
If you found this project helpful, please give it a star! It helps others discover the project.
For inquiries or collaborations about using the content or the code, reach out to me at kayzspace@outlook.com
This project is licensed under the MIT License - see the LICENSE file for details.