From 8b90327bb174f2c2928a0b8eaa96334bff99b946 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 19 Sep 2025 19:07:12 +0200 Subject: [PATCH] Refactor support component and integrate Sentry feedback button --- .../src/components/layout/support.tsx | 101 ++++-------------- .../new-layout/layout.component.tsx | 46 +++++++- 2 files changed, 66 insertions(+), 81 deletions(-) diff --git a/apps/frontend/src/components/layout/support.tsx b/apps/frontend/src/components/layout/support.tsx index 6b4779c7..3dc543d7 100644 --- a/apps/frontend/src/components/layout/support.tsx +++ b/apps/frontend/src/components/layout/support.tsx @@ -1,100 +1,43 @@ 'use client'; -import * as Sentry from '@sentry/nextjs'; import { EventEmitter } from 'events'; -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState } from 'react'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; export const supportEmitter = new EventEmitter(); -function AttachToFeedbackButton() { - const [feedback, setFeedback] = useState(); - const t = useT(); - // Read `getFeedback` on the client only, to avoid hydration errors during server rendering - useEffect(() => { - // Sentry.getFeedback is only available when Sentry is initialized on the client - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fb = (Sentry as any).getFeedback?.(); - setFeedback(fb); - } catch (e) { - setFeedback(undefined); - } - }, []); - const buttonRef = useRef(null); - useEffect(() => { - if (feedback && buttonRef.current) { - const unsubscribe = feedback.attachTo(buttonRef.current); - return unsubscribe; - } - return () => {}; - }, [feedback]); - return ( - - ); -} - export const Support = () => { const [show, setShow] = useState(true); - const { discordUrl, sentryDsn } = useVariables(); + const { discordUrl } = useVariables(); const t = useT(); useEffect(() => { supportEmitter.on('change', setShow); return () => { - supportEmitter.off('change', setShow); + supportEmitter.off('state', setShow); }; }, []); - if (!discordUrl || !show) return null; - return ( -
- {sentryDsn ? ( - - ) : null} - -
window.open(discordUrl)} - > -
- - - -
-
{t('discord_support', 'Discord Support')}
+
window.open(discordUrl)} + > +
+ + +
+
{t('discord_support', 'Discord Support')}
); }; diff --git a/apps/frontend/src/components/new-layout/layout.component.tsx b/apps/frontend/src/components/new-layout/layout.component.tsx index c27c7a94..092e1c41 100644 --- a/apps/frontend/src/components/new-layout/layout.component.tsx +++ b/apps/frontend/src/components/new-layout/layout.component.tsx @@ -1,6 +1,7 @@ -'use client'; + 'use client'; -import React, { ReactNode, useCallback } from 'react'; +import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; +import * as Sentry from '@sentry/nextjs'; import { Logo } from '@gitroom/frontend/components/new-layout/logo'; import { Plus_Jakarta_Sans } from 'next/font/google'; const ModeComponent = dynamic( @@ -50,6 +51,45 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => { const fetch = useFetch(); const { backendUrl, billingEnabled, isGeneral } = useVariables(); + + // Feedback icon component attaches Sentry feedback to a top-bar icon when DSN is present + function AttachToFeedbackIcon({ sentryDsn }: { sentryDsn?: string }) { + const [feedback, setFeedback] = useState(); + const buttonRef = useRef(null); + + useEffect(() => { + if (!sentryDsn) return; + try { + const fb = (Sentry as any).getFeedback?.(); + setFeedback(fb); + } catch (e) { + setFeedback(undefined); + } + }, [sentryDsn]); + + useEffect(() => { + if (feedback && buttonRef.current) { + const unsubscribe = feedback.attachTo(buttonRef.current); + return unsubscribe; + } + return () => {}; + }, [feedback]); + + if (!sentryDsn) return null; + + return ( + + ); + } const searchParams = useSearchParams(); const load = useCallback(async (path: string) => { return await (await fetch(path)).json(); @@ -118,6 +158,8 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => {
+ {/* Feedback icon (icon-only) - only show when Sentry DSN is present */} +