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:

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.lockb and package-lock.json exist 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:

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

  1. Keep components pure — derive data from props or GraphQL hooks; avoid hidden module-level state
  2. Use optimistic updates for drag-and-drop, inline edits, and other interactions where waiting for the server feels slow
  3. Co-locate GraphQL fragments with the components that use them, then re-export from src/graphql/ if shared
  4. Test on mobile early — Tailwind responsiveness is solid out of the box, but verify gesture ergonomics on real devices
  5. Treat the GraphQL schema as the contract — never assume a field exists without checking the schema

Testing Strategy

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

Known Cleanup Tasks

These are not blockers for current work but should be addressed:

Roadmap