Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled

Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:52:51 +01:00
commit 67d7e6e3d5
572 changed files with 86496 additions and 0 deletions

45
src/lib/api/client.ts Normal file
View File

@@ -0,0 +1,45 @@
'use client';
import { useUIStore } from '@/stores/ui-store';
export interface ApiFetchOptions extends Omit<RequestInit, 'body'> {
body?: unknown;
}
/**
* Client-side fetch wrapper that attaches the `X-Port-Id` header from the
* UI store to every request. Used by all queryFn/mutationFn callbacks.
*/
export async function apiFetch<T = unknown>(
url: string,
opts: ApiFetchOptions = {},
): Promise<T> {
const portId = useUIStore.getState().currentPortId;
const headers = new Headers(opts.headers);
if (portId) {
headers.set('X-Port-Id', portId);
}
if (opts.body !== undefined && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
const res = await fetch(url, {
...opts,
headers,
credentials: 'include',
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
});
if (!res.ok) {
const error = await res.json().catch(() => ({ error: res.statusText }));
throw Object.assign(new Error(error.error ?? 'Request failed'), {
status: res.status,
code: error.code,
details: error.details,
});
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}