feat(statuspage): added statuspage, updated list of tools in footer, renamed routes (#2139)

* feat(statuspage): added statuspage, updated list of tools in footer, renamed routes

* ack PR comments
This commit is contained in:
Waleed
2025-11-30 10:46:18 -08:00
committed by GitHub
parent 08d57b4f8b
commit f25db707d7
18 changed files with 521 additions and 225 deletions

View File

@@ -1,12 +1,12 @@
import { createLogger } from '@/lib/logs/console/logger'
const DEFAULT_STARS = '15.4k'
const DEFAULT_STARS = '18.6k'
const logger = createLogger('GitHubStars')
export async function getFormattedGitHubStars(): Promise<string> {
try {
const response = await fetch('/api/github-stars', {
const response = await fetch('/api/stars', {
headers: {
'Cache-Control': 'max-age=3600', // Cache for 1 hour
},

View File

@@ -0,0 +1,26 @@
import Image from 'next/image'
import Link from 'next/link'
import { HIPAABadgeIcon } from '@/components/icons'
export default function ComplianceBadges() {
return (
<div className='mt-[6px] flex items-center gap-[12px]'>
{/* SOC2 badge */}
<Link href='https://trust.delve.co/sim-studio' target='_blank' rel='noopener noreferrer'>
<Image
src='/footer/soc2.png'
alt='SOC2 Compliant'
width={54}
height={54}
className='object-contain'
loading='lazy'
quality={75}
/>
</Link>
{/* HIPAA badge */}
<Link href='https://trust.delve.co/sim-studio' target='_blank' rel='noopener noreferrer'>
<HIPAABadgeIcon className='h-[54px] w-[54px]' />
</Link>
</div>
)
}

View File

@@ -0,0 +1,6 @@
import ComplianceBadges from './compliance-badges'
import Logo from './logo'
import SocialLinks from './social-links'
import StatusIndicator from './status-indicator'
export { ComplianceBadges, Logo, SocialLinks, StatusIndicator }

View File

@@ -0,0 +1,17 @@
import Image from 'next/image'
import Link from 'next/link'
export default function Logo() {
return (
<Link href='/' aria-label='Sim home'>
<Image
src='/logo/b&w/text/b&w.svg'
alt='Sim - Workflows for LLMs'
width={49.78314}
height={24.276}
priority
quality={90}
/>
</Link>
)
}

View File

@@ -0,0 +1,44 @@
import { DiscordIcon, GithubIcon, LinkedInIcon, xIcon as XIcon } from '@/components/icons'
export default function SocialLinks() {
return (
<div className='flex items-center gap-[12px]'>
<a
href='https://discord.gg/Hr4UWYEcTT'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='Discord'
>
<DiscordIcon className='h-[20px] w-[20px]' aria-hidden='true' />
</a>
<a
href='https://x.com/simdotai'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='X (Twitter)'
>
<XIcon className='h-[18px] w-[18px]' aria-hidden='true' />
</a>
<a
href='https://www.linkedin.com/company/simstudioai/'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='LinkedIn'
>
<LinkedInIcon className='h-[18px] w-[18px]' aria-hidden='true' />
</a>
<a
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='GitHub'
>
<GithubIcon className='h-[20px] w-[20px]' aria-hidden='true' />
</a>
</div>
)
}

View File

@@ -0,0 +1,40 @@
'use client'
import Link from 'next/link'
import { StatusDotIcon } from '@/components/icons'
import type { StatusType } from '@/app/api/status/types'
import { useStatus } from '@/hooks/queries/status'
const STATUS_COLORS: Record<StatusType, string> = {
operational: 'text-[#10B981] hover:text-[#059669]',
degraded: 'text-[#F59E0B] hover:text-[#D97706]',
outage: 'text-[#EF4444] hover:text-[#DC2626]',
maintenance: 'text-[#3B82F6] hover:text-[#2563EB]',
loading: 'text-muted-foreground hover:text-foreground',
error: 'text-muted-foreground hover:text-foreground',
}
export default function StatusIndicator() {
const { data, isLoading, isError } = useStatus()
const status = isLoading ? 'loading' : isError ? 'error' : data?.status || 'error'
const message = isLoading
? 'Checking Status...'
: isError
? 'Status Unknown'
: data?.message || 'Status Unknown'
const statusUrl = data?.url || 'https://status.sim.ai'
return (
<Link
href={statusUrl}
target='_blank'
rel='noopener noreferrer'
className={`flex items-center gap-[6px] whitespace-nowrap text-[12px] transition-colors ${STATUS_COLORS[status]}`}
aria-label={`System status: ${message}`}
>
<StatusDotIcon status={status} className='h-[6px] w-[6px]' aria-hidden='true' />
<span>{message}</span>
</Link>
)
}

View File

@@ -0,0 +1,96 @@
export const FOOTER_BLOCKS = [
'Agent',
'API',
'Condition',
'Evaluator',
'Function',
'Human In The Loop',
'Loop',
'Parallel',
'Response',
'Router',
'Starter',
'Webhook',
'Workflow',
]
export const FOOTER_TOOLS = [
'Airtable',
'Apify',
'Apollo',
'ArXiv',
'Browser Use',
'Calendly',
'Clay',
'Confluence',
'Discord',
'ElevenLabs',
'Exa',
'Firecrawl',
'GitHub',
'Gmail',
'Google Drive',
'Guardrails',
'HubSpot',
'HuggingFace',
'Hunter',
'Incidentio',
'Intercom',
'Jina',
'Jira',
'Knowledge',
'Linear',
'LinkUp',
'LinkedIn',
'Mailchimp',
'Mailgun',
'MCP',
'Mem0',
'Microsoft Excel',
'Microsoft Planner',
'Microsoft Teams',
'Mistral Parse',
'MongoDB',
'MySQL',
'Neo4j',
'Notion',
'OneDrive',
'OpenAI',
'Outlook',
'Parallel AI',
'Perplexity',
'Pinecone',
'Pipedrive',
'PostHog',
'PostgreSQL',
'Pylon',
'Qdrant',
'Reddit',
'Resend',
'S3',
'Salesforce',
'SendGrid',
'Serper',
'SharePoint',
'Slack',
'Smtp',
'Stagehand',
'Stripe',
'Supabase',
'Tavily',
'Telegram',
'Translate',
'Trello',
'Twilio',
'Typeform',
'Vision',
'Wait',
'Wealthbox',
'Webflow',
'WhatsApp',
'Wikipedia',
'X',
'YouTube',
'Zendesk',
'Zep',
]

View File

@@ -1,97 +1,12 @@
import Image from 'next/image'
import Link from 'next/link'
import {
DiscordIcon,
GithubIcon,
HIPAABadgeIcon,
LinkedInIcon,
xIcon as XIcon,
} from '@/components/icons'
import { inter } from '@/app/_styles/fonts/inter/inter'
const blocks = [
'Agent',
'API',
'Condition',
'Evaluator',
'Function',
'Loop',
'Parallel',
'Response',
'Router',
'Starter',
'Webhook',
'Workflow',
]
const tools = [
'Airtable',
'ArXiv',
'Browser Use',
'Clay',
'Confluence',
'Discord',
'ElevenLabs',
'Exa',
'File',
'Firecrawl',
'Generic Webhook',
'GitHub',
'Gmail',
'Google Calendar',
'Google Docs',
'Google Drive',
'Google Vault',
'Google Search',
'Google Sheets',
'HuggingFace',
'Hunter',
'Image Generator',
'Jina',
'Jira',
'Knowledge',
'Linear',
'LinkUp',
'Mem0',
'Memory',
'Microsoft Excel',
'Microsoft Planner',
'Microsoft Teams',
'Mistral Parse',
'MySQL',
'Notion',
'OneDrive',
'OpenAI',
'Outlook',
'Parallel AI',
'Perplexity',
'Pinecone',
'PostgreSQL',
'Qdrant',
'Reddit',
'S3',
'Schedule',
'Serper',
'SharePoint',
'Slack',
'Stagehand',
'Stagehand Agent',
'Supabase',
'Tavily',
'Telegram',
'Thinking',
'Translate',
'Twilio SMS',
'Typeform',
'Vision',
'Wealthbox',
'Webhook',
'WhatsApp',
'Wikipedia',
'X',
'YouTube',
'Zep',
]
import {
ComplianceBadges,
Logo,
SocialLinks,
StatusIndicator,
} from '@/app/(landing)/components/footer/components'
import { FOOTER_BLOCKS, FOOTER_TOOLS } from '@/app/(landing)/components/footer/consts'
interface FooterProps {
fullWidth?: boolean
@@ -110,84 +25,10 @@ export default function Footer({ fullWidth = false }: FooterProps) {
<div className={`flex gap-[80px] ${fullWidth ? 'justify-center' : ''}`}>
{/* Logo and social links */}
<div className='flex flex-col gap-[24px]'>
<Link href='/' aria-label='Sim home'>
<Image
src='/logo/b&w/text/b&w.svg'
alt='Sim - Workflows for LLMs'
width={49.78314}
height={24.276}
priority
quality={90}
/>
</Link>
{/* Social links */}
<div className='flex items-center gap-[12px]'>
<a
href='https://discord.gg/Hr4UWYEcTT'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='Discord'
>
<DiscordIcon className='h-[20px] w-[20px]' aria-hidden='true' />
</a>
<a
href='https://x.com/simdotai'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='X (Twitter)'
>
<XIcon className='h-[18px] w-[18px]' aria-hidden='true' />
</a>
<a
href='https://www.linkedin.com/company/simstudioai/'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='LinkedIn'
>
<LinkedInIcon className='h-[18px] w-[18px]' aria-hidden='true' />
</a>
<a
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
className='flex items-center text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label='GitHub'
>
<GithubIcon className='h-[20px] w-[20px]' aria-hidden='true' />
</a>
</div>
{/* Compliance badges */}
<div className='mt-[6px] flex items-center gap-[12px]'>
{/* SOC2 badge */}
<Link
href='https://trust.delve.co/sim-studio'
target='_blank'
rel='noopener noreferrer'
>
<Image
src='/footer/soc2.png'
alt='SOC2 Compliant'
width={54}
height={54}
className='object-contain'
loading='lazy'
quality={75}
/>
</Link>
{/* HIPAA badge placeholder - add when available */}
<Link
href='https://trust.delve.co/sim-studio'
target='_blank'
rel='noopener noreferrer'
>
<HIPAABadgeIcon className='h-[54px] w-[54px]' />
</Link>
</div>
<Logo />
<SocialLinks />
<ComplianceBadges />
<StatusIndicator />
</div>
{/* Links section */}
@@ -228,6 +69,14 @@ export default function Footer({ fullWidth = false }: FooterProps) {
>
Changelog
</Link>
<Link
href='https://status.sim.ai'
target='_blank'
rel='noopener noreferrer'
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'
>
Status
</Link>
<Link
href='/careers'
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'
@@ -257,7 +106,7 @@ export default function Footer({ fullWidth = false }: FooterProps) {
<div className='hidden sm:block'>
<h2 className='mb-[16px] font-medium text-[14px] text-foreground'>Blocks</h2>
<div className='flex flex-col gap-[12px]'>
{blocks.map((block) => (
{FOOTER_BLOCKS.map((block) => (
<Link
key={block}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replace(' ', '-')}`}
@@ -277,7 +126,7 @@ export default function Footer({ fullWidth = false }: FooterProps) {
<div className='flex gap-[80px]'>
{/* First column */}
<div className='flex flex-col gap-[12px]'>
{tools.slice(0, Math.ceil(tools.length / 4)).map((tool) => (
{FOOTER_TOOLS.slice(0, Math.ceil(FOOTER_TOOLS.length / 4)).map((tool) => (
<Link
key={tool}
href={`https://docs.sim.ai/tools/${tool.toLowerCase().replace(/\s+/g, '_')}`}
@@ -291,39 +140,41 @@ export default function Footer({ fullWidth = false }: FooterProps) {
</div>
{/* Second column */}
<div className='flex flex-col gap-[12px]'>
{tools
.slice(Math.ceil(tools.length / 4), Math.ceil((tools.length * 2) / 4))
.map((tool) => (
<Link
key={tool}
href={`https://docs.sim.ai/tools/${tool.toLowerCase().replace(/\s+/g, '_')}`}
target='_blank'
rel='noopener noreferrer'
className='whitespace-nowrap text-[14px] text-muted-foreground transition-colors hover:text-foreground'
>
{tool}
</Link>
))}
{FOOTER_TOOLS.slice(
Math.ceil(FOOTER_TOOLS.length / 4),
Math.ceil((FOOTER_TOOLS.length * 2) / 4)
).map((tool) => (
<Link
key={tool}
href={`https://docs.sim.ai/tools/${tool.toLowerCase().replace(/\s+/g, '_')}`}
target='_blank'
rel='noopener noreferrer'
className='whitespace-nowrap text-[14px] text-muted-foreground transition-colors hover:text-foreground'
>
{tool}
</Link>
))}
</div>
{/* Third column */}
<div className='flex flex-col gap-[12px]'>
{tools
.slice(Math.ceil((tools.length * 2) / 4), Math.ceil((tools.length * 3) / 4))
.map((tool) => (
<Link
key={tool}
href={`https://docs.sim.ai/tools/${tool.toLowerCase().replace(/\s+/g, '_')}`}
target='_blank'
rel='noopener noreferrer'
className='whitespace-nowrap text-[14px] text-muted-foreground transition-colors hover:text-foreground'
>
{tool}
</Link>
))}
{FOOTER_TOOLS.slice(
Math.ceil((FOOTER_TOOLS.length * 2) / 4),
Math.ceil((FOOTER_TOOLS.length * 3) / 4)
).map((tool) => (
<Link
key={tool}
href={`https://docs.sim.ai/tools/${tool.toLowerCase().replace(/\s+/g, '_')}`}
target='_blank'
rel='noopener noreferrer'
className='whitespace-nowrap text-[14px] text-muted-foreground transition-colors hover:text-foreground'
>
{tool}
</Link>
))}
</div>
{/* Fourth column */}
<div className='flex flex-col gap-[12px]'>
{tools.slice(Math.ceil((tools.length * 3) / 4)).map((tool) => (
{FOOTER_TOOLS.slice(Math.ceil((FOOTER_TOOLS.length * 3) / 4)).map((tool) => (
<Link
key={tool}
href={`https://docs.sim.ai/tools/${tool.toLowerCase().replace(/\s+/g, '_')}`}

View File

@@ -20,7 +20,7 @@ interface NavProps {
}
export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) {
const [githubStars, setGithubStars] = useState('18.5k')
const [githubStars, setGithubStars] = useState('18.6k')
const [isHovered, setIsHovered] = useState(false)
const [isLoginHovered, setIsLoginHovered] = useState(false)
const router = useRouter()

View File

@@ -0,0 +1,97 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import type { IncidentIOWidgetResponse, StatusResponse, StatusType } from '@/app/api/status/types'
const logger = createLogger('StatusAPI')
let cachedResponse: { data: StatusResponse; timestamp: number } | null = null
const CACHE_TTL = 2 * 60 * 1000
function determineStatus(data: IncidentIOWidgetResponse): {
status: StatusType
message: string
} {
if (data.ongoing_incidents && data.ongoing_incidents.length > 0) {
const worstImpact = data.ongoing_incidents[0].current_worst_impact
if (worstImpact === 'full_outage') {
return { status: 'outage', message: 'Service Disruption' }
}
if (worstImpact === 'partial_outage') {
return { status: 'degraded', message: 'Experiencing Issues' }
}
return { status: 'degraded', message: 'Experiencing Issues' }
}
if (data.in_progress_maintenances && data.in_progress_maintenances.length > 0) {
return { status: 'maintenance', message: 'Under Maintenance' }
}
return { status: 'operational', message: 'All Systems Operational' }
}
export async function GET() {
try {
const now = Date.now()
if (cachedResponse && now - cachedResponse.timestamp < CACHE_TTL) {
return NextResponse.json(cachedResponse.data, {
headers: {
'Cache-Control': 'public, max-age=60, s-maxage=60',
'X-Cache': 'HIT',
},
})
}
const response = await fetch('https://status.sim.ai/api/v1/summary', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
signal: AbortSignal.timeout(5000),
})
if (!response.ok) {
throw new Error(`incident.io API returned ${response.status}`)
}
const data: IncidentIOWidgetResponse = await response.json()
const { status, message } = determineStatus(data)
const statusResponse: StatusResponse = {
status,
message,
url: data.page_url || 'https://status.sim.ai',
lastUpdated: new Date().toISOString(),
}
cachedResponse = {
data: statusResponse,
timestamp: now,
}
return NextResponse.json(statusResponse, {
headers: {
'Cache-Control': 'public, max-age=60, s-maxage=60',
'X-Cache': 'MISS',
},
})
} catch (error) {
logger.error('Error fetching status from incident.io:', error)
const errorResponse: StatusResponse = {
status: 'error',
message: 'Status Unknown',
url: 'https://status.sim.ai',
lastUpdated: new Date().toISOString(),
}
return NextResponse.json(errorResponse, {
status: 200,
headers: {
'Cache-Control': 'public, max-age=30, s-maxage=30',
},
})
}
}

View File

@@ -0,0 +1,48 @@
export interface IncidentIOComponent {
id: string
name: string
group_name?: string
current_status: 'operational' | 'degraded_performance' | 'partial_outage' | 'full_outage'
}
export interface IncidentIOIncident {
id: string
name: string
status: 'investigating' | 'identified' | 'monitoring'
url: string
last_update_at: string
last_update_message: string
current_worst_impact: 'degraded_performance' | 'partial_outage' | 'full_outage'
affected_components: IncidentIOComponent[]
}
export interface IncidentIOMaintenance {
id: string
name: string
status: 'maintenance_scheduled' | 'maintenance_in_progress'
url: string
last_update_at: string
last_update_message: string
affected_components: IncidentIOComponent[]
started_at?: string
scheduled_end_at?: string
starts_at?: string
ends_at?: string
}
export interface IncidentIOWidgetResponse {
page_title: string
page_url: string
ongoing_incidents: IncidentIOIncident[]
in_progress_maintenances: IncidentIOMaintenance[]
scheduled_maintenances: IncidentIOMaintenance[]
}
export type StatusType = 'operational' | 'degraded' | 'outage' | 'maintenance' | 'loading' | 'error'
export interface StatusResponse {
status: StatusType
message: string
url: string
lastUpdated: string
}

View File

@@ -164,7 +164,7 @@ export function useWand({
// Keep track of the current prompt for history
const currentPrompt = prompt
const response = await fetch('/api/wand-generate', {
const response = await fetch('/api/wand', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -836,17 +836,18 @@ export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
export function LinkedInIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} height='72' viewBox='0 0 72 72' width='72' xmlns='http://www.w3.org/2000/svg'>
<g fill='none' fillRule='evenodd'>
<path
d='M8,72 L64,72 C68.418278,72 72,68.418278 72,64 L72,8 C72,3.581722 68.418278,-8.11624501e-16 64,0 L8,0 C3.581722,8.11624501e-16 -5.41083001e-16,3.581722 0,8 L0,64 C5.41083001e-16,68.418278 3.581722,72 8,72 Z'
fill='#0072B1'
/>
<path
d='M62,62 L51.315625,62 L51.315625,43.8021149 C51.315625,38.8127542 49.4197917,36.0245323 45.4707031,36.0245323 C41.1746094,36.0245323 38.9300781,38.9261103 38.9300781,43.8021149 L38.9300781,62 L28.6333333,62 L28.6333333,27.3333333 L38.9300781,27.3333333 L38.9300781,32.0029283 C38.9300781,32.0029283 42.0260417,26.2742151 49.3825521,26.2742151 C56.7356771,26.2742151 62,30.7644705 62,40.051212 L62,62 Z M16.349349,22.7940133 C12.8420573,22.7940133 10,19.9296567 10,16.3970067 C10,12.8643566 12.8420573,10 16.349349,10 C19.8566406,10 22.6970052,12.8643566 22.6970052,16.3970067 C22.6970052,19.9296567 19.8566406,22.7940133 16.349349,22.7940133 Z M11.0325521,62 L21.769401,62 L21.769401,27.3333333 L11.0325521,27.3333333 L11.0325521,62 Z'
fill='#FFF'
/>
</g>
<svg
{...props}
fill='currentColor'
height='72'
viewBox='0 0 72 72'
width='72'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M62,62 L51.315625,62 L51.315625,43.8021149 C51.315625,38.8127542 49.4197917,36.0245323 45.4707031,36.0245323 C41.1746094,36.0245323 38.9300781,38.9261103 38.9300781,43.8021149 L38.9300781,62 L28.6333333,62 L28.6333333,27.3333333 L38.9300781,27.3333333 L38.9300781,32.0029283 C38.9300781,32.0029283 42.0260417,26.2742151 49.3825521,26.2742151 C56.7356771,26.2742151 62,30.7644705 62,40.051212 L62,62 Z M16.349349,22.7940133 C12.8420573,22.7940133 10,19.9296567 10,16.3970067 C10,12.8643566 12.8420573,10 16.349349,10 C19.8566406,10 22.6970052,12.8643566 22.6970052,16.3970067 C22.6970052,19.9296567 19.8566406,22.7940133 16.349349,22.7940133 Z M11.0325521,62 L21.769401,62 L21.769401,27.3333333 L11.0325521,27.3333333 L11.0325521,62 Z'
fill='currentColor'
/>
</svg>
)
}
@@ -3833,3 +3834,32 @@ export function ApifyIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
interface StatusDotIconProps extends SVGProps<SVGSVGElement> {
status: 'operational' | 'degraded' | 'outage' | 'maintenance' | 'loading' | 'error'
}
export function StatusDotIcon({ status, className, ...props }: StatusDotIconProps) {
const colors = {
operational: '#10B981',
degraded: '#F59E0B',
outage: '#EF4444',
maintenance: '#3B82F6',
loading: '#9CA3AF',
error: '#9CA3AF',
}
return (
<svg
xmlns='http://www.w3.org/2000/svg'
width={6}
height={6}
viewBox='0 0 6 6'
fill='none'
className={className}
{...props}
>
<circle cx={3} cy={3} r={3} fill={colors[status]} />
</svg>
)
}

View File

@@ -0,0 +1,42 @@
import { useQuery } from '@tanstack/react-query'
import type { StatusResponse } from '@/app/api/status/types'
/**
* Query key factories for status-related queries
* This ensures consistent cache invalidation across the app
*/
export const statusKeys = {
all: ['status'] as const,
current: () => [...statusKeys.all, 'current'] as const,
}
/**
* Fetch current system status from the API
* The API proxies incident.io and caches for 2 minutes server-side
*/
async function fetchStatus(): Promise<StatusResponse> {
const response = await fetch('/api/status')
if (!response.ok) {
throw new Error('Failed to fetch status')
}
return response.json()
}
/**
* Hook to fetch current system status
* - Polls every 60 seconds to keep status up-to-date
* - Refetches when user returns to tab for immediate updates
* - Caches for 1 minute to reduce unnecessary requests
*/
export function useStatus() {
return useQuery({
queryKey: statusKeys.current(),
queryFn: fetchStatus,
staleTime: 60 * 1000, // 1 minute
refetchInterval: 60 * 1000, // Poll every 60 seconds
refetchOnWindowFocus: true, // Refetch when user returns to tab
retry: 2,
})
}

View File

@@ -31,11 +31,11 @@ export function getTriggerOptions(): TriggerOption[] {
const providerMap = new Map<string, TriggerOption>()
const coreTypes: TriggerOption[] = [
{ value: 'manual', label: 'Manual', color: '#6b7280' }, // gray-500
{ value: 'api', label: 'API', color: '#3b82f6' }, // blue-500
{ value: 'schedule', label: 'Schedule', color: '#10b981' }, // green-500
{ value: 'chat', label: 'Chat', color: '#8b5cf6' }, // purple-500
{ value: 'webhook', label: 'Webhook', color: '#f97316' }, // orange-500 (for backward compatibility)
{ value: 'manual', label: 'Manual', color: '#6b7280' },
{ value: 'api', label: 'API', color: '#3b82f6' },
{ value: 'schedule', label: 'Schedule', color: '#10b981' },
{ value: 'chat', label: 'Chat', color: '#8b5cf6' },
{ value: 'webhook', label: 'Webhook', color: '#f97316' },
]
for (const trigger of triggers) {

View File

@@ -82,13 +82,12 @@ export const FILTER_DEFINITIONS: FilterDefinition[] = [
},
]
// Core trigger types that are always available
const CORE_TRIGGERS: TriggerData[] = [
{ value: 'api', label: 'API', color: '#3b82f6' }, // blue-500
{ value: 'manual', label: 'Manual', color: '#6b7280' }, // gray-500
{ value: 'webhook', label: 'Webhook', color: '#f97316' }, // orange-500
{ value: 'chat', label: 'Chat', color: '#8b5cf6' }, // purple-500
{ value: 'schedule', label: 'Schedule', color: '#10b981' }, // green-500
{ value: 'api', label: 'API', color: '#3b82f6' },
{ value: 'manual', label: 'Manual', color: '#6b7280' },
{ value: 'webhook', label: 'Webhook', color: '#f97316' },
{ value: 'chat', label: 'Chat', color: '#8b5cf6' },
{ value: 'schedule', label: 'Schedule', color: '#10b981' },
]
export class SearchSuggestions {