+ // EXPANDED STATE - Popup dropdown
+
+ {/* Header */}
+
-
API Key Active
-
- {organizationSlug} / {projectSlug}
-
+
+ {apiKeyValidated ? 'API Key Active' : 'Enter API Key'}
+
+ {apiKeyValidated && apiKeyInfo && (
+
+ {apiKeyInfo.organizationSlug} / {apiKeyInfo.projectSlug}
+
+ )}
+ {/* API Key Input/Display */}
-
+
-
- {keyVisible ? apiKey : '•'.repeat(32)}
-
+
setApiKey(e.target.value)}
+ onKeyDown={handleKeyDown}
+ placeholder="Enter your API key"
+ disabled={apiKeyValidated}
+ className="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-sm text-gray-300 font-mono placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
+ />
-
+ {/* Error Message */}
+ {apiKeyError && (
+
+ )}
+
+ {/* Actions */}
+
+ {!apiKeyValidated ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Helper Text */}
+ {!apiKeyValidated && (
+
+ Press Enter or click Validate to verify your API key
+
+ )}
)}
diff --git a/apps/landing/src/lib/apikey/constants.ts b/apps/landing/src/lib/apikey/constants.ts
new file mode 100644
index 0000000..9e35fef
--- /dev/null
+++ b/apps/landing/src/lib/apikey/constants.ts
@@ -0,0 +1,26 @@
+/**
+ * API Key Management Constants
+ *
+ * Centralized constants for API key functionality across demo pages
+ */
+
+/**
+ * LocalStorage key for persisting API keys
+ */
+export const API_KEY_STORAGE_KEY = 'banatie_demo_api_key';
+
+/**
+ * Base URL for API requests
+ * Uses environment variable or falls back to localhost
+ */
+export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
+
+/**
+ * API endpoints
+ */
+export const API_ENDPOINTS = {
+ INFO: '/api/info',
+ TEXT_TO_IMAGE: '/api/text-to-image',
+ UPLOAD: '/api/upload',
+ IMAGES_GENERATED: '/api/images/generated',
+} as const;
diff --git a/apps/landing/src/lib/apikey/index.ts b/apps/landing/src/lib/apikey/index.ts
new file mode 100644
index 0000000..b5db538
--- /dev/null
+++ b/apps/landing/src/lib/apikey/index.ts
@@ -0,0 +1,24 @@
+/**
+ * API Key Management Library
+ *
+ * Centralized utilities for API key validation, storage, and management
+ * Used across demo pages and components
+ */
+
+// Constants
+export { API_KEY_STORAGE_KEY, API_BASE_URL, API_ENDPOINTS } from './constants';
+
+// Types
+export type {
+ ApiKeyInfo,
+ ApiInfoResponse,
+ ValidationResult,
+ ApiKeyContextValue,
+ ApiKeyProviderProps,
+} from './types';
+
+// Validation utilities
+export { validateApiKeyRequest, parseKeyInfo } from './validation';
+
+// Storage utilities
+export { getStoredKey, setStoredKey, clearStoredKey } from './storage';
diff --git a/apps/landing/src/lib/apikey/storage.ts b/apps/landing/src/lib/apikey/storage.ts
new file mode 100644
index 0000000..e19a6e3
--- /dev/null
+++ b/apps/landing/src/lib/apikey/storage.ts
@@ -0,0 +1,69 @@
+/**
+ * API Key Storage Utilities
+ *
+ * localStorage utilities for persisting API keys
+ */
+
+import { API_KEY_STORAGE_KEY } from './constants';
+
+/**
+ * Check if localStorage is available (browser environment)
+ */
+function isLocalStorageAvailable(): boolean {
+ try {
+ return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Get stored API key from localStorage
+ *
+ * @returns The stored API key or null if not found
+ */
+export function getStoredKey(): string | null {
+ if (!isLocalStorageAvailable()) {
+ return null;
+ }
+
+ try {
+ return localStorage.getItem(API_KEY_STORAGE_KEY);
+ } catch (error) {
+ console.error('Error reading API key from localStorage:', error);
+ return null;
+ }
+}
+
+/**
+ * Save API key to localStorage
+ *
+ * @param apiKey - The API key to store
+ */
+export function setStoredKey(apiKey: string): void {
+ if (!isLocalStorageAvailable()) {
+ console.warn('localStorage is not available');
+ return;
+ }
+
+ try {
+ localStorage.setItem(API_KEY_STORAGE_KEY, apiKey);
+ } catch (error) {
+ console.error('Error saving API key to localStorage:', error);
+ }
+}
+
+/**
+ * Remove API key from localStorage
+ */
+export function clearStoredKey(): void {
+ if (!isLocalStorageAvailable()) {
+ return;
+ }
+
+ try {
+ localStorage.removeItem(API_KEY_STORAGE_KEY);
+ } catch (error) {
+ console.error('Error removing API key from localStorage:', error);
+ }
+}
diff --git a/apps/landing/src/lib/apikey/types.ts b/apps/landing/src/lib/apikey/types.ts
new file mode 100644
index 0000000..ce04218
--- /dev/null
+++ b/apps/landing/src/lib/apikey/types.ts
@@ -0,0 +1,72 @@
+/**
+ * API Key Management Types
+ *
+ * TypeScript interfaces and types for API key functionality
+ */
+
+/**
+ * Information about an API key extracted from validation response
+ */
+export interface ApiKeyInfo {
+ organizationSlug?: string;
+ projectSlug?: string;
+}
+
+/**
+ * Response from API info endpoint
+ */
+export interface ApiInfoResponse {
+ message?: string;
+ keyInfo?: {
+ organizationSlug?: string;
+ organizationId?: string;
+ projectSlug?: string;
+ projectId?: string;
+ };
+}
+
+/**
+ * Result of API key validation
+ */
+export interface ValidationResult {
+ success: boolean;
+ keyInfo?: ApiKeyInfo;
+ error?: string;
+}
+
+/**
+ * Context value for API Key Provider
+ */
+export interface ApiKeyContextValue {
+ // Data State
+ apiKey: string;
+ apiKeyValidated: boolean;
+ apiKeyInfo: ApiKeyInfo | null;
+ apiKeyError: string;
+ validatingKey: boolean;
+ isReady: boolean; // true when initial validation complete
+
+ // UI State (grouped)
+ ui: {
+ expanded: boolean;
+ keyVisible: boolean;
+ };
+
+ // Actions
+ setApiKey: (key: string) => void;
+ validateApiKey: () => Promise
;
+ revokeApiKey: (clearPageState?: () => void) => void;
+ toggleKeyVisibility: () => void;
+ setExpanded: (expanded: boolean) => void;
+
+ // Focus method for external components
+ focus: () => void;
+}
+
+/**
+ * Props for ApiKeyProvider component
+ */
+export interface ApiKeyProviderProps {
+ children: React.ReactNode;
+ onValidationSuccess?: (keyInfo: ApiKeyInfo) => void;
+}
diff --git a/apps/landing/src/lib/apikey/validation.ts b/apps/landing/src/lib/apikey/validation.ts
new file mode 100644
index 0000000..5a6ff98
--- /dev/null
+++ b/apps/landing/src/lib/apikey/validation.ts
@@ -0,0 +1,79 @@
+/**
+ * API Key Validation Logic
+ *
+ * Core validation functions for API key management
+ */
+
+import { API_BASE_URL, API_ENDPOINTS } from './constants';
+import type { ApiKeyInfo, ApiInfoResponse, ValidationResult } from './types';
+
+/**
+ * Parse API key info from API response
+ * Handles various response formats (organizationSlug vs organizationId)
+ */
+export function parseKeyInfo(data: ApiInfoResponse): ApiKeyInfo {
+ if (data.keyInfo) {
+ return {
+ organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId || 'Unknown',
+ projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId || 'Unknown',
+ };
+ }
+
+ return {
+ organizationSlug: 'Unknown',
+ projectSlug: 'Unknown',
+ };
+}
+
+/**
+ * Validate API key by making request to /api/info endpoint
+ *
+ * @param apiKey - The API key to validate
+ * @returns ValidationResult with success status, keyInfo, or error
+ */
+export async function validateApiKeyRequest(apiKey: string): Promise {
+ if (!apiKey.trim()) {
+ return {
+ success: false,
+ error: 'Please enter an API key',
+ };
+ }
+
+ try {
+ const response = await fetch(`${API_BASE_URL}${API_ENDPOINTS.INFO}`, {
+ headers: {
+ 'X-API-Key': apiKey,
+ },
+ });
+
+ if (response.ok) {
+ const data: ApiInfoResponse = await response.json();
+ const keyInfo = parseKeyInfo(data);
+
+ return {
+ success: true,
+ keyInfo,
+ };
+ } else {
+ // Try to parse error message from response
+ try {
+ const error = await response.json();
+ return {
+ success: false,
+ error: error.message || 'Invalid API key',
+ };
+ } catch {
+ return {
+ success: false,
+ error: response.status === 401 ? 'Invalid API key' : `Request failed with status ${response.status}`,
+ };
+ }
+ }
+ } catch (error) {
+ console.error('API key validation error:', error);
+ return {
+ success: false,
+ error: 'Failed to validate API key. Please check your connection.',
+ };
+ }
+}