Skip to content

Repository files navigation

TechSolutions BD

A pixel-perfect, responsive Figma landing page built with React 19, Vite 8, and Tailwind CSS 4

A literal implementation of a two-breakpoint Figma design, served its content by a small standalone Express API. Every section renders explicit loading, error, and success states, and the app is covered by two independent test suites: one for the API and data contract, one for component behaviour.

React 19 TypeScript Vite 8 Tailwind CSS 4 Express Tested

Live demo · Preview · Getting started


Table of contents

Preview

TechSolutions BD — site walkthrough

▶️ Full-quality walkthrough with audio (MP4)

Back to top


About the project

The design brief was a single Figma file with desktop and mobile frames for a marketing landing page. The fictional client is "MetaTech," which is why that name appears in the UI. The task was to implement it faithfully, including the places where the source file was ambiguous, contradictory, or silent.

This repository is the result, kept as a portfolio piece. It is a Vite single-page app with no router beyond a 404 fallback, backed by a standalone Express service that returns static JSON. There is no database and no authentication. The focus is the front end: a layout that matches both breakpoints, a strict one-direction data flow, an async surface that always resolves to a visible state, and a test suite that holds the data contract in place.

Back to top


Key features

Area What it does
Dual-breakpoint UI Desktop and mobile frames are implemented literally, including sections that are styled differently per breakpoint by design rather than as a hover or interaction state.
Layered architecture One enforced data direction: ui/sections/pages/services/api.ts → backend. UI primitives never fetch; sections never call fetch directly.
Three states per surface Each section fetches its own endpoint and renders a skeleton loader, an error state on any failed or non-2xx response, and the populated view.
Ambiguous specs resolved White-on-white contrast bugs, undefined click targets, and tabs with no content are each handled explicitly and written up in Design decisions.
Tested contract Backend controllers, routes, and JSON shapes are verified against the shared TypeScript types; components are tested for rendering and interaction with the API mocked.
Minimal dependencies No routing framework beyond a 404 fallback, no state-management or data-fetching library. Plain useEffect / useState is enough for six read-only endpoints.

Back to top


Built with

Choice Reason it is here
React 19 with TypeScript Component model plus a type system that is the main safety net for the data coming off the API.
Vite 8 Fast dev server and a tsc -b && vite build pipeline that type-checks before it bundles.
Tailwind CSS 4, CSS-first config Design tokens live as CSS custom properties in src/index.css; there is no tailwind.config.js.
Express 4 Serves static JSON content from backend/src/data/*.json. No database, one controller.
React Router 7 Present only for the NotFound route; every real screen is on /.
react-loading-skeleton Per-section loading skeletons that match each section's layout.
node:test + Vitest Two independent suites: Node's built-in runner for the API and data contract, Vitest and RTL for components.
Manrope and Bricolage Grotesque The two typefaces named in the Figma specification.
Netlify Static frontend plus the same Express app wrapped as a serverless function, served from one origin.

A data-fetching library, a state manager, and a heavier framework were all considered and left out. Six read-only endpoints do not need them.

Back to top


Architecture

src/
  pages/            One file per route (Home.tsx, NotFound.tsx), mounted with a
                    <Route> in App.tsx. Home.tsx fetches its own content, like a section.
  components/
    sections/       One file per page section (Pillars, PillarsNav, Showcase, Footer).
                    A section with a test gets its own folder (Feature/, Header/,
                    TechStack/) as Name/Name.tsx + Name.test.tsx. Sections compose
                    ui/ primitives and fetch their content from services/api.ts.
    ui/             Reusable primitives: flat files (Card, Container, VideoModal),
                    or a Name/ folder once a Name.test.tsx exists (Button/). No fetching.
    skeletons/      Per-section loading skeletons.
  services/api.ts   The only module that talks to the backend.
  types/index.ts    Shared content and API types.
  assets/images/    Images and logos exported from the Figma file.

backend/
  src/
    controllers/    One controller serving the content JSON.
    routes/         REST routes, mounted under /api.
    data/*.json     Static content, one file per section.

netlify/functions/  The same Express app wrapped for Netlify (serverless-http),
                    so the deployed frontend calls /api on its own origin.
  • Data direction is one-way. ui/sections/pages/services/api.ts → backend. UI primitives never fetch data; sections and pages never call fetch directly, only functions from services/api.ts.
  • Each section owns its data. Every section, and the Home page, fetches its own endpoint with useEffect / useState and manages its own loading, error, and success states.
  • One backend, two entry points. backend/src/server.js runs it locally; netlify/functions/api.js wraps the same app for deployment. The route and controller code is shared.

Back to top


Content API

Endpoint Powers
GET /api/home Hero copy, trusted-by logos, intro statement
GET /api/pillars Service-pillar tabs and the active panel
GET /api/features The three highlight cards
GET /api/showcase The case-study block
GET /api/techstack Tech-stack grid
GET /api/solutions The "Solutions" menu in the header

All responses are static JSON reads from backend/src/data/*.json served through Express, with no database involved.

Back to top


Testing

npm test

npm run test:unit runs __tests__/*.test.mjs on Node's built-in test runner, with no framework dependency:

File What it checks
contentController.test.mjs Each controller serves the matching JSON file, and each /api/* route is wired to the right controller.
contentDataShape.test.mjs backend/src/data/*.json matches the shapes in src/types/index.ts (e.g. activeTabId must reference a real tab).

npm run test:components runs Vitest and React Testing Library against src/**/*.test.{ts,tsx}:

File What it checks
services/api.test.ts api.ts targets the right path per endpoint, falls back to http://localhost:4000/api, and throws on a non-2xx response. Mocks fetch, not api.ts.
ui/Button/Button.test.tsx Renders children, forwards onClick, applies the right class per variant.
sections/Feature/Feature.test.tsx Loading, error, and success rendering for a fetch-on-mount section.
sections/TechStack/TechStack.test.tsx The same three states, plus that every stack item's id resolves to a rendered logo <img>.
sections/Header/Header.test.tsx The mobile menu opens and closes on toggle (aria-expanded, mount/unmount), and the mobile solutions submenu expands to show every fetched solution.

The four component tests mock services/api.ts with vi.mock, so they cover rendering and interaction, not the network. api.test.ts is the exception: it exercises the real module against a mocked fetch.

Back to top


Getting started

Prerequisites: Node 20.19 or newer and npm.

The frontend and backend are separate npm projects with their own node_modules, so install and run each independently.

Frontend (from the repo root):

git clone https://github.com/parvej-brur/metatech-site.git
cd metatech-site
npm install
npm run dev          # Vite dev server on http://localhost:5173

Backend (in a second terminal):

cd backend
npm install
npm run dev          # Express API on http://localhost:4000, with --watch

Environment

The frontend reads its API base URL from VITE_API_URL:

Context Value Set in
Local dev http://localhost:4000/api fallback in src/services/api.ts
Deployed (Netlify) /api (same origin) .env

To point the frontend at a different backend, create a .env file in the repo root:

VITE_API_URL=http://localhost:4000/api

Scripts

Command Purpose
npm run dev Vite dev server on port 5173
npm run build Type-checks (tsc -b) and builds the frontend to dist/
npm run preview Serves the production build locally
npm run lint ESLint
npm test Runs both test suites
npm run test:unit __tests__/*.test.mjs on node:test (backend contract)
npm run test:components Vitest + React Testing Library (component behaviour)
cd backend && npm start Runs the backend without --watch

Back to top


Deployment

Deployed on Netlify as a single origin:

  • npm run build publishes the static frontend from dist/.
  • netlify/functions/api.js wraps the Express app with serverless-http; a redirect rewrites /api/* to that function, so the deployed frontend calls its own API with no separate backend URL.
  • An SPA fallback redirect sends unknown paths to index.html for client-side routing.

See netlify.toml for the build command, redirects, and function bundler.

Back to top


Design decisions

Choices made where the Figma file was ambiguous, contradictory, or silent:

  • White-on-white contrast bugs were fixed. The nav links, the "Book a meeting" button text, the hero <h1>, and the second half of the wordmark are all styled white on a white surface in the source file. Treated as an authoring artifact and re-coloured to dark text, using the colour values found on every other text layer on white as the source of truth.
  • Only the "Data + AI" pillar tab has real content. "Custom Software" and "Tech Staffing" have no panel in the design, so they render as visually accurate but inactive tabs; nothing is invented.
  • The three highlight cards render differently per breakpoint on purpose. The desktop frame shows them as white cards with a centred heading only; the mobile frame shows the same cards dark, with heading and full body copy. Both are implemented literally, matching the frame that specifies each breakpoint.
  • The header's "Solutions" menu has no mobile design. Only the desktop hover state exists. Implemented as an expandable submenu inside the mobile nav, consistent with the rest of the mobile menu.
  • The hero's "Book a Demo" button has no defined behaviour. It has no destination and no linked frame, so it is rendered as a visually accurate static button with no onClick. The hero's play button does open the video modal, because an annotation frame explicitly specifies that.

Back to top


Roadmap

  • Wire the "Custom Software" and "Tech Staffing" pillar tabs to real content once it exists in the design.
  • Rebuild the header's "Solutions" mobile menu against a real mobile spec; the current expandable submenu is a stand-in.
  • Give the hero's "Book a Demo" button a destination once one is defined.
  • Implement real carousel behaviour for the showcase image dots once that interaction is specified; they are currently static markers copied from the Figma frame.

Back to top


About the developer

Built by Parvej Sikdar.

Back to top


License

Released as a portfolio and assessment project. No formal licence is attached; please contact the author before reusing the code.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages