From 8517f815249d03e1509961d0b14c662e0791e821 Mon Sep 17 00:00:00 2001 From: Madison Date: Tue, 30 Dec 2025 04:22:02 -0600 Subject: [PATCH 1/2] api window overhaul --- docs/src/components/api/auth-panel.tsx | 625 ++++++++++++------------- 1 file changed, 304 insertions(+), 321 deletions(-) diff --git a/docs/src/components/api/auth-panel.tsx b/docs/src/components/api/auth-panel.tsx index 1e84ae550a..019e61a208 100644 --- a/docs/src/components/api/auth-panel.tsx +++ b/docs/src/components/api/auth-panel.tsx @@ -3,12 +3,16 @@ import { AdminOwnedProject, CurrentInternalUser, useUser } from '@stackframe/stack'; import { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises'; import { stringCompare } from '@stackframe/stack-shared/dist/utils/strings'; -import { AlertTriangle, ChevronDown, Key, X } from 'lucide-react'; +import { AlertTriangle, ChevronDown, X } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; +import { cn } from '../../lib/cn'; import { useSidebar } from '../layouts/sidebar-context'; +import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { useAPIPageContext } from './api-page-wrapper'; import { Button } from './button'; +type AuthMode = 'project' | 'manual'; + type StackAuthHeaderKey = | 'X-Stack-Access-Type' | 'X-Stack-Project-Id' @@ -29,10 +33,10 @@ type StackAuthHeaderField = { const stackAuthHeaders: StackAuthHeaderField[] = [ { key: 'X-Stack-Access-Type', label: 'Access Type', placeholder: 'client, server, or admin', required: true }, { key: 'X-Stack-Project-Id', label: 'Project ID', placeholder: 'your-project-uuid', required: true }, - { key: 'X-Stack-Publishable-Client-Key', label: 'Client Key', placeholder: 'pck_your_key_here', required: false, hideWhenProjectSelected: true }, - { key: 'X-Stack-Secret-Server-Key', label: 'Server Key', placeholder: 'ssk_your_key_here', required: false, hideWhenProjectSelected: true }, - { key: 'X-Stack-Access-Token', label: 'Access Token', placeholder: 'user_access_token', required: false, hideWhenProjectSelected: true }, - { key: 'X-Stack-Admin-Access-Token', label: 'Admin Access Token', placeholder: 'admin_access_token', required: false, isSensitive: true }, + { key: 'X-Stack-Publishable-Client-Key', label: 'Client Key', placeholder: 'pck_your_key_here', required: false }, + { key: 'X-Stack-Secret-Server-Key', label: 'Server Key', placeholder: 'ssk_your_key_here', required: false }, + { key: 'X-Stack-Access-Token', label: 'Access Token', placeholder: 'user_access_token', required: false }, + { key: 'X-Stack-Admin-Access-Token', label: 'Admin Access Token', placeholder: 'admin_access_token', required: false }, ]; type UserHookResult = ReturnType; @@ -41,6 +45,80 @@ function isInternalUser(user: UserHookResult): user is CurrentInternalUser { return Boolean(user && 'useOwnedProjects' in user && typeof user.useOwnedProjects === 'function'); } +function ProjectDropdown({ + projects, + selectedProjectId, + onSelect, +}: { + projects: AdminOwnedProject[], + selectedProjectId: string, + onSelect: (projectId: string) => void, +}) { + const [open, setOpen] = useState(false); + const selectedProject = projects.find(p => p.id === selectedProjectId); + + return ( + + + + + +
+ + {projects.map((project) => ( + + ))} +
+
+
+ ); +} + export function AuthPanel() { const sidebarContext = useSidebar(); @@ -59,6 +137,10 @@ export function AuthPanel() { // State for project selection const [selectedProjectId, setSelectedProjectId] = useState(''); + // Mode: project selection OR manual entry + const canUseProjectMode = hasOwnedProjects && projects.length > 0; + const [authMode, setAuthMode] = useState(canUseProjectMode ? 'project' : 'manual'); + // Use default functions if sidebar context is not available const { isAuthOpen, toggleAuth } = sidebarContext || { isAuthOpen: false, @@ -106,43 +188,6 @@ export function AuthPanel() { }); }, [selectedProjectId, user, updateSharedHeaders]); - const [isHomePage, setIsHomePage] = useState(false); - const [isScrolled, setIsScrolled] = useState(false); - - // Detect if we're on homepage and scroll state (same as AIChatDrawer) - useEffect(() => { - const checkHomePage = () => { - setIsHomePage(document.body.classList.contains('home-page')); - }; - - const checkScrolled = () => { - setIsScrolled(document.body.classList.contains('scrolled')); - }; - - // Initial check - checkHomePage(); - checkScrolled(); - - // Set up observers for class changes - const observer = new MutationObserver(() => { - checkHomePage(); - checkScrolled(); - }); - - observer.observe(document.body, { - attributes: true, - attributeFilter: ['class'] - }); - - return () => { - observer.disconnect(); - }; - }, []); - - // Calculate position based on homepage and scroll state (same as AIChatDrawer) - const topPosition = isHomePage && isScrolled ? 'top-0' : 'top-0'; - const height = isHomePage && isScrolled ? 'h-screen' : 'h-[calc(100vh)]'; - const missingRequiredHeaders = stackAuthHeaders.filter( header => header.required && !(headers[header.key] ?? '').trim() ); @@ -178,166 +223,136 @@ export function AuthPanel() { <> {/* Desktop Auth Panel - Matching AIChatDrawer design */}
- {/* Header - Matching AIChatDrawer */} -
-
-
- {highlightMissingHeaders ? ( - - ) : ( - - )} -
-
-

- {highlightMissingHeaders ? 'Authentication Required' : 'API Authentication'} -

-

- Configure headers for requests -

-
-
+ {/* Header */} +
+

+ API Authentication +

- {/* Error Message - Reserve space to prevent layout shifts */} -
- {highlightMissingHeaders && lastError ? ( -
-
- - - {lastError.status} Error - Authentication required - -
- {missingRequiredHeaders.length > 0 && ( -

- Missing: {missingRequiredHeaders.map(h => h.label).join(', ')} -

- )} + {/* Error Message */} + {highlightMissingHeaders && lastError && ( +
+
+ + + {lastError.status} Error - Authentication required +
- ) : null} -
+
+ )} + + {/* Mode Toggle - only show if user has projects */} + {canUseProjectMode && ( +
+ + +
+ )} - {/* Content - Fixed height to prevent layout shifts */} + {/* Content */}
-
- {/* Project Selector - Show only if user has owned projects */} - {hasOwnedProjects && projects.length > 0 && ( -
- -
- - -
+
+ {/* Project Selection Mode */} + {authMode === 'project' && canUseProjectMode && ( +
+ {selectedProjectId && ( -

- ✓ Headers auto-populated for admin authentication -

+
+
+ Ready to make requests +
)} +

+ Because you're signed in, requests are automatically authenticated with your account. +

)} - {/* Manual Header Inputs */} - {stackAuthHeaders.map((header) => { - // Hide certain fields when project is selected - if (selectedProjectId && header.hideWhenProjectSelected) { - return null; - } - - const value = headers[header.key] ?? ''; - const isMissing = highlightMissingHeaders && header.required && !value.trim(); - const isAutoPopulated = Boolean(header.isSensitive && selectedProjectId && value.length > 0); - - return ( -
- - updateSharedHeaders({ ...headers, [header.key]: e.target.value })} - readOnly={isAutoPopulated} - className={`w-full px-2 py-1.5 border rounded-md text-xs bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-1 focus:border-transparent transition-all duration-200 ${ - isMissing - ? 'border-red-300 focus:ring-red-500 dark:border-red-700' - : 'border-fd-border focus:ring-fd-primary focus:border-fd-primary' - } ${isAutoPopulated ? 'bg-fd-muted/50 cursor-not-allowed' : ''}`} - /> - {isAutoPopulated && ( -

Auto-populated from your account

- )} -
- ); - })} + {/* Manual Mode */} + {(authMode === 'manual' || !canUseProjectMode) && ( +
+ {stackAuthHeaders.map((header) => { + const value = headers[header.key] ?? ''; + const isMissing = highlightMissingHeaders && header.required && !value.trim(); + + return ( +
+ + updateSharedHeaders({ ...headers, [header.key]: e.target.value })} + className={cn( + "w-full px-3 py-2 border rounded-lg text-sm bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-2 focus:ring-fd-primary/20 focus:border-fd-primary transition-colors", + isMissing ? 'border-red-300 dark:border-red-700' : 'border-fd-border' + )} + /> +
+ ); + })} +
+ )}
- {/* Footer Status */} -
-
-
- - {Object.values(headers).filter(v => v.trim()).length} configured - {selectedProjectId && ' (via project selection)'} - -
- {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) && ( -
-
- - Ready for API requests + {/* Status Footer */} +
+
+
+
v.trim()) + ? 'bg-green-500' + : 'bg-fd-muted-foreground/50' + )} /> + + {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) + ? 'Ready' + : 'Not configured'}
- )} +
@@ -348,170 +363,138 @@ export function AuthPanel() { }`} aria-hidden={!isAuthOpen} > - {/* Mobile Header */} -
-
-
- {highlightMissingHeaders ? ( - - ) : ( - - )} -
-
-

- {highlightMissingHeaders ? 'Authentication Required' : 'API Authentication'} -

-

- Configure headers for requests -

-
-
+ {/* Mobile Header - with safe area for notched devices */} +
+

+ API Authentication +

{/* Error Message - Mobile */} -
- {highlightMissingHeaders && lastError ? ( -
-
- - - {lastError.status} Error - Authentication required - -
- {missingRequiredHeaders.length > 0 && ( -

- Missing: {missingRequiredHeaders.map(h => h.label).join(', ')} -

- )} + {highlightMissingHeaders && lastError && ( +
+
+ + + {lastError.status} Error - Authentication required +
- ) : null} -
+
+ )} + + {/* Mode Toggle - Mobile */} + {canUseProjectMode && ( +
+ + +
+ )} - {/* Mobile Content - Fixed height to prevent layout shifts */} + {/* Mobile Content */}
-
-
- {/* Project Selector - Mobile */} - {hasOwnedProjects && projects.length > 0 && ( -
- -
- - +
+ {/* Project Selection Mode - Mobile */} + {authMode === 'project' && canUseProjectMode && ( +
+ + {selectedProjectId && ( +
+
+ Ready to make requests
- {selectedProjectId && ( -

- ✓ Headers auto-populated for admin authentication -

- )} -
- )} + )} +

+ Because you're signed in, requests are automatically authenticated with your account. +

+
+ )} - {/* Manual Header Inputs - Mobile */} - {stackAuthHeaders.map((header) => { - // Hide certain fields when project is selected - if (selectedProjectId && header.hideWhenProjectSelected) { - return null; - } - - const value = headers[header.key] ?? ''; - const isMissing = highlightMissingHeaders && header.required && !value.trim(); - const isAutoPopulated = Boolean(header.isSensitive && selectedProjectId && value.length > 0); - - return ( -
- - updateSharedHeaders({ ...headers, [header.key]: e.target.value })} - readOnly={isAutoPopulated} - className={`w-full px-3 py-2 border rounded-md text-sm bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-1 focus:border-transparent transition-all duration-200 ${ - isMissing - ? 'border-red-300 focus:ring-red-500 dark:border-red-700' - : 'border-fd-border focus:ring-fd-primary focus:border-fd-primary' - } ${isAutoPopulated ? 'bg-fd-muted/50 cursor-not-allowed' : ''}`} - /> - {isAutoPopulated && ( -

Auto-populated from your account

- )} -
- ); - })} -
+ {/* Manual Mode - Mobile */} + {(authMode === 'manual' || !canUseProjectMode) && ( +
+ {stackAuthHeaders.map((header) => { + const value = headers[header.key] ?? ''; + const isMissing = highlightMissingHeaders && header.required && !value.trim(); + + return ( +
+ + updateSharedHeaders({ ...headers, [header.key]: e.target.value })} + className={cn( + "w-full px-4 py-3 border rounded-lg text-base bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-2 focus:ring-fd-primary/20 focus:border-fd-primary transition-colors", + isMissing ? 'border-red-300 dark:border-red-700' : 'border-fd-border' + )} + /> +
+ ); + })} +
+ )}
- {/* Mobile Footer */} -
+ {/* Mobile Status Footer - with safe area for home indicator */} +
-
-
- - {Object.values(headers).filter(v => v.trim()).length} configured - {selectedProjectId && ' (via project)'} +
+
v.trim()) + ? 'bg-green-500' + : 'bg-fd-muted-foreground/50' + )} /> + + {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) + ? 'Ready' + : 'Not configured'}
-
- {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) && ( -
-
- - Ready for API requests - -
- )}
); } + + From 824a2c590370da9d6daa586df3cd7ad372927bed Mon Sep 17 00:00:00 2001 From: Madison Date: Wed, 14 Jan 2026 08:20:08 -0600 Subject: [PATCH 2/2] fix deselection --- docs/src/components/api/auth-panel.tsx | 634 +++++++++++++------------ 1 file changed, 330 insertions(+), 304 deletions(-) diff --git a/docs/src/components/api/auth-panel.tsx b/docs/src/components/api/auth-panel.tsx index 019e61a208..c8dfe98279 100644 --- a/docs/src/components/api/auth-panel.tsx +++ b/docs/src/components/api/auth-panel.tsx @@ -3,16 +3,12 @@ import { AdminOwnedProject, CurrentInternalUser, useUser } from '@stackframe/stack'; import { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises'; import { stringCompare } from '@stackframe/stack-shared/dist/utils/strings'; -import { AlertTriangle, ChevronDown, X } from 'lucide-react'; +import { AlertTriangle, ChevronDown, Key, X } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; -import { cn } from '../../lib/cn'; import { useSidebar } from '../layouts/sidebar-context'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { useAPIPageContext } from './api-page-wrapper'; import { Button } from './button'; -type AuthMode = 'project' | 'manual'; - type StackAuthHeaderKey = | 'X-Stack-Access-Type' | 'X-Stack-Project-Id' @@ -33,10 +29,10 @@ type StackAuthHeaderField = { const stackAuthHeaders: StackAuthHeaderField[] = [ { key: 'X-Stack-Access-Type', label: 'Access Type', placeholder: 'client, server, or admin', required: true }, { key: 'X-Stack-Project-Id', label: 'Project ID', placeholder: 'your-project-uuid', required: true }, - { key: 'X-Stack-Publishable-Client-Key', label: 'Client Key', placeholder: 'pck_your_key_here', required: false }, - { key: 'X-Stack-Secret-Server-Key', label: 'Server Key', placeholder: 'ssk_your_key_here', required: false }, - { key: 'X-Stack-Access-Token', label: 'Access Token', placeholder: 'user_access_token', required: false }, - { key: 'X-Stack-Admin-Access-Token', label: 'Admin Access Token', placeholder: 'admin_access_token', required: false }, + { key: 'X-Stack-Publishable-Client-Key', label: 'Client Key', placeholder: 'pck_your_key_here', required: false, hideWhenProjectSelected: true }, + { key: 'X-Stack-Secret-Server-Key', label: 'Server Key', placeholder: 'ssk_your_key_here', required: false, hideWhenProjectSelected: true }, + { key: 'X-Stack-Access-Token', label: 'Access Token', placeholder: 'user_access_token', required: false, hideWhenProjectSelected: true }, + { key: 'X-Stack-Admin-Access-Token', label: 'Admin Access Token', placeholder: 'admin_access_token', required: false, isSensitive: true }, ]; type UserHookResult = ReturnType; @@ -45,80 +41,6 @@ function isInternalUser(user: UserHookResult): user is CurrentInternalUser { return Boolean(user && 'useOwnedProjects' in user && typeof user.useOwnedProjects === 'function'); } -function ProjectDropdown({ - projects, - selectedProjectId, - onSelect, -}: { - projects: AdminOwnedProject[], - selectedProjectId: string, - onSelect: (projectId: string) => void, -}) { - const [open, setOpen] = useState(false); - const selectedProject = projects.find(p => p.id === selectedProjectId); - - return ( - - - - - -
- - {projects.map((project) => ( - - ))} -
-
-
- ); -} - export function AuthPanel() { const sidebarContext = useSidebar(); @@ -137,10 +59,6 @@ export function AuthPanel() { // State for project selection const [selectedProjectId, setSelectedProjectId] = useState(''); - // Mode: project selection OR manual entry - const canUseProjectMode = hasOwnedProjects && projects.length > 0; - const [authMode, setAuthMode] = useState(canUseProjectMode ? 'project' : 'manual'); - // Use default functions if sidebar context is not available const { isAuthOpen, toggleAuth } = sidebarContext || { isAuthOpen: false, @@ -188,12 +106,58 @@ export function AuthPanel() { }); }, [selectedProjectId, user, updateSharedHeaders]); + const [isHomePage, setIsHomePage] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); + + // Detect if we're on homepage and scroll state (same as AIChatDrawer) + useEffect(() => { + const checkHomePage = () => { + setIsHomePage(document.body.classList.contains('home-page')); + }; + + const checkScrolled = () => { + setIsScrolled(document.body.classList.contains('scrolled')); + }; + + // Initial check + checkHomePage(); + checkScrolled(); + + // Set up observers for class changes + const observer = new MutationObserver(() => { + checkHomePage(); + checkScrolled(); + }); + + observer.observe(document.body, { + attributes: true, + attributeFilter: ['class'] + }); + + return () => { + observer.disconnect(); + }; + }, []); + + // Calculate position based on homepage and scroll state (same as AIChatDrawer) + const topPosition = isHomePage && isScrolled ? 'top-0' : 'top-0'; + const height = isHomePage && isScrolled ? 'h-screen' : 'h-[calc(100vh)]'; + const missingRequiredHeaders = stackAuthHeaders.filter( header => header.required && !(headers[header.key] ?? '').trim() ); // Handle project selection const handleProjectSelect = (projectId: string) => { + // Handle deselection (empty string) + if (projectId === '') { + setSelectedProjectId(''); + // Clear all headers when deselecting + updateSharedHeaders(defaultHeaders); + return; + } + + // Validate project exists if (!projects.some((projectItem) => projectItem.id === projectId)) { return; } @@ -223,136 +187,166 @@ export function AuthPanel() { <> {/* Desktop Auth Panel - Matching AIChatDrawer design */}
- {/* Header */} -
-

- API Authentication -

+ {/* Header - Matching AIChatDrawer */} +
+
+
+ {highlightMissingHeaders ? ( + + ) : ( + + )} +
+
+

+ {highlightMissingHeaders ? 'Authentication Required' : 'API Authentication'} +

+

+ Configure headers for requests +

+
+
- {/* Error Message */} - {highlightMissingHeaders && lastError && ( -
-
- - - {lastError.status} Error - Authentication required - -
-
- )} - - {/* Mode Toggle - only show if user has projects */} - {canUseProjectMode && ( -
- - -
- )} +
+ ) : null} +
- {/* Content */} + {/* Content - Fixed height to prevent layout shifts */}
-
- {/* Project Selection Mode */} - {authMode === 'project' && canUseProjectMode && ( -
- +
+ {/* Project Selector - Show only if user has owned projects */} + {hasOwnedProjects && projects.length > 0 && ( +
+ +
+ + +
{selectedProjectId && ( -
-
- Ready to make requests -
+

+ ✓ Headers auto-populated for admin authentication +

)} -

- Because you're signed in, requests are automatically authenticated with your account. -

)} - {/* Manual Mode */} - {(authMode === 'manual' || !canUseProjectMode) && ( -
- {stackAuthHeaders.map((header) => { - const value = headers[header.key] ?? ''; - const isMissing = highlightMissingHeaders && header.required && !value.trim(); - - return ( -
- - updateSharedHeaders({ ...headers, [header.key]: e.target.value })} - className={cn( - "w-full px-3 py-2 border rounded-lg text-sm bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-2 focus:ring-fd-primary/20 focus:border-fd-primary transition-colors", - isMissing ? 'border-red-300 dark:border-red-700' : 'border-fd-border' - )} - /> -
- ); - })} -
- )} + {/* Manual Header Inputs */} + {stackAuthHeaders.map((header) => { + // Hide certain fields when project is selected + if (selectedProjectId && header.hideWhenProjectSelected) { + return null; + } + + const value = headers[header.key] ?? ''; + const isMissing = highlightMissingHeaders && header.required && !value.trim(); + const isAutoPopulated = Boolean(header.isSensitive && selectedProjectId && value.length > 0); + + return ( +
+ + updateSharedHeaders({ ...headers, [header.key]: e.target.value })} + readOnly={isAutoPopulated} + className={`w-full px-2 py-1.5 border rounded-md text-xs bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-1 focus:border-transparent transition-all duration-200 ${ + isMissing + ? 'border-red-300 focus:ring-red-500 dark:border-red-700' + : 'border-fd-border focus:ring-fd-primary focus:border-fd-primary' + } ${isAutoPopulated ? 'bg-fd-muted/50 cursor-not-allowed' : ''}`} + /> + {isAutoPopulated && ( +

Auto-populated from your account

+ )} +
+ ); + })}
- {/* Status Footer */} -
-
-
-
v.trim()) - ? 'bg-green-500' - : 'bg-fd-muted-foreground/50' - )} /> - - {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) - ? 'Ready' - : 'Not configured'} + {/* Footer Status */} +
+
+
+ + {Object.values(headers).filter(v => v.trim()).length} configured + {selectedProjectId && ' (via project selection)'} + +
+ {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) && ( +
+
+ + Ready for API requests
-
+ )}
@@ -363,138 +357,170 @@ export function AuthPanel() { }`} aria-hidden={!isAuthOpen} > - {/* Mobile Header - with safe area for notched devices */} -
-

- API Authentication -

+ {/* Mobile Header */} +
+
+
+ {highlightMissingHeaders ? ( + + ) : ( + + )} +
+
+

+ {highlightMissingHeaders ? 'Authentication Required' : 'API Authentication'} +

+

+ Configure headers for requests +

+
+
{/* Error Message - Mobile */} - {highlightMissingHeaders && lastError && ( -
-
- - - {lastError.status} Error - Authentication required - -
-
- )} - - {/* Mode Toggle - Mobile */} - {canUseProjectMode && ( -
- - -
- )} +
+ ) : null} +
- {/* Mobile Content */} + {/* Mobile Content - Fixed height to prevent layout shifts */}
-
- {/* Project Selection Mode - Mobile */} - {authMode === 'project' && canUseProjectMode && ( -
- - {selectedProjectId && ( -
-
- Ready to make requests +
+
+ {/* Project Selector - Mobile */} + {hasOwnedProjects && projects.length > 0 && ( +
+ +
+ +
- )} -

- Because you're signed in, requests are automatically authenticated with your account. -

-
- )} + {selectedProjectId && ( +

+ ✓ Headers auto-populated for admin authentication +

+ )} +
+ )} - {/* Manual Mode - Mobile */} - {(authMode === 'manual' || !canUseProjectMode) && ( -
- {stackAuthHeaders.map((header) => { - const value = headers[header.key] ?? ''; - const isMissing = highlightMissingHeaders && header.required && !value.trim(); - - return ( -
- - updateSharedHeaders({ ...headers, [header.key]: e.target.value })} - className={cn( - "w-full px-4 py-3 border rounded-lg text-base bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-2 focus:ring-fd-primary/20 focus:border-fd-primary transition-colors", - isMissing ? 'border-red-300 dark:border-red-700' : 'border-fd-border' - )} - /> -
- ); - })} -
- )} + {/* Manual Header Inputs - Mobile */} + {stackAuthHeaders.map((header) => { + // Hide certain fields when project is selected + if (selectedProjectId && header.hideWhenProjectSelected) { + return null; + } + + const value = headers[header.key] ?? ''; + const isMissing = highlightMissingHeaders && header.required && !value.trim(); + const isAutoPopulated = Boolean(header.isSensitive && selectedProjectId && value.length > 0); + + return ( +
+ + updateSharedHeaders({ ...headers, [header.key]: e.target.value })} + readOnly={isAutoPopulated} + className={`w-full px-3 py-2 border rounded-md text-sm bg-fd-background text-fd-foreground placeholder:text-fd-muted-foreground focus:outline-none focus:ring-1 focus:border-transparent transition-all duration-200 ${ + isMissing + ? 'border-red-300 focus:ring-red-500 dark:border-red-700' + : 'border-fd-border focus:ring-fd-primary focus:border-fd-primary' + } ${isAutoPopulated ? 'bg-fd-muted/50 cursor-not-allowed' : ''}`} + /> + {isAutoPopulated && ( +

Auto-populated from your account

+ )} +
+ ); + })} +
- {/* Mobile Status Footer - with safe area for home indicator */} -
+ {/* Mobile Footer */} +
-
-
v.trim()) - ? 'bg-green-500' - : 'bg-fd-muted-foreground/50' - )} /> - - {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) - ? 'Ready' - : 'Not configured'} +
+
+ + {Object.values(headers).filter(v => v.trim()).length} configured + {selectedProjectId && ' (via project)'}
-
+ {missingRequiredHeaders.length === 0 && Object.values(headers).some(v => v.trim()) && ( +
+
+ + Ready for API requests + +
+ )}
); } - -