From b2a91696df2f4f543c3c564a4a1d197ac076e68f Mon Sep 17 00:00:00 2001 From: devjiwonchoi Date: Fri, 3 Jan 2025 01:46:47 +0900 Subject: [PATCH] [DevOverlay] Add error overlay footer and feedback --- .../components/Dialog/dialog-footer.tsx | 12 +++ .../internal/components/Dialog/index.ts | 1 + .../ErrorOverlayLayout/ErrorOverlayLayout.tsx | 14 ++- .../error-feedback-toast.stories.tsx | 17 ++++ .../error-feedback/error-feedback-toast.tsx | 29 ++++++ .../error-feedback/error-feedback.tsx | 44 +++++++++ .../error-overlay-footer.tsx | 18 ++++ .../Errors/error-overlay-footer/styles.ts | 91 +++++++++++++++++++ .../internal/icons/thumbs/thumbs-down.tsx | 19 ++++ .../internal/icons/thumbs/thumbs-up.tsx | 22 +++++ .../internal/styles/ComponentStyles.tsx | 2 + 11 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/dialog-footer.tsx create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.stories.tsx create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.tsx create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback.tsx create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-overlay-footer.tsx create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/styles.ts create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-down.tsx create mode 100644 packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-up.tsx diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/dialog-footer.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/dialog-footer.tsx new file mode 100644 index 0000000000000..97fbbbae39c66 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/dialog-footer.tsx @@ -0,0 +1,12 @@ +export type DialogFooterProps = { + children?: React.ReactNode + className?: string +} + +export function DialogFooter({ children, className }: DialogFooterProps) { + return ( +
+ {children} +
+ ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/index.ts b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/index.ts index 15a3c57a88686..38886ddcaad6c 100644 --- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/index.ts +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Dialog/index.ts @@ -2,4 +2,5 @@ export { Dialog } from './Dialog' export { DialogBody } from './DialogBody' export { DialogContent } from './DialogContent' export { DialogHeader } from './DialogHeader' +export { DialogFooter } from './dialog-footer' export { styles } from './styles' diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/ErrorOverlayLayout/ErrorOverlayLayout.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/ErrorOverlayLayout/ErrorOverlayLayout.tsx index f5a44b408f001..73a3614f410da 100644 --- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/ErrorOverlayLayout/ErrorOverlayLayout.tsx +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/ErrorOverlayLayout/ErrorOverlayLayout.tsx @@ -1,12 +1,19 @@ import type { ReadyRuntimeError } from '../../../helpers/get-error-by-type' import type { DebugInfo } from '../../../../../types' import type { VersionInfo } from '../../../../../../../../server/dev/parse-version-info' -import { Dialog, DialogHeader, DialogBody, DialogContent } from '../../Dialog' +import { + Dialog, + DialogHeader, + DialogBody, + DialogContent, + DialogFooter, +} from '../../Dialog' import { Overlay } from '../../Overlay' import { ErrorPagination } from '../ErrorPagination/ErrorPagination' import { ToolButtonsGroup } from '../../ToolButtonsGroup/ToolButtonsGroup' import { VersionStalenessInfo } from '../../VersionStalenessInfo' import { ErrorOverlayBottomStacks } from '../error-overlay-bottom-stacks/error-overlay-bottom-stacks' +import { ErrorOverlayFooter } from '../error-overlay-footer/error-overlay-footer' type ErrorOverlayLayoutProps = { errorMessage: string | React.ReactNode @@ -88,6 +95,11 @@ export function ErrorOverlayLayout({ {children} + + {/* TODO: Replace message from BuildError.tsx */} + {/* TODO: errorCode should not be undefined whatsoever */} + + = { + title: 'ErrorFeedbackToast', + component: ErrorFeedbackToast, + parameters: { + layout: 'centered', + }, + decorators: [withShadowPortal], +} + +export default meta +type Story = StoryObj + +export const Default: Story = {} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.tsx new file mode 100644 index 0000000000000..cc032a50b7386 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback-toast.tsx @@ -0,0 +1,29 @@ +import { Toast } from '../../../Toast' +import { CloseIcon } from '../../../../icons/CloseIcon' + +export function ErrorFeedbackToast({ + isVisible, + setIsVisible, +}: { + isVisible: boolean + setIsVisible: (isVisible: boolean) => void +}) { + if (!isVisible) { + return null + } + + return ( + +
+ Thanks for your feedback! + +
+
+ ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback.tsx new file mode 100644 index 0000000000000..6bac1f7200bc9 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react' + +import { ThumbsUp } from '../../../../icons/thumbs/thumbs-up' +import { ThumbsDown } from '../../../../icons/thumbs/thumbs-down' +import { ErrorFeedbackToast } from './error-feedback-toast' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function ErrorFeedback({ errorCode }: { errorCode: string }) { + const [voted, setVoted] = useState<'good' | 'bad' | null>(null) + const [isToastVisible, setIsToastVisible] = useState(false) + const hasVoted = voted !== null + + // TODO: make API call to /__nextjs_error_feedback + const handleFeedback = (value: 'good' | 'bad') => { + setVoted(value) + setIsToastVisible(true) + } + + return ( + <> +
+

Was this helpful?

+ + +
+ + + ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-overlay-footer.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-overlay-footer.tsx new file mode 100644 index 0000000000000..724ce8cb4a381 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-overlay-footer.tsx @@ -0,0 +1,18 @@ +import { ErrorFeedback } from './error-feedback/error-feedback' + +export type ErrorOverlayFooterProps = { + errorCode: string + message: string +} + +export function ErrorOverlayFooter({ + errorCode, + message, +}: ErrorOverlayFooterProps) { + return ( +
+

{message}

+ +
+ ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/styles.ts b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/styles.ts new file mode 100644 index 0000000000000..4cb499f333cf5 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/styles.ts @@ -0,0 +1,91 @@ +import { noop as css } from '../../../helpers/noop-template' + +const styles = css` + .error-overlay-footer { + display: flex; + align-items: center; + justify-content: space-between; + } + + .error-overlay-footer p { + color: var(--color-gray-900); + margin: 0; + } + + .error-feedback { + display: flex; + align-items: center; + gap: var(--size-gap); + } + + .feedback-button { + background: none; + border: none; + border-radius: var(--rounded-md); + padding: var(--size-gap-half); + width: 1.5rem; /* 24px */ + height: 1.5rem; /* 24px */ + display: flex; + align-items: center; + cursor: pointer; + + &:focus { + outline: none; + } + + &:hover { + background: var(--color-gray-alpha-100); + } + + &:active { + background: var(--color-gray-alpha-200); + } + } + + .feedback-button:disabled { + opacity: 0.7; + cursor: not-allowed; + } + + .feedback-button.voted { + background: var(--color-gray-alpha-200); + } + + .thumbs-up-icon, + .thumbs-down-icon { + color: var(--color-gray-900); + } + + .error-feedback-toast { + width: 420px; + height: auto; + overflow: hidden; + border: 0; + padding: var(--size-gap-double); + border-radius: var(--rounded-xl); + background: var(--color-blue-700); + bottom: var(--size-gap); + right: var(--size-gap); + left: auto; + } + + .error-feedback-toast-text { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--color-font); + } + + .error-feedback-toast-hide-button { + width: var(--size-gap-quad); + height: var(--size-gap-quad); + border: none; + background: none; + &:focus { + outline: none; + } + color: var(--color-font); + } +` + +export { styles } diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-down.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-down.tsx new file mode 100644 index 0000000000000..487e4c517a948 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-down.tsx @@ -0,0 +1,19 @@ +export function ThumbsDown() { + return ( + + + + ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-up.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-up.tsx new file mode 100644 index 0000000000000..85d7583bf7d29 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/icons/thumbs/thumbs-up.tsx @@ -0,0 +1,22 @@ +export function ThumbsUp() { + return ( + + + + + + ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/ComponentStyles.tsx b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/ComponentStyles.tsx index 3fade1439600b..72e0c58589865 100644 --- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/ComponentStyles.tsx +++ b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/styles/ComponentStyles.tsx @@ -3,6 +3,7 @@ import { styles as dialog } from '../components/Dialog' import { styles as bottomStacks } from '../components/Errors/error-overlay-bottom-stacks/error-overlay-bottom-stacks' import { styles as pagination } from '../components/Errors/ErrorPagination/styles' import { styles as overlay } from '../components/Overlay/styles' +import { styles as footer } from '../components/Errors/error-overlay-footer/styles' import { styles as terminal } from '../components/Terminal/styles' import { styles as toast } from '../components/Toast' import { styles as versionStaleness } from '../components/VersionStalenessInfo' @@ -18,6 +19,7 @@ export function ComponentStyles() { ${overlay} ${toast} ${dialog} + ${footer} ${bottomStacks} ${pagination} ${codeFrame}