Overview
HireCrowd is a full-stack hiring platform connecting job seekers with recruiters through one unified application workflow. It is deliberately two products sharing one backend:
- A marketing surface — a motion-led landing page built to earn attention before asking for a signup.
- A candidate surface — discover, filter, bookmark, build a resume once, and apply everywhere from stored profile data.
- A recruiter surface — post jobs, review applicants, and move candidates through a hiring pipeline from a single ATS console.
The landing page targets three distinct audiences rather than one generic "job seekers" segment: students, companies, and colleges. Each gets its own narrative card, since a student and a hiring manager need completely different reasons to continue.
System Architecture
High-Level System Architecture
A single Express API serves three client surfaces. Mongoose models back five collections; validation, rate limiting, and auth are middleware, not per-route boilerplate.
Impact & Results
The Landing Experience
The landing page was the primary design investment. The brief was to avoid the templated hero-plus-three-cards pattern, so the page is built from layered motion and a deliberately non-generic palette.
Scroll-triggered reveals. A small TextAnimation wrapper drives framer-motion's whileInView with viewport={{ once: true }} — content rises into place as it enters the viewport and never re-animates on scroll-back. Staggered delays let a group of elements arrive in sequence rather than all at once.
An infinite marquee headline. The "Built for" band runs on react-fast-marquee at speed 70, with alternating opacity (odd:opacity-50 even:opacity-100) so the repeated words read as a texture rather than duplicated text.
A self-advancing stats carousel. The "Numbers we are proud of" section is a Swiper instance on a 2-second Autoplay delay with EffectFade, cross-fading between metrics. It is also a Headless UI tab group, so the same content is manually navigable on desktop and auto-advances on mobile — with a useMediaQuery branch at 768px and 718px deciding which of the two treatments renders.
A custom vertical slider. VerticleSlider is a hand-rolled vertical track that swaps panels on a vertical axis — the kind of component that is normally reached for in a library but written by hand here to control the easing and the snap behaviour.
A three-audience role grid. Instead of one generic value proposition, the page addresses students, companies, and colleges with their own card, icon treatment, and accent colour — teal #0AA482, coral #FF6E76, violet #693CF3 on tinted backgrounds.
Palette and motion discipline. The page runs on a near-black #150f04 base with a single gold accent #ddb15c, set in font-grotesk. Transitions lean on a custom cubic-bezier (.215, .61, .355, 1) at 700ms — slow enough to feel deliberate rather than snappy.
Candidate Experience
The candidate side optimises for one thing: a candidate should never re-type information they have already given the platform.
- Virtualized infinite feed. The job listing uses
react-virtuosofor windowed rendering, driven by TanStack Query'suseInfiniteQuerywithfetchNextPage/hasNextPage. Only visible rows mount, so scroll depth stays flat regardless of listing size. - Debounced filtering. Filter state runs through a
useFiltershook (itself built onuseInfiniteQuery) with auseDebouncehook on input, so typing does not fire a request per keystroke. - A profile that becomes a resume. Professional details — experience, education, projects — are captured once through validated forms and rendered into a resume via
react-quillfor free-form description fields. That resume is what gets attached on apply. - One-click apply. Applying pulls the stored resume and profile rather than prompting for an upload, which was the single largest source of drop-off.
- Bookmarks and applied-job tracking. Saved jobs and application history live in their own Settings views, so a candidate can always answer "what did I apply to, and where does it stand?"
Candidate Job Feed — windowed rendering
Pagination is handled by useInfiniteQuery; virtualization by react-virtuoso. The two are independent concerns and splitting them keeps the cache queryable while keeping the DOM small.
Recruiter Experience
Recruiters get the mirror image: a console for turning a pile of applications into a decision.
- A dashboard with real aggregates. Recent applications, recent job postings, and candidate counts by status — the counts come from the status enum, not a separate analytics store.
- Job authoring with rich text.
react-quillpowers the job description editor, so postings keep formatting instead of degrading to plain text. - Applicant review in context. An application drawer surfaces the applicant's card, contact details, and resume without navigating away from the job.
- A visual pipeline.
CandidateStatusrenders the five-stage pipeline as a stepper inside a sheet, and writes the new status through a TanStack mutation that invalidates the application queries — the dashboard counts update without a manual refresh. - Invite-by-code onboarding. Employers onboard candidates with
InviteCodes. The code can be deep-linked:Register.jsxreadsinviteCodefrom the query string and pre-fills the field, so the candidate lands pre-filled instead of retyping.
Recruiter Pipeline — status transitions
The Application model enforces the stage enum in the schema, so an invalid status cannot be persisted regardless of what the client sends.
Engineering Decisions
Why virtualization instead of pagination controls?
A job board is a scanning surface, not a document. Pagination forces a candidate to stop and click; windowed infinite scroll keeps the browsing gesture continuous. react-virtuoso handles the windowing while TanStack Query owns the cursor, so the two concerns stay separable.
Why TanStack Query alongside Zustand?
Server state and client state have different lifecycles. Job listings, applications, and profiles are all cacheable server state with background refetch — that is TanStack Query's job. Zustand is reserved for genuinely local UI state. Putting fetched data in a global store would mean hand-rolling staleness, retries, and deduplication.
Why an enum in the schema rather than app-level validation?
The hiring pipeline has a fixed shape. Encoding it as a Mongoose enum means the database refuses any stage outside the pipeline, so an invalid transition is impossible even from a hand-crafted request — not merely rejected by a client that might be bypassed.
Why invite codes instead of open registration?
Employers onboard candidates they intend to hire. An InviteCode gates registration so a recruiter controls who reaches the platform, and the code travels as a link with the value pre-filled.
Why Swagger UI?
The API was the contract between two independently-built surfaces. swagger-jsdoc annotations generate the spec from the route definitions, so the docs cannot drift out of sync with the implementation the way hand-written docs do.
Technical Challenges
API Reference
Engineering Details
Lessons Learned
- Motion is a design decision, not decoration. The scroll reveals, marquee, and autoplay carousel each carry information — pacing the page, or surfacing metrics — rather than existing to look busy.
- One breakpoint strategy beats one flexible layout. Branching composition at
768pxand718pxproduced a better mobile page than letting a desktop layout compress. - Server state and client state are different problems. TanStack Query for anything cacheable, Zustand for local UI — mixing them costs staleness bugs.
- Schema-level constraints beat application-level checks. Putting the pipeline enum on the Mongoose model made invalid states unrepresentable rather than merely rejected.
- Reuse is a product feature. Generating the resume from stored profile data removed an upload step from every application — the highest-leverage friction reduction in the project.
- Invite links should arrive pre-filled. Reading
inviteCodefrom the query string is a two-line change that removes a manual step from onboarding.
Screenshots
Design References
The three images below are third-party design references that informed this project — concept work sourced from Behance and Dribbble while planning the interface. They are not screenshots of HireCrowd, and none of the imagery is my own. Included for attribution and design context only. All application screenshots appear in the gallery above.
The job-listing layout was inspired by a public Dribbble job-search concept. The implemented feed diverges substantially — it adds cursor pagination and windowed virtualization that the static concept has no equivalent for.
Liked this project?
Check out more of my work or get in touch.