Skip to content

Commit 3aa60ba

Browse files
committed
feat(assistant): introduce AssistantProvider and refactor context management for improved state handling
1 parent 7e872f7 commit 3aa60ba

File tree

9 files changed

+148
-130
lines changed

9 files changed

+148
-130
lines changed

apps/console-v5/src/routes/_authenticated/organization/route.tsx

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Outlet, createFileRoute, useLocation, useMatches, useParams } from '@ta
33
import posthog from 'posthog-js'
44
import { Suspense, useEffect, useLayoutEffect, useRef, useState } from 'react'
55
import { useServiceSummary } from '@qovery/domains/services/feature'
6+
import { AssistantProvider } from '@qovery/shared/assistant/feature'
67
import { DevopsCopilotContext } from '@qovery/shared/devops-copilot/context'
78
import { DevopsCopilotTrigger } from '@qovery/shared/devops-copilot/feature'
89
import { ErrorBoundary, Icon, LoaderSpinner, Navbar } from '@qovery/shared/ui'
@@ -501,8 +502,10 @@ function OrganizationRoute() {
501502
sendMessageRef,
502503
}}
503504
>
504-
<Outlet />
505-
<DevopsCopilotTrigger />
505+
<AssistantProvider>
506+
<Outlet />
507+
<DevopsCopilotTrigger />
508+
</AssistantProvider>
506509
</DevopsCopilotContext.Provider>
507510
)
508511
}
@@ -515,32 +518,34 @@ function OrganizationRoute() {
515518
sendMessageRef,
516519
}}
517520
>
518-
<div className="flex h-dvh w-full flex-col bg-background">
519-
{/* TODO: Conflicts with body main:not(.h-screen, .layout-onboarding) */}
520-
<div ref={scrollContainerRef} className="min-h-0 flex-1 overflow-auto">
521-
<ErrorBoundary>
522-
<OrganizationBanners />
523-
<div ref={headerRef}>
524-
<Header compactAssistantPanel={compactAssistantPanel} />
525-
</div>
526-
527-
<Suspense fallback={<MainLoader />}>
528-
<>
529-
<div className="sticky top-0 z-header border-b border-neutral bg-background-secondary px-4">
530-
<Navbar.Root activeId={activeTabId} className="container relative top-[1px] mx-0 -mt-[1px]">
531-
{navigationContext && <NavigationBar context={navigationContext} />}
532-
</Navbar.Root>
533-
</div>
534-
535-
<div className={needsFullWidth ? 'min-h-0' : 'container mx-auto min-h-0 px-4'}>
536-
<Outlet />
537-
</div>
538-
</>
539-
</Suspense>
540-
</ErrorBoundary>
521+
<AssistantProvider>
522+
<div className="flex h-dvh w-full flex-col bg-background">
523+
{/* TODO: Conflicts with body main:not(.h-screen, .layout-onboarding) */}
524+
<div ref={scrollContainerRef} className="min-h-0 flex-1 overflow-auto">
525+
<ErrorBoundary>
526+
<OrganizationBanners />
527+
<div ref={headerRef}>
528+
<Header compactAssistantPanel={compactAssistantPanel} />
529+
</div>
530+
531+
<Suspense fallback={<MainLoader />}>
532+
<>
533+
<div className="sticky top-0 z-header border-b border-neutral bg-background-secondary px-4">
534+
<Navbar.Root activeId={activeTabId} className="container relative top-[1px] mx-0 -mt-[1px]">
535+
{navigationContext && <NavigationBar context={navigationContext} />}
536+
</Navbar.Root>
537+
</div>
538+
539+
<div className={needsFullWidth ? 'min-h-0' : 'container mx-auto min-h-0 px-4'}>
540+
<Outlet />
541+
</div>
542+
</>
543+
</Suspense>
544+
</ErrorBoundary>
545+
</div>
541546
</div>
542-
</div>
543-
<DevopsCopilotTrigger />
547+
<DevopsCopilotTrigger />
548+
</AssistantProvider>
544549
</DevopsCopilotContext.Provider>
545550
)
546551
}

libs/pages/layout/src/lib/ui/layout-page/layout-page.tsx

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { match } from 'ts-pattern'
66
import { ClusterDeploymentProgressCard, useClusterStatuses } from '@qovery/domains/clusters/feature'
77
import { useAlerts } from '@qovery/domains/observability/feature'
88
import { FreeTrialBanner, InvoiceBanner, useOrganization } from '@qovery/domains/organizations/feature'
9-
import { AssistantTrigger } from '@qovery/shared/assistant/feature'
9+
import { AssistantProvider, AssistantTrigger } from '@qovery/shared/assistant/feature'
1010
import { DevopsCopilotButton, DevopsCopilotTrigger } from '@qovery/shared/devops-copilot/feature'
1111
import { useUserRole } from '@qovery/shared/iam/feature'
1212
import { AnnouncementBanner } from '@qovery/shared/posthog/feature'
@@ -150,45 +150,47 @@ export function LayoutPage(props: PropsWithChildren<LayoutPageProps>) {
150150
alertingNotification={hasFiringAlerts ? 'error' : undefined}
151151
/>
152152
</div>
153-
<div className="flex w-full grow flex-col-reverse">
154-
<div>
155-
<div
156-
className={`relative flex ${
157-
clusterCredentialError ? 'min-h-page-container-wbanner' : 'min-h-page-container'
158-
}`}
159-
>
160-
<div className="flex grow flex-col px-2 pt-2 dark:px-0 dark:pt-0">{children}</div>
161-
<AssistantTrigger />
162-
{isFeatureFlag && <DevopsCopilotTrigger />}
153+
<AssistantProvider>
154+
<div className="flex w-full grow flex-col-reverse">
155+
<div>
156+
<div
157+
className={`relative flex ${
158+
clusterCredentialError ? 'min-h-page-container-wbanner' : 'min-h-page-container'
159+
}`}
160+
>
161+
<div className="flex grow flex-col px-2 pt-2 dark:px-0 dark:pt-0">{children}</div>
162+
<AssistantTrigger />
163+
{isFeatureFlag && <DevopsCopilotTrigger />}
164+
</div>
163165
</div>
166+
{clusterCredentialError && (
167+
<Banner
168+
color="yellow"
169+
onClickButton={() =>
170+
navigate(
171+
CLUSTER_URL(organizationId, invalidCluster?.id) +
172+
CLUSTER_SETTINGS_URL +
173+
CLUSTER_SETTINGS_CREDENTIALS_URL
174+
)
175+
}
176+
buttonLabel="Check the credentials configuration"
177+
>
178+
The credentials for the cluster <span className="mx-1 block font-bold">{invalidCluster?.name}</span>{' '}
179+
are invalid.
180+
</Banner>
181+
)}
182+
<FreeTrialBanner />
183+
<InvoiceBanner />
184+
{topBar && (
185+
<TopBar>
186+
<div className="flex items-center">
187+
{spotlight && <SpotlightTrigger />}
188+
{isFeatureFlag && <DevopsCopilotButton />}
189+
</div>
190+
</TopBar>
191+
)}
164192
</div>
165-
{clusterCredentialError && (
166-
<Banner
167-
color="yellow"
168-
onClickButton={() =>
169-
navigate(
170-
CLUSTER_URL(organizationId, invalidCluster?.id) +
171-
CLUSTER_SETTINGS_URL +
172-
CLUSTER_SETTINGS_CREDENTIALS_URL
173-
)
174-
}
175-
buttonLabel="Check the credentials configuration"
176-
>
177-
The credentials for the cluster <span className="mx-1 block font-bold">{invalidCluster?.name}</span> are
178-
invalid.
179-
</Banner>
180-
)}
181-
<FreeTrialBanner />
182-
<InvoiceBanner />
183-
{topBar && (
184-
<TopBar>
185-
<div className="flex items-center">
186-
{spotlight && <SpotlightTrigger />}
187-
{isFeatureFlag && <DevopsCopilotButton />}
188-
</div>
189-
</TopBar>
190-
)}
191-
</div>
193+
</AssistantProvider>
192194
</div>
193195
{showFloatingDeploymentCard && (
194196
<ClusterDeploymentProgressCard organizationId={organizationId} clusters={deployingClusters} />
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export * from './lib/assistant-trigger/assistant-trigger'
22
export * from './lib/need-help/need-help'
33
export * from './lib/assistant-context/assistant-context'
4-
export * from './lib/assistant-store/assistant-store'
54
export * from './lib/hooks/use-contextual-doc-links/use-contextual-doc-links'

libs/shared/assistant/feature/src/lib/assistant-context/assistant-context.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { type PropsWithChildren, createContext, useCallback, useContext, useMemo, useState } from 'react'
2+
3+
type AssistantActionsContextValue = {
4+
setAssistantOpen: (assistantOpen: boolean) => void
5+
toggleAssistantOpen: () => void
6+
}
7+
8+
const AssistantOpenContext = createContext(false)
9+
const AssistantActionsContext = createContext<AssistantActionsContextValue>({
10+
// eslint-disable-next-line @typescript-eslint/no-empty-function
11+
setAssistantOpen: () => {},
12+
// eslint-disable-next-line @typescript-eslint/no-empty-function
13+
toggleAssistantOpen: () => {},
14+
})
15+
16+
export function AssistantProvider({ children }: PropsWithChildren) {
17+
const [assistantOpen, setAssistantOpen] = useState(false)
18+
19+
const toggleAssistantOpen = useCallback(() => {
20+
setAssistantOpen((currentAssistantOpen) => !currentAssistantOpen)
21+
}, [])
22+
23+
const actionsValue = useMemo(
24+
() => ({
25+
setAssistantOpen,
26+
toggleAssistantOpen,
27+
}),
28+
[toggleAssistantOpen]
29+
)
30+
31+
return (
32+
<AssistantActionsContext.Provider value={actionsValue}>
33+
<AssistantOpenContext.Provider value={assistantOpen}>{children}</AssistantOpenContext.Provider>
34+
</AssistantActionsContext.Provider>
35+
)
36+
}
37+
38+
export function useAssistantOpen() {
39+
return useContext(AssistantOpenContext)
40+
}
41+
42+
export function useSetAssistantOpen() {
43+
return useContext(AssistantActionsContext).setAssistantOpen
44+
}
45+
46+
export function useToggleAssistantOpen() {
47+
return useContext(AssistantActionsContext).toggleAssistantOpen
48+
}
49+
50+
export default AssistantProvider

libs/shared/assistant/feature/src/lib/assistant-store/assistant-store.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

libs/shared/assistant/feature/src/lib/assistant-trigger/assistant-trigger.tsx

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@ import { AnimatePresence } from 'framer-motion'
22
import { useEffect } from 'react'
33
import { Button, Icon } from '@qovery/shared/ui'
44
import { useSupportChat } from '@qovery/shared/util-hooks'
5+
import { useAssistantOpen, useSetAssistantOpen, useToggleAssistantOpen } from '../assistant-context/assistant-context'
56
import { AssistantPanel } from '../assistant-panel/assistant-panel'
6-
import { setAssistantOpen, toggleAssistantOpen, useAssistantOpen } from '../assistant-store/assistant-store'
77

88
export interface AssistantTriggerProps {
99
defaultOpen?: boolean
1010
compactTopOffset?: boolean
11+
renderPanel?: boolean
1112
}
1213

13-
export function AssistantTrigger({ defaultOpen = false, compactTopOffset = false }: AssistantTriggerProps) {
14+
export function AssistantTrigger({
15+
defaultOpen = false,
16+
compactTopOffset = false,
17+
renderPanel = true,
18+
}: AssistantTriggerProps) {
1419
const { initChat } = useSupportChat()
1520
const assistantOpen = useAssistantOpen()
21+
const setAssistantOpen = useSetAssistantOpen()
22+
const toggleAssistantOpen = useToggleAssistantOpen()
1623

1724
useEffect(() => {
1825
// Initialize support chat (either Pylon or Intercom depending on the route: Intercom for onboarding views, Pylon for the rest of the Console)
@@ -26,15 +33,17 @@ export function AssistantTrigger({ defaultOpen = false, compactTopOffset = false
2633
</Button>
2734

2835
{/* XXX: rely on defaultOpen boolean for `smaller` prop as all funnel flows require smaller panel */}
29-
<AnimatePresence>
30-
{assistantOpen && (
31-
<AssistantPanel
32-
onClose={() => setAssistantOpen(false)}
33-
smaller={defaultOpen}
34-
compactTopOffset={compactTopOffset}
35-
/>
36-
)}
37-
</AnimatePresence>
36+
{renderPanel && (
37+
<AnimatePresence>
38+
{assistantOpen && (
39+
<AssistantPanel
40+
onClose={() => setAssistantOpen(false)}
41+
smaller={defaultOpen}
42+
compactTopOffset={compactTopOffset}
43+
/>
44+
)}
45+
</AnimatePresence>
46+
)}
3847
</>
3948
)
4049
}

libs/shared/assistant/feature/src/lib/need-help/need-help.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Button, Icon } from '@qovery/shared/ui'
22
import { twMerge } from '@qovery/shared/util-js'
3-
import { toggleAssistantOpen } from '../assistant-store/assistant-store'
3+
import { useToggleAssistantOpen } from '../assistant-context/assistant-context'
44

55
export interface NeedHelpProps {
66
className?: string
77
}
88

99
export function NeedHelp({ className }: NeedHelpProps) {
10+
const toggleAssistantOpen = useToggleAssistantOpen()
11+
1012
return (
1113
<Button
1214
size="xs"

libs/shared/spotlight/feature/src/lib/spotlight/spotlight.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useNavigate } from '@tanstack/react-router'
33
import { type ServiceLightResponse } from 'qovery-typescript-axios'
44
import { useCallback, useMemo, useRef, useState } from 'react'
55
import { ServiceAvatar, useFavoriteServices, useRecentServices } from '@qovery/domains/services/feature'
6-
import { setAssistantOpen } from '@qovery/shared/assistant/feature'
6+
import { useSetAssistantOpen } from '@qovery/shared/assistant/feature'
77
import { IconEnum } from '@qovery/shared/enums'
88
import { UserSettingsModal } from '@qovery/shared/iam/feature'
99
import { Command, type CommandDialogProps, Icon, Truncate, useModal } from '@qovery/shared/ui'
@@ -82,6 +82,7 @@ export function Spotlight({ organizationId, open, onOpenChange }: SpotlightProps
8282
const navigate = useNavigate()
8383
const quickActions = useQuickActions()
8484
const { openModal } = useModal()
85+
const setAssistantOpen = useSetAssistantOpen()
8586
const { data: services = [], isLoading: isLoadingServices } = useServicesSearch({ organizationId })
8687
const [searchInput, setSearchInput] = useState('')
8788
const { getRecentServices, addToRecentServices } = useRecentServices({ organizationId })
@@ -212,7 +213,7 @@ export function Spotlight({ organizationId, open, onOpenChange }: SpotlightProps
212213
iconName: 'robot',
213214
},
214215
],
215-
[openExternalLink, onOpenChange]
216+
[openExternalLink, onOpenChange, setAssistantOpen]
216217
)
217218

218219
const filteredServices = useMemo(() => {

0 commit comments

Comments
 (0)