Skip to content

Commit a69da6a

Browse files
authored
feat: send content feedback plausible events (#12400)
* feat: collect content feedback with Plausible analytics and Sentry User Feedback
1 parent 9412871 commit a69da6a

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

src/components/docFeedback/index.tsx

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
'use client';
2+
import {Fragment, useEffect, useState} from 'react';
3+
import {CheckIcon as Check} from '@radix-ui/react-icons';
4+
import {Button} from '@radix-ui/themes';
5+
import * as Sentry from '@sentry/browser';
6+
7+
import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent';
8+
9+
type Props = {
10+
pathname: string;
11+
};
12+
13+
export function DocFeedback({pathname}: Props) {
14+
const {emit} = usePlausibleEvent();
15+
const [showFeedback, setShowFeedback] = useState(false);
16+
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
17+
const [feedbackType, setFeedbackType] = useState<'helpful' | 'not_helpful' | null>(
18+
null
19+
);
20+
21+
// Initialize feedback state from sessionStorage
22+
useEffect(() => {
23+
const storedFeedback = sessionStorage.getItem(`feedback_${pathname}`);
24+
if (storedFeedback === 'submitted') {
25+
setFeedbackSubmitted(true);
26+
}
27+
}, [pathname]);
28+
29+
const handleFeedback = (helpful: boolean) => {
30+
emit('Doc Feedback', {props: {page: pathname, helpful}});
31+
setFeedbackType(helpful ? 'helpful' : 'not_helpful');
32+
setShowFeedback(true);
33+
};
34+
35+
const handleSubmitFeedback = (e: React.FormEvent<HTMLFormElement>) => {
36+
e.preventDefault();
37+
const formData = new FormData(e.currentTarget);
38+
const comments = formData.get('comments') as string;
39+
40+
try {
41+
Sentry.captureFeedback(
42+
{
43+
message: comments,
44+
},
45+
{captureContext: {tags: {page: pathname, type: feedbackType}}}
46+
);
47+
setFeedbackSubmitted(true);
48+
sessionStorage.setItem(`feedback_${pathname}`, 'submitted');
49+
} catch (error) {
50+
if (process.env.NODE_ENV === 'development') {
51+
// eslint-disable-next-line no-console
52+
console.error('Failed to submit feedback:', error);
53+
}
54+
Sentry.captureException(error);
55+
}
56+
};
57+
58+
return (
59+
<Fragment>
60+
<div className="space-y-4 py-4 border-[var(--gray-6)]">
61+
{feedbackSubmitted ? (
62+
<div className="flex items-center gap-2 text-sm text-[var(--gray-11)]">
63+
<Check className="w-4 h-4" /> Thanks for your feedback
64+
</div>
65+
) : (
66+
<Fragment>
67+
<div className="flex items-center gap-4 text-sm mt-8">
68+
<span className="font-medium">Was this helpful?</span>
69+
<div className="flex">
70+
<button
71+
onClick={() => handleFeedback(true)}
72+
className="py-1 px-2 gap-4 hover:bg-[var(--gray-3)] rounded flex items-center justify-center"
73+
aria-label="Yes, this was helpful"
74+
>
75+
Yes 👍
76+
</button>
77+
<button
78+
onClick={() => handleFeedback(false)}
79+
className="py-1 px-2 gap-4 hover:bg-[var(--gray-3)] rounded flex items-center justify-center"
80+
aria-label="No, this wasn't helpful"
81+
>
82+
No 👎
83+
</button>
84+
</div>
85+
</div>
86+
87+
<div
88+
className={`overflow-hidden transition-all duration-300 ease-in-out ${
89+
showFeedback ? 'max-h-[300px] opacity-100' : 'max-h-0 opacity-0'
90+
}`}
91+
>
92+
<form onSubmit={handleSubmitFeedback} className="space-y-4">
93+
<div>
94+
<label htmlFor="comments" className="block text-sm font-medium mb-4">
95+
{feedbackType === 'helpful'
96+
? 'What did you like about this page?'
97+
: 'How can we improve this page?'}
98+
</label>
99+
<textarea
100+
id="comments"
101+
name="comments"
102+
required
103+
rows={2}
104+
className="w-[calc(100%-4px)] ml-[2px] px-3 py-2 border border-[var(--gray-6)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--accent)] bg-transparent text-sm"
105+
placeholder="Please share your thoughts..."
106+
/>
107+
</div>
108+
<Button
109+
type="submit"
110+
className="px-4 py-2 text-sm rounded-lg bg-[var(--accent-purple)]"
111+
size={'3'}
112+
>
113+
Submit feedback
114+
</Button>
115+
</form>
116+
</div>
117+
</Fragment>
118+
)}
119+
</div>
120+
</Fragment>
121+
);
122+
}

src/components/docPage/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import './type.scss';
1212
import {Banner} from '../banner';
1313
import {Breadcrumbs} from '../breadcrumbs';
1414
import {CodeContextProvider} from '../codeContext';
15+
import {DocFeedback} from '../docFeedback';
1516
import {GitHubCTA} from '../githubCTA';
1617
import {Header} from '../header';
1718
import Mermaid from '../mermaid';
@@ -100,6 +101,8 @@ export function DocPage({
100101
</div>
101102
</div>
102103

104+
<DocFeedback pathname={pathname} />
105+
103106
{hasGithub && <GitHubCTA />}
104107
</div>
105108
</div>

src/hooks/usePlausibleEvent.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import {ReadProgressMilestone} from 'sentry-docs/types/plausible';
44

55
// Adding custom events here will make them available via the hook
66
type PlausibleEventProps = {
7+
['Doc Feedback']: {
8+
helpful: boolean;
9+
page: string;
10+
};
711
['Read Progress']: {
812
page: string;
913
readProgress: ReadProgressMilestone;

0 commit comments

Comments
 (0)