Timestamp and registration checking

This commit is contained in:
2026-04-25 18:09:02 +02:00
parent f550b3400f
commit b9654d7a7c
13 changed files with 525 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
import { getSessionUser } from "@/lib/session";
import { getTranslations } from "next-intl/server";
import { getTranslations, getFormatter } from "next-intl/server";
import { redirect } from "next/navigation";
import { listTenants } from "@/lib/k8s";
import { getTenantRequestByOrgId } from "@/lib/db";
@@ -7,6 +7,7 @@ import { Card, CardHeader } from "@/components/ui/card";
import { StatusBadge } from "@/components/ui/status-badge";
import { UsageDisplay } from "@/components/dashboard/usage-display";
import { OnboardingFlow } from "@/components/onboarding/onboarding-flow";
import { formatDateTime } from "@/lib/format";
import Link from "next/link";
export default async function DashboardPage() {
@@ -15,6 +16,7 @@ export default async function DashboardPage() {
const t = await getTranslations("dashboard");
const tAdmin = await getTranslations("admin");
const f = await getFormatter();
const allTenants = await listTenants();
@@ -110,9 +112,7 @@ export default async function DashboardPage() {
{tenant.spec.packages?.join(", ") || "—"}
</td>
<td className="px-5 py-3 text-xs text-text-muted tabular-nums">
{tenant.metadata.creationTimestamp
? new Date(tenant.metadata.creationTimestamp).toLocaleDateString()
: "—"}
{formatDateTime(tenant.metadata.creationTimestamp, f)}
</td>
<td className="px-5 py-3 text-right">
<Link

View File

@@ -44,6 +44,12 @@ export default function RegisterPage() {
if (!res.ok) {
const data = await res.json();
// Localize known structured codes; fall back to server-supplied
// English message for everything else (validation, ZITADEL errors,
// generic 500s).
if (data.code === "duplicate_domain" && data.domain) {
throw new Error(t("duplicateDomain", { domain: data.domain }));
}
throw new Error(data.error || "Registration failed");
}

View File

@@ -1,5 +1,5 @@
import { getSessionUser } from "@/lib/session";
import { getTranslations } from "next-intl/server";
import { getTranslations, getFormatter } from "next-intl/server";
import { redirect, notFound } from "next/navigation";
import { getTenant } from "@/lib/k8s";
import { StatusBadge } from "@/components/ui/status-badge";
@@ -7,6 +7,7 @@ import { UsageDisplay } from "@/components/dashboard/usage-display";
import { PackageList } from "@/components/packages/package-list";
import { WorkspaceEditor } from "@/components/packages/workspace-editor";
import { ChannelUsers } from "@/components/channel-users/channel-users";
import { formatDateTime, formatRelative } from "@/lib/format";
const CHANNEL_PACKAGES = ["telegram", "discord", "email"];
@@ -20,6 +21,7 @@ export default async function TenantDetailPage({
const { name } = await params;
const t = await getTranslations("tenantDetail");
const f = await getFormatter();
const tenant = await getTenant(name);
if (!tenant) notFound();
@@ -60,6 +62,18 @@ export default async function TenantDetailPage({
{t("agent")}: {tenant.spec.agentName}
</p>
)}
{tenant.metadata.creationTimestamp && (
<p
className="text-xs text-text-muted mt-1"
title={formatDateTime(tenant.metadata.creationTimestamp, f)}
>
{t("provisioned")}{" "}
{formatRelative(tenant.metadata.creationTimestamp, f)}{" "}
<span className="text-text-muted/60">
({formatDateTime(tenant.metadata.creationTimestamp, f)})
</span>
</p>
)}
</div>
{/* Usage */}

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { registerCustomer } from "@/lib/zitadel";
import { rateLimit } from "@/lib/rate-limit";
import { checkDuplicateDomain } from "@/lib/db";
import type { RegistrationInput } from "@/types";
import { z } from "zod";
@@ -53,6 +54,28 @@ export async function POST(request: NextRequest) {
const input: RegistrationInput = parsed.data;
// --- Duplicate-domain check ---
//
// Block if another active tenant_request or ZITADEL org already exists
// for this corporate email domain. Public domains (gmail, gmx, etc.)
// are exempted by checkDuplicateDomain.
//
// We return a structured `code: "duplicate_domain"` with the matched
// domain so the client can render the localized message via
// register.duplicateDomain (with {domain} interpolation). The fallback
// English string is included for non-i18n clients (curl, monitoring).
const dup = await checkDuplicateDomain(input.email);
if (dup.blocked && dup.domain) {
return NextResponse.json(
{
error: `An account for the email domain ${dup.domain} is already registered. Please contact your company administrator or PieCed IT support.`,
code: "duplicate_domain",
domain: dup.domain,
},
{ status: 409 },
);
}
const result = await registerCustomer({
companyName: input.companyName,
email: input.email,