fix(#338): Gate mock data behind NODE_ENV check

- Create ComingSoon component for production placeholders
- Federation connections page shows Coming Soon in production
- Workspaces settings page shows Coming Soon in production
- Teams page shows Coming Soon in production
- Add comprehensive tests for environment-based rendering

Refs #338

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-05 17:15:35 -06:00
parent 344e5df3bb
commit 587272e2d0
8 changed files with 447 additions and 5 deletions

View File

@@ -0,0 +1,51 @@
/**
* ComingSoon Component Tests
* Tests for the production placeholder component
*/
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { ComingSoon } from "./ComingSoon";
describe("ComingSoon", (): void => {
it("should render with default props", (): void => {
render(<ComingSoon feature="Test Feature" />);
expect(screen.getByText("Coming Soon")).toBeInTheDocument();
expect(screen.getByText("Test Feature")).toBeInTheDocument();
expect(screen.getByText(/This feature is currently under development/i)).toBeInTheDocument();
});
it("should render custom description", (): void => {
render(<ComingSoon feature="Custom Feature" description="Custom description text" />);
expect(screen.getByText("Custom Feature")).toBeInTheDocument();
expect(screen.getByText("Custom description text")).toBeInTheDocument();
});
it("should render children when provided", (): void => {
render(
<ComingSoon feature="Feature">
<div data-testid="child-content">Child content</div>
</ComingSoon>
);
expect(screen.getByTestId("child-content")).toBeInTheDocument();
expect(screen.getByText("Child content")).toBeInTheDocument();
});
it("should apply custom className", (): void => {
render(<ComingSoon feature="Test" className="custom-class" />);
const container = screen.getByRole("main");
expect(container).toHaveClass("custom-class");
});
it("should render construction icon by default", (): void => {
render(<ComingSoon feature="Test" />);
// The icon should be present (as an SVG)
const svg = document.querySelector("svg");
expect(svg).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,72 @@
/**
* ComingSoon Component
* Displays a placeholder for features not yet available in production.
* Used to prevent mock data from being shown to production users.
*/
import type { ReactElement, ReactNode } from "react";
export interface ComingSoonProps {
/** The name of the feature being developed */
feature: string;
/** Optional custom description */
description?: string;
/** Optional children to render below the message */
children?: ReactNode;
/** Optional className for the container */
className?: string;
}
/**
* ComingSoon displays a friendly placeholder for incomplete features.
* Use this in production when a feature is under development to avoid
* showing mock or placeholder data to users.
*/
export function ComingSoon({
feature,
description,
children,
className = "",
}: ComingSoonProps): ReactElement {
const defaultDescription =
"This feature is currently under development and will be available soon.";
return (
<main className={`container mx-auto px-4 py-8 max-w-3xl ${className}`} role="main">
<div className="flex flex-col items-center justify-center min-h-[400px] bg-gray-50 rounded-lg border border-gray-200 p-8 text-center">
{/* Construction Icon */}
<svg
className="w-16 h-16 text-blue-400 mb-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"
/>
</svg>
{/* Coming Soon Badge */}
<span className="inline-block px-4 py-1.5 bg-blue-100 text-blue-700 text-sm font-medium rounded-full mb-4">
Coming Soon
</span>
{/* Feature Name */}
<h1 className="text-2xl font-bold text-gray-900 mb-3">{feature}</h1>
{/* Description */}
<p className="text-gray-600 max-w-md mb-6">{description ?? defaultDescription}</p>
{/* Optional Children */}
{children && <div className="mt-4">{children}</div>}
</div>
</main>
);
}
export default ComingSoon;