Plings-Web — Frontend Implementation
Created: Wed 06 May 2026 17:14:18 CEST — replaces previous Lovable-centric version Document Version: 2.0 — Plings-Web (Vite + React + Apollo) Security Classification: Internal Technical Documentation Target Audience: Frontend Developers Author: Paul Wisén
Overview
Plings-Web is the public web frontend at https://plings.io. It is one of several clients of the unified GraphQL API at api.plings.io — the others being the Gateway (s.plings.io) and the planned native apps (Plings-iOS, Plings-Android). All clients share the same GraphQL contract; this document covers Plings-Web specifically.
Disk path: Plings-Web/ (folder rename pending Phase 2)
Technical Stack
| Concern | Technology |
|---|---|
| UI Framework | React 18 (function components + hooks) |
| Language | TypeScript 5 |
| Build Tool | Vite 5 with @vitejs/plugin-react-swc (SWC for fast compilation) |
| Routing | React Router DOM v6 |
| GraphQL Client | Apollo Client v3 (with apollo-upload-client for file uploads) |
| Server State | Apollo cache + @tanstack/react-query |
| Forms / Validation | react-hook-form + zod |
| UI Components | shadcn/ui pattern over Radix UI primitives |
| Icons | lucide-react |
| Styling | Tailwind CSS + tailwindcss-animate + tailwind-merge |
| Auth | Supabase JS (@supabase/supabase-js) — JWT used in Apollo headers |
| Toasts | sonner |
| Linting | ESLint 9 + typescript-eslint |
| Deployment | Vercel CDN |
Quick Start
1. Start the local backend
cd Plings-API
uvicorn app.main:app --reload # runs on http://localhost:8000/graphql
2. Pick an environment for the web app
The repo ships two pre-baked env files:
.env.localhost→ points the GraphQL client athttp://localhost:8000/graphql.env.vercel→ points at the deployedapi.plings.io
package.json exposes shortcuts:
npm run dev:local # copies .env.localhost → .env.local, starts vite
npm run dev:vercel # copies .env.vercel → .env.local, starts vite
3. Run the dev server
npm install # or bun install — both lockfiles exist; pick one and stick with it
npm run dev # vite dev server on http://localhost:5173 by default
Both
bun.lockbandpackage-lock.jsonexist in the repo. This is a leftover from the project’s history; future cleanup should pick one package manager and remove the other lockfile.
Real-time Architecture
The unified GraphQL API exposes three operation types; Plings-Web uses all three through Apollo Client:
- Queries — initial data fetch, cached by Apollo
- Mutations — create / update / delete; pair with
optimisticResponsefor latency-sensitive UX - Subscriptions — bidirectional WebSocket for live updates (planned in several flows)
The frontend choice does not affect the backend contract. Native apps will speak the same GraphQL — Plings-Web is just one consumer.
Source Layout
src/
├── App.tsx # Root component with routes
├── main.tsx # Entry point (createRoot)
├── index.css # Tailwind base layer
├── components/ # UI components (feature folders + shadcn/ui in components/ui/)
├── contexts/ # AuthContext, OrganizationContext
├── graphql/
│ ├── queries.ts # All GraphQL queries
│ └── mutations.ts # All GraphQL mutations
├── hooks/ # Custom React hooks
├── integrations/
│ └── supabase/ # Supabase client config
├── lib/ # Apollo setup, supabase, utils
├── pages/ # Page-level components for React Router routes
├── types/ # Shared TypeScript types
├── utils/ # Generic helpers
└── vite-env.d.ts # Vite type declarations
GraphQL operations live in src/graphql/ as raw gql template literals — there is no codegen pipeline currently. If type safety becomes important enough to warrant the build complexity, GraphQL Code Generator can be added; until then, types for query results are written by hand or inferred where possible.
Patterns
Authentication
import { useAuth } from "@/contexts/AuthContext";
const { user, session, signOut } = useAuth();
The Apollo client reads session.access_token and sends it as Authorization: Bearer <token>.
GraphQL queries
import { useQuery } from "@apollo/client";
import { GET_MY_OBJECTS } from "@/graphql/queries";
const { data, loading, error } = useQuery(GET_MY_OBJECTS, {
variables: { organizationId },
fetchPolicy: "cache-first",
});
Drag-and-drop with optimistic mutation
For latency-sensitive interactions (drag-and-drop in the spatial dashboard, inline edits), apply an optimistic response so the UI reflects the change before the server confirms.
import { useMutation, gql } from "@apollo/client";
import { DndContext, useDraggable, useDroppable } from "@dnd-kit/core";
const MOVE_OBJECT = gql`
mutation MoveObject($id: ID!, $target: ID!) {
moveObject(objectId: $id, newContainerId: $target) {
id
containerId
}
}
`;
function Container({ container }: { container: SpatialObject }) {
const { isOver, setNodeRef } = useDroppable({ id: container.id });
const [moveObject] = useMutation(MOVE_OBJECT);
return (
<div
ref={setNodeRef}
className={`container ${isOver ? "bg-blue-50" : ""}`}
onDrop={({ active }) =>
moveObject({
variables: { id: active.id, target: container.id },
optimisticResponse: {
moveObject: { __typename: "SpatialObject", id: active.id, containerId: container.id },
},
})
}
/>
);
}
Error handling
try {
await mutation({ variables: input });
toast.success("Saved");
} catch (err) {
const message = err instanceof Error ? err.message : "An error occurred";
toast.error(message);
}
Development Guidelines
- Keep components pure — derive data from props or GraphQL hooks; avoid hidden module-level state
- Use optimistic updates for drag-and-drop, inline edits, and other interactions where waiting for the server feels slow
- Co-locate GraphQL fragments with the components that use them, then re-export from
src/graphql/if shared - Test on mobile early — Tailwind responsiveness is solid out of the box, but verify gesture ergonomics on real devices
- Treat the GraphQL schema as the contract — never assume a field exists without checking the schema
Testing Strategy
- Unit tests — pure logic in hooks and utility functions
- Component tests — React Testing Library for component behaviour
- E2E tests — Playwright (when added)
- Performance — Lighthouse in mobile emulation; watch bundle size in the Vite output
A formal test setup is not yet wired into this repo; this section describes the intended approach. Adding Vitest + React Testing Library would be a worthwhile follow-up.
Build & Deploy
- Build:
npm run build→ Vite produces a static bundle indist/ - Preview:
npm run preview→ serves the built bundle locally - Deploy: Vercel auto-deploys on commits to
mainvia the project’s Vercel integration.vercel.jsoncontrols deployment configuration.
Known Cleanup Tasks
These are not blockers for current work but should be addressed:
- No codegen pipeline; consider adding GraphQL Code Generator if type drift becomes a problem
- No formal test suite — Vitest + React Testing Library would be a worthwhile addition
Roadmap
- Add GraphQL subscriptions for live updates (today the app uses
refetchQueriesafter mutations) - Add Service Worker for offline support (PWA capabilities)
- Tag scanning UX improvements (handing off cleanly from
s.plings.ioGateway) - Native iOS / Android apps (separate repos — Plings-iOS, Plings-Android — will reuse the GraphQL contract and Supabase auth, not the React codebase)