diff --git a/README.md b/README.md index 50fd2a2..85680d3 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,15 @@ A powerful tool for generating realistic mock data using AI LLMs. -## New in v1.1.0: External API +## New in v1.2.0: Template & UI overhaul -**Programmatic Access with API Keys** +**Enhanced UX, templates, and event analytics** -- **External API Endpoint**: `POST /api/v1/generate` for programmatic data generation -- **Secure API Key Management**: Create, manage, and revoke API keys with 90-day expiry -- **Bearer Token Authentication**: Use API keys in Authorization header -- **Clean Data Response**: Get clean JSON/SQL/CSV/etc, without explanatory text -- **Rate Limiting**: 5 free generations per day (paid tiers coming soon). -- **Multiple Formats**: JSON, SQL, CSV, XML, HTML, TXT support +- Dedicated **Templates page** with searchable categories, SQL/NoSQL toggles, and generator injection +- Modal template browser using gradient CTAs, skeleton loaders, and dual-schema cards that prefill the generator +- Refreshed **Generator UI**: skeleton loaders, progress/cancel controls, CTA gradient utilities, and repositioned save/reset controls +- **Events dashboard table** with filters plus portrait jsPDF export for auditing +- Unified dark theme and CSS overhual, nav/footer polish, and consistent CTA styles ### Simplest Quick Start with External API @@ -103,9 +102,9 @@ VERCEL_OIDC_TOKEN=token_to_connect_to_redis_service - Real-time record generation request tracking using Redis Streams/Upstash - Rate limitting of free API record generation requests using Redis - Export data in JSON, SQL, or CSV formats -- **NEW**: External API with secure API key authentication -- **NEW**: Programmatic data generation for integrations -- **NEW**: API key management with usage tracking +- **NEW**: Templates catalogue & modal picker with SQL/NoSQL toggle and generator injection +- **NEW**: Generator progress/cancel controls plus CTA gradient system +- **NEW**: Events dashboard table with filters and portrait jsPDF export ## Tech Stack @@ -214,6 +213,12 @@ npm run test:critical ## Releases +### v1.2.0 - UI, templates, and analytics +- **Templates catalogue**: Dedicated templates page + modal picker with SQL/NoSQL toggle, dual-schema data, and generator injection +- **Generator polish**: Skeletons, CTA gradient system, progress/cancellation controls, and repositioned save/reset actions +- **Events dashboard**: Filterable table prepared for audits and portrait jsPDF export +- **Global theming**: Unified dark CSS theme, navbar/footer polish, and CTA color tokens + ### v1.1.0 - External API & API Key Management - **External API**: New `/api/v1/generate` endpoint for programmatic access - **API Key Management**: Create, manage, and revoke API keys with 90-day expiry diff --git a/__tests__/components/ui/Button.test.tsx b/__tests__/components/ui/Button.test.tsx index a203d10..069e13e 100644 --- a/__tests__/components/ui/Button.test.tsx +++ b/__tests__/components/ui/Button.test.tsx @@ -51,8 +51,8 @@ describe('Button Component', () => { expect(button).toBeInTheDocument(); // Default variant classes should be applied - expect(button).toHaveClass('bg-primary'); - expect(button).toHaveClass('text-primary-foreground'); + expect(button).toHaveClass('cta-button'); + expect(button).toHaveClass('text-white'); // Default size classes should be applied expect(button).toHaveClass('h-9'); diff --git a/__tests__/components/ui/alert.test.tsx b/__tests__/components/ui/alert.test.tsx new file mode 100644 index 0000000..ddbed8d --- /dev/null +++ b/__tests__/components/ui/alert.test.tsx @@ -0,0 +1,46 @@ +/** + * @jest-environment jsdom + */ + +import React from "react" +import { render, screen } from "@testing-library/react" +import "@testing-library/jest-dom" + +jest.mock("@/lib/utils", () => ({ + cn: (...args: string[]) => args.filter(Boolean).join(" "), +})) + +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/components/ui/alert" + +describe("Alert components", () => { + it("renders default alert with expected classes", () => { + render( + + Title + Description + + ) + + const alert = screen.getByTestId("alert-default") + expect(alert).toHaveRole("alert") + expect(alert).toHaveClass("bg-background", "text-foreground") + expect(screen.getByText("Title")).toHaveClass("font-medium") + expect(screen.getByText("Description")).toHaveClass("text-sm") + }) + + it("applies destructive variant classes", () => { + render( + + Destructive + + ) + + const alert = screen.getByTestId("alert-destructive") + expect(alert).toHaveClass("border-destructive/50", "text-destructive") + }) +}) + diff --git a/__tests__/components/ui/card.test.tsx b/__tests__/components/ui/card.test.tsx new file mode 100644 index 0000000..b22c36c --- /dev/null +++ b/__tests__/components/ui/card.test.tsx @@ -0,0 +1,81 @@ +/** + * @jest-environment jsdom + */ + +import React from "react" +import { render, screen } from "@testing-library/react" +import "@testing-library/jest-dom" + +jest.mock("@/lib/utils", () => ({ + cn: (...args: string[]) => args.filter(Boolean).join(" "), +})) + +import { + Card, + CardAction, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" + +describe("Card components", () => { + it("renders the Card wrapper and inner surface with provided props", () => { + render( + + card body + + ) + + const inner = screen.getByTestId("card-inner") + expect(inner).toBeInTheDocument() + expect(inner).toHaveClass("min-h-full", "extra-class") + + const outer = inner.closest("[data-slot='card']") + expect(outer).toBeInTheDocument() + expect(outer).toHaveStyle( + "background-image: linear-gradient(135deg, var(--card-gradient-start), var(--card-gradient-middle), var(--card-gradient-end))" + ) + }) + + it("applies slot classes for header, title, and description", () => { + render( + + + Title + + Description + + + + ) + + const header = screen.getByTestId("card-header") + expect(header).toHaveAttribute("data-slot", "card-header") + expect(header).toHaveClass("grid", "items-start") + + expect(screen.getByTestId("card-title")).toHaveClass("font-semibold") + expect(screen.getByTestId("card-description")).toHaveClass( + "text-muted-foreground" + ) + }) + + it("renders action, content, and footer slots", () => { + render( + + Content + Action + Footer + + ) + + expect(screen.getByTestId("card-content")).toHaveClass("px-6") + expect(screen.getByTestId("card-action")).toHaveAttribute( + "data-slot", + "card-action" + ) + expect(screen.getByTestId("card-footer")).toHaveClass("flex") + }) +}) + diff --git a/__tests__/components/ui/label.test.tsx b/__tests__/components/ui/label.test.tsx new file mode 100644 index 0000000..d3efa27 --- /dev/null +++ b/__tests__/components/ui/label.test.tsx @@ -0,0 +1,41 @@ +/** + * @jest-environment jsdom + */ + +import React from "react" +import { render, screen } from "@testing-library/react" +import "@testing-library/jest-dom" + +jest.mock("@/lib/utils", () => ({ + cn: (...args: string[]) => args.filter(Boolean).join(" "), +})) + +import { Label } from "@/components/ui/label" + +describe("Label component", () => { + it("renders the label root with the provided text and classes", () => { + render( + + Label Text + + ) + + const label = screen.getByTestId("label-root") + expect(label).toHaveTextContent("Label Text") + expect(label).toHaveAttribute("data-slot", "label") + expect(label).toHaveClass("flex", "items-center", "custom-label") + }) + + it("supports disabled styling via attributes", () => { + render( + + Disabled + + ) + + expect(screen.getByTestId("label-disabled")).toHaveClass( + "group-data-[disabled=true]:pointer-events-none" + ) + }) +}) + diff --git a/__tests__/components/ui/skeleton.test.tsx b/__tests__/components/ui/skeleton.test.tsx new file mode 100644 index 0000000..2dcce20 --- /dev/null +++ b/__tests__/components/ui/skeleton.test.tsx @@ -0,0 +1,24 @@ +/** + * @jest-environment jsdom + */ + +import React from "react" +import { render, screen } from "@testing-library/react" +import "@testing-library/jest-dom" + +jest.mock("@/lib/utils", () => ({ + cn: (...args: string[]) => args.filter(Boolean).join(" "), +})) + +import Skeleton from "@/components/ui/skeleton" + +describe("Skeleton component", () => { + it("renders the skeleton surface with default classes", () => { + render() + + const skeleton = screen.getByTestId("skeleton") + expect(skeleton).toHaveAttribute("role", "presentation") + expect(skeleton).toHaveClass("animate-pulse", "rounded-lg", "extra") + }) +}) + diff --git a/__tests__/components/ui/toast.test.tsx b/__tests__/components/ui/toast.test.tsx index 9ae3093..ffefcfe 100644 --- a/__tests__/components/ui/toast.test.tsx +++ b/__tests__/components/ui/toast.test.tsx @@ -98,10 +98,9 @@ describe('Toast Components', () => { ); const toast = screen.getByRole('alert', { name: 'toast' }); - expect(toast).toHaveClass('destructive'); - expect(toast).toHaveClass('border-destructive'); - expect(toast).toHaveClass('bg-destructive'); - expect(toast).toHaveClass('text-destructive-foreground'); + expect(toast).toHaveClass('border-destructive'); + expect(toast).toHaveClass('bg-destructive'); + expect(toast).toHaveClass('text-destructive-foreground'); }); it('renders with success variant', () => { @@ -112,11 +111,11 @@ describe('Toast Components', () => { ); const toast = screen.getByRole('alert', { name: 'toast' }); - expect(toast).toHaveClass('border-green-500'); - expect(toast).toHaveClass('bg-green-100'); - expect(toast).toHaveClass('text-green-900'); - expect(toast).toHaveClass('dark:bg-green-900/20'); - expect(toast).toHaveClass('dark:border-green-500/30'); + expect(toast).toHaveClass('border-green-500'); + expect(toast).toHaveClass('bg-green-50'); + expect(toast).toHaveClass('text-green-900'); + expect(toast).toHaveClass('dark:bg-green-900/20'); + expect(toast).toHaveClass('dark:border-green-500/30'); expect(toast).toHaveClass('dark:text-green-300'); }); @@ -174,7 +173,6 @@ describe('Toast Components', () => { render(Destructive Toast); const toast = screen.getByRole('alert', { name: 'toast' }); expect(toast).toBeInTheDocument(); - expect(toast).toHaveClass('destructive'); }); it('renders Toast with success variant', () => { diff --git a/app/api-keys/page.tsx b/app/api-keys/page.tsx index f759464..5b10f4d 100644 --- a/app/api-keys/page.tsx +++ b/app/api-keys/page.tsx @@ -48,6 +48,7 @@ import { CheckCircle, Clock, } from "lucide-react"; +import Skeleton from "@/components/ui/skeleton"; interface ApiKey { id: string; @@ -217,18 +218,7 @@ export default function ApiKeysPage() { return { status: "active", color: "default", text: `${days} days left` }; }; - if (status === "loading" || loading) { - return ( -
Loading API keys...
Security
- Manage your API keys for external access to AI Mocker +
+ Control third-party access with scoped API keys, monitor their usage, and rotate them easily.