From 000f66911cab64d823bc64f46fe8efd002e7bcd6 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Sat, 1 Mar 2025 22:35:38 -0700 Subject: [PATCH] update error card --- .../features/errors/GlobalErrorComponent.tsx | 87 ++++++++----------- .../SidebarUserFooter.stories.tsx | 13 +-- .../components/ui/card/CodeCard.stories.tsx | 2 +- .../ui/charts/BusterChartErrorWrapper.tsx | 45 +++------- web/src/components/ui/error/ErrorBoundary.tsx | 3 +- .../components/ui/error/ErrorCard.stories.tsx | 58 +++++++++++++ web/src/components/ui/error/ErrorCard.tsx | 42 +++++++++ 7 files changed, 153 insertions(+), 97 deletions(-) create mode 100644 web/src/components/ui/error/ErrorCard.stories.tsx create mode 100644 web/src/components/ui/error/ErrorCard.tsx diff --git a/web/src/components/features/errors/GlobalErrorComponent.tsx b/web/src/components/features/errors/GlobalErrorComponent.tsx index fe54b9d89..d5de986d0 100644 --- a/web/src/components/features/errors/GlobalErrorComponent.tsx +++ b/web/src/components/features/errors/GlobalErrorComponent.tsx @@ -1,64 +1,47 @@ 'use client'; -import { Component, ErrorInfo, ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import { Button } from '@/components/ui/buttons/Button'; import { Card, CardContent, CardFooter } from '@/components/ui/card/CardBase'; +import { ErrorBoundary } from '@/components/ui/error'; interface Props { children: ReactNode; } -interface State { - hasError: boolean; -} +const ErrorCard = () => { + return ( +
+ + +
+

+ Looks like we hit an error! 😅 +

-export class GlobalErrorComponent extends Component { - state = { - hasError: false - }; +
+ Don't worry, it's not you - it's us! +
+
+ If this error persists, please contact Buster support! +
+
+
- static getDerivedStateFromError(_: Error): State { - return { hasError: true }; - } + + + + + +
+
+ ); +}; - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('Global error:', error, errorInfo); - } - - render() { - if (this.state.hasError) { - return ( -
- - -
-

- Looks like we hit an error! 😅 -

- -
- Don't worry, it's not you - it's us! -
-
- If this error persists, please contact Buster support! -
-
-
- - - - - - -
-
- ); - } - - return this.props.children; - } -} +export const GlobalErrorComponent = ({ children }: Props) => { + return }>{children}; +}; diff --git a/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.stories.tsx b/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.stories.tsx index c9faed0fe..1a4f7fd34 100644 --- a/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.stories.tsx +++ b/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.stories.tsx @@ -23,20 +23,15 @@ type Story = StoryObj; export const Default: Story = { args: { name: 'John Doe', - email: 'john.doe@example.com' - } -}; - -export const WithoutAvatar: Story = { - args: { - name: 'Jane Smith', - email: 'jane.smith@example.com' + email: 'john.doe@example.com', + signOut: () => Promise.resolve({ error: '' }) } }; export const LongName: Story = { args: { name: 'Alexander Bartholomew Christopherson III', - email: 'alexander@example.com' + email: 'alexander@example.com', + signOut: () => Promise.resolve({ error: '' }) } }; diff --git a/web/src/components/ui/card/CodeCard.stories.tsx b/web/src/components/ui/card/CodeCard.stories.tsx index c850c17af..bf8ffd4c1 100644 --- a/web/src/components/ui/card/CodeCard.stories.tsx +++ b/web/src/components/ui/card/CodeCard.stories.tsx @@ -25,7 +25,7 @@ const sampleCode = `function helloWorld() { }`; const longCode = `import React from 'react'; -import { Button } from 'antd'; +import { Button } from '../buttons'; export const MyComponent = () => { const handleClick = () => { diff --git a/web/src/components/ui/charts/BusterChartErrorWrapper.tsx b/web/src/components/ui/charts/BusterChartErrorWrapper.tsx index 257d0c4e9..4c94b6c86 100644 --- a/web/src/components/ui/charts/BusterChartErrorWrapper.tsx +++ b/web/src/components/ui/charts/BusterChartErrorWrapper.tsx @@ -1,40 +1,17 @@ -import { Alert } from 'antd'; -import { Component, ErrorInfo, ReactNode } from 'react'; +import { ErrorCard } from '@/components/ui/error/ErrorCard'; +import { ReactNode } from 'react'; +import { ErrorBoundary } from '../error/ErrorBoundary'; interface Props { children: ReactNode; } -interface State { - hasError: boolean; -} +const ErrorCardComponent: React.FC = () => { + return ( + + ); +}; -export class BusterChartErrorWrapper extends Component { - state = { - hasError: false - }; - - static getDerivedStateFromError(_: Error): State { - return { hasError: true }; - } - - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('Chart Error:', error, errorInfo); - } - - render() { - if (this.state.hasError) { - return ( -
- -
- ); - } - - return this.props.children; - } -} +export const BusterChartErrorWrapper: React.FC = ({ children }) => { + return }>{children}; +}; diff --git a/web/src/components/ui/error/ErrorBoundary.tsx b/web/src/components/ui/error/ErrorBoundary.tsx index 03b5a63d7..6bf09d306 100644 --- a/web/src/components/ui/error/ErrorBoundary.tsx +++ b/web/src/components/ui/error/ErrorBoundary.tsx @@ -2,6 +2,7 @@ import React, { Component, ErrorInfo, ReactNode } from 'react'; interface Props { children: ReactNode; + errorComponent?: ReactNode; onError?: () => void; } @@ -24,7 +25,7 @@ export class ErrorBoundary extends Component { render() { if (this.state.hasError) { - return Error boundary...; + return this.props.errorComponent; } return this.props.children; diff --git a/web/src/components/ui/error/ErrorCard.stories.tsx b/web/src/components/ui/error/ErrorCard.stories.tsx new file mode 100644 index 000000000..a5efa29ca --- /dev/null +++ b/web/src/components/ui/error/ErrorCard.stories.tsx @@ -0,0 +1,58 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ErrorCard } from './ErrorCard'; + +const meta: Meta = { + title: 'UI/Error/ErrorCard', + component: ErrorCard, + tags: ['autodocs'], + argTypes: { + error: { + control: 'text', + description: 'The error message to display' + }, + title: { + control: 'text', + description: 'Optional title for the error alert' + }, + variant: { + control: { type: 'select' }, + options: ['danger', 'default'], + description: 'The visual style of the error alert' + } + } +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + error: 'Something went wrong. Please try again later.', + title: 'Error', + variant: 'default' + } +}; + +export const Danger: Story = { + args: { + error: 'Failed to save changes. Please check your connection and try again.', + title: 'Connection Error', + variant: 'danger' + } +}; + +export const WithoutTitle: Story = { + args: { + error: 'This is an error message without a title.', + variant: 'default' + } +}; + +export const LongErrorMessage: Story = { + args: { + error: + 'This is a very long error message that might wrap to multiple lines. It contains detailed information about what went wrong and possibly some suggestions on how to fix the issue.', + title: 'Detailed Error', + variant: 'danger' + } +}; diff --git a/web/src/components/ui/error/ErrorCard.tsx b/web/src/components/ui/error/ErrorCard.tsx new file mode 100644 index 000000000..29168e134 --- /dev/null +++ b/web/src/components/ui/error/ErrorCard.tsx @@ -0,0 +1,42 @@ +import { cva, VariantProps } from 'class-variance-authority'; +import React from 'react'; +import { cn } from '@/lib/classMerge'; +import { Text } from '../typography'; +import { Xmark } from '../icons'; + +const errorVariants = cva('shadow p-3 rounded', { + variants: { + variant: { + danger: 'bg-danger-foreground text-white', + default: 'bg-background text-foreground' + } + } +}); + +export const ErrorCard: React.FC< + { + error: string; + title?: string; + className?: string; + onClose?: () => void; + } & VariantProps +> = ({ error, title, variant, className, onClose }) => { + return ( +
+ {title && {title}} + + {error} + + {onClose && ( +
+ +
+ )} +
+ ); +};