rs

2026 · Completed

Maitecrafts — a family workshop, organised

A web application I built for my parents to track custom products, configure their production workflow, and connect online orders with the workshop.

Production board in Spanish, edited from a real screenshot with fictional sample data.

Stack

Next.js, React, TypeScript, PostgreSQL, Prisma, TanStack Query, Tailwind CSS

Code

GitHub →

Software for the people doing the work

I built Maitecrafts for my parents’ workshop, where they make personalised T-shirts, mugs and other custom products. The aim was practical: give them a shared place to see what needs doing and how far each product has progressed.

A single order can contain several products with different production needs. A mug might be ready while a T-shirt is still being printed. That distinction shaped the application: progress belongs to each product line, while the order brings the work together.

My role: full-stack development, covering the data model, production interface, administration tools and integrations.

From an order to a shared production board

The application centres on a Kanban board. On larger screens, orders appear in columns and can be dragged between stages. On mobile, the same information becomes a vertical list grouped by stage, with buttons to move products forward or back.

An order appears in the column of its least advanced product. This makes unfinished work visible even when some of the order is already ready. Variants sharing a design can be grouped visually and moved together, while keeping their individual records.

Around that board, the application includes:

  • Order creation and editing, quantities, delivery dates, notes and reference photos.
  • Search, delivery-date filters and saved filters for recurring views.
  • An administration panel for production stages, product types, fields and notification rules.
  • A separate design catalogue for photos and reusable cutting notes.
  • WooCommerce order imports and WhatsApp notifications.

My parents use the application in production. Its interface is in Spanish because that is the language they use in the workshop. The screenshots here were edited from the real interface: customer details, order references and product artwork have been replaced with fictional examples.

Open the production board at full size ↗
Spanish order detail with fictional order DEMO-1001, product attributes, reference photo, notes and stage controls.
Order detail: product attributes, reference photos and production controls in one view. Edited screenshot with sample data.

A compact architecture

Next.js brings the React interface and server-side application logic into one project. The repository documents deployment on Vercel, with PostgreSQL on Neon and Vercel Blob for photos. The service diagram shows these hosting boundaries alongside Google sign-in, WooCommerce and WhatsApp integration.

Service architecture showing Vercel, Next.js, Prisma, Neon PostgreSQL, Google OAuth, Vercel Blob, WooCommerce and CallMeBot for WhatsApp.
Open diagram at full size ↗

This structure keeps a small internal tool manageable. Production rules live on the server and can be reused across entry points. For example, manual orders and WooCommerce imports both use createOrderRecord, instead of maintaining separate order-creation implementations.

Three decisions that shaped the application

1. Keep the core relational and the workshop configurable

Orders, product lines and production stages have explicit relationships. An order’s serial number is unique, and each product line references its current stage by ID.

The parts that vary between products are more flexible. Administrators define fields such as size, colour or printing material, and their values are stored in JSON columns. Field definitions can apply to several product types. Stages and product types are also database records, so everyday configuration changes do not require a schema migration.

Entity-relationship diagram with database tables, selected columns, primary and foreign keys, and crow’s-foot cardinalities.
Open diagram at full size ↗

The trade-off is that flexible data needs application-level interpretation and validation. The form helpers convert incoming values and check required fields; JSON does not provide the same database constraints as a dedicated column for every attribute. Some business rules still depend on specific field keys, so this is configurable software with workshop-specific conventions.

Spanish admin interface for reordering production stages and choosing which printing methods skip a stage.
The actual stage configuration screen. Here, DTF products skip the plotting stage; the workflow is configured through the interface.

2. Use polling suited to a small team

The query provider refreshes data every four seconds and when the window regains focus. For a tool designed around two workshop users, this keeps synchronisation straightforward without introducing a separate real-time service.

The current board waits for a mutation to finish, disables the relevant controls while it is pending, and then invalidates the cached Kanban query. Moving a design can affect multiple variants, so the client does not attempt to reproduce all those changes optimistically.

This means updates are not instantaneous across devices. Polling also produces repeated reads, so the interval and data-fetching strategy would need revisiting if usage grew substantially.

3. Treat progress as a business rule

A product does not always need every stage. Configurable skip rules let the server resolve the next applicable stage for its printing material.

Once the target is known, the product update and its history entry are written in one transaction. The following excerpt is taken from applyStageChange:

const updated = await prisma.$transaction(async (tx) => {
  const updatedItem = await tx.orderItem.update({
    where: { id: itemId },
    data: { stageId: newStageId },
  });
  await tx.orderItemStatusHistory.create({
    data: {
      orderItemId: itemId,
      fromStageId: item.stageId,
      toStageId: newStageId,
      changedById: userId,
    },
  });
  return updatedItem;
});

This keeps the movement and its audit record together. Notification checks and order-level timestamps run afterwards, outside that transaction.

UML sequence diagram showing browser, Server Action, database and notification lifelines, with the transaction boundary before notifications and order timestamp updates.
Open diagram at full size ↗

The order’s delivery timestamp is derived from whether all its products have reached the final stage. Adding a new product or moving one backwards can clear that timestamp again. This avoids relying on a separate manual “complete order” action that could drift away from the products’ actual progress.

Connecting the shop to the workshop

The WooCommerce endpoint accepts signed webhooks and imports orders whose status is processing. It verifies the signature against the raw request body before handling the payload.

Repeated deliveries are handled using a stable order reference:

const serialNumber = `WOO-${payload.number}`;

const existing = await prisma.order.findUnique({ where: { serialNumber } });
if (existing) {
  return NextResponse.json({ ok: true, skipped: "duplicate" });
}

That initial check is backed by a unique database constraint. If two deliveries race, the error handler checks whether the order now exists and acknowledges the duplicate. SKU and attribute mappings translate shop products into the workshop’s model; unmapped SKUs trigger an administrator notification for review.

WhatsApp messages go through a small provider adapter. Delivery errors are caught so a failed message does not undo a saved production change. The current implementation awaits delivery attempts and has no durable retry queue: it is best-effort notification delivery.

Small details that matter in daily use

Phone photos are resized in the browser before upload, using native image and canvas APIs. The default maximum dimension is 1,600 pixels, with JPEG quality set to 0.75. This addresses upload size without adding an image-processing dependency to the client.

Google sign-in provides authentication. Server-side helpers check the session and enforce administrator roles for restricted operations such as configuration changes. The board’s navigation controls are also disabled during relevant pending mutations to reduce accidental repeated actions.

The design catalogue addresses another workshop need: keeping reference images and cutting notes together. It remains independent of orders, so a useful design reference does not have to belong to a particular sale.

Spanish design catalogue with a fictional mountain T-shirt and reusable vinyl cutting notes.
Reusable cutting notes remain available independently of individual orders. Edited screenshot with sample artwork.

Completed work can leave the active board without being deleted. An administrator chooses a production stage and an archiving delay; the application determines which orders belong in the archive when reading the data, without relying on a scheduled cleanup job.

Archived orders in Spanish with fictional DEMO references and example customer labels.
Archived orders remain accessible while keeping the active board focused on current work. Edited screenshot with sample data.

What this project demonstrates

Maitecrafts brings order tracking, configurable production steps, design references and shop integration into one application built around a concrete family-business need.

The engineering work is in translating that workflow into consistent behaviour: product-level progress, derived order status, shared creation logic and configurable attributes with a stable relational core.

I have not attached time-saving or error-reduction figures to this case study because those outcomes have not been measured. The next improvements I would prioritise are automated coverage for stage transitions and duplicate webhooks, stronger concurrency guarantees for grouped movements, and a durable notification queue if message delivery becomes operationally critical.

Implementation references

These links point to the source revision used for this case study: