Skip to content

Commit 1355dfa

Browse files
authored
[DevOverlay] Add error overlay footer and feedback (vercel#74472)
This PR added the footer of the overlay with the "Was this helpful?" feedback UI. It includes a message toast when the user selects the feedback button. https://github.com/user-attachments/assets/fba618ec-a47d-4fc0-a1da-264d52fb1ff1 Closes NDX-580
1 parent e5f60d6 commit 1355dfa

File tree

11 files changed

+268
-1
lines changed

11 files changed

+268
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type DialogFooterProps = {
2+
children?: React.ReactNode
3+
className?: string
4+
}
5+
6+
export function DialogFooter({ children, className }: DialogFooterProps) {
7+
return (
8+
<div data-nextjs-dialog-footer className={className}>
9+
{children}
10+
</div>
11+
)
12+
}

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export { Dialog } from './Dialog'
22
export { DialogBody } from './DialogBody'
33
export { DialogContent } from './DialogContent'
44
export { DialogHeader } from './DialogHeader'
5+
export { DialogFooter } from './dialog-footer'
56
export { styles } from './styles'

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/ErrorOverlayLayout/ErrorOverlayLayout.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import type { ReadyRuntimeError } from '../../../helpers/get-error-by-type'
22
import type { DebugInfo } from '../../../../../types'
33
import type { VersionInfo } from '../../../../../../../../server/dev/parse-version-info'
4-
import { Dialog, DialogHeader, DialogBody, DialogContent } from '../../Dialog'
4+
import {
5+
Dialog,
6+
DialogHeader,
7+
DialogBody,
8+
DialogContent,
9+
DialogFooter,
10+
} from '../../Dialog'
511
import { Overlay } from '../../Overlay'
612
import { ErrorPagination } from '../ErrorPagination/ErrorPagination'
713
import { ToolButtonsGroup } from '../../ToolButtonsGroup/ToolButtonsGroup'
814
import { VersionStalenessInfo } from '../../VersionStalenessInfo'
915
import { ErrorOverlayBottomStacks } from '../error-overlay-bottom-stacks/error-overlay-bottom-stacks'
16+
import { ErrorOverlayFooter } from '../error-overlay-footer/error-overlay-footer'
1017

1118
type ErrorOverlayLayoutProps = {
1219
errorMessage: string | React.ReactNode
@@ -88,6 +95,11 @@ export function ErrorOverlayLayout({
8895
<DialogBody className="nextjs-container-errors-body">
8996
{children}
9097
</DialogBody>
98+
<DialogFooter>
99+
{/* TODO: Replace message from BuildError.tsx */}
100+
{/* TODO: errorCode should not be undefined whatsoever */}
101+
<ErrorOverlayFooter message={''} errorCode={errorCode!} />
102+
</DialogFooter>
91103
</DialogContent>
92104
</Dialog>
93105
<ErrorOverlayBottomStacks
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { ErrorFeedbackToast } from './error-feedback-toast'
3+
import { withShadowPortal } from '../../../../storybook/with-shadow-portal'
4+
5+
const meta: Meta<typeof ErrorFeedbackToast> = {
6+
title: 'ErrorFeedbackToast',
7+
component: ErrorFeedbackToast,
8+
parameters: {
9+
layout: 'centered',
10+
},
11+
decorators: [withShadowPortal],
12+
}
13+
14+
export default meta
15+
type Story = StoryObj<typeof ErrorFeedbackToast>
16+
17+
export const Default: Story = {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Toast } from '../../../Toast'
2+
import { CloseIcon } from '../../../../icons/CloseIcon'
3+
4+
export function ErrorFeedbackToast({
5+
isVisible,
6+
setIsVisible,
7+
}: {
8+
isVisible: boolean
9+
setIsVisible: (isVisible: boolean) => void
10+
}) {
11+
if (!isVisible) {
12+
return null
13+
}
14+
15+
return (
16+
<Toast role="status" className="error-feedback-toast">
17+
<div className="error-feedback-toast-text">
18+
Thanks for your feedback!
19+
<button
20+
onClick={() => setIsVisible(false)}
21+
className="error-feedback-toast-hide-button"
22+
aria-label="Hide error feedback toast"
23+
>
24+
<CloseIcon />
25+
</button>
26+
</div>
27+
</Toast>
28+
)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useState } from 'react'
2+
3+
import { ThumbsUp } from '../../../../icons/thumbs/thumbs-up'
4+
import { ThumbsDown } from '../../../../icons/thumbs/thumbs-down'
5+
import { ErrorFeedbackToast } from './error-feedback-toast'
6+
7+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8+
export function ErrorFeedback({ errorCode }: { errorCode: string }) {
9+
const [voted, setVoted] = useState<'good' | 'bad' | null>(null)
10+
const [isToastVisible, setIsToastVisible] = useState(false)
11+
const hasVoted = voted !== null
12+
13+
// TODO: make API call to /__nextjs_error_feedback
14+
const handleFeedback = (value: 'good' | 'bad') => {
15+
setVoted(value)
16+
setIsToastVisible(true)
17+
}
18+
19+
return (
20+
<>
21+
<div className="error-feedback">
22+
<p>Was this helpful?</p>
23+
<button
24+
onClick={() => handleFeedback('good')}
25+
disabled={hasVoted}
26+
className={`feedback-button ${voted === 'good' ? 'voted' : ''}`}
27+
>
28+
<ThumbsUp />
29+
</button>
30+
<button
31+
onClick={() => handleFeedback('bad')}
32+
disabled={hasVoted}
33+
className={`feedback-button ${voted === 'bad' ? 'voted' : ''}`}
34+
>
35+
<ThumbsDown />
36+
</button>
37+
</div>
38+
<ErrorFeedbackToast
39+
isVisible={isToastVisible}
40+
setIsVisible={setIsToastVisible}
41+
/>
42+
</>
43+
)
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ErrorFeedback } from './error-feedback/error-feedback'
2+
3+
export type ErrorOverlayFooterProps = {
4+
errorCode: string
5+
message: string
6+
}
7+
8+
export function ErrorOverlayFooter({
9+
errorCode,
10+
message,
11+
}: ErrorOverlayFooterProps) {
12+
return (
13+
<footer className="error-overlay-footer">
14+
<p>{message}</p>
15+
<ErrorFeedback errorCode={errorCode} />
16+
</footer>
17+
)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { noop as css } from '../../../helpers/noop-template'
2+
3+
const styles = css`
4+
.error-overlay-footer {
5+
display: flex;
6+
align-items: center;
7+
justify-content: space-between;
8+
}
9+
10+
.error-overlay-footer p {
11+
color: var(--color-gray-900);
12+
margin: 0;
13+
}
14+
15+
.error-feedback {
16+
display: flex;
17+
align-items: center;
18+
gap: var(--size-gap);
19+
}
20+
21+
.feedback-button {
22+
background: none;
23+
border: none;
24+
border-radius: var(--rounded-md);
25+
padding: var(--size-gap-half);
26+
width: 1.5rem; /* 24px */
27+
height: 1.5rem; /* 24px */
28+
display: flex;
29+
align-items: center;
30+
cursor: pointer;
31+
32+
&:focus {
33+
outline: none;
34+
}
35+
36+
&:hover {
37+
background: var(--color-gray-alpha-100);
38+
}
39+
40+
&:active {
41+
background: var(--color-gray-alpha-200);
42+
}
43+
}
44+
45+
.feedback-button:disabled {
46+
opacity: 0.7;
47+
cursor: not-allowed;
48+
}
49+
50+
.feedback-button.voted {
51+
background: var(--color-gray-alpha-200);
52+
}
53+
54+
.thumbs-up-icon,
55+
.thumbs-down-icon {
56+
color: var(--color-gray-900);
57+
}
58+
59+
.error-feedback-toast {
60+
width: 420px;
61+
height: auto;
62+
overflow: hidden;
63+
border: 0;
64+
padding: var(--size-gap-double);
65+
border-radius: var(--rounded-xl);
66+
background: var(--color-blue-700);
67+
bottom: var(--size-gap);
68+
right: var(--size-gap);
69+
left: auto;
70+
}
71+
72+
.error-feedback-toast-text {
73+
display: flex;
74+
align-items: center;
75+
justify-content: space-between;
76+
color: var(--color-font);
77+
}
78+
79+
.error-feedback-toast-hide-button {
80+
width: var(--size-gap-quad);
81+
height: var(--size-gap-quad);
82+
border: none;
83+
background: none;
84+
&:focus {
85+
outline: none;
86+
}
87+
color: var(--color-font);
88+
}
89+
`
90+
91+
export { styles }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export function ThumbsDown() {
2+
return (
3+
<svg
4+
width="16"
5+
height="16"
6+
viewBox="0 0 16 16"
7+
fill="none"
8+
xmlns="http://www.w3.org/2000/svg"
9+
className="thumbs-down-icon"
10+
>
11+
<path
12+
fill-rule="evenodd"
13+
clip-rule="evenodd"
14+
d="M5.89531 12.7603C5.72984 12.8785 5.5 12.7602 5.5 12.5569V9.75C5.5 8.7835 4.7165 8 3.75 8H1.5V1.5H11.1884C11.762 1.5 12.262 1.89037 12.4011 2.44683L13.4011 6.44683C13.5984 7.23576 13.0017 8 12.1884 8H8.25H7.5V8.75V11.4854C7.5 11.5662 7.46101 11.6419 7.39531 11.6889L5.89531 12.7603ZM4 12.5569C4 13.9803 5.6089 14.8082 6.76717 13.9809L8.26717 12.9095C8.72706 12.581 9 12.0506 9 11.4854V9.5H12.1884C13.9775 9.5 15.2903 7.81868 14.8563 6.08303L13.8563 2.08303C13.5503 0.858816 12.4503 0 11.1884 0H0.75H0V0.75V8.75V9.5H0.75H3.75C3.88807 9.5 4 9.61193 4 9.75V12.5569Z"
15+
fill="currentColor"
16+
/>
17+
</svg>
18+
)
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export function ThumbsUp() {
2+
return (
3+
<svg
4+
width="16"
5+
height="16"
6+
viewBox="0 0 16 16"
7+
fill="none"
8+
xmlns="http://www.w3.org/2000/svg"
9+
className="thumbs-up-icon"
10+
>
11+
<g id="thumb-up-16">
12+
<path
13+
id="Union"
14+
fill-rule="evenodd"
15+
clip-rule="evenodd"
16+
d="M6.89531 2.23959C6.72984 2.1214 6.5 2.23968 6.5 2.44303V5.24989C6.5 6.21639 5.7165 6.99989 4.75 6.99989H2.5V13.4999H12.1884C12.762 13.4999 13.262 13.1095 13.4011 12.5531L14.4011 8.55306C14.5984 7.76412 14.0017 6.99989 13.1884 6.99989H9.25H8.5V6.24989V3.51446C8.5 3.43372 8.46101 3.35795 8.39531 3.31102L6.89531 2.23959ZM5 2.44303C5 1.01963 6.6089 0.191656 7.76717 1.01899L9.26717 2.09042C9.72706 2.41892 10 2.94929 10 3.51446V5.49989H13.1884C14.9775 5.49989 16.2903 7.18121 15.8563 8.91686L14.8563 12.9169C14.5503 14.1411 13.4503 14.9999 12.1884 14.9999H1.75H1V14.2499V6.24989V5.49989H1.75H4.75C4.88807 5.49989 5 5.38796 5 5.24989V2.44303Z"
17+
fill="currentColor"
18+
/>
19+
</g>
20+
</svg>
21+
)
22+
}

0 commit comments

Comments
 (0)