rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled

rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #61.
This commit is contained in:
2026-05-21 16:28:40 +02:00
committed by Hartmut
parent d9a7ec0338
commit b41c1d2501
943 changed files with 24548 additions and 16832 deletions
@@ -2,7 +2,7 @@ import { HolidayCalendarEditor } from "~/components/vacations/HolidayCalendarEdi
import { PublicHolidayBatch } from "~/components/vacations/PublicHolidayBatch.js";
import { EntitlementManager } from "~/components/vacations/EntitlementManager.js";
export const metadata = { title: "Vacation Management — CapaKraken" };
export const metadata = { title: "Vacation Management — Nexus" };
export default function AdminVacationsPage() {
return (
@@ -10,15 +10,19 @@ export default function AdminVacationsPage() {
<div>
<h1 className="text-2xl font-bold text-gray-900">Vacation Management</h1>
<p className="mt-1 text-sm text-gray-500">
Verwalte Feiertagskalender pro Land, Bundesland und Stadt sowie Entitlements und Fallback-Importe.
Verwalte Feiertagskalender pro Land, Bundesland und Stadt sowie Entitlements und
Fallback-Importe.
</p>
</div>
<section className="space-y-3">
<div>
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Holiday Calendars</h2>
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
Holiday Calendars
</h2>
<p className="text-sm text-gray-600">
Fachliche Quelle fuer regionale Feiertage. Diese Kalender werden fuer Urlaubszaehlung, Timeline-Overlay und Assistant-Abfragen verwendet.
Fachliche Quelle fuer regionale Feiertage. Diese Kalender werden fuer Urlaubszaehlung,
Timeline-Overlay und Assistant-Abfragen verwendet.
</p>
</div>
<HolidayCalendarEditor />
@@ -26,9 +30,12 @@ export default function AdminVacationsPage() {
<section className="space-y-3">
<div>
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Legacy Batch Import</h2>
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
Legacy Batch Import
</h2>
<p className="text-sm text-gray-600">
Nur als Fallback fuer bestaende Prozesse. Bevorzugt sollen Feiertage ueber die Kalenderlogik und nicht als statische Urlaubseintraege gepflegt werden.
Nur als Fallback fuer bestaende Prozesse. Bevorzugt sollen Feiertage ueber die
Kalenderlogik und nicht als statische Urlaubseintraege gepflegt werden.
</p>
</div>
<PublicHolidayBatch />
@@ -36,9 +43,12 @@ export default function AdminVacationsPage() {
<section className="space-y-3">
<div>
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Entitlements</h2>
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
Entitlements
</h2>
<p className="text-sm text-gray-600">
Jahresansprueche und Resttage im gleichen Kontext pruefen, nachdem Feiertage regional aufgeloest wurden.
Jahresansprueche und Resttage im gleichen Kontext pruefen, nachdem Feiertage regional
aufgeloest wurden.
</p>
</div>
<EntitlementManager />
@@ -2,7 +2,7 @@
import { useMemo, useState } from "react";
import Link from "next/link";
import { EstimateStatus, type EstimateVersionStatus } from "@capakraken/shared";
import { EstimateStatus, type EstimateVersionStatus } from "@nexus/shared";
import { clsx } from "clsx";
import { EstimateWizard } from "~/components/estimates/EstimateWizard.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
@@ -122,7 +122,8 @@ function EstimateDetailPanel({
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-400">
Estimate detail <InfoTooltip content="Pre-project cost and effort calculation. Estimates model staffing demand, scope, and financials before work begins." />
Estimate detail{" "}
<InfoTooltip content="Pre-project cost and effort calculation. Estimates model staffing demand, scope, and financials before work begins." />
</p>
<h2 className="mt-2 text-xl font-semibold text-gray-900 dark:text-gray-50">
{estimate.name}
@@ -206,7 +207,8 @@ function EstimateDetailPanel({
<section>
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
Scope items <InfoTooltip content="Deliverables or work packages that define what is included in this estimate." />
Scope items{" "}
<InfoTooltip content="Deliverables or work packages that define what is included in this estimate." />
</h3>
<span className="text-xs text-gray-400">{latestVersion.scopeItems.length}</span>
</div>
@@ -239,7 +241,8 @@ function EstimateDetailPanel({
<section>
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
Demand lines <InfoTooltip content="Staffing demand rows. Each line represents a role or resource with hours, cost rate, and sell rate." />
Demand lines{" "}
<InfoTooltip content="Staffing demand rows. Each line represents a role or resource with hours, cost rate, and sell rate." />
</h3>
<span className="text-xs text-gray-400">{latestVersion.demandLines.length}</span>
</div>
@@ -345,13 +348,19 @@ function EstimateCard({
<div className="mt-5 grid gap-3 sm:grid-cols-2">
<div>
<p className="text-xs uppercase tracking-wide text-gray-400">Opportunity <InfoTooltip content="External CRM or sales reference ID linking this estimate to a sales opportunity." /></p>
<p className="text-xs uppercase tracking-wide text-gray-400">
Opportunity{" "}
<InfoTooltip content="External CRM or sales reference ID linking this estimate to a sales opportunity." />
</p>
<p className="mt-1 text-sm text-gray-700 dark:text-gray-200">
{estimate.opportunityId ?? "Not set"}
</p>
</div>
<div>
<p className="text-xs uppercase tracking-wide text-gray-400">Updated <InfoTooltip content="When this estimate or any of its versions was last modified." /></p>
<p className="text-xs uppercase tracking-wide text-gray-400">
Updated{" "}
<InfoTooltip content="When this estimate or any of its versions was last modified." />
</p>
<p className="mt-1 text-sm text-gray-700 dark:text-gray-200">
{formatDateLong(estimate.updatedAt)}
</p>
@@ -466,7 +475,7 @@ export function EstimatesClient() {
No estimates yet
</p>
<p className="mt-2 text-sm text-gray-400 dark:text-gray-500">
Start with the wizard to create a connected estimate from CapaKraken data.
Start with the wizard to create a connected estimate from Nexus data.
</p>
</div>
) : (
+1 -1
View File
@@ -1,7 +1,7 @@
import { MobileSummaryClient } from "~/components/mobile/MobileSummaryClient.js";
export const metadata = {
title: "CapaKraken — Mobile Summary",
title: "Nexus — Mobile Summary",
};
export default function MobilePage() {
@@ -5,8 +5,8 @@ import { useUrlFilters } from "~/hooks/useUrlFilters.js";
import { useDebounce } from "~/hooks/useDebounce.js";
import { createPortal } from "react-dom";
import { formatDate, formatMoney } from "~/lib/format.js";
import type { Project, ColumnDef, ProjectStatus } from "@capakraken/shared";
import { PROJECT_COLUMNS, BlueprintTarget } from "@capakraken/shared";
import type { Project, ColumnDef, ProjectStatus } from "@nexus/shared";
import { PROJECT_COLUMNS, BlueprintTarget } from "@nexus/shared";
import Link from "next/link";
import Image from "next/image";
import { clsx } from "clsx";
@@ -4,9 +4,9 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { useUrlFilters } from "~/hooks/useUrlFilters.js";
import { useDebounce } from "~/hooks/useDebounce.js";
import Link from "next/link";
import type { Resource, SkillEntry } from "@capakraken/shared";
import { RESOURCE_COLUMNS } from "@capakraken/shared";
import { BlueprintTarget, ResourceType } from "@capakraken/shared";
import type { Resource, SkillEntry } from "@nexus/shared";
import { RESOURCE_COLUMNS } from "@nexus/shared";
import { BlueprintTarget, ResourceType } from "@nexus/shared";
import { trpc } from "~/lib/trpc/client.js";
import { formatMoney } from "~/lib/format.js";
import { generateCsv, downloadCsv } from "~/lib/csv-export.js";
@@ -945,7 +945,7 @@ export function ResourcesClient() {
sortField={sortField}
sortDir={sortDir}
onSort={toggle}
tooltip="Unique employee identifier used across all CapaKraken records."
tooltip="Unique employee identifier used across all Nexus records."
/>
);
case "displayName":
+8 -10
View File
@@ -2,24 +2,22 @@ import type { Metadata } from "next";
import { createCaller } from "~/server/trpc.js";
import { ResourceDetail } from "~/components/resources/ResourceDetail.js";
export async function generateMetadata(
{ params }: { params: Promise<{ id: string }> },
): Promise<Metadata> {
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}): Promise<Metadata> {
const { id } = await params;
try {
const trpc = await createCaller();
const resource = await trpc.resource.getById({ id });
return { title: `${resource.displayName} — Resources | CapaKraken` };
return { title: `${resource.displayName} — Resources | Nexus` };
} catch {
return { title: "Resource — CapaKraken" };
return { title: "Resource — Nexus" };
}
}
export default async function ResourceDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
export default async function ResourceDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <ResourceDetail resourceId={id} />;
}
@@ -1,5 +1,5 @@
import type { Resource } from "@capakraken/shared";
import { ResourceType } from "@capakraken/shared";
import type { Resource } from "@nexus/shared";
import { ResourceType } from "@nexus/shared";
export type ModalState =
| { type: "closed" }
+1 -1
View File
@@ -1,6 +1,6 @@
import { MyVacationsClient } from "~/components/vacations/MyVacationsClient.js";
export const metadata = { title: "My Vacations — CapaKraken" };
export const metadata = { title: "My Vacations — Nexus" };
export default function MyVacationsPage() {
return <MyVacationsClient />;
@@ -1,4 +1,4 @@
import { prisma } from "@capakraken/db";
import { prisma } from "@nexus/db";
/** Window over which auth events are analysed. */
const WINDOW_MS = 30 * 60 * 1000; // 30 minutes
@@ -17,7 +17,7 @@ import { THRESHOLDS } from "./detect.js";
const auditLogFindManyMock = vi.hoisted(() => vi.fn());
const userFindManyMock = vi.hoisted(() => vi.fn());
vi.mock("@capakraken/db", () => ({
vi.mock("@nexus/db", () => ({
prisma: {
auditLog: { findMany: auditLogFindManyMock },
user: { findMany: userFindManyMock },
@@ -27,11 +27,11 @@ vi.mock("@capakraken/db", () => ({
// ─── createNotificationsForUsers mock ─────────────────────────────────────────
const createNotificationsMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
vi.mock("@capakraken/api", () => ({
vi.mock("@nexus/api", () => ({
createNotificationsForUsers: createNotificationsMock,
}));
vi.mock("@capakraken/api/lib/logger", () => ({
vi.mock("@nexus/api/lib/logger", () => ({
logger: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
}));
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { createNotificationsForUsers } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { createNotificationsForUsers } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { verifyCronSecret } from "~/lib/cron-auth.js";
import { detectAuthAnomalies } from "./detect.js";
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { checkChargeabilityAlerts } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { checkChargeabilityAlerts } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { checkPendingEstimateReminders } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { checkPendingEstimateReminders } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { createNotificationsForUsers } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { createNotificationsForUsers } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { createConnection } from "net";
import { verifyCronSecret } from "~/lib/cron-auth.js";
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { autoImportPublicHolidays } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { autoImportPublicHolidays } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
@@ -45,10 +45,10 @@ export async function GET(request: Request) {
skippedExisting: result.skippedExisting,
});
} catch (error) {
logger.error({ error, route: "/api/cron/public-holidays", year }, "Public holiday import cron failed");
return NextResponse.json(
{ ok: false, error: "Internal error" },
{ status: 500 },
logger.error(
{ error, route: "/api/cron/public-holidays", year },
"Public holiday import cron failed",
);
return NextResponse.json({ ok: false, error: "Internal error" }, { status: 500 });
}
}
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { createNotificationsForUsers } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { createNotificationsForUsers } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { readFileSync } from "fs";
import { join } from "path";
import { verifyCronSecret } from "~/lib/cron-auth.js";
@@ -1,7 +1,7 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { sendWeeklyDigest } from "@capakraken/api";
import { logger } from "@capakraken/api/lib/logger";
import { prisma } from "@nexus/db";
import { sendWeeklyDigest } from "@nexus/api";
import { logger } from "@nexus/api/lib/logger";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
+9 -3
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { prisma } from "@nexus/db";
import { createConnection } from "net";
export const dynamic = "force-dynamic";
@@ -30,8 +30,14 @@ async function checkRedis(): Promise<"ok" | "error"> {
socket.destroy();
resolve(data.toString().includes("PONG") ? "ok" : "error");
});
socket.on("timeout", () => { socket.destroy(); resolve("error"); });
socket.on("error", () => { socket.destroy(); resolve("error"); });
socket.on("timeout", () => {
socket.destroy();
resolve("error");
});
socket.on("error", () => {
socket.destroy();
resolve("error");
});
} catch {
resolve("error");
}
+7 -3
View File
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@capakraken/api/sse", () => ({
vi.mock("@nexus/api/sse", () => ({
eventBus: { subscriberCount: 0 },
}));
@@ -33,7 +33,7 @@ describe("GET /api/perf — security hardening", () => {
const response = await GET(request);
expect(response.status).toBe(200);
const body = await response.json() as { timestamp: string; uptime: unknown; memory: unknown };
const body = (await response.json()) as { timestamp: string; uptime: unknown; memory: unknown };
expect(typeof body.timestamp).toBe("string");
expect(body.uptime).toBeDefined();
expect(body.memory).toBeDefined();
@@ -81,7 +81,11 @@ describe("GET /api/perf — security hardening", () => {
const response = await GET(request);
expect(response.status).toBe(401);
const body = await response.json() as { error?: string; timestamp?: string; memory?: unknown };
const body = (await response.json()) as {
error?: string;
timestamp?: string;
memory?: unknown;
};
expect(body.timestamp).toBeUndefined();
expect(body.memory).toBeUndefined();
});
+1 -1
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { eventBus } from "@capakraken/api/sse";
import { eventBus } from "@nexus/api/sse";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
+3 -6
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { prisma } from "@capakraken/db";
import { prisma } from "@nexus/db";
import { createConnection } from "net";
export const dynamic = "force-dynamic";
@@ -18,7 +18,7 @@ async function checkPostgres(): Promise<"ok" | "error"> {
/**
* Lightweight Redis PING check using a raw TCP socket.
* Avoids importing ioredis (which is only a dependency of @capakraken/api).
* Avoids importing ioredis (which is only a dependency of @nexus/api).
*/
async function checkRedis(): Promise<"ok" | "error"> {
return new Promise((resolve) => {
@@ -58,10 +58,7 @@ async function checkRedis(): Promise<"ok" | "error"> {
}
export async function GET() {
const [postgres, redis] = await Promise.all([
checkPostgres(),
checkRedis(),
]);
const [postgres, redis] = await Promise.all([checkPostgres(), checkRedis()]);
const allHealthy = postgres === "ok" && redis === "ok";
@@ -13,7 +13,7 @@ const authMock = vi.hoisted(() => vi.fn());
vi.mock("~/server/auth.js", () => ({ auth: authMock }));
// ─── heavy dep stubs ─────────────────────────────────────────────────────────
vi.mock("@capakraken/db", () => ({
vi.mock("@nexus/db", () => ({
prisma: {
demandRequirement: { findMany: vi.fn().mockResolvedValue([]) },
assignment: { findMany: vi.fn().mockResolvedValue([]) },
@@ -21,11 +21,11 @@ vi.mock("@capakraken/db", () => ({
},
}));
vi.mock("@capakraken/application", () => ({
vi.mock("@nexus/application", () => ({
buildSplitAllocationReadModel: vi.fn().mockReturnValue({ assignments: [] }),
}));
vi.mock("@capakraken/api", () => ({
vi.mock("@nexus/api", () => ({
anonymizeResource: vi.fn((r: unknown) => r),
getAnonymizationDirectory: vi.fn().mockResolvedValue({}),
}));
@@ -2,10 +2,10 @@ import { renderToBuffer } from "@react-pdf/renderer";
import { createElement } from "react";
import { NextResponse } from "next/server";
import { z } from "zod";
import { buildSplitAllocationReadModel } from "@capakraken/application";
import { anonymizeResource, getAnonymizationDirectory } from "@capakraken/api";
import { prisma } from "@capakraken/db";
import type { AllocationLike } from "@capakraken/shared";
import { buildSplitAllocationReadModel } from "@nexus/application";
import { anonymizeResource, getAnonymizationDirectory } from "@nexus/api";
import { prisma } from "@nexus/db";
import type { AllocationLike } from "@nexus/shared";
import { auth } from "~/server/auth.js";
import { AllocationReport } from "~/components/reports/AllocationReport.js";
import { createWorkbookArrayBuffer } from "~/lib/workbook-export.js";
+6 -6
View File
@@ -1,9 +1,9 @@
import { loadRoleDefaults } from "@capakraken/api";
import { deriveUserSseSubscription, eventBus } from "@capakraken/api/sse";
import { startReminderScheduler } from "@capakraken/api/lib/reminder-scheduler";
import { prisma } from "@capakraken/db";
import type { SystemRole } from "@capakraken/shared";
import { SSE_EVENT_TYPES, type PermissionOverrides } from "@capakraken/shared";
import { loadRoleDefaults } from "@nexus/api";
import { deriveUserSseSubscription, eventBus } from "@nexus/api/sse";
import { startReminderScheduler } from "@nexus/api/lib/reminder-scheduler";
import { prisma } from "@nexus/db";
import type { SystemRole } from "@nexus/shared";
import { SSE_EVENT_TYPES, type PermissionOverrides } from "@nexus/shared";
import { auth } from "~/server/auth.js";
export const dynamic = "force-dynamic";
+3 -3
View File
@@ -1,6 +1,6 @@
import { createTRPCContext, loadRoleDefaults } from "@capakraken/api";
import { appRouter } from "@capakraken/api/router";
import { prisma } from "@capakraken/db";
import { createTRPCContext, loadRoleDefaults } from "@nexus/api";
import { appRouter } from "@nexus/api/router";
import { prisma } from "@nexus/db";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { getToken } from "next-auth/jwt";
import type { NextRequest } from "next/server";
@@ -2,7 +2,7 @@
import { use, useState } from "react";
import { useRouter } from "next/navigation";
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@capakraken/shared";
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
import { trpc } from "~/lib/trpc/client.js";
export default function ResetPasswordPage({ params }: { params: Promise<{ token: string }> }) {
+2 -2
View File
@@ -98,7 +98,7 @@ export default function SignInPage() {
<div className="hidden rounded-[2rem] border border-white/70 bg-white/75 p-10 shadow-2xl backdrop-blur lg:flex lg:flex-col lg:justify-between dark:border-slate-800 dark:bg-slate-950/60">
<div>
<span className="inline-flex rounded-full border border-brand-200 bg-brand-50 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-brand-700 dark:border-brand-900/50 dark:bg-brand-900/20 dark:text-brand-300">
CapaKraken Control Center
Nexus Control Center
</span>
<h1 className="mt-6 font-display text-5xl font-semibold leading-tight text-gray-900 dark:text-gray-50">
Resource planning that stays readable under pressure.
@@ -137,7 +137,7 @@ export default function SignInPage() {
Welcome Back
</p>
<h2 className="mt-3 font-display text-4xl font-semibold text-gray-900 dark:text-gray-50">
{mfaRequired ? "Two-Factor Authentication" : "Sign in to CapaKraken"}
{mfaRequired ? "Two-Factor Authentication" : "Sign in to Nexus"}
</h2>
<p className="mt-2 text-sm text-gray-500">
{mfaRequired
+2 -2
View File
@@ -2,7 +2,7 @@
import { useState, use } from "react";
import { useRouter } from "next/navigation";
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@capakraken/shared";
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
import { trpc } from "~/lib/trpc/client.js";
export default function AcceptInvitePage({ params }: { params: Promise<{ token: string }> }) {
@@ -91,7 +91,7 @@ export default function AcceptInvitePage({ params }: { params: Promise<{ token:
<div className="mb-6">
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">Accept invitation</h1>
<p className="mt-1 text-sm text-gray-500">
You have been invited as <strong>{invite.role}</strong> to CapaKraken. Set a password to
You have been invited as <strong>{invite.role}</strong> to Nexus. Set a password to
activate your account (<span className="font-medium">{invite.email}</span>).
</p>
</div>
+51 -10
View File
@@ -19,8 +19,8 @@ const displayFont = Manrope({
});
export const metadata: Metadata = {
metadataBase: new URL("https://capakraken.hartmut-noerenberg.com"),
title: "CapaKraken — Resource & Capacity Planning",
metadataBase: new URL("https://nexus.hartmut-noerenberg.com"),
title: "Nexus — Resource & Capacity Planning",
description: "Interactive resource planning and project staffing tool",
manifest: "/manifest.json",
icons: {
@@ -35,17 +35,17 @@ export const metadata: Metadata = {
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "CapaKraken",
title: "Nexus",
},
openGraph: {
title: "CapaKraken — Resource & Capacity Planning",
title: "Nexus — Resource & Capacity Planning",
description: "Estimates, staffing, chargeability, and timelines in one workspace.",
images: [{ url: "/og-image.png", width: 1024, height: 1024, alt: "CapaKraken Logo" }],
images: [{ url: "/og-image.png", width: 1024, height: 1024, alt: "Nexus Logo" }],
type: "website",
},
twitter: {
card: "summary_large_image",
title: "CapaKraken — Resource & Capacity Planning",
title: "Nexus — Resource & Capacity Planning",
description: "Estimates, staffing, chargeability, and timelines in one workspace.",
images: ["/og-image.png"],
},
@@ -60,15 +60,56 @@ export default async function RootLayout({ children }: { children: React.ReactNo
return (
<html lang="en" suppressHydrationWarning>
<head>
<script nonce={nonce} suppressHydrationWarning dangerouslySetInnerHTML={{__html: `
<script
nonce={nonce}
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `
try {
var p = JSON.parse(localStorage.getItem('capakraken_theme') || '{}');
if (!localStorage.getItem('nexus_migrated_v1')) {
var underscoreKeys = ['theme','sidebar_collapsed','mfa_prompt_snoozed_until','prefs','pwa_dismiss'];
underscoreKeys.forEach(function(k){
var oldK = 'capakraken_' + k, newK = 'nexus_' + k;
var v = localStorage.getItem(oldK);
if (v !== null && localStorage.getItem(newK) === null) localStorage.setItem(newK, v);
localStorage.removeItem(oldK);
});
var dashKeys = [];
for (var i = 0; i < localStorage.length; i++) {
var lk = localStorage.key(i);
if (lk && lk.indexOf('capakraken_dashboard_v1_') === 0) dashKeys.push(lk);
}
dashKeys.forEach(function(lk){
var newLk = 'nexus_' + lk.substring('capakraken_'.length);
var v = localStorage.getItem(lk);
if (v !== null && localStorage.getItem(newLk) === null) localStorage.setItem(newLk, v);
localStorage.removeItem(lk);
});
['capakraken-chat-messages','capakraken-chat-conversation-id'].forEach(function(lk){
var newLk = 'nexus-' + lk.substring('capakraken-'.length);
var v = localStorage.getItem(lk);
if (v !== null && localStorage.getItem(newLk) === null) localStorage.setItem(newLk, v);
localStorage.removeItem(lk);
});
var av = localStorage.getItem('capakraken:allocations:viewMode');
if (av !== null && localStorage.getItem('nexus:allocations:viewMode') === null) {
localStorage.setItem('nexus:allocations:viewMode', av);
}
localStorage.removeItem('capakraken:allocations:viewMode');
localStorage.setItem('nexus_migrated_v1', '1');
if (typeof caches !== 'undefined') caches.delete('capakraken-v2');
}
var p = JSON.parse(localStorage.getItem('nexus_theme') || '{}');
if (p.mode === 'dark') document.documentElement.classList.add('dark');
if (p.accent) document.documentElement.setAttribute('data-accent', p.accent);
} catch(e) {}
`}} />
`,
}}
/>
</head>
<body className={`${uiFont.variable} ${displayFont.variable} min-h-screen bg-gray-50 font-sans antialiased`}>
<body
className={`${uiFont.variable} ${displayFont.variable} min-h-screen bg-gray-50 font-sans antialiased`}
>
<TRPCProvider>{children}</TRPCProvider>
<ServiceWorkerRegistration />
<InstallPrompt />
+2 -2
View File
@@ -2,7 +2,7 @@
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@capakraken/shared";
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
import { createFirstAdmin } from "./actions.js";
export function SetupClient() {
@@ -76,7 +76,7 @@ export function SetupClient() {
<div className="mb-6">
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">First-run setup</h1>
<p className="mt-1 text-sm text-gray-500">
Create the initial administrator account for CapaKraken.
Create the initial administrator account for Nexus.
</p>
</div>
+3 -7
View File
@@ -1,11 +1,7 @@
"use server";
import { prisma } from "@capakraken/db";
import { SystemRole } from "@capakraken/db";
import {
PASSWORD_MAX_LENGTH,
PASSWORD_MIN_LENGTH,
PASSWORD_POLICY_MESSAGE,
} from "@capakraken/shared";
import { prisma } from "@nexus/db";
import { SystemRole } from "@nexus/db";
import { PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
export type SetupResult =
| { success: true }
+1 -1
View File
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
import { prisma } from "@capakraken/db";
import { prisma } from "@nexus/db";
import { SetupClient } from "./SetupClient.js";
export default async function SetupPage() {