OverviewI worked on NinjaLingo as part of a team, in a full-stack role. It is a language-learning flashcard web app aimed at helping users study vocabulary in a structured way. The product solves the problem of finding trustworthy card sets, staying motivated, and reviewing on a schedule instead of cramming. Public card set catalog and card content live in the database and are loaded through the app's API, so learners always pull curated, consistent content while their own progress can be tracked over time.
The main flow takes users from choosing a card set to a dashboard, into an interactive review session, and then to a completion screen with session feedback. Review uses spaced repetition (SM-2-style scheduling) so cards come back at sensible intervals. There is a profile area for account-related needs, shareable paths for specific sets (e.g. opening a set from a URL), and an admin area for managing content behind stricter access. On the UI side we focused on a clear, friendly experience: Tailwind CSS for layout and theming (including light/dark), and Framer Motion for motion so interactions feel smooth without getting in the way of studying.
On the frontend I helped build an Astro site that hydrates/islands React 19 for the interactive app, with React Router handling in-app routes (dashboard, review, card sets, admin entry, etc.). Styling uses Tailwind; we use TypeScript and Vitest for type safety and tests. Authentication is Firebase Auth; the data layer calls Astro API routes and sends the Firebase ID token on protected requests. The database is Neon (Postgres) for public sets, cards, progress, stats, and admin-related data. Astro middleware enforces separation between learner routes and admin routes using a session cookie and role checks. We also maintain Node scripts for seeding, CSV to JSON conversion for content pipelines, and deployment (e.g. build + hosting).
One tricky part was keeping the experience simple for guests while still supporting signed-in users and not exposing admin tools to the wrong people. We addressed that with a clear split in routing and server-side checks: middleware decides whether a path is public, learner-only, or admin-only, validates the Firebase token where needed, and redirects so admins and regular users do not land in each other's areas by mistake. On the data side, centralizing API access (authenticated fetch with the Bearer token to Astro endpoints backed by Neon) kept one consistent pattern for progress and catalog data instead of scattering logic across the client. That made it easier for the team to reason about security, roles, and data flow as the app grew.