TDesk

TDesk technical documentation

React & backend integration

Reuse the markup and design. Choose which system will own routing, state, and writes.

Choose an architecture

ApproachWhat to keepWhat to replace
Static HTML + APIHTML, classes, page styles and the UI behaviors you still need.Demo data adapters, form handlers, auth, and notifications.
React or another SPADesign tokens, CSS, fonts, and HTML structure as components.Router, data loading, event handlers, and any script that mutates framework-owned DOM.
Server-rendered templatesHTML layouts, fields, CSS, and small UI scripts.Hard-coded fixtures with server-rendered data; demo submit hooks with real routes.

Port one screen to React

  1. Start with the ticket list. Extract AdminLayout, Sidebar, TicketFilters, and TicketRow.
  2. Copy the markup into JSX. Convert class to className, for to htmlFor, and inline style strings to objects. Keep aria-* and data-* attributes.
  3. Load the admin styles in their original order. Put the assets and vendor trees under your app’s public assets with the relative CSS font paths intact.
  4. Own each interaction in one place. Do not load the old page script on a component whose DOM it used to render. For third-party widgets, initialize after mounting and destroy the instance during cleanup.
  5. Use router parameters for detail IDs. Render loading, empty, error, and success states explicitly.
export function TicketRow({ ticket, href }) {
  return (
    <tr>
      <td><a href={href}>{ticket.subject}</a></td>
      <td><span className="status status--blue">{ticket.status}</span></td>
    </tr>
  );
}

See the official React guides for JSX and effect cleanup and external widgets.

Connect a backend in any language

Use Django, Laravel, Express, Rails, ASP.NET, or any server that can serve HTML or JSON. Extract shared HTML into a layout, render ticket rows with your template engine, and bind your real form endpoints. Keep IDs, labels, and CSS classes stable while you migrate one screen at a time.

Suggested routeResponsibility
GET /api/tickets?status=OpenReturn a filtered page of tickets the current user may see.
GET /api/tickets/:idReturn a permitted ticket and its conversation.
POST /api/ticketsValidate and create a ticket; return its stable ID.
POST /api/tickets/:id/repliesValidate and persist a reply or staff-only note with the proper visibility.
PATCH /api/tickets/:idAuthorize a status, priority, or assignment change.

Example list response:

{
  "items": [{ "id": "1042", "subject": "Unable to invite new team members", "status": "Open", "priority": "High" }],
  "nextCursor": null
}

Example client adapter:

// A proposed same-origin endpoint. Implement it on your server first.
export async function listTickets(status, signal) {
  const params = new URLSearchParams({ status });
  const response = await fetch(`/api/tickets?${params}`, {
    credentials: "same-origin",
    headers: { Accept: "application/json" },
    signal
  });
  if (!response.ok) throw new Error(`Ticket request failed: ${response.status}`);
  return response.json();
}

Use abortable reads on filter changes. Keep a draft after a failed write; disable duplicate submissions while saving and update the UI only after the server confirms. Map validation responses to their associated fields.

Keep access and data rules on the server

  • Authenticate with your framework’s maintained session or identity system. Verify each ticket, reply, and attachment against the current user and workspace.
  • Validate all writes. For cookie-based sessions, use HTTPS and your framework’s CSRF protections. Keep secrets and provider credentials on the server.
  • Use server-side permissions for staff-only notes and admin actions. Escape text; sanitize allowed HTML before rendering rich replies. Validate upload types and sizes, and enforce access when downloading.
  • Replace the dashboard sample metrics with real scoped data after ticket events are connected.