1- import { useEffect , useState } from "react" ;
2- import { ConnectedIcon , DisconnectedIcon } from "~/assets/icons/ConnectionIcons" ;
1+ import { createContext , type ReactNode , useContext , useEffect , useMemo , useState } from "react" ;
32import { useDebounce } from "~/hooks/useDebounce" ;
43import { useEnvironment } from "~/hooks/useEnvironment" ;
54import { useEventSource } from "~/hooks/useEventSource" ;
65import { useOrganization } from "~/hooks/useOrganizations" ;
76import { useProject } from "~/hooks/useProject" ;
8- import {
9- Dialog ,
10- DialogContent ,
11- DialogFooter ,
12- DialogHeader ,
13- DialogTrigger ,
14- } from "./primitives/Dialog" ;
15- import { Button , LinkButton } from "./primitives/Buttons" ;
16- import connectedImage from "../assets/images/cli-connected.png" ;
17- import disconnectedImage from "../assets/images/cli-disconnected.png" ;
18- import { Paragraph } from "./primitives/Paragraph" ;
19- import { PackageManagerProvider , TriggerDevStepV3 } from "./SetupCommands" ;
20- import { docsPath } from "~/utils/pathBuilder" ;
21- import { BookOpenIcon } from "@heroicons/react/20/solid" ;
227
23- export function useDevPresence ( ) {
8+ // Define Context types
9+ type DevPresenceContextType = {
10+ lastSeen : Date | null ;
11+ isConnected : boolean ;
12+ } ;
13+
14+ // Create Context with default values
15+ const DevPresenceContext = createContext < DevPresenceContextType > ( {
16+ lastSeen : null ,
17+ isConnected : false ,
18+ } ) ;
19+
20+ // Provider component with enabled prop
21+ interface DevPresenceProviderProps {
22+ children : ReactNode ;
23+ enabled ?: boolean ;
24+ }
25+
26+ export function DevPresenceProvider ( { children, enabled = true } : DevPresenceProviderProps ) {
2427 const organization = useOrganization ( ) ;
2528 const project = useProject ( ) ;
2629 const environment = useEnvironment ( ) ;
30+
31+ // Only subscribe to event source if enabled is true
2732 const streamedEvents = useEventSource (
2833 `/resources/orgs/${ organization . slug } /projects/${ project . slug } /env/${ environment . slug } /dev/presence` ,
2934 {
3035 event : "presence" ,
36+ disabled : ! enabled ,
3137 }
3238 ) ;
3339
@@ -38,15 +44,15 @@ export function useDevPresence() {
3844 } , 3_000 ) ;
3945
4046 useEffect ( ( ) => {
41- if ( streamedEvents === null ) {
47+ // If disabled or no events, set lastSeen to null
48+ if ( ! enabled || streamedEvents === null ) {
4249 debouncer ( null ) ;
4350 return ;
4451 }
4552
4653 try {
4754 const data = JSON . parse ( streamedEvents ) as any ;
4855 if ( "lastSeen" in data && data . lastSeen ) {
49- // Parse the timestamp string into a Date object
5056 try {
5157 const lastSeenDate = new Date ( data . lastSeen ) ;
5258 debouncer ( lastSeenDate ) ;
@@ -61,68 +67,22 @@ export function useDevPresence() {
6167 console . log ( "DevPresence: Failed to parse presence message" , { error } ) ;
6268 debouncer ( null ) ;
6369 }
64- } , [ streamedEvents ] ) ;
70+ } , [ streamedEvents , enabled ] ) ;
6571
66- return { lastSeen } ;
67- }
72+ // Calculate isConnected and memoize the context value
73+ const contextValue = useMemo ( ( ) => {
74+ const isConnected = enabled && lastSeen !== null && lastSeen > new Date ( Date . now ( ) - 120_000 ) ;
75+ return { lastSeen, isConnected } ;
76+ } , [ lastSeen , enabled ] ) ;
6877
69- export function DevPresence ( ) {
70- const { lastSeen } = useDevPresence ( ) ;
71- const isConnected = lastSeen && lastSeen > new Date ( Date . now ( ) - 120_000 ) ;
78+ return < DevPresenceContext . Provider value = { contextValue } > { children } </ DevPresenceContext . Provider > ;
79+ }
7280
73- return (
74- < Dialog >
75- < DialogTrigger asChild >
76- < Button
77- variant = "minimal/small"
78- className = "px-1"
79- LeadingIcon = {
80- isConnected ? (
81- < ConnectedIcon className = "size-5" />
82- ) : (
83- < DisconnectedIcon className = "size-5" />
84- )
85- }
86- />
87- </ DialogTrigger >
88- < DialogContent >
89- < DialogHeader >
90- { isConnected
91- ? "Your dev server is connected to Trigger.dev"
92- : "Your dev server is not connected to Trigger.dev" }
93- </ DialogHeader >
94- < div className = "mt-2 flex flex-col gap-3 px-2" >
95- < div className = "flex flex-col items-center justify-center gap-6 px-6 py-10" >
96- < img
97- src = { isConnected ? connectedImage : disconnectedImage }
98- alt = { isConnected ? "Connected" : "Disconnected" }
99- width = { 282 }
100- height = { 45 }
101- />
102- < Paragraph variant = "small" className = { isConnected ? "text-success" : "text-error" } >
103- { isConnected
104- ? "Your local dev server is connected to Trigger.dev"
105- : "Your local dev server is not connected to Trigger.dev" }
106- </ Paragraph >
107- </ div >
108- { isConnected ? null : (
109- < div className = "space-y-3" >
110- < PackageManagerProvider >
111- < TriggerDevStepV3 />
112- </ PackageManagerProvider >
113- < Paragraph variant = "small" >
114- Run this CLI `dev` command to connect to the Trigger.dev servers to start developing
115- locally. Keep it running while you develop to stay connected.
116- </ Paragraph >
117- </ div >
118- ) }
119- </ div >
120- < DialogFooter >
121- < LinkButton variant = "tertiary/medium" LeadingIcon = { BookOpenIcon } to = { docsPath ( "cli-dev" ) } >
122- CLI docs
123- </ LinkButton >
124- </ DialogFooter >
125- </ DialogContent >
126- </ Dialog >
127- ) ;
81+ // Custom hook to use the context
82+ export function useDevPresence ( ) {
83+ const context = useContext ( DevPresenceContext ) ;
84+ if ( context === undefined ) {
85+ throw new Error ( "useDevPresence must be used within a DevPresenceProvider" ) ;
86+ }
87+ return context ;
12888}
0 commit comments