Skip to content
Merged
117 changes: 117 additions & 0 deletions src/components/docFeedback/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use client';
import {Fragment, useState} from 'react';
import * as Sentry from '@sentry/browser';

import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent';

import {Modal} from '../modal';

type Props = {
pathname: string;
};

export function DocFeedback({pathname}: Props) {
const {emit} = usePlausibleEvent();
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);

const handleFeedback = (helpful: boolean) => {
emit('Doc Feedback', {props: {page: pathname, helpful}});

if (!helpful) {
setShowFeedbackModal(true);
}
};

const handleSubmitFeedback = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const comments = formData.get('comments') as string;

try {
Sentry.captureFeedback(
{message: comments},
{captureContext: {tags: {page: pathname}}}
);
setFeedbackSubmitted(true);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to submit feedback:', error);
}
};

return (
<Fragment>
<div className="flex items-center gap-2 py-4 border-t border-[var(--gray-6)]">
<span className="text-sm">Was this helpful?</span>
<button
onClick={() => handleFeedback(true)}
className="py-2 px-4 gap-4 hover:bg-[var(--gray-3)] rounded-full flex items-center justify-center"
aria-label="Yes, this was helpful"
>
Yes 👍
</button>
<button
onClick={() => handleFeedback(false)}
className="py-2 px-4 gap-4 hover:bg-[var(--gray-3)] rounded-full flex items-center justify-center"
aria-label="No, this wasn't helpful"
>
No 👎
</button>
</div>

<Modal
isOpen={showFeedbackModal}
onClose={() => {
setShowFeedbackModal(false);
setFeedbackSubmitted(false);
}}
title="Help us improve"
>
{feedbackSubmitted ? (
<div className="text-center">
<h3 className="text-lg font-medium mb-2">Thank you for your feedback!</h3>
<p className="text-[var(--gray-11)] p-0 m-0">
We appreciate your help in making our documentation better.
</p>
</div>
) : (
<form onSubmit={handleSubmitFeedback} className="space-y-4">
<p className="text-[var(--gray-11)] p-0 m-0">
We'd love to hear more about how we can improve this page. Your feedback
helps us make our documentation better for everyone.
</p>
<div>
<label htmlFor="comments" className="block text-sm font-medium mb-4">
What could we improve?
</label>
<textarea
id="comments"
name="comments"
required
rows={4}
className="w-full px-3 py-2 border border-[var(--gray-6)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--accent)] bg-transparent"
placeholder="Please share your suggestions..."
/>
</div>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={() => setShowFeedbackModal(false)}
className="px-4 py-2 text-sm hover:bg-[var(--gray-3)] rounded-lg"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 text-sm bg-[var(--accent-purple)] rounded-lg"
>
Submit feedback
</button>
</div>
</form>
)}
</Modal>
</Fragment>
);
}
3 changes: 3 additions & 0 deletions src/components/docPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import './type.scss';
import {Banner} from '../banner';
import {Breadcrumbs} from '../breadcrumbs';
import {CodeContextProvider} from '../codeContext';
import {DocFeedback} from '../docFeedback';
import {GitHubCTA} from '../githubCTA';
import {Header} from '../header';
import Mermaid from '../mermaid';
Expand Down Expand Up @@ -91,6 +92,8 @@ export function DocPage({
<CodeContextProvider>{children}</CodeContextProvider>
</div>

<DocFeedback pathname={pathname} />

<div className="grid grid-cols-2 gap-4 not-prose mt-16">
<div className="col-span-1">
{previousPage && <PaginationNav node={previousPage} title="Previous" />}
Expand Down
100 changes: 100 additions & 0 deletions src/components/modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use client';
import {useEffect, useRef} from 'react';

type Props = {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
title: string;
};

export function Modal({isOpen, onClose, children, title}: Props) {
const modalRef = useRef<HTMLDialogElement>(null);

useEffect(() => {
if (isOpen) {
modalRef.current?.showModal();
document.body.style.overflow = 'hidden';
} else {
modalRef.current?.close();
document.body.style.overflow = 'unset';
}
}, [isOpen]);

const handleClose = () => {
onClose();
};

const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
if (e.target === modalRef.current) {
handleClose();
}
};

if (!isOpen) {
return null;
}

return (
<dialog
ref={modalRef}
className="backdrop:bg-[var(--gray-12)]/50 backdrop:backdrop-blur-sm p-0 bg-transparent w-full max-w-lg m-auto data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[2%] data-[state=open]:slide-in-from-top-[2%] duration-200"
onClick={handleBackdropClick}
>
<div className="bg-[var(--gray-1)] rounded-lg shadow-lg border border-[var(--gray-6)] motion-safe:animate-modal-enter px-4">
<div className="flex items-center justify-between py-5 border-b border-[var(--gray-6)] px-4">
<h2 className="text-lg font-medium p!-0 !m-0">{title}</h2>
<button
onClick={handleClose}
className="p-2 hover:bg-[var(--gray-3)] rounded-full transition-colors"
aria-label="Close modal"
>
<svg width="12" height="12" viewBox="0 0 12 12">
<path
d="M6 4.586l4.293-4.293 1.414 1.414L7.414 6l4.293 4.293-1.414 1.414L6 7.414l-4.293 4.293-1.414-1.414L4.586 6 .293 1.707 1.707.293 6 4.586z"
fill="currentColor"
/>
</svg>
</button>
</div>
<div className="p-4">{children}</div>
</div>

<style>{`
@keyframes modal-enter {
from {
opacity: 0;
transform: translate3d(0, -1rem, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}

.animate-modal-enter {
animation: modal-enter 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

::backdrop {
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
background: rgba(var(--gray-12-rgb), 0.5);
transition: all 0.2s ease-in-out;
}

dialog[open]::backdrop {
opacity: 1;
}

dialog:not([open])::backdrop {
opacity: 0;
}

dialog::backdrop {
opacity: 0;
}
`}</style>
</dialog>
);
}
4 changes: 4 additions & 0 deletions src/hooks/usePlausibleEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {ReadProgressMilestone} from 'sentry-docs/types/plausible';

// Adding custom events here will make them available via the hook
type PlausibleEventProps = {
['Doc Feedback']: {
helpful: boolean;
page: string;
};
['Read Progress']: {
page: string;
readProgress: ReadProgressMilestone;
Expand Down
Loading