FeedbackDropdown.tsx122 lines · main
| 1 | import { IS_PLATFORM } from 'common' |
| 2 | import { Lightbulb, TriangleAlert } from 'lucide-react' |
| 3 | import { useRouter } from 'next/router' |
| 4 | import { useState } from 'react' |
| 5 | import { Button, Popover, PopoverContent, PopoverSeparator, PopoverTrigger } from 'ui' |
| 6 | |
| 7 | import { FeedbackWidget } from './FeedbackWidget' |
| 8 | import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' |
| 9 | import { ASSISTANT_SUGGESTIONS } from '@/components/ui/HelpPanel/HelpPanel.constants' |
| 10 | import { getSupportLinkQueryParams } from '@/components/ui/HelpPanel/HelpPanel.utils' |
| 11 | import { HelpSection } from '@/components/ui/HelpPanel/HelpSection' |
| 12 | import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' |
| 13 | import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' |
| 14 | import { useTrack } from '@/lib/telemetry/track' |
| 15 | import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' |
| 16 | import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' |
| 17 | |
| 18 | export const FeedbackDropdown = ({ className }: { className?: string }) => { |
| 19 | const router = useRouter() |
| 20 | const { data: project } = useSelectedProjectQuery() |
| 21 | const { data: org } = useSelectedOrganizationQuery() |
| 22 | const snap = useAiAssistantStateSnapshot() |
| 23 | const { openSidebar } = useSidebarManagerSnapshot() |
| 24 | const [isOpen, setIsOpen] = useState(false) |
| 25 | const [stage, setStage] = useState<'select' | 'issue-options' | 'widget'>('select') |
| 26 | const track = useTrack() |
| 27 | |
| 28 | const projectRef = project?.parent_project_ref ?? (router.query.ref as string | undefined) |
| 29 | const supportLinkQueryParams = getSupportLinkQueryParams( |
| 30 | project, |
| 31 | org, |
| 32 | router.query.ref as string | undefined |
| 33 | ) |
| 34 | |
| 35 | return ( |
| 36 | <Popover |
| 37 | modal={false} |
| 38 | open={isOpen} |
| 39 | onOpenChange={(e) => { |
| 40 | if (e) track('header_feedback_dropdown_opened') |
| 41 | setIsOpen(e) |
| 42 | if (!e) setStage('select') |
| 43 | }} |
| 44 | > |
| 45 | <PopoverTrigger asChild> |
| 46 | <Button |
| 47 | asChild |
| 48 | onClick={() => { |
| 49 | setIsOpen((isOpen) => !isOpen) |
| 50 | setStage('select') |
| 51 | }} |
| 52 | type="text" |
| 53 | className="rounded-full h-[32px] text-foreground-light hover:text-foreground" |
| 54 | > |
| 55 | <span className={className}>Feedback</span> |
| 56 | </Button> |
| 57 | </PopoverTrigger> |
| 58 | <PopoverContent |
| 59 | side="bottom" |
| 60 | align="end" |
| 61 | className="p-0 flex flex-col w-96" |
| 62 | id="feedback-widget" |
| 63 | > |
| 64 | {stage === 'select' && ( |
| 65 | <div className="flex flex-col gap-4 p-4"> |
| 66 | <div className="font-medium text-sm">What would you like to share?</div> |
| 67 | <div className="grid grid-cols-2 gap-3"> |
| 68 | <Button type="default" className="h-32" onClick={() => setStage('issue-options')}> |
| 69 | <div className="grid gap-1.5 text-center"> |
| 70 | <TriangleAlert size="28" className="mx-auto text-destructive-600" /> |
| 71 | <div className="flex flex-col items-center"> |
| 72 | <span className="text-base">Issue</span> |
| 73 | <span className="text-xs text-foreground-lighter">with my project</span> |
| 74 | </div> |
| 75 | </div> |
| 76 | </Button> |
| 77 | <Button type="default" className="h-32" onClick={() => setStage('widget')}> |
| 78 | <div className="grid gap-1.5 text-center"> |
| 79 | <Lightbulb size="28" className="mx-auto text-warning" /> |
| 80 | <div className="flex flex-col items-center"> |
| 81 | <span className="text-base">Idea</span> |
| 82 | <span className="text-xs text-foreground-lighter">to improve Briven</span> |
| 83 | </div> |
| 84 | </div> |
| 85 | </Button> |
| 86 | </div> |
| 87 | </div> |
| 88 | )} |
| 89 | {stage === 'issue-options' && ( |
| 90 | <> |
| 91 | <div className="flex flex-col gap-4 p-4"> |
| 92 | <HelpSection |
| 93 | excludeIds={[]} |
| 94 | isPlatform={IS_PLATFORM} |
| 95 | projectRef={projectRef} |
| 96 | supportLinkQueryParams={supportLinkQueryParams} |
| 97 | onAssistantClick={() => { |
| 98 | openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) |
| 99 | snap.newChat(ASSISTANT_SUGGESTIONS) |
| 100 | setIsOpen(false) |
| 101 | }} |
| 102 | onSupportClick={() => setIsOpen(false)} |
| 103 | /> |
| 104 | </div> |
| 105 | <PopoverSeparator /> |
| 106 | <div className="px-4 pt-4 pb-4"> |
| 107 | <Button type="default" size="tiny" onClick={() => setStage('widget')}> |
| 108 | Leave feedback instead |
| 109 | </Button> |
| 110 | </div> |
| 111 | </> |
| 112 | )} |
| 113 | {stage === 'widget' && ( |
| 114 | <FeedbackWidget |
| 115 | onClose={() => setIsOpen(false)} |
| 116 | onSwitchToIssueOptions={() => setStage('issue-options')} |
| 117 | /> |
| 118 | )} |
| 119 | </PopoverContent> |
| 120 | </Popover> |
| 121 | ) |
| 122 | } |