Files
supabase/apps/studio/components/interfaces/Support/AIAssistantOption.tsx
Saxon FletcherandGildas Garcia 4452e0ac2e Support form sidebar (#45203)
Refactors our help sidebar within Studio to include the actual support
form itself when contact is selected. This PR also cleans up the initial
state of the sidebar and the options within.

## To test:
- Open an org and click the help icon top right
- Click contact support
- Submit a support ticket
- Click done to return to support sidebar state

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Support form V3 and support sidebar with status button; direct-email
helper and URL prefill
* Success screen supports onFinish callback and customizable finish
label
* AI Assistant and Help options accept optional click callbacks;
resource items gain keyboard/accessibility support

* **Refactor**
  * Help panel split into home/support views with back navigation
* Support components accept flexible align/className props and
layout/styling tweaks
  * Initial URL params loader added for support form

* **Tests**
* New/updated tests for support flows, success screen, and help options
interactions
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
2026-05-08 13:51:49 +10:00

115 lines
4.4 KiB
TypeScript

import { AnimatePresence, motion } from 'framer-motion'
import { MessageSquare } from 'lucide-react'
import Link from 'next/link'
import { useCallback, useEffect, useState } from 'react'
import { AiIconAnimation, Button } from 'ui'
import { NO_ORG_MARKER, NO_PROJECT_MARKER } from './SupportForm.utils'
import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
interface AIAssistantOptionProps {
projectRef?: string | null
organizationSlug?: string | null
onClick?: () => void
}
export const AIAssistantOption = ({
projectRef,
organizationSlug,
onClick,
}: AIAssistantOptionProps) => {
const { mutate: sendEvent } = useSendEventMutation()
const [isVisible, setIsVisible] = useState(false)
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), 800)
return () => clearTimeout(timer)
}, [])
const onAiAssistantClicked = useCallback(() => {
sendEvent({
action: 'ai_assistant_in_support_form_clicked',
groups: {
project: projectRef === null || projectRef === NO_PROJECT_MARKER ? undefined : projectRef,
organization:
organizationSlug === null || organizationSlug === NO_ORG_MARKER
? undefined
: organizationSlug,
},
})
onClick?.()
}, [onClick, projectRef, organizationSlug, sendEvent])
// If no specific project selected, use the wildcard route
const aiLink = `/project/${projectRef !== NO_PROJECT_MARKER ? projectRef : '_'}?sidebar=ai-assistant&slug=${organizationSlug}`
if (!organizationSlug || organizationSlug === NO_ORG_MARKER) return null
return (
<AnimatePresence>
{isVisible && (
<motion.aside
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="w-full overflow-hidden border rounded-md relative bg-200"
>
<div className="flex items-center p-6">
<div className="flex flex-col gap-3 z-2 shrink-0 w-full">
<div>
<h5 className="text-sm font-medium text-foreground">Try Supabase Assistant</h5>
<p className="text-sm text-foreground-lighter">
Ask our AI assistant to help you with your support issue.
</p>
</div>
<div>
{onClick ? (
<Button
size="tiny"
type="default"
icon={<AiIconAnimation size={14} />}
onClick={onAiAssistantClicked}
>
Ask the Assistant
</Button>
) : (
<Link href={aiLink} onClick={onAiAssistantClicked}>
<Button size="tiny" type="default" icon={<AiIconAnimation size={14} />}>
Ask the Assistant
</Button>
</Link>
)}
</div>
</div>
{/* Decorative background */}
<div className="absolute z-1 scale-75 -right-40 md:-right-24 -top-6 md:top-0">
<div className="relative grow flex flex-col gap-3 w-[400px]">
<div className="flex items-start gap-3 pl-12">
<div className="w-8 h-8 rounded-full bg-background-surface-300 flex items-center justify-center">
<MessageSquare size={16} className="text-foreground-lighter" />
</div>
<div className="flex-1 bg-background-surface-200 rounded-lg p-4 max-w-[280px]">
<p className="text-sm text-foreground-lighter">
Hi! I'm your AI assistant. How can I help you today?
</p>
</div>
</div>
<div className="flex items-start justify-end gap-3 pr-10">
<div className="bg-background-surface-200 rounded-lg p-4 max-w-[280px]">
<p className="text-sm text-foreground-lighter">
I can help you with database queries, API endpoints, or any other technical
questions you might have.
</p>
</div>
</div>
</div>
<div className="absolute -inset-2 bg-linear-to-l from-transparent via-background-200 via-90% to-background-200 to-100% z-1" />
</div>
</div>
</motion.aside>
)}
</AnimatePresence>
)
}