From 4caa475f30b053ab61d461c9dc9d8202e82bf10a Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Wed, 22 Oct 2025 23:01:37 +0700 Subject: [PATCH] feat: start apikey component --- apps/landing/src/app/docs/layout.tsx | 2 + .../shared/ApiKeyWidget/apikey-context.tsx | 17 + .../shared/ApiKeyWidget/apikey-widget.tsx | 8 +- .../src/components/shared/SubsectionNav.tsx | 345 +++--------------- 4 files changed, 64 insertions(+), 308 deletions(-) create mode 100644 apps/landing/src/components/shared/ApiKeyWidget/apikey-context.tsx diff --git a/apps/landing/src/app/docs/layout.tsx b/apps/landing/src/app/docs/layout.tsx index 69e1d52..67e1f43 100644 --- a/apps/landing/src/app/docs/layout.tsx +++ b/apps/landing/src/app/docs/layout.tsx @@ -5,6 +5,7 @@ import { usePathname } from 'next/navigation'; import { SubsectionNav } from '@/components/shared/SubsectionNav'; import { DocsSidebar } from '@/components/docs/layout/DocsSidebar'; import { ThreeColumnLayout } from '@/components/layout/ThreeColumnLayout'; +import { ApiKeyWidget } from '@/components/shared/ApiKeyWidget/apikey-widget'; /** * Root Documentation Layout @@ -57,6 +58,7 @@ export default function DocsRootLayout({ children }: DocsRootLayoutProps) { currentPath={pathname} ctaText="Join Beta" ctaHref="/signup" + rightSlot={} /> {/* Three-column Documentation Layout */} diff --git a/apps/landing/src/components/shared/ApiKeyWidget/apikey-context.tsx b/apps/landing/src/components/shared/ApiKeyWidget/apikey-context.tsx new file mode 100644 index 0000000..bc8f795 --- /dev/null +++ b/apps/landing/src/components/shared/ApiKeyWidget/apikey-context.tsx @@ -0,0 +1,17 @@ +/* + +TODO: create ApiKeyProvider and useApiKey hook to access provider values + +usage example: const { organizationSlug, projectSlug, apiKey, onRevoke } = useApiKey(); + +see ApiKeyWidget component (src/components/shared/ApiKeyWidget/apikey-widget.tsx) + +the functionality should be strictly copied from src/app/demo/tti/page.tsx (see apikey related functionality) and MinimizedApiKey + +Move apikey simple actions in src/lib/apikey + +Provider should centralize apikey information and organize actions call from lib and then provide apikey related functionality down to children + + +This is phase 1 of a task of centralizing apikey functionality that now is not DRY in demo pages +*/ \ No newline at end of file diff --git a/apps/landing/src/components/shared/ApiKeyWidget/apikey-widget.tsx b/apps/landing/src/components/shared/ApiKeyWidget/apikey-widget.tsx index a4c3d41..4a49fe9 100644 --- a/apps/landing/src/components/shared/ApiKeyWidget/apikey-widget.tsx +++ b/apps/landing/src/components/shared/ApiKeyWidget/apikey-widget.tsx @@ -9,14 +9,10 @@ interface ApiKeyWidgetProps { onRevoke: () => void; } -export function ApiKeyWidget({ - organizationSlug, - projectSlug, - apiKey, - onRevoke, -}: ApiKeyWidgetProps) { +export function ApiKeyWidget() { const [expanded, setExpanded] = useState(false); const [keyVisible, setKeyVisible] = useState(false); + const { organizationSlug, projectSlug, apiKey, onRevoke } = useApiKey(); return (
diff --git a/apps/landing/src/components/shared/SubsectionNav.tsx b/apps/landing/src/components/shared/SubsectionNav.tsx index 33ce3aa..b226bf5 100644 --- a/apps/landing/src/components/shared/SubsectionNav.tsx +++ b/apps/landing/src/components/shared/SubsectionNav.tsx @@ -7,7 +7,6 @@ * Features: * - Dark nav bar with decorative wave line * - Active state indicator (purple color) - * - Optional API Key input on the right * - Responsive (hamburger menu on mobile) * - Three-column layout matching docs structure * - Customizable left/right slots @@ -19,13 +18,12 @@ * } * rightSlot={} * /> */ -import { useState, useEffect, useRef, ReactNode } from 'react'; +import { useState, ReactNode } from 'react'; import { ThreeColumnLayout } from '@/components/layout/ThreeColumnLayout'; interface NavItem { @@ -39,210 +37,17 @@ interface SubsectionNavProps { ctaText?: string; ctaHref?: string; onCtaClick?: () => void; - showApiKeyInput?: boolean; /** Optional content for left column (w-64, hidden until lg) */ leftSlot?: ReactNode; - /** Optional content for right column (w-56, hidden until xl). If not provided and showApiKeyInput is true, API key input is used. */ + /** Optional content for right column (w-56, hidden until xl) */ rightSlot?: ReactNode; } -const API_KEY_STORAGE = 'banatie_docs_api_key'; - -export const SubsectionNav = ({ - items, - currentPath, - showApiKeyInput = false, - leftSlot, - rightSlot, -}: SubsectionNavProps) => { +export const SubsectionNav = ({ items, currentPath, leftSlot, rightSlot }: SubsectionNavProps) => { const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const [apiKey, setApiKey] = useState(''); - const [isApiKeyExpanded, setIsApiKeyExpanded] = useState(false); - const [keyVisible, setKeyVisible] = useState(false); - const dropdownRef = useRef(null); const isActive = (href: string) => currentPath.startsWith(href); - // Load API key from localStorage - useEffect(() => { - if (showApiKeyInput) { - const stored = localStorage.getItem(API_KEY_STORAGE); - if (stored) setApiKey(stored); - } - }, [showApiKeyInput]); - - // Handle API key changes - const handleApiKeyChange = (value: string) => { - setApiKey(value); - if (value) { - localStorage.setItem(API_KEY_STORAGE, value); - } else { - localStorage.removeItem(API_KEY_STORAGE); - } - - // Dispatch event to notify widgets - window.dispatchEvent(new CustomEvent('apiKeyChanged', { detail: value })); - }; - - // Handle clear API key - const handleClear = () => { - setApiKey(''); - localStorage.removeItem(API_KEY_STORAGE); - window.dispatchEvent(new CustomEvent('apiKeyChanged', { detail: '' })); - }; - - // Close dropdown when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsApiKeyExpanded(false); - } - }; - - if (isApiKeyExpanded) { - document.addEventListener('mousedown', handleClickOutside); - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [isApiKeyExpanded]); - - // Listen for custom event to expand API key from widgets - useEffect(() => { - const handleExpandApiKey = () => { - setIsApiKeyExpanded(true); - // Scroll to top to show nav - window.scrollTo({ top: 0, behavior: 'smooth' }); - }; - - window.addEventListener('expandApiKeyInput', handleExpandApiKey); - - return () => { - window.removeEventListener('expandApiKeyInput', handleExpandApiKey); - }; - }, []); - - // Desktop API Key Input Component - const apiKeyComponent = showApiKeyInput && ( -
- - - {/* Dropdown Card */} - {isApiKeyExpanded && ( -
-
-
-

API Key

-

- Enter once, use across all examples -

-
- -
- -
- -
- handleApiKeyChange(e.target.value)} - placeholder="Enter your API key" - className="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 focus:border-purple-500 focus:ring-1 focus:ring-purple-500 rounded-lg text-sm text-gray-300 font-mono placeholder-gray-500 focus:outline-none transition-colors" - /> - -
- - {apiKey && ( - - )} -
- -
-

Stored locally in your browser

-
-
- )} -
- ); - return (
} - right={rightSlot || apiKeyComponent} + right={rightSlot} /> {/* Decorative Wave Line */} @@ -352,65 +152,6 @@ export const SubsectionNav = ({ ); })} - - {/* API Key Input in Mobile Menu */} - {showApiKeyInput && ( -
-

API Key

-

- Enter once, use across all examples -

-
- handleApiKeyChange(e.target.value)} - placeholder="Enter your API key" - className="flex-1 px-3 py-2 bg-slate-800 border border-slate-700 focus:border-purple-500 focus:ring-1 focus:ring-purple-500 rounded-lg text-sm text-gray-300 font-mono placeholder-gray-500 focus:outline-none transition-colors" - /> - -
- {apiKey && ( - - )} -

Stored locally in your browser

-
- )} )}